From f6e46e4d447a4e84192e27c05fd78e10cdddeed5 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 14 Apr 2025 15:05:57 +0200 Subject: [PATCH 001/474] PoC: extract unsat core --- silver | 2 +- src/main/scala/decider/Decider.scala | 17 +++++++++----- src/main/scala/decider/ProverStdIO.scala | 22 +++++++++++++++---- src/main/scala/decider/Z3ProverAPI.scala | 4 ++++ .../scala/interfaces/decider/Prover.scala | 1 + .../scala/verifier/DefaultMainVerifier.scala | 5 +++++ .../verifier/VerificationPoolManager.scala | 1 + src/test/resources/andrea/assert-assume.vpr | 14 ++++++++++++ 8 files changed, 56 insertions(+), 10 deletions(-) create mode 100644 src/test/resources/andrea/assert-assume.vpr diff --git a/silver b/silver index e8521cbb1..78f08c750 160000 --- a/silver +++ b/silver @@ -1 +1 @@ -Subproject commit e8521cbb1b4277a7ac8fc210d00211593f9cd6ed +Subproject commit 78f08c750641ee7642bcabbbb7db2e38b78e4e4f diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index fbf93b066..159c4f0b7 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -309,11 +309,17 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => filteredAssumptions foreach (a => addDebugExp(a._2.get.withTerm(a._1))) } - if (filteredAssumptions.nonEmpty) assumeWithoutSmokeChecks(filteredAssumptions map (_._1), isDefinition=isDefinition) + + if (filteredAssumptions.nonEmpty){ + val d = filteredAssumptions.head._2 // FIXME ake + val labelPrefix = if (d.isDefined) "debugExp_" + d.get.id else "" + assumeWithoutSmokeChecks(filteredAssumptions map (_._1), isDefinition=isDefinition, labelPrefix=labelPrefix) + } } def assume(assumptions: Seq[Term], debugExps: Option[Seq[DebugExp]]): Unit = { - assumeWithoutSmokeChecks(InsertionOrderedSet(assumptions)) + val labelPrefix = if(debugExps.isDefined) "debugExp_" + debugExps.get.head.id else "" // FIXME ake + assumeWithoutSmokeChecks(InsertionOrderedSet(assumptions), labelPrefix=labelPrefix) if (debugMode) { debugExps.get foreach (e => addDebugExp(e)) } @@ -328,7 +334,8 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => addDebugExp(debugExp.get.withTerm(And(filteredTerms))) } - if (filteredTerms.nonEmpty) assumeWithoutSmokeChecks(InsertionOrderedSet(filteredTerms)) + val labelPrefix = if(debugExp.isDefined) "debugExp_" + debugExp.get.id else "" // FIXME ake + if (filteredTerms.nonEmpty) assumeWithoutSmokeChecks(InsertionOrderedSet(filteredTerms), labelPrefix=labelPrefix) } def debuggerAssume(terms: Iterable[Term], de: DebugExp) = { @@ -341,7 +348,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => }) } - private def assumeWithoutSmokeChecks(terms: InsertionOrderedSet[Term], isDefinition: Boolean = false) = { + private def assumeWithoutSmokeChecks(terms: InsertionOrderedSet[Term], isDefinition: Boolean = false, labelPrefix: String = "") = { val assumeRecord = new DeciderAssumeRecord(terms) val sepIdentifier = symbExLog.openScope(assumeRecord) @@ -353,7 +360,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } /* Add terms to the prover's assumptions */ - terms foreach prover.assume + terms.zipWithIndex.iterator.foreach{case (t, tid) => prover.assume(t, if(labelPrefix.isEmpty) "" else labelPrefix + "_" + tid)} symbExLog.closeScope(sepIdentifier) None diff --git a/src/main/scala/decider/ProverStdIO.scala b/src/main/scala/decider/ProverStdIO.scala index d64fbddd7..03bc7d3fc 100644 --- a/src/main/scala/decider/ProverStdIO.scala +++ b/src/main/scala/decider/ProverStdIO.scala @@ -39,6 +39,7 @@ abstract class ProverStdIO(uniqueId: String, protected var output: PrintWriter = _ protected var allDecls: Seq[Decl] = Seq() protected var allEmits: Seq[String] = Seq() + protected var proverLabelId: Int = 0 var proverPath: Path = _ var lastReasonUnknown : String = _ @@ -220,6 +221,11 @@ abstract class ProverStdIO(uniqueId: String, // private val quantificationLogger = bookkeeper.logfiles("quantification-problems") def assume(term: Term): Unit = { + assume(term, "prover_" + proverLabelId) + proverLabelId += 1 + } + + def assume(term: Term, label: String): Unit = { // /* Detect certain simple problems with quantifiers. // * Note that the current checks don't take in account whether or not a // * quantification occurs in positive or negative position. @@ -233,14 +239,18 @@ abstract class ProverStdIO(uniqueId: String, // problems.foreach(p => quantificationLogger.println(s" $p")) // } // }) - - assume(termConverter.convert(term)) + if(label.isEmpty){ + assume(term, "prover_" + proverLabelId) + proverLabelId += 1 + }else{ + assume(termConverter.convert(term), label) + } } - def assume(term: String): Unit = { + def assume(term: String, label: String): Unit = { // bookkeeper.assumptionCounter += 1 - writeLine("(assert " + term + ")") + writeLine("(assert (!" + term + ":named " + label + "))") readSuccess() } @@ -394,6 +404,10 @@ abstract class ProverStdIO(uniqueId: String, .replaceAll("\n", "\n; ") logToFile("; " + sanitisedStr) + if(sanitisedStr.equals("unsat")){ + writeLine("(get-unsat-core)") + comment("unsat core: " + input.readLine()) + } } def fresh(name: String, argSorts: Seq[Sort], resultSort: Sort): Fun = { diff --git a/src/main/scala/decider/Z3ProverAPI.scala b/src/main/scala/decider/Z3ProverAPI.scala index c4dccab4b..686d22425 100644 --- a/src/main/scala/decider/Z3ProverAPI.scala +++ b/src/main/scala/decider/Z3ProverAPI.scala @@ -263,6 +263,10 @@ class Z3ProverAPI(uniqueId: String, throw new ProverInteractionFailed(uniqueId, "Dynamically setting prover options via Z3 API is currently not supported.") } + def assume(term: Term, label: String): Unit = { + assume(term) // TODO ake + } + def assume(term: Term): Unit = { try { if (preamblePhaseOver) diff --git a/src/main/scala/interfaces/decider/Prover.scala b/src/main/scala/interfaces/decider/Prover.scala index 8c881b1e2..084529d71 100644 --- a/src/main/scala/interfaces/decider/Prover.scala +++ b/src/main/scala/interfaces/decider/Prover.scala @@ -34,6 +34,7 @@ trait ProverLike { } def setOption(name: String, value: String): String def assume(term: Term): Unit + def assume(term: Term, label: String): Unit def declare(decl: Decl): Unit def comment(content: String): Unit def saturate(timeout: Int, comment: String): Unit diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index e9450aa9c..d59001540 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -127,6 +127,11 @@ class DefaultMainVerifier(config: Config, _verificationPoolManager.pooledVerifiers.emit(contents) } + def assume(term: Term, label: String): Unit = { + decider.prover.assume(term, label) + _verificationPoolManager.pooledVerifiers.assume(term, label) + } + def assume(term: Term): Unit = { decider.prover.assume(term) _verificationPoolManager.pooledVerifiers.assume(term) diff --git a/src/main/scala/verifier/VerificationPoolManager.scala b/src/main/scala/verifier/VerificationPoolManager.scala index f694fb4ca..094dc8c19 100644 --- a/src/main/scala/verifier/VerificationPoolManager.scala +++ b/src/main/scala/verifier/VerificationPoolManager.scala @@ -27,6 +27,7 @@ class VerificationPoolManager(mainVerifier: MainVerifier) extends StatefulCompon def emit(content: String): Unit = workerVerifiers foreach (_.decider.prover.emit(content)) override def emit(contents: Iterable[String]): Unit = workerVerifiers foreach (_.decider.prover.emit(contents)) def assume(term: Term): Unit = workerVerifiers foreach (_.decider.prover.assume(term)) + def assume(term: Term, label: String): Unit = workerVerifiers foreach (_.decider.prover.assume(term, label)) override def assumeAxioms(terms: InsertionOrderedSet[Term], description: String): Unit = workerVerifiers foreach (_.decider.prover.assumeAxioms(terms, description)) def declare(decl: Decl): Unit = workerVerifiers foreach (_.decider.prover.declare(decl)) def comment(content: String): Unit = workerVerifiers foreach (_.decider.prover.comment(content)) diff --git a/src/test/resources/andrea/assert-assume.vpr b/src/test/resources/andrea/assert-assume.vpr new file mode 100644 index 000000000..1bbcb3ff6 --- /dev/null +++ b/src/test/resources/andrea/assert-assume.vpr @@ -0,0 +1,14 @@ + +function foo(n: Int): Int + requires n > 0 + ensures result >= 0 + + +method test(n: Int) + requires n > 0 +{ + var a: Int + a := foo(n) + assert a < 0 +} + From 1e0c108a51229bdf945ab0f43cab2b0c6d177800 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 17 Apr 2025 08:44:20 +0200 Subject: [PATCH 002/474] fix bug in debugger --- src/main/scala/debugger/SiliconDebugger.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/debugger/SiliconDebugger.scala b/src/main/scala/debugger/SiliconDebugger.scala index 9151ba603..93f7b807a 100644 --- a/src/main/scala/debugger/SiliconDebugger.scala +++ b/src/main/scala/debugger/SiliconDebugger.scala @@ -261,7 +261,7 @@ class SiliconDebugger(verificationResults: List[VerificationResult], } private def initVerifier(obl: ProofObligation, proverName: String, userArgsString: Option[String]): ProofObligation = { - val v = new WorkerVerifier(this.mainVerifier, obl.v.uniqueId, NoopReporter, false) + val v = new WorkerVerifier(this.mainVerifier, "debugger_01", NoopReporter, false) counter += 1 v.start() v.decider.createProver(proverName, userArgsString) From b7fc33979863fcbb3bf72accc7647238a4b6f487 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 22 Apr 2025 16:12:11 +0200 Subject: [PATCH 003/474] fix smt labels --- src/main/scala/decider/ProverStdIO.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/decider/ProverStdIO.scala b/src/main/scala/decider/ProverStdIO.scala index 03bc7d3fc..3f461aeda 100644 --- a/src/main/scala/decider/ProverStdIO.scala +++ b/src/main/scala/decider/ProverStdIO.scala @@ -250,7 +250,7 @@ abstract class ProverStdIO(uniqueId: String, def assume(term: String, label: String): Unit = { // bookkeeper.assumptionCounter += 1 - writeLine("(assert (!" + term + ":named " + label + "))") + writeLine("(assert (! " + term + " :named " + label + "))") readSuccess() } From e46bf551e42d1d2cdce14acc9955601ceb8d0cc5 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 22 Apr 2025 16:12:41 +0200 Subject: [PATCH 004/474] disable isKnownToBeTrue --- src/main/scala/Config.scala | 6 ++++++ src/main/scala/decider/Decider.scala | 22 ++++++++++++++++------ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/main/scala/Config.scala b/src/main/scala/Config.scala index b0fd1cfb6..efe4f8a28 100644 --- a/src/main/scala/Config.scala +++ b/src/main/scala/Config.scala @@ -824,6 +824,12 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { noshort = true ) + val enableAssumptionAnalysis: ScallopOption[Boolean] = opt[Boolean]("enableAssumptionAnalysis", + descr = "Enable assumption analysis mode", + default = Some(false), + noshort = true + ) + /* Option validation (trailing file argument is validated by parent class) */ validateOpt(prover) { diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 159c4f0b7..8dba14e77 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -404,12 +404,22 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => result } - private def isKnownToBeTrue(t: Term) = t match { - case True => true - // case eq: BuiltinEquals => eq.p0 == eq.p1 /* WARNING: Blocking trivial equalities might hinder axiom triggering. */ - case _ if pcs.assumptions contains t => true - case q: Quantification if q.body == True => true - case _ => false + private def isKnownToBeTrue(t: Term) = { + if(Verifier.config.enableAssumptionAnalysis()){ + false + }else{ + t match { + case True => + true + // case eq: BuiltinEquals => eq.p0 == eq.p1 /* WARNING: Blocking trivial equalities might hinder axiom triggering. */ + case _ if pcs.assumptions contains t => + true + case q: Quantification if q.body == True => + true + case _ => + false + } + } } private def proverAssert(t: Term, timeout: Option[Int]) = { From 599ecb27a3bc222ee2f02a206f63cfd11212b477 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 22 Apr 2025 16:13:16 +0200 Subject: [PATCH 005/474] add some test programs --- src/test/resources/andrea/assert-assume.vpr | 1 + src/test/resources/andrea/copy-inc.vpr | 23 +++++++++++++++++++ src/test/resources/andrea/framing.vpr | 19 +++++++++++++++ src/test/resources/andrea/gaussian.vpr | 15 ++++++++++++ src/test/resources/andrea/predicates-fold.vpr | 19 +++++++++++++++ .../resources/andrea/predicates-unfold.vpr | 21 +++++++++++++++++ .../resources/andrea/presentation-unsat.vpr | 16 +++++++++++++ src/test/resources/andrea/too-trivial.vpr | 10 ++++++++ 8 files changed, 124 insertions(+) create mode 100644 src/test/resources/andrea/copy-inc.vpr create mode 100644 src/test/resources/andrea/framing.vpr create mode 100644 src/test/resources/andrea/gaussian.vpr create mode 100644 src/test/resources/andrea/predicates-fold.vpr create mode 100644 src/test/resources/andrea/predicates-unfold.vpr create mode 100644 src/test/resources/andrea/presentation-unsat.vpr create mode 100644 src/test/resources/andrea/too-trivial.vpr diff --git a/src/test/resources/andrea/assert-assume.vpr b/src/test/resources/andrea/assert-assume.vpr index 1bbcb3ff6..272fef7c6 100644 --- a/src/test/resources/andrea/assert-assume.vpr +++ b/src/test/resources/andrea/assert-assume.vpr @@ -9,6 +9,7 @@ method test(n: Int) { var a: Int a := foo(n) + assert a >= 0 assert a < 0 } diff --git a/src/test/resources/andrea/copy-inc.vpr b/src/test/resources/andrea/copy-inc.vpr new file mode 100644 index 000000000..e8ad3b94b --- /dev/null +++ b/src/test/resources/andrea/copy-inc.vpr @@ -0,0 +1,23 @@ +field f: Int + +method copyAndInc(x: Ref, y: Ref) + requires acc(x.f) && acc(y.f, 1/2) + ensures acc(x.f) && acc(y.f, 1/2) + ensures x.f == y.f + 1 +{ + x.f := y.f + 1 +} + +method client(a: Ref, b: Ref) { + var L0: Bool, L1: Bool + inhale L0 ==> acc(a.f) + inhale L1 ==> acc(b.f) + assume L0 && L1 + + a.f := 1 + b.f := 3 + + copyAndInc(a, b) + + assert b.f == 3 && a.f == 4 +} diff --git a/src/test/resources/andrea/framing.vpr b/src/test/resources/andrea/framing.vpr new file mode 100644 index 000000000..c61503725 --- /dev/null +++ b/src/test/resources/andrea/framing.vpr @@ -0,0 +1,19 @@ + + +field f: Int + +method foo(a: Ref) returns(n:Int) + requires acc(a.f, 1/2) + ensures acc(a.f, 1/2) && n == a.f +{ + n := a.f +} + +method framing1(a: Ref) + requires acc(a.f) && a.f > 0 +{ + var n: Int + n := foo(a) + assert n > 0 + a.f := 5 +} diff --git a/src/test/resources/andrea/gaussian.vpr b/src/test/resources/andrea/gaussian.vpr new file mode 100644 index 000000000..03399a4e2 --- /dev/null +++ b/src/test/resources/andrea/gaussian.vpr @@ -0,0 +1,15 @@ + +method sum(n: Int) returns (res: Int) + requires 0 <= n + ensures res == n * (n + 1) / 2 +{ + res := 0 + var i: Int := 0; + while(i <= n) + invariant i <= (n + 1) + invariant res == (i - 1) * i / 2 + { + res := res + i + i := i + 1 + } +} diff --git a/src/test/resources/andrea/predicates-fold.vpr b/src/test/resources/andrea/predicates-fold.vpr new file mode 100644 index 000000000..23c039d9c --- /dev/null +++ b/src/test/resources/andrea/predicates-fold.vpr @@ -0,0 +1,19 @@ + + +predicate greater0(n: Int) +{ + n > 0 +} + +method foo(n: Int) + requires greater0(n) +{ + + var x: Int := 1 + + x := n + x + unfold greater0(n) + fold greater0(x) + + assert x > 1 +} \ No newline at end of file diff --git a/src/test/resources/andrea/predicates-unfold.vpr b/src/test/resources/andrea/predicates-unfold.vpr new file mode 100644 index 000000000..0efe58040 --- /dev/null +++ b/src/test/resources/andrea/predicates-unfold.vpr @@ -0,0 +1,21 @@ + +predicate greater0(n: Int) +{ + n > 0 +} + +method foo(n: Int, b: Bool) + requires b ==> greater0(n) // this is not reported in any unsat core! + // requires n > 0 +{ + var x: Int + if(b){ + unfold greater0(n) + x := n + 1 + fold greater0(n) + }else{ + x := 19 + } + + assert x > 1 +} \ No newline at end of file diff --git a/src/test/resources/andrea/presentation-unsat.vpr b/src/test/resources/andrea/presentation-unsat.vpr new file mode 100644 index 000000000..b71e897ee --- /dev/null +++ b/src/test/resources/andrea/presentation-unsat.vpr @@ -0,0 +1,16 @@ +field f: Int + +method foo(x: Ref, y: Ref) + requires acc(x.f) && acc(y.f) +{ + assume 0 < x.f + assume 0 < y.f + assume x.f < 100 + assume y.f < 100 + assume 0 < y.f + assume x.f < y.f + + assert x.f / y.f <= 1 +} + + diff --git a/src/test/resources/andrea/too-trivial.vpr b/src/test/resources/andrea/too-trivial.vpr new file mode 100644 index 000000000..7030ec0f2 --- /dev/null +++ b/src/test/resources/andrea/too-trivial.vpr @@ -0,0 +1,10 @@ +method foo() +{ + var i: Int + var n: Int + assume i > 0 + + n := i + + assert n > 0 // no unsat core, does not even make it to the SMT-LIB file -> isKnownToBeTrue +} \ No newline at end of file From 1120594e113edde68026306f183c683bbd922728 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 25 Apr 2025 15:43:15 +0200 Subject: [PATCH 006/474] poc: statement scope --- src/main/scala/decider/Decider.scala | 31 ++++++++++++++++++++++------ src/main/scala/rules/Executor.scala | 2 ++ 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 8dba14e77..6676f9750 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -34,6 +34,8 @@ import scala.collection.mutable trait Decider { def functionDecls: Set[FunctionDecl] + def pushStatementScope(stmt: ast.Stmt): Unit + def popStatementScope(): (Set[Term], Set[DebugExp]) def macroDecls: Vector[MacroDecl] def prover: Prover @@ -139,6 +141,21 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => var debugVariableTypes : Map[String, PType] = Map.empty + var stmtAssumptions: Set[DebugExp] = Set.empty + var stmtAssertions: Set[Term] = Set.empty + + override def pushStatementScope(stmt: ast.Stmt): Unit = { + stmtAssertions = Set.empty + stmtAssumptions = Set.empty + } + + override def popStatementScope(): (Set[Term], Set[DebugExp]) = { + val res = (stmtAssertions, stmtAssumptions) + stmtAssertions = Set.empty + stmtAssumptions = Set.empty + res + } + def setPcs(other: PathConditionStack) = { /* [BRANCH-PARALLELISATION] */ pathConditions = other @@ -307,6 +324,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => if (debugMode) { filteredAssumptions foreach (a => addDebugExp(a._2.get.withTerm(a._1))) + stmtAssumptions = stmtAssumptions.++(filteredAssumptions filter(t => t._2.isDefined) map(t => t._2.get.withTerm(t._1))) } @@ -318,6 +336,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } def assume(assumptions: Seq[Term], debugExps: Option[Seq[DebugExp]]): Unit = { + if(debugExps.isDefined) stmtAssumptions = stmtAssumptions.++(debugExps.get) val labelPrefix = if(debugExps.isDefined) "debugExp_" + debugExps.get.head.id else "" // FIXME ake assumeWithoutSmokeChecks(InsertionOrderedSet(assumptions), labelPrefix=labelPrefix) if (debugMode) { @@ -326,12 +345,14 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } def assume(terms: Iterable[Term], debugExp: Option[DebugExp], enforceAssumption: Boolean): Unit = { + val filteredTerms = if (enforceAssumption) terms else terms filterNot isKnownToBeTrue if (debugMode && filteredTerms.nonEmpty) { addDebugExp(debugExp.get.withTerm(And(filteredTerms))) + if(debugExp.isDefined) stmtAssumptions = stmtAssumptions.+(debugExp.get.withTerm(And(filteredTerms))) } val labelPrefix = if(debugExp.isDefined) "debugExp_" + debugExp.get.id else "" // FIXME ake @@ -397,7 +418,9 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => val assertRecord = new DeciderAssertRecord(t, timeout) val sepIdentifier = symbExLog.openScope(assertRecord) - val asserted = isKnownToBeTrue(t) + stmtAssertions = stmtAssertions.+(t) + + val asserted = if(Verifier.config.enableAssumptionAnalysis()) false else isKnownToBeTrue(t) val result = asserted || proverAssert(t, timeout) symbExLog.closeScope(sepIdentifier) @@ -405,10 +428,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } private def isKnownToBeTrue(t: Term) = { - if(Verifier.config.enableAssumptionAnalysis()){ - false - }else{ - t match { + t match { case True => true // case eq: BuiltinEquals => eq.p0 == eq.p1 /* WARNING: Blocking trivial equalities might hinder axiom triggering. */ @@ -419,7 +439,6 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => case _ => false } - } } private def proverAssert(t: Term, timeout: Option[Int]) = { diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 1de678d61..5ae6c4589 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -557,6 +557,7 @@ object executor extends ExecutionRules { Q(s1, v) case call @ ast.MethodCall(methodName, eArgs, lhs) => + v.decider.pushStatementScope(call) val meth = s.program.findMethod(methodName) val fargs = meth.formalArgs.map(_.localVar) val formalsToActuals: Map[ast.LocalVar, ast.Exp] = fargs.zip(eArgs).to(Map) @@ -599,6 +600,7 @@ object executor extends ExecutionRules { oldHeaps = s1.oldHeaps, recordVisited = s1.recordVisited) v3.symbExLog.closeScope(sepIdentifier) + val (assertionsA, assumptionsA) = v3.decider.popStatementScope() Q(s6, v3)})})}) case fold @ ast.Fold(pap @ ast.PredicateAccessPredicate(predAcc @ ast.PredicateAccess(eArgs, predicateName), _)) => From 5939bb52bab71883508fc59801722bd7bb726f72 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 30 Apr 2025 10:21:44 +0200 Subject: [PATCH 007/474] poc: assumption analyzer and graph --- .../debugger/AssumptionAnalysisGraph.scala | 129 ++++++++++++++++++ .../scala/debugger/AssumptionAnalyzer.scala | 73 ++++++++++ src/main/scala/decider/CVC5ProverStdIO.scala | 6 +- src/main/scala/decider/Decider.scala | 35 ++--- src/main/scala/decider/ProverStdIO.scala | 18 ++- src/main/scala/decider/Z3ProverStdIO.scala | 6 +- src/main/scala/rules/Executor.scala | 4 +- src/test/resources/andrea/branches.vpr | 31 +++++ src/test/resources/andrea/gaussian.vpr | 4 +- src/test/resources/andrea/method-sum.vpr | 23 ++++ .../resources/andrea/presentation-unsat.vpr | 10 +- src/test/resources/andrea/quickTest.vpr | 13 ++ 12 files changed, 315 insertions(+), 37 deletions(-) create mode 100644 src/main/scala/debugger/AssumptionAnalysisGraph.scala create mode 100644 src/main/scala/debugger/AssumptionAnalyzer.scala create mode 100644 src/test/resources/andrea/branches.vpr create mode 100644 src/test/resources/andrea/method-sum.vpr create mode 100644 src/test/resources/andrea/quickTest.vpr diff --git a/src/main/scala/debugger/AssumptionAnalysisGraph.scala b/src/main/scala/debugger/AssumptionAnalysisGraph.scala new file mode 100644 index 000000000..f74aba50b --- /dev/null +++ b/src/main/scala/debugger/AssumptionAnalysisGraph.scala @@ -0,0 +1,129 @@ +package debugger + +import viper.silicon.debugger.DebugExp +import viper.silicon.state.terms.Term +import viper.silver.ast + +import java.util.concurrent.atomic.AtomicInteger +import scala.collection.mutable + +trait AssumptionAnalysisGraph { + var nodes: mutable.Map[Int, AssumptionAnalysisNode] + + // TODO ake + // var groups: mutable.Map[GroupNode, Set[Int]] // e.g. statements -> assumptions/assertions + + var edges: mutable.Map[Int, Set[Int]] // e.g. assertion -> assumptions + + def addNode(node: AssumptionAnalysisNode): Unit + def addNodes(nodes: Set[AssumptionAnalysisNode]): Unit + def createAndAddNode(assumptions: Set[ast.Exp], assertions: Set[Term]): Unit + def addEdges(source: Int, targets: Set[Int]): Unit + + // def findDependentAssumptions(assertion, enableTransitivity=false) + // def findDependentAssertions(assumption, enableTransitivity=false) + // def findUnnecessaryAssumptions(enableTransitivity=false) + // def mergeIdenticalNodes() +} + +object AssumptionAnalysisGraphHelper { + private val idCounter: AtomicInteger = new AtomicInteger(0) + + def nextId(): Int = { + idCounter.getAndIncrement() + } + + def createNode(assumptions: Set[ast.Exp], assertions: Set[Term]): Set[AssumptionAnalysisNode] = { + (assumptions.size, assertions.size) match { + case (0, _) => assertions.map(new SimpleAssertionNode(_)) + case (_, 0) => assumptions.map(new SimpleAssumptionNode(_)) + case (_, _) => + val a0 = assertions.map(new SimpleAssertionNode(_)) + val a1 = assumptions.map(new SimpleAssumptionNode(_)) + Set{new ComplexAssumptionNode(a0, a1)} + } + } +} + +class DefaultAssumptionAnalysisGraph extends AssumptionAnalysisGraph { + override var nodes: mutable.Map[Int, AssumptionAnalysisNode] = mutable.Map.empty + override var edges: mutable.Map[Int, Set[Int]] = mutable.Map.empty + + override def addNode(node: AssumptionAnalysisNode): Unit = { + val identicalNodes = nodes.values.filter(node.equals) // TODO ake: when to merge identical nodes? + nodes.update(node.id, node) + } + + override def addNodes(nodes: Set[AssumptionAnalysisNode]): Unit = { + nodes foreach addNode + } + + override def addEdges(source: Int, targets: Set[Int]): Unit = { + val oldTargets = edges.getOrElse(source, Set.empty) + edges.update(source, oldTargets ++ targets) + } + + def createAndAddNode(assumptions: Set[ast.Exp], assertions: Set[Term]): Unit = { + AssumptionAnalysisGraphHelper.createNode(assumptions, assertions).foreach(addNode) + } +} + +trait AssumptionAnalysisNode { + val id: Int = AssumptionAnalysisGraphHelper.nextId() + + def equals(other: AssumptionAnalysisNode): Boolean +} + +class SimpleAssumptionNode(assumption: ast.Exp) extends AssumptionAnalysisNode { + + override def toString: String = assumption.toString + + def getAssumption: ast.Exp = assumption + + override def equals(other: AssumptionAnalysisNode): Boolean = { + other match { + case node: SimpleAssumptionNode => + node.getAssumption.equals(this.assumption) && node.getAssumption.pos.equals(this.assumption.pos) + case _ => false + } + } +} + +// TODO ake: ast.Exp instead of Term +class SimpleAssertionNode(assertion: Term) extends AssumptionAnalysisNode { + override def toString: String = assertion.toString + + def getAssertion: Term = assertion + + override def equals(other: AssumptionAnalysisNode): Boolean = { + other match { + case node: SimpleAssertionNode => node.getAssertion.equals(this.assertion) + case _ => false + } + } +} + +class StatementGroupNode(stmt: ast.Stmt, nodes: Set[AssumptionAnalysisNode]) extends AssumptionAnalysisNode { + override def toString: String = stmt.toString + " --> " + nodes.mkString(" && ") + + def getStmt: ast.Stmt = stmt + + override def equals(other: AssumptionAnalysisNode): Boolean = { + other match { + case node: StatementGroupNode => + node.getStmt.equals(this.stmt) && node.getStmt.pos.equals(this.stmt.pos) + case _ => false + } + } +} + +// TODO ake: or maybe instead have an map from Stmt -> (Set[Int], Set[Int]) in Assumption Graph +class ComplexAssumptionNode(assertions: Set[SimpleAssertionNode], assumptions: Set[SimpleAssumptionNode]) extends AssumptionAnalysisNode { + + override def toString: String = + assertions.mkString(" && ") + " --> " + assumptions.mkString(" && ") + + override def equals(other: AssumptionAnalysisNode): Boolean = { + false // TODO ake + } +} diff --git a/src/main/scala/debugger/AssumptionAnalyzer.scala b/src/main/scala/debugger/AssumptionAnalyzer.scala new file mode 100644 index 000000000..c3c1c9e07 --- /dev/null +++ b/src/main/scala/debugger/AssumptionAnalyzer.scala @@ -0,0 +1,73 @@ +package viper.silicon.debugger + +import debugger._ +import viper.silicon.state.terms.Term +import viper.silver.ast +import viper.silver.ast.Stmt + +import scala.collection.mutable + + +trait AssumptionAnalyzer { + def pushScope(stmt: ast.Stmt): Unit + def closeScope(): Unit + def addAssumptions(assumptions: Iterable[DebugExp]): Unit + def addAssertion(assertion: Term): Unit + def addDependency(dep: String): Unit + + val assumptionGraph: AssumptionAnalysisGraph = new DefaultAssumptionAnalysisGraph() +} + +class DefaultAssumptionAnalyzer extends AssumptionAnalyzer { + private var scope : mutable.Set[AssumptionAnalysisNode] = mutable.Set.empty + private var isScopeOpen: Boolean = false + private var scopeStmt: ast.Stmt = ast.Goto("nop")() + + override def pushScope(stmt: ast.Stmt): Unit = { + scopeStmt = stmt + scope = mutable.Set.empty + isScopeOpen = true + } + + override def closeScope(): Unit = { + assumptionGraph.addNode(new StatementGroupNode(scopeStmt, scope.toSet)) + scope = mutable.Set.empty + isScopeOpen = false + } + + override def addAssumptions(assumptions: Iterable[DebugExp]): Unit = { + val newNodes = assumptions.filter(_.originalExp.isDefined) + .map(a => new SimpleAssumptionNode(a.originalExp.get)) + assumptionGraph.addNodes(newNodes.toSet) + if(isScopeOpen) scope.addAll(newNodes) + } + + override def addAssertion(assertion: Term): Unit = { + val newNode = new SimpleAssertionNode(assertion) + assumptionGraph.addNode(newNode) + if(isScopeOpen) scope.addOne(newNode) + } + + override def addDependency(dep: String): Unit = { + val assumptions = dep.split(" ") + } +} + +class NoAssumptionAnalyzer extends AssumptionAnalyzer { + + override def pushScope(stmt: Stmt): Unit = { + } + + override def closeScope(): Unit = { + } + + override def addAssumptions(assumptions: Iterable[DebugExp]): Unit = { + } + + override def addAssertion(assertion: Term): Unit = { + } + + override def addDependency(dep: String): Unit = { + + } +} \ No newline at end of file diff --git a/src/main/scala/decider/CVC5ProverStdIO.scala b/src/main/scala/decider/CVC5ProverStdIO.scala index cece2a4e0..233f146eb 100644 --- a/src/main/scala/decider/CVC5ProverStdIO.scala +++ b/src/main/scala/decider/CVC5ProverStdIO.scala @@ -12,6 +12,7 @@ import viper.silicon.verifier.Verifier import viper.silver.verifier.{DefaultDependency => SilDefaultDependency} import viper.silver.reporter.Reporter import viper.silicon.common.config.Version +import viper.silicon.debugger.AssumptionAnalyzer object Cvc5ProverStdIO { val name = "cvc5" @@ -27,8 +28,9 @@ object Cvc5ProverStdIO { class Cvc5ProverStdIO(uniqueId: String, termConverter: TermToSMTLib2Converter, identifierFactory: IdentifierFactory, - reporter: Reporter) - extends ProverStdIO(uniqueId, termConverter, identifierFactory, reporter) { + reporter: Reporter, + assumptionAnalyzer: AssumptionAnalyzer) + extends ProverStdIO(uniqueId, termConverter, identifierFactory, reporter, assumptionAnalyzer) { val name: String = Cvc5ProverStdIO.name val minVersion: Version = Cvc5ProverStdIO.minVersion diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 6676f9750..b1d355676 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -7,7 +7,7 @@ package viper.silicon.decider import com.typesafe.scalalogging.Logger -import viper.silicon.debugger.DebugExp +import viper.silicon.debugger.{AssumptionAnalyzer, DebugExp, DefaultAssumptionAnalyzer, NoAssumptionAnalyzer} import viper.silicon._ import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.interfaces._ @@ -34,8 +34,6 @@ import scala.collection.mutable trait Decider { def functionDecls: Set[FunctionDecl] - def pushStatementScope(stmt: ast.Stmt): Unit - def popStatementScope(): (Set[Term], Set[DebugExp]) def macroDecls: Vector[MacroDecl] def prover: Prover @@ -103,6 +101,8 @@ trait Decider { def setPcs(other: PathConditionStack): Unit def statistics(): Map[String, String] + + val assumptionAnalyzer: AssumptionAnalyzer } /* @@ -131,6 +131,8 @@ 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 + + val assumptionAnalyzer: AssumptionAnalyzer = new DefaultAssumptionAnalyzer() // TODO ake: only if analysis is enabled def functionDecls: Set[FunctionDecl] = _declaredFreshFunctions def macroDecls: Vector[MacroDecl] = _declaredFreshMacros @@ -141,21 +143,6 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => var debugVariableTypes : Map[String, PType] = Map.empty - var stmtAssumptions: Set[DebugExp] = Set.empty - var stmtAssertions: Set[Term] = Set.empty - - override def pushStatementScope(stmt: ast.Stmt): Unit = { - stmtAssertions = Set.empty - stmtAssumptions = Set.empty - } - - override def popStatementScope(): (Set[Term], Set[DebugExp]) = { - val res = (stmtAssertions, stmtAssumptions) - stmtAssertions = Set.empty - stmtAssumptions = Set.empty - res - } - def setPcs(other: PathConditionStack) = { /* [BRANCH-PARALLELISATION] */ pathConditions = other @@ -171,8 +158,8 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } private def getProver(prover: String): Prover = prover match { - case Z3ProverStdIO.name => new Z3ProverStdIO(uniqueId, termConverter, identifierFactory, reporter) - case Cvc5ProverStdIO.name => new Cvc5ProverStdIO(uniqueId, termConverter, identifierFactory, reporter) + case Z3ProverStdIO.name => new Z3ProverStdIO(uniqueId, termConverter, identifierFactory, reporter, assumptionAnalyzer) + case Cvc5ProverStdIO.name => new Cvc5ProverStdIO(uniqueId, termConverter, identifierFactory, reporter, assumptionAnalyzer) case Z3ProverAPI.name => new Z3ProverAPI(uniqueId, new TermToZ3APIConverter(), identifierFactory, reporter, triggerGenerator) case prover => val msg1 = s"Unknown prover '$prover' provided. Defaulting to ${Z3ProverStdIO.name}." @@ -324,7 +311,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => if (debugMode) { filteredAssumptions foreach (a => addDebugExp(a._2.get.withTerm(a._1))) - stmtAssumptions = stmtAssumptions.++(filteredAssumptions filter(t => t._2.isDefined) map(t => t._2.get.withTerm(t._1))) + assumptionAnalyzer.addAssumptions(filteredAssumptions filter(t => t._2.isDefined) map(t => t._2.get.withTerm(t._1))) } @@ -336,7 +323,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } def assume(assumptions: Seq[Term], debugExps: Option[Seq[DebugExp]]): Unit = { - if(debugExps.isDefined) stmtAssumptions = stmtAssumptions.++(debugExps.get) + if(debugExps.isDefined) assumptionAnalyzer.addAssumptions(debugExps.get) val labelPrefix = if(debugExps.isDefined) "debugExp_" + debugExps.get.head.id else "" // FIXME ake assumeWithoutSmokeChecks(InsertionOrderedSet(assumptions), labelPrefix=labelPrefix) if (debugMode) { @@ -352,7 +339,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => if (debugMode && filteredTerms.nonEmpty) { addDebugExp(debugExp.get.withTerm(And(filteredTerms))) - if(debugExp.isDefined) stmtAssumptions = stmtAssumptions.+(debugExp.get.withTerm(And(filteredTerms))) + if(debugExp.isDefined) assumptionAnalyzer.addAssumptions(Set(debugExp.get.withTerm(And(filteredTerms)))) } val labelPrefix = if(debugExp.isDefined) "debugExp_" + debugExp.get.id else "" // FIXME ake @@ -418,7 +405,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => val assertRecord = new DeciderAssertRecord(t, timeout) val sepIdentifier = symbExLog.openScope(assertRecord) - stmtAssertions = stmtAssertions.+(t) + assumptionAnalyzer.addAssertion(t) val asserted = if(Verifier.config.enableAssumptionAnalysis()) false else isKnownToBeTrue(t) val result = asserted || proverAssert(t, timeout) diff --git a/src/main/scala/decider/ProverStdIO.scala b/src/main/scala/decider/ProverStdIO.scala index 3f461aeda..d8cecd539 100644 --- a/src/main/scala/decider/ProverStdIO.scala +++ b/src/main/scala/decider/ProverStdIO.scala @@ -11,6 +11,7 @@ import java.nio.file.Path import java.util.concurrent.TimeUnit import com.typesafe.scalalogging.LazyLogging import viper.silicon.common.config.Version +import viper.silicon.debugger.AssumptionAnalyzer import viper.silicon.interfaces.decider.{Prover, Result, Sat, Unknown, Unsat} import viper.silicon.reporting.{ExternalToolError, ProverInteractionFailed} import viper.silicon.state.IdentifierFactory @@ -26,7 +27,8 @@ import scala.collection.mutable abstract class ProverStdIO(uniqueId: String, termConverter: TermToSMTLib2Converter, identifierFactory: IdentifierFactory, - reporter: Reporter) + reporter: Reporter, + assumptionAnalyzer: AssumptionAnalyzer) extends Prover with LazyLogging { @@ -286,6 +288,9 @@ abstract class ProverStdIO(uniqueId: String, if (!result) { retrieveAndSaveModel() retrieveReasonUnknown() + }else{ + val unsatCore = extractUnsatCore() + assumptionAnalyzer.addDependency(unsatCore) } pop() @@ -293,6 +298,13 @@ abstract class ProverStdIO(uniqueId: String, (result, endTime - startTime) } + def extractUnsatCore(): String = { + writeLine("(get-unsat-core)") + val unsatCore = input.readLine() + comment("unsat core: " + unsatCore) + unsatCore + } + def saturate(data: Option[Config.ProverStateSaturationTimeout]): Unit = { data match { case Some(Config.ProverStateSaturationTimeout(timeout, comment)) => saturate(timeout, comment) @@ -404,10 +416,6 @@ abstract class ProverStdIO(uniqueId: String, .replaceAll("\n", "\n; ") logToFile("; " + sanitisedStr) - if(sanitisedStr.equals("unsat")){ - writeLine("(get-unsat-core)") - comment("unsat core: " + input.readLine()) - } } def fresh(name: String, argSorts: Seq[Sort], resultSort: Sort): Fun = { diff --git a/src/main/scala/decider/Z3ProverStdIO.scala b/src/main/scala/decider/Z3ProverStdIO.scala index cfd2f9122..30ea63c2d 100644 --- a/src/main/scala/decider/Z3ProverStdIO.scala +++ b/src/main/scala/decider/Z3ProverStdIO.scala @@ -12,6 +12,7 @@ import viper.silicon.verifier.Verifier import viper.silver.verifier.{DefaultDependency => SilDefaultDependency} import viper.silver.reporter.Reporter import viper.silicon.common.config.Version +import viper.silicon.debugger.AssumptionAnalyzer object Z3ProverStdIO { @@ -28,8 +29,9 @@ object Z3ProverStdIO { class Z3ProverStdIO(uniqueId: String, termConverter: TermToSMTLib2Converter, identifierFactory: IdentifierFactory, - reporter: Reporter) - extends ProverStdIO(uniqueId, termConverter, identifierFactory, reporter) { + reporter: Reporter, + assumptionAnalyzer: AssumptionAnalyzer) + extends ProverStdIO(uniqueId, termConverter, identifierFactory, reporter, assumptionAnalyzer) { val name = Z3ProverStdIO.name val minVersion = Z3ProverStdIO.minVersion diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 5ae6c4589..7c24d9499 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -557,7 +557,7 @@ object executor extends ExecutionRules { Q(s1, v) case call @ ast.MethodCall(methodName, eArgs, lhs) => - v.decider.pushStatementScope(call) + v.decider.assumptionAnalyzer.pushScope(call) val meth = s.program.findMethod(methodName) val fargs = meth.formalArgs.map(_.localVar) val formalsToActuals: Map[ast.LocalVar, ast.Exp] = fargs.zip(eArgs).to(Map) @@ -600,7 +600,7 @@ object executor extends ExecutionRules { oldHeaps = s1.oldHeaps, recordVisited = s1.recordVisited) v3.symbExLog.closeScope(sepIdentifier) - val (assertionsA, assumptionsA) = v3.decider.popStatementScope() + v3.decider.assumptionAnalyzer.closeScope() Q(s6, v3)})})}) case fold @ ast.Fold(pap @ ast.PredicateAccessPredicate(predAcc @ ast.PredicateAccess(eArgs, predicateName), _)) => diff --git a/src/test/resources/andrea/branches.vpr b/src/test/resources/andrea/branches.vpr new file mode 100644 index 000000000..00ebb755a --- /dev/null +++ b/src/test/resources/andrea/branches.vpr @@ -0,0 +1,31 @@ + +field f: Int + +method foo(a: Ref, b: Ref) + requires acc(a.f) && acc(b.f) +{ + var n:Int, c: Bool + + assume a.f > 0 + assume b.f > 0 + + if(c){ + n := a.f + }else{ + n := b.f + } + + var x : Int := a.f + + if(x > n){ + x := 2*x + }else{ + x := 2*n + b.f := 10 + assert x >= 1 + } + + n := b.f + + assert x > 0 +} \ No newline at end of file diff --git a/src/test/resources/andrea/gaussian.vpr b/src/test/resources/andrea/gaussian.vpr index 03399a4e2..eadc69710 100644 --- a/src/test/resources/andrea/gaussian.vpr +++ b/src/test/resources/andrea/gaussian.vpr @@ -1,12 +1,14 @@ method sum(n: Int) returns (res: Int) requires 0 <= n + requires n <= 5 ensures res == n * (n + 1) / 2 { res := 0 - var i: Int := 0; + var i: Int := 0 while(i <= n) invariant i <= (n + 1) + invariant i <= 6 invariant res == (i - 1) * i / 2 { res := res + i diff --git a/src/test/resources/andrea/method-sum.vpr b/src/test/resources/andrea/method-sum.vpr new file mode 100644 index 000000000..78f0e6d94 --- /dev/null +++ b/src/test/resources/andrea/method-sum.vpr @@ -0,0 +1,23 @@ +field f: Int + +method sum(x: Int, y: Int) returns(res: Int) + requires x >= 0 && y >= 0 + ensures res == x + y +//{ +// res := x + y +//} + + +method foo(x: Int, y: Int) +{ + assume x >= 0 + assume y >= 0 + assume x < y + var n: Int := sum(x, y) + var n2: Int := sum(x, y) + n := n + n2 + + assert n == 2*x + 2*y + assert n < 0 + +} \ No newline at end of file diff --git a/src/test/resources/andrea/presentation-unsat.vpr b/src/test/resources/andrea/presentation-unsat.vpr index b71e897ee..82015db77 100644 --- a/src/test/resources/andrea/presentation-unsat.vpr +++ b/src/test/resources/andrea/presentation-unsat.vpr @@ -1,8 +1,16 @@ field f: Int +field g: Int method foo(x: Ref, y: Ref) - requires acc(x.f) && acc(y.f) + { + var b1: Bool, b2: Bool, b3: Bool + inhale b1 ==> acc(x.f) + inhale b2 ==> acc(y.f) + inhale b3 ==> acc(y.g) + assume b1 + assume b2 + assume b3 assume 0 < x.f assume 0 < y.f assume x.f < 100 diff --git a/src/test/resources/andrea/quickTest.vpr b/src/test/resources/andrea/quickTest.vpr new file mode 100644 index 000000000..7b55a2250 --- /dev/null +++ b/src/test/resources/andrea/quickTest.vpr @@ -0,0 +1,13 @@ + +method foo() +{ + var n: Int + assume n > 0 && n < 100 + var b: Bool + var a: Int + a := 2*n + n := (a+1) * 4 + a := 2*n + + assert a == 4*n +} From 94b040d3e9683220ac2757cd0901055d47c58450 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 2 May 2025 14:26:42 +0200 Subject: [PATCH 008/474] create dedicated assumption analysis package --- .../AssumptionAnalysisGraph.scala | 3 +-- .../AssumptionAnalyzer.scala | 5 ++--- src/main/scala/decider/CVC5ProverStdIO.scala | 2 +- src/main/scala/decider/Decider.scala | 3 ++- src/main/scala/decider/ProverStdIO.scala | 2 +- src/main/scala/decider/Z3ProverStdIO.scala | 2 +- 6 files changed, 8 insertions(+), 9 deletions(-) rename src/main/scala/{debugger => assumptionAnalysis}/AssumptionAnalysisGraph.scala (98%) rename src/main/scala/{debugger => assumptionAnalysis}/AssumptionAnalyzer.scala (96%) diff --git a/src/main/scala/debugger/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala similarity index 98% rename from src/main/scala/debugger/AssumptionAnalysisGraph.scala rename to src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index f74aba50b..73bbf4b7e 100644 --- a/src/main/scala/debugger/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -1,6 +1,5 @@ -package debugger +package assumptionAnalysis -import viper.silicon.debugger.DebugExp import viper.silicon.state.terms.Term import viper.silver.ast diff --git a/src/main/scala/debugger/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala similarity index 96% rename from src/main/scala/debugger/AssumptionAnalyzer.scala rename to src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index c3c1c9e07..b145671a7 100644 --- a/src/main/scala/debugger/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -1,6 +1,6 @@ -package viper.silicon.debugger +package assumptionAnalysis -import debugger._ +import viper.silicon.debugger.DebugExp import viper.silicon.state.terms.Term import viper.silver.ast import viper.silver.ast.Stmt @@ -68,6 +68,5 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { } override def addDependency(dep: String): Unit = { - } } \ No newline at end of file diff --git a/src/main/scala/decider/CVC5ProverStdIO.scala b/src/main/scala/decider/CVC5ProverStdIO.scala index 233f146eb..98eab09ef 100644 --- a/src/main/scala/decider/CVC5ProverStdIO.scala +++ b/src/main/scala/decider/CVC5ProverStdIO.scala @@ -6,13 +6,13 @@ package viper.silicon.decider +import assumptionAnalysis.AssumptionAnalyzer import java.nio.file.{Path, Paths} import viper.silicon.state.IdentifierFactory import viper.silicon.verifier.Verifier import viper.silver.verifier.{DefaultDependency => SilDefaultDependency} import viper.silver.reporter.Reporter import viper.silicon.common.config.Version -import viper.silicon.debugger.AssumptionAnalyzer object Cvc5ProverStdIO { val name = "cvc5" diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index b1d355676..41d4dccc0 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -6,8 +6,9 @@ package viper.silicon.decider +import assumptionAnalysis.{AssumptionAnalyzer, DefaultAssumptionAnalyzer, NoAssumptionAnalyzer} import com.typesafe.scalalogging.Logger -import viper.silicon.debugger.{AssumptionAnalyzer, DebugExp, DefaultAssumptionAnalyzer, NoAssumptionAnalyzer} +import viper.silicon.debugger.DebugExp import viper.silicon._ import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.interfaces._ diff --git a/src/main/scala/decider/ProverStdIO.scala b/src/main/scala/decider/ProverStdIO.scala index d8cecd539..afe1388cc 100644 --- a/src/main/scala/decider/ProverStdIO.scala +++ b/src/main/scala/decider/ProverStdIO.scala @@ -6,12 +6,12 @@ package viper.silicon.decider +import assumptionAnalysis.AssumptionAnalyzer import java.io._ import java.nio.file.Path import java.util.concurrent.TimeUnit import com.typesafe.scalalogging.LazyLogging import viper.silicon.common.config.Version -import viper.silicon.debugger.AssumptionAnalyzer import viper.silicon.interfaces.decider.{Prover, Result, Sat, Unknown, Unsat} import viper.silicon.reporting.{ExternalToolError, ProverInteractionFailed} import viper.silicon.state.IdentifierFactory diff --git a/src/main/scala/decider/Z3ProverStdIO.scala b/src/main/scala/decider/Z3ProverStdIO.scala index 30ea63c2d..2e9663617 100644 --- a/src/main/scala/decider/Z3ProverStdIO.scala +++ b/src/main/scala/decider/Z3ProverStdIO.scala @@ -6,13 +6,13 @@ package viper.silicon.decider +import assumptionAnalysis.AssumptionAnalyzer import java.nio.file.{Path, Paths} import viper.silicon.state.IdentifierFactory import viper.silicon.verifier.Verifier import viper.silver.verifier.{DefaultDependency => SilDefaultDependency} import viper.silver.reporter.Reporter import viper.silicon.common.config.Version -import viper.silicon.debugger.AssumptionAnalyzer object Z3ProverStdIO { From 541b9be25224b035f38d0b93f5c856662a2ab88b Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 2 May 2025 14:27:10 +0200 Subject: [PATCH 009/474] add test examples --- src/test/resources/andrea/quickTest.vpr | 4 +++- src/test/resources/andrea/quickTestDep.vpr | 8 ++++++++ src/test/resources/andrea/transitivity.vpr | 22 ++++++++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 src/test/resources/andrea/quickTestDep.vpr create mode 100644 src/test/resources/andrea/transitivity.vpr diff --git a/src/test/resources/andrea/quickTest.vpr b/src/test/resources/andrea/quickTest.vpr index 7b55a2250..c1a44d513 100644 --- a/src/test/resources/andrea/quickTest.vpr +++ b/src/test/resources/andrea/quickTest.vpr @@ -1,4 +1,6 @@ +import "quickTestDep.vpr" + method foo() { var n: Int @@ -6,7 +8,7 @@ method foo() var b: Bool var a: Int a := 2*n - n := (a+1) * 4 + n := bar(a) a := 2*n assert a == 4*n diff --git a/src/test/resources/andrea/quickTestDep.vpr b/src/test/resources/andrea/quickTestDep.vpr new file mode 100644 index 000000000..f62a0cfec --- /dev/null +++ b/src/test/resources/andrea/quickTestDep.vpr @@ -0,0 +1,8 @@ + +method bar(x: Int) returns(y: Int) + requires x > 0 + ensures y > 0 && y == 2 * x +{ + y := 2 * x + assert y >= 0 +} \ No newline at end of file diff --git a/src/test/resources/andrea/transitivity.vpr b/src/test/resources/andrea/transitivity.vpr new file mode 100644 index 000000000..098b71862 --- /dev/null +++ b/src/test/resources/andrea/transitivity.vpr @@ -0,0 +1,22 @@ + +method level2(n: Int) returns(a:Int) + requires n > 0 + ensures a > 0 +{ + a := 2 * n +} + +method level1(n: Int) returns(a:Int) + requires n > 0 + ensures a > 0 +{ + a := level2(n) +} + +method client(n: Int) + requires n > 0 +{ + var a: Int + a := level1(n) + assert a > 0 +} \ No newline at end of file From b7b4485adbe2a9977e7a6efb99d499e4ab5066a4 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 5 May 2025 13:48:45 +0200 Subject: [PATCH 010/474] add test examples --- src/test/resources/andrea/dependencyTest.vpr | 15 ++++++++++ ...quickTestDep.vpr => dependencyTestDep.vpr} | 0 src/test/resources/andrea/permissions.vpr | 19 +++++++++++++ src/test/resources/andrea/quickTest.vpr | 28 +++++++++++-------- 4 files changed, 50 insertions(+), 12 deletions(-) create mode 100644 src/test/resources/andrea/dependencyTest.vpr rename src/test/resources/andrea/{quickTestDep.vpr => dependencyTestDep.vpr} (100%) create mode 100644 src/test/resources/andrea/permissions.vpr diff --git a/src/test/resources/andrea/dependencyTest.vpr b/src/test/resources/andrea/dependencyTest.vpr new file mode 100644 index 000000000..c03defa39 --- /dev/null +++ b/src/test/resources/andrea/dependencyTest.vpr @@ -0,0 +1,15 @@ + +import "dependencyTestDep.vpr" + +method foo() +{ + var n: Int + assume n > 0 && n < 100 + var b: Bool + var a: Int + a := 2*n + n := bar(a) + a := 2*n + + assert a == 4*n +} diff --git a/src/test/resources/andrea/quickTestDep.vpr b/src/test/resources/andrea/dependencyTestDep.vpr similarity index 100% rename from src/test/resources/andrea/quickTestDep.vpr rename to src/test/resources/andrea/dependencyTestDep.vpr diff --git a/src/test/resources/andrea/permissions.vpr b/src/test/resources/andrea/permissions.vpr new file mode 100644 index 000000000..90898b88d --- /dev/null +++ b/src/test/resources/andrea/permissions.vpr @@ -0,0 +1,19 @@ +field f: Int + + +method foo(a: Ref) + requires acc(a.f, 1/2) + ensures acc(a.f, 1/2) + + +method copyAndInc(x: Ref, y: Ref, z: Ref, p: Perm) + requires p > none + requires acc(x.f) && acc(y.f, p) && acc(z.f, 1/2) + ensures acc(x.f) && acc(y.f, p) +{ + assume p > 1/2 + x.f := y.f + 1 + assume y == z + foo(x) + assert x.f == z.f + 1 +} \ No newline at end of file diff --git a/src/test/resources/andrea/quickTest.vpr b/src/test/resources/andrea/quickTest.vpr index c1a44d513..90898b88d 100644 --- a/src/test/resources/andrea/quickTest.vpr +++ b/src/test/resources/andrea/quickTest.vpr @@ -1,15 +1,19 @@ +field f: Int -import "quickTestDep.vpr" -method foo() +method foo(a: Ref) + requires acc(a.f, 1/2) + ensures acc(a.f, 1/2) + + +method copyAndInc(x: Ref, y: Ref, z: Ref, p: Perm) + requires p > none + requires acc(x.f) && acc(y.f, p) && acc(z.f, 1/2) + ensures acc(x.f) && acc(y.f, p) { - var n: Int - assume n > 0 && n < 100 - var b: Bool - var a: Int - a := 2*n - n := bar(a) - a := 2*n - - assert a == 4*n -} + assume p > 1/2 + x.f := y.f + 1 + assume y == z + foo(x) + assert x.f == z.f + 1 +} \ No newline at end of file From ceb40ce6ecf7132a2cc073f6a764939a0c0d4f6b Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 5 May 2025 13:50:05 +0200 Subject: [PATCH 011/474] poc: symbolic heap --- .../AssumptionAnalysisGraph.scala | 96 +++++++++++++------ .../AssumptionAnalyzer.scala | 26 ++++- src/main/scala/decider/CVC5ProverStdIO.scala | 2 +- src/main/scala/decider/Decider.scala | 2 +- src/main/scala/decider/ProverStdIO.scala | 2 +- src/main/scala/decider/Z3ProverStdIO.scala | 2 +- src/main/scala/rules/ChunkSupporter.scala | 5 + src/main/scala/rules/Executor.scala | 6 +- .../rules/MoreCompleteExhaleSupporter.scala | 8 +- src/main/scala/rules/PredicateSupporter.scala | 2 + src/main/scala/rules/Producer.scala | 4 + src/main/scala/rules/StateConsolidator.scala | 8 +- 12 files changed, 123 insertions(+), 40 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index 73bbf4b7e..5ae8a6fe8 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -1,11 +1,18 @@ -package assumptionAnalysis +package viper.silicon.assumptionAnalysis +import viper.silicon.interfaces.state.Chunk import viper.silicon.state.terms.Term import viper.silver.ast import java.util.concurrent.atomic.AtomicInteger import scala.collection.mutable +object AssumptionType extends Enumeration { + type AssumptionType = Value + val Explicit, PathCondition, Implicit, Unknown = Value +} +import AssumptionType._ + trait AssumptionAnalysisGraph { var nodes: mutable.Map[Int, AssumptionAnalysisNode] @@ -16,8 +23,8 @@ trait AssumptionAnalysisGraph { def addNode(node: AssumptionAnalysisNode): Unit def addNodes(nodes: Set[AssumptionAnalysisNode]): Unit - def createAndAddNode(assumptions: Set[ast.Exp], assertions: Set[Term]): Unit def addEdges(source: Int, targets: Set[Int]): Unit + def addEdges(sources: Set[Int], target: Int): Unit // def findDependentAssumptions(assertion, enableTransitivity=false) // def findDependentAssertions(assumption, enableTransitivity=false) @@ -31,17 +38,6 @@ object AssumptionAnalysisGraphHelper { def nextId(): Int = { idCounter.getAndIncrement() } - - def createNode(assumptions: Set[ast.Exp], assertions: Set[Term]): Set[AssumptionAnalysisNode] = { - (assumptions.size, assertions.size) match { - case (0, _) => assertions.map(new SimpleAssertionNode(_)) - case (_, 0) => assumptions.map(new SimpleAssumptionNode(_)) - case (_, _) => - val a0 = assertions.map(new SimpleAssertionNode(_)) - val a1 = assumptions.map(new SimpleAssumptionNode(_)) - Set{new ComplexAssumptionNode(a0, a1)} - } - } } class DefaultAssumptionAnalysisGraph extends AssumptionAnalysisGraph { @@ -62,20 +58,39 @@ class DefaultAssumptionAnalysisGraph extends AssumptionAnalysisGraph { edges.update(source, oldTargets ++ targets) } - def createAndAddNode(assumptions: Set[ast.Exp], assertions: Set[Term]): Unit = { - AssumptionAnalysisGraphHelper.createNode(assumptions, assertions).foreach(addNode) + override def addEdges(sources: Set[Int], target: Int): Unit = { + sources.foreach(addEdges(_, Set(target))) } } trait AssumptionAnalysisNode { val id: Int = AssumptionAnalysisGraphHelper.nextId() + val assumptionType: AssumptionType def equals(other: AssumptionAnalysisNode): Boolean + + override def toString: String = id.toString + + def isIncludedInAnalysis: Boolean = assumptionType match { + case Explicit => true + case PathCondition => true + case Implicit => true + case Unknown => true + } +} + + +trait ChunkAnalysisInfo { + val chunk: Chunk + + override def toString: String = super.toString + " with chunk " + chunk.toString + + def getChunk: Chunk = chunk } -class SimpleAssumptionNode(assumption: ast.Exp) extends AssumptionAnalysisNode { +class SimpleAssumptionNode(assumption: ast.Exp, val assumptionType: AssumptionType = Unknown) extends AssumptionAnalysisNode { - override def toString: String = assumption.toString + override def toString: String = super.toString + ": " + assumption.toString def getAssumption: ast.Exp = assumption @@ -90,7 +105,8 @@ class SimpleAssumptionNode(assumption: ast.Exp) extends AssumptionAnalysisNode { // TODO ake: ast.Exp instead of Term class SimpleAssertionNode(assertion: Term) extends AssumptionAnalysisNode { - override def toString: String = assertion.toString + override val assumptionType: AssumptionType = AssumptionType.Explicit + override def toString: String = super.toString + ": " + assertion.toString def getAssertion: Term = assertion @@ -102,27 +118,49 @@ class SimpleAssertionNode(assertion: Term) extends AssumptionAnalysisNode { } } -class StatementGroupNode(stmt: ast.Stmt, nodes: Set[AssumptionAnalysisNode]) extends AssumptionAnalysisNode { - override def toString: String = stmt.toString + " --> " + nodes.mkString(" && ") - - def getStmt: ast.Stmt = stmt +class PermissionAssumptionNode(assumption: ast.Exp, val chunk: Chunk, assumptionType: AssumptionType = Unknown) extends SimpleAssumptionNode(assumption, assumptionType) with ChunkAnalysisInfo { + override def equals(other: AssumptionAnalysisNode): Boolean = { + other match { + case node: PermissionAssumptionNode => + node.getChunk.equals(this.chunk) && super.equals(other) + case _ => false + } + } +} +class PermissionAssertionNode(assertion: Term, val chunk: Chunk, assumptionType: AssumptionType = Unknown) extends SimpleAssertionNode(assertion) with ChunkAnalysisInfo { override def equals(other: AssumptionAnalysisNode): Boolean = { other match { - case node: StatementGroupNode => - node.getStmt.equals(this.stmt) && node.getStmt.pos.equals(this.stmt.pos) + case node: PermissionAssertionNode => + node.getChunk.equals(this.chunk) && super.equals(other) case _ => false } } } -// TODO ake: or maybe instead have an map from Stmt -> (Set[Int], Set[Int]) in Assumption Graph -class ComplexAssumptionNode(assertions: Set[SimpleAssertionNode], assumptions: Set[SimpleAssumptionNode]) extends AssumptionAnalysisNode { +class ChunkGroupNode(description: String, val chunk: Chunk, val assumptionType: AssumptionType = Unknown) extends AssumptionAnalysisNode with ChunkAnalysisInfo { + override def toString: String = description + " - " + super.toString - override def toString: String = - assertions.mkString(" && ") + " --> " + assumptions.mkString(" && ") override def equals(other: AssumptionAnalysisNode): Boolean = { - false // TODO ake + other match { + case node: ChunkAnalysisInfo => + node.getChunk.equals(this.chunk) + case _ => false + } } } + +//class StatementGroupNode(stmt: ast.Stmt, nodes: Set[AssumptionAnalysisNode], val assumptionType: AssumptionType) extends AssumptionAnalysisNode { +// override def toString: String = super.toString + ": " + stmt.toString + " --> " + nodes.mkString(" && ") +// +// def getStmt: ast.Stmt = stmt +// +// override def equals(other: AssumptionAnalysisNode): Boolean = { +// other match { +// case node: StatementGroupNode => +// node.getStmt.equals(this.stmt) && node.getStmt.pos.equals(this.stmt.pos) +// case _ => false +// } +// } +//} diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index b145671a7..ef9237454 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -1,6 +1,8 @@ -package assumptionAnalysis +package viper.silicon.assumptionAnalysis import viper.silicon.debugger.DebugExp +import viper.silicon.interfaces.state.Chunk +import viper.silicon.state.BasicChunk import viper.silicon.state.terms.Term import viper.silver.ast import viper.silver.ast.Stmt @@ -11,8 +13,10 @@ import scala.collection.mutable trait AssumptionAnalyzer { def pushScope(stmt: ast.Stmt): Unit def closeScope(): Unit + def addAssumptionNode(assumption: AssumptionAnalysisNode): Unit def addAssumptions(assumptions: Iterable[DebugExp]): Unit def addAssertion(assertion: Term): Unit + def addChunkNode(oldChunks: Set[Chunk], newChunkNode: AssumptionAnalysisNode) def addDependency(dep: String): Unit val assumptionGraph: AssumptionAnalysisGraph = new DefaultAssumptionAnalysisGraph() @@ -30,7 +34,7 @@ class DefaultAssumptionAnalyzer extends AssumptionAnalyzer { } override def closeScope(): Unit = { - assumptionGraph.addNode(new StatementGroupNode(scopeStmt, scope.toSet)) +// assumptionGraph.addNode(new StatementGroupNode(scopeStmt, scope.toSet)) scope = mutable.Set.empty isScopeOpen = false } @@ -51,6 +55,18 @@ class DefaultAssumptionAnalyzer extends AssumptionAnalyzer { override def addDependency(dep: String): Unit = { val assumptions = dep.split(" ") } + + override def addAssumptionNode(assumption: AssumptionAnalysisNode): Unit = { + assumptionGraph.addNode(assumption) + } + + override def addChunkNode(oldChunks: Set[Chunk], newChunkNode: AssumptionAnalysisNode): Unit = { + val analysisChunks = assumptionGraph.nodes.values + .filter(c => c.isInstanceOf[ChunkAnalysisInfo] && oldChunks.contains(c.asInstanceOf[ChunkAnalysisInfo].getChunk)) + .map(_.id).toSet + addAssumptionNode(newChunkNode) + assumptionGraph.addEdges(analysisChunks, newChunkNode.id) + } } class NoAssumptionAnalyzer extends AssumptionAnalyzer { @@ -69,4 +85,10 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { override def addDependency(dep: String): Unit = { } + + override def addAssumptionNode(assumption: AssumptionAnalysisNode): Unit = { + } + + override def addChunkNode(oldChunks: Set[Chunk], newChunkNode: AssumptionAnalysisNode): Unit = { + } } \ No newline at end of file diff --git a/src/main/scala/decider/CVC5ProverStdIO.scala b/src/main/scala/decider/CVC5ProverStdIO.scala index 98eab09ef..c2cdc0e91 100644 --- a/src/main/scala/decider/CVC5ProverStdIO.scala +++ b/src/main/scala/decider/CVC5ProverStdIO.scala @@ -6,7 +6,7 @@ package viper.silicon.decider -import assumptionAnalysis.AssumptionAnalyzer +import viper.silicon.assumptionAnalysis.AssumptionAnalyzer import java.nio.file.{Path, Paths} import viper.silicon.state.IdentifierFactory import viper.silicon.verifier.Verifier diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 41d4dccc0..7bf7fe65c 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -6,7 +6,7 @@ package viper.silicon.decider -import assumptionAnalysis.{AssumptionAnalyzer, DefaultAssumptionAnalyzer, NoAssumptionAnalyzer} +import viper.silicon.assumptionAnalysis.{AssumptionAnalyzer, DefaultAssumptionAnalyzer, NoAssumptionAnalyzer} import com.typesafe.scalalogging.Logger import viper.silicon.debugger.DebugExp import viper.silicon._ diff --git a/src/main/scala/decider/ProverStdIO.scala b/src/main/scala/decider/ProverStdIO.scala index afe1388cc..63b09780f 100644 --- a/src/main/scala/decider/ProverStdIO.scala +++ b/src/main/scala/decider/ProverStdIO.scala @@ -6,7 +6,7 @@ package viper.silicon.decider -import assumptionAnalysis.AssumptionAnalyzer +import viper.silicon.assumptionAnalysis.AssumptionAnalyzer import java.io._ import java.nio.file.Path import java.util.concurrent.TimeUnit diff --git a/src/main/scala/decider/Z3ProverStdIO.scala b/src/main/scala/decider/Z3ProverStdIO.scala index 2e9663617..50ac84bdb 100644 --- a/src/main/scala/decider/Z3ProverStdIO.scala +++ b/src/main/scala/decider/Z3ProverStdIO.scala @@ -6,7 +6,7 @@ package viper.silicon.decider -import assumptionAnalysis.AssumptionAnalyzer +import viper.silicon.assumptionAnalysis.AssumptionAnalyzer import java.nio.file.{Path, Paths} import viper.silicon.state.IdentifierFactory import viper.silicon.verifier.Verifier diff --git a/src/main/scala/rules/ChunkSupporter.scala b/src/main/scala/rules/ChunkSupporter.scala index b9ae2535c..58393107b 100644 --- a/src/main/scala/rules/ChunkSupporter.scala +++ b/src/main/scala/rules/ChunkSupporter.scala @@ -6,6 +6,7 @@ package viper.silicon.rules +import viper.silicon.assumptionAnalysis.{AssumptionType, PermissionAssumptionNode} import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.state._ import viper.silicon.interfaces.{Success, VerificationResult} @@ -181,6 +182,8 @@ object chunkSupporter extends ChunkSupportRules { val newPermExp = permsExp.map(pe => ast.PermSub(ch.permExp.get, toTakeExp.get)(pe.pos, pe.info, pe.errT)) val newChunk = ch.withPerm(PermMinus(ch.perm, toTake), newPermExp) val takenChunk = Some(ch.withPerm(toTake, toTakeExp)) + Option.when(withExp)(v.decider.assumptionAnalyzer.addChunkNode(Set(ch), new PermissionAssumptionNode(permsExp.get, newChunk, AssumptionType.Unknown))) + Option.when(withExp)(v.decider.assumptionAnalyzer.addChunkNode(Set(ch), new PermissionAssumptionNode(permsExp.get, takenChunk.get, AssumptionType.Unknown))) var newHeap = h - ch if (!v.decider.check(newChunk.perm === NoPerm, Verifier.config.checkTimeout())) { newHeap = newHeap + newChunk @@ -195,6 +198,8 @@ object chunkSupporter extends ChunkSupportRules { val newPermExp = permsExp.map(pe => ast.PermSub(ch.permExp.get, pe)(pe.pos, pe.info, pe.errT)) val newChunk = ch.withPerm(PermMinus(ch.perm, perms), newPermExp) val takenChunk = ch.withPerm(perms, permsExp) + Option.when(withExp)(v.decider.assumptionAnalyzer.addChunkNode(Set(ch), new PermissionAssumptionNode(permsExp.get, newChunk, AssumptionType.Unknown))) + Option.when(withExp)(v.decider.assumptionAnalyzer.addChunkNode(Set(ch), new PermissionAssumptionNode(permsExp.get, takenChunk, AssumptionType.Unknown))) val newHeap = h - ch + newChunk assumeProperties(newChunk, newHeap) (Complete(), s, newHeap, Some(takenChunk)) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 7c24d9499..dd5a89077 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -9,6 +9,7 @@ package viper.silicon.rules import viper.silicon.debugger.DebugExp import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.Config.JoinMode +import viper.silicon.assumptionAnalysis.{AssumptionType, PermissionAssumptionNode} import scala.annotation.unused import viper.silver.cfg.silver.SilverCfg @@ -440,6 +441,7 @@ object executor extends ExecutionRules { val (tSnap, _) = ssaifyRhs(tRhs, rhs, rhsNew, field.name, field.typ, v3, s3) val id = BasicChunkIdentifier(field.name) val newChunk = BasicChunk(FieldID, id, Seq(tRcvr), eRcvrNew.map(Seq(_)), tSnap, rhsNew, FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT))) + v3.decider.assumptionAnalyzer.addAssumptionNode(new PermissionAssumptionNode(eRcvr, newChunk, AssumptionType.Implicit)) chunkSupporter.produce(s3, h3, newChunk, v3)((s4, h4, v4) => { val s5 = s4.copy(h = h4) val (debugHeapName, _) = v4.getDebugOldLabel(s5, fa.pos) @@ -469,7 +471,9 @@ object executor extends ExecutionRules { quantifiedChunkSupporter.createSingletonQuantifiedChunk(Seq(`?r`), Option.when(withExp)(Seq(ast.LocalVarDecl("r", ast.Ref)(stmt.pos, stmt.info, stmt.errT))), field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), p, pExp, sm, s.program) } else { - BasicChunk(FieldID, BasicChunkIdentifier(field.name), Seq(tRcvr), Option.when(withExp)(Seq(x)), snap, snapExp, p, pExp) + val newChunk = BasicChunk(FieldID, BasicChunkIdentifier(field.name), Seq(tRcvr), Option.when(withExp)(Seq(x)), snap, snapExp, p, pExp) + Option.when(withExp)(v.decider.assumptionAnalyzer.addAssumptionNode(new PermissionAssumptionNode(pExp.get, newChunk, AssumptionType.Explicit))) + newChunk } }) val ts = viper.silicon.state.utils.computeReferenceDisjointnesses(s, tRcvr) diff --git a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala index 1aa582efd..ad37d85f7 100644 --- a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala +++ b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala @@ -6,11 +6,12 @@ package viper.silicon.rules +import viper.silicon.assumptionAnalysis.{AssumptionType, PermissionAssumptionNode} import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.state._ import viper.silicon.interfaces.{Success, VerificationResult} import viper.silicon.resources.{FieldID, NonQuantifiedPropertyInterpreter, Resources} -import viper.silicon.rules.chunkSupporter.findChunksWithID +import viper.silicon.rules.chunkSupporter.{findChunksWithID, withExp} import viper.silicon.state._ import viper.silicon.state.terms._ import viper.silicon.state.terms.perms.{IsNonPositive, IsPositive} @@ -360,6 +361,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { pSumExp = eqExp.map(eq => ast.PermAdd(pSumExp.get, ast.CondExp(eq, ch.permExp.get, ast.NoPerm()())(eq.pos, eq.info, eq.errT))()) val newChunk = ch.withPerm(PermMinus(ch.perm, pTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, pTakenExp.get)(pe.pos, pe.info, pe.errT))) + Option.when(withExp)(v.decider.assumptionAnalyzer.addChunkNode(Set(ch), new PermissionAssumptionNode(permsExp.get, newChunk, AssumptionType.Unknown))) pNeeded = PermMinus(pNeeded, pTaken) pNeededExp = permsExp.map(pe => ast.PermSub(pNeededExp.get, pTakenExp.get)(pe.pos, pe.info, pe.errT)) @@ -472,7 +474,9 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { newFr = newFr.recordPathSymbol(permTaken.applicable.asInstanceOf[Function]).recordConstraint(constraint) - ch.withPerm(PermMinus(ch.perm, permTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, permTakenExp.get)(pe.pos, pe.info, pe.errT))) + val newChunk = ch.withPerm(PermMinus(ch.perm, permTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, permTakenExp.get)(pe.pos, pe.info, pe.errT))) + Option.when(withExp)(v.decider.assumptionAnalyzer.addChunkNode(Set(ch), new PermissionAssumptionNode(permsExp.get, newChunk, AssumptionType.Unknown))) + newChunk }) val totalTakenBounds = diff --git a/src/main/scala/rules/PredicateSupporter.scala b/src/main/scala/rules/PredicateSupporter.scala index 7a0f814c1..654ef457c 100644 --- a/src/main/scala/rules/PredicateSupporter.scala +++ b/src/main/scala/rules/PredicateSupporter.scala @@ -6,6 +6,7 @@ package viper.silicon.rules +import viper.silicon.assumptionAnalysis.{AssumptionType, PermissionAssumptionNode} import viper.silicon.debugger.DebugExp import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.interfaces.VerificationResult @@ -114,6 +115,7 @@ object predicateSupporter extends PredicateSupportRules { Q(s3, v1) } else { val ch = BasicChunk(PredicateID, BasicChunkIdentifier(predicate.name), tArgs, eArgs, snap.get.convert(sorts.Snap), None, tPerm, ePerm) + Option.when(withExp)(v1.decider.assumptionAnalyzer.addAssumptionNode(new PermissionAssumptionNode(ePerm.get, ch, AssumptionType.Unknown))) val s3 = s2.copy(g = s.g, smDomainNeeded = s.smDomainNeeded, permissionScalingFactor = s.permissionScalingFactor, diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index abc3b9aa9..4777e80ae 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -8,6 +8,7 @@ package viper.silicon.rules import viper.silicon.debugger.DebugExp import viper.silicon.Config.JoinMode +import viper.silicon.assumptionAnalysis.{AssumptionType, PermissionAssumptionNode} import scala.collection.mutable import viper.silver.ast @@ -16,6 +17,7 @@ import viper.silver.verifier.PartialVerificationError import viper.silicon.interfaces.{Unreachable, VerificationResult} import viper.silicon.logger.records.data.{CondExpRecord, ImpliesRecord, ProduceRecord} import viper.silicon.resources.{FieldID, PredicateID} +import viper.silicon.rules.predicateSupporter.withExp import viper.silicon.state._ import viper.silicon.state.terms._ import viper.silicon.state.terms.predef.`?r` @@ -337,6 +339,7 @@ object producer extends ProductionRules { val (debugHeapName, debugLabel) = v3.getDebugOldLabel(s3, accPred.pos) val snapExp = Option.when(withExp)(ast.DebugLabelledOld(ast.FieldAccess(eRcvrNew.get, field)(), debugLabel)(accPred.pos, accPred.info, accPred.errT)) val ch = BasicChunk(FieldID, BasicChunkIdentifier(field.name), Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), snap, snapExp, gain, gainExp) + Option.when(withExp)(v1.decider.assumptionAnalyzer.addAssumptionNode(new PermissionAssumptionNode(gainExp.get, ch, AssumptionType.Unknown))) chunkSupporter.produce(s3, s3.h, ch, v3)((s4, h4, v4) => { val s5 = s4.copy(h = h4) val s6 = if (withExp) s5.copy(oldHeaps = s5.oldHeaps + (debugHeapName -> magicWandSupporter.getEvalHeap(s4))) else s5 @@ -364,6 +367,7 @@ object producer extends ProductionRules { } else { val snap1 = snap.convert(sorts.Snap) val ch = BasicChunk(PredicateID, BasicChunkIdentifier(predicate.name), tArgs, eArgsNew, snap1, None, gain, gainExp) + Option.when(withExp)(v1.decider.assumptionAnalyzer.addAssumptionNode(new PermissionAssumptionNode(gainExp.get, ch, AssumptionType.Unknown))) chunkSupporter.produce(s2, s2.h, ch, v2)((s3, h3, v3) => { if (Verifier.config.enablePredicateTriggersOnInhale() && s3.functionRecorder == NoopFunctionRecorder && !Verifier.config.disableFunctionUnfoldTrigger()) { diff --git a/src/main/scala/rules/StateConsolidator.scala b/src/main/scala/rules/StateConsolidator.scala index 24e987726..f54d01fa6 100644 --- a/src/main/scala/rules/StateConsolidator.scala +++ b/src/main/scala/rules/StateConsolidator.scala @@ -8,10 +8,12 @@ package viper.silicon.rules import viper.silicon.debugger.DebugExp import viper.silicon.Config +import viper.silicon.assumptionAnalysis.{AssumptionType, ChunkGroupNode, PermissionAssumptionNode} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.interfaces.state._ import viper.silicon.logger.records.data.{CommentRecord, SingleMergeRecord} import viper.silicon.resources.{NonQuantifiedPropertyInterpreter, Resources} +import viper.silicon.rules.predicateSupporter.withExp import viper.silicon.state._ import viper.silicon.state.terms._ import viper.silicon.state.terms.perms._ @@ -200,8 +202,10 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol private def mergeChunks(fr1: FunctionRecorder, chunk1: Chunk, chunk2: Chunk, qvars: Seq[Var], v: Verifier): Option[(FunctionRecorder, Chunk, Term)] = (chunk1, chunk2) match { case (BasicChunk(rid1, id1, args1, args1Exp, snap1, snap1Exp, perm1, perm1Exp), BasicChunk(_, _, _, _, snap2, _, perm2, perm2Exp)) => val (fr2, combinedSnap, snapEq) = combineSnapshots(fr1, snap1, snap2, perm1, perm2, qvars, v) - - Some(fr2, BasicChunk(rid1, id1, args1, args1Exp, combinedSnap, snap1Exp, PermPlus(perm1, perm2), perm1Exp.map(p1 => ast.PermAdd(p1, perm2Exp.get)())), snapEq) + val newExp = perm1Exp.map(p1 => ast.PermAdd(p1, perm2Exp.get)()) + val newChunk = BasicChunk(rid1, id1, args1, args1Exp, combinedSnap, snap1Exp, PermPlus(perm1, perm2), newExp) + Option.when(withExp)(v.decider.assumptionAnalyzer.addChunkNode(Set(chunk1, chunk2), new ChunkGroupNode("mergeChunks", newChunk, AssumptionType.Implicit))) + Some(fr2, newChunk, snapEq) case (l@QuantifiedFieldChunk(id1, fvf1, condition1, condition1Exp, perm1, perm1Exp, invs1, singletonRcvr1, singletonRcvr1Exp, hints1), r@QuantifiedFieldChunk(_, fvf2, _, _, perm2, perm2Exp, _, _, _, hints2)) => assert(l.quantifiedVars == Seq(`?r`)) From 4a7cfd1418700d88e474eb0d2a48246a1993ee05 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 5 May 2025 14:36:14 +0200 Subject: [PATCH 012/474] remove analysis scope --- src/main/scala/rules/Executor.scala | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index dd5a89077..ce0fd4e6a 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -561,7 +561,6 @@ object executor extends ExecutionRules { Q(s1, v) case call @ ast.MethodCall(methodName, eArgs, lhs) => - v.decider.assumptionAnalyzer.pushScope(call) val meth = s.program.findMethod(methodName) val fargs = meth.formalArgs.map(_.localVar) val formalsToActuals: Map[ast.LocalVar, ast.Exp] = fargs.zip(eArgs).to(Map) @@ -604,7 +603,6 @@ object executor extends ExecutionRules { oldHeaps = s1.oldHeaps, recordVisited = s1.recordVisited) v3.symbExLog.closeScope(sepIdentifier) - v3.decider.assumptionAnalyzer.closeScope() Q(s6, v3)})})}) case fold @ ast.Fold(pap @ ast.PredicateAccessPredicate(predAcc @ ast.PredicateAccess(eArgs, predicateName), _)) => From 3b0c03a3f1c9032de09f1414c78f620cfa5b3fe7 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 6 May 2025 13:43:59 +0200 Subject: [PATCH 013/474] change assumption graph internal representation --- .../assumptionAnalysis/AssumptionAnalysisGraph.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index 5ae8a6fe8..9ccd2646e 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -14,7 +14,7 @@ object AssumptionType extends Enumeration { import AssumptionType._ trait AssumptionAnalysisGraph { - var nodes: mutable.Map[Int, AssumptionAnalysisNode] + var nodes: Seq[AssumptionAnalysisNode] // TODO ake // var groups: mutable.Map[GroupNode, Set[Int]] // e.g. statements -> assumptions/assertions @@ -41,12 +41,12 @@ object AssumptionAnalysisGraphHelper { } class DefaultAssumptionAnalysisGraph extends AssumptionAnalysisGraph { - override var nodes: mutable.Map[Int, AssumptionAnalysisNode] = mutable.Map.empty + override var nodes: Seq[AssumptionAnalysisNode] = Seq() override var edges: mutable.Map[Int, Set[Int]] = mutable.Map.empty override def addNode(node: AssumptionAnalysisNode): Unit = { - val identicalNodes = nodes.values.filter(node.equals) // TODO ake: when to merge identical nodes? - nodes.update(node.id, node) + val identicalNodes = nodes.filter(node.equals) // TODO ake: when to merge identical nodes? + nodes = nodes :+ node } override def addNodes(nodes: Set[AssumptionAnalysisNode]): Unit = { From d222d56de08355fbd0a9873315318b63c03d6e9e Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 6 May 2025 13:45:25 +0200 Subject: [PATCH 014/474] poc: symbolic heap --- .../AssumptionAnalyzer.scala | 54 ++++++++---------- src/main/scala/rules/MagicWandSupporter.scala | 11 ++-- src/main/scala/rules/StateConsolidator.scala | 57 ++++++++++--------- src/main/scala/state/Chunks.scala | 37 ++++++++++-- 4 files changed, 96 insertions(+), 63 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index ef9237454..1dacaef72 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -2,17 +2,13 @@ package viper.silicon.assumptionAnalysis import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.state.Chunk -import viper.silicon.state.BasicChunk import viper.silicon.state.terms.Term import viper.silver.ast -import viper.silver.ast.Stmt - -import scala.collection.mutable trait AssumptionAnalyzer { - def pushScope(stmt: ast.Stmt): Unit - def closeScope(): Unit +// def pushScope(stmt: ast.Stmt): Unit +// def closeScope(): Unit def addAssumptionNode(assumption: AssumptionAnalysisNode): Unit def addAssumptions(assumptions: Iterable[DebugExp]): Unit def addAssertion(assertion: Term): Unit @@ -20,36 +16,38 @@ trait AssumptionAnalyzer { def addDependency(dep: String): Unit val assumptionGraph: AssumptionAnalysisGraph = new DefaultAssumptionAnalysisGraph() + + def getMethod: Option[ast.Method] + } -class DefaultAssumptionAnalyzer extends AssumptionAnalyzer { - private var scope : mutable.Set[AssumptionAnalysisNode] = mutable.Set.empty - private var isScopeOpen: Boolean = false - private var scopeStmt: ast.Stmt = ast.Goto("nop")() +class DefaultAssumptionAnalyzer(method: ast.Method) extends AssumptionAnalyzer { +// private var scope : mutable.Set[AssumptionAnalysisNode] = mutable.Set.empty +// private var isScopeOpen: Boolean = false +// private var scopeStmt: ast.Stmt = ??? - override def pushScope(stmt: ast.Stmt): Unit = { - scopeStmt = stmt - scope = mutable.Set.empty - isScopeOpen = true - } - override def closeScope(): Unit = { -// assumptionGraph.addNode(new StatementGroupNode(scopeStmt, scope.toSet)) - scope = mutable.Set.empty - isScopeOpen = false - } +// override def pushScope(stmt: ast.Stmt): Unit = { +//// scopeStmt = stmt +// scope = mutable.Set.empty +// isScopeOpen = true +// } +// +// override def closeScope(): Unit = { +//// assumptionGraph.addNode(new StatementGroupNode(scopeStmt, scope.toSet)) +// scope = mutable.Set.empty +// isScopeOpen = false +// } override def addAssumptions(assumptions: Iterable[DebugExp]): Unit = { val newNodes = assumptions.filter(_.originalExp.isDefined) .map(a => new SimpleAssumptionNode(a.originalExp.get)) assumptionGraph.addNodes(newNodes.toSet) - if(isScopeOpen) scope.addAll(newNodes) } override def addAssertion(assertion: Term): Unit = { val newNode = new SimpleAssertionNode(assertion) assumptionGraph.addNode(newNode) - if(isScopeOpen) scope.addOne(newNode) } override def addDependency(dep: String): Unit = { @@ -61,22 +59,18 @@ class DefaultAssumptionAnalyzer extends AssumptionAnalyzer { } override def addChunkNode(oldChunks: Set[Chunk], newChunkNode: AssumptionAnalysisNode): Unit = { - val analysisChunks = assumptionGraph.nodes.values + val analysisChunks = assumptionGraph.nodes .filter(c => c.isInstanceOf[ChunkAnalysisInfo] && oldChunks.contains(c.asInstanceOf[ChunkAnalysisInfo].getChunk)) .map(_.id).toSet addAssumptionNode(newChunkNode) assumptionGraph.addEdges(analysisChunks, newChunkNode.id) } + + override def getMethod: Option[ast.Method] = Some(method) } class NoAssumptionAnalyzer extends AssumptionAnalyzer { - override def pushScope(stmt: Stmt): Unit = { - } - - override def closeScope(): Unit = { - } - override def addAssumptions(assumptions: Iterable[DebugExp]): Unit = { } @@ -91,4 +85,6 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { override def addChunkNode(oldChunks: Set[Chunk], newChunkNode: AssumptionAnalysisNode): Unit = { } + + override def getMethod: Option[ast.Method] = None } \ No newline at end of file diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 53bbd71c5..c62df9d9b 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -8,6 +8,7 @@ package viper.silicon.rules import viper.silicon.debugger.DebugExp import viper.silicon._ +import viper.silicon.assumptionAnalysis.{AssumptionType, PermissionAssumptionNode} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.decider.RecordedPathConditions import viper.silicon.interfaces._ @@ -92,10 +93,12 @@ object magicWandSupporter extends SymbolicExecutionRules { v: Verifier) (Q: (State, MagicWandChunk, Verifier) => VerificationResult) : VerificationResult = { - evaluateWandArguments(s, wand, pve, v)((s1, ts, esNew, v1) => - Q(s1, MagicWandChunk(MagicWandIdentifier(wand, s.program), s1.g.values, ts, esNew, snap, FullPerm, - Option.when(withExp)(ast.FullPerm()(wand.pos, wand.info, wand.errT))), v1) - ) + evaluateWandArguments(s, wand, pve, v)((s1, ts, esNew, v1) => { + val newChunk = MagicWandChunk(MagicWandIdentifier(wand, s.program), s1.g.values, ts, esNew, snap, FullPerm, + Option.when(withExp)(ast.FullPerm()(wand.pos, wand.info, wand.errT))) + v.decider.assumptionAnalyzer.addAssumptionNode(new PermissionAssumptionNode(wand, newChunk, AssumptionType.Explicit)) + Q(s1, newChunk, v1) + }) } /** diff --git a/src/main/scala/rules/StateConsolidator.scala b/src/main/scala/rules/StateConsolidator.scala index f54d01fa6..1e0a69038 100644 --- a/src/main/scala/rules/StateConsolidator.scala +++ b/src/main/scala/rules/StateConsolidator.scala @@ -199,32 +199,37 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol // Merges two chunks that are aliases (i.e. that have the same id and the args are proven to be equal) // and returns the merged chunk or None, if the chunks could not be merged - private def mergeChunks(fr1: FunctionRecorder, chunk1: Chunk, chunk2: Chunk, qvars: Seq[Var], v: Verifier): Option[(FunctionRecorder, Chunk, Term)] = (chunk1, chunk2) match { - case (BasicChunk(rid1, id1, args1, args1Exp, snap1, snap1Exp, perm1, perm1Exp), BasicChunk(_, _, _, _, snap2, _, perm2, perm2Exp)) => - val (fr2, combinedSnap, snapEq) = combineSnapshots(fr1, snap1, snap2, perm1, perm2, qvars, v) - val newExp = perm1Exp.map(p1 => ast.PermAdd(p1, perm2Exp.get)()) - val newChunk = BasicChunk(rid1, id1, args1, args1Exp, combinedSnap, snap1Exp, PermPlus(perm1, perm2), newExp) - Option.when(withExp)(v.decider.assumptionAnalyzer.addChunkNode(Set(chunk1, chunk2), new ChunkGroupNode("mergeChunks", newChunk, AssumptionType.Implicit))) - Some(fr2, newChunk, snapEq) - case (l@QuantifiedFieldChunk(id1, fvf1, condition1, condition1Exp, perm1, perm1Exp, invs1, singletonRcvr1, singletonRcvr1Exp, hints1), - r@QuantifiedFieldChunk(_, fvf2, _, _, perm2, perm2Exp, _, _, _, hints2)) => - assert(l.quantifiedVars == Seq(`?r`)) - assert(r.quantifiedVars == Seq(`?r`)) - // We need to use l.perm/r.perm here instead of perm1 and perm2 since the permission amount might be dependent on the condition/domain - val (fr2, combinedSnap, snapEq) = quantifiedChunkSupporter.combineFieldSnapshotMaps(fr1, id1.name, fvf1, fvf2, l.perm, r.perm, v) - val permSum = PermPlus(perm1, perm2) - val permSumExp = perm1Exp.map(p1 => ast.PermAdd(p1, perm2Exp.get)()) - val bestHints = if (hints1.nonEmpty) hints1 else hints2 - Some(fr2, QuantifiedFieldChunk(id1, combinedSnap, condition1, condition1Exp, permSum, permSumExp, invs1, singletonRcvr1, singletonRcvr1Exp, bestHints), snapEq) - case (l@QuantifiedPredicateChunk(id1, qVars1, qVars1Exp, psf1, _, _, perm1, perm1Exp, _, _, _, _), - r@QuantifiedPredicateChunk(_, qVars2, qVars2Exp, psf2, condition2, condition2Exp, perm2, perm2Exp, invs2, singletonArgs2, singletonArgs2Exp, hints2)) => - val (fr2, combinedSnap, snapEq) = quantifiedChunkSupporter.combinePredicateSnapshotMaps(fr1, id1.name, qVars2, psf1, psf2, l.perm.replace(qVars1, qVars2), r.perm, v) - - val permSum = PermPlus(perm1.replace(qVars1, qVars2), perm2) - val permSumExp = perm1Exp.map(p1 => ast.PermAdd(p1.replace(qVars1Exp.get.zip(qVars2Exp.get).toMap), perm2Exp.get)()) - Some(fr2, QuantifiedPredicateChunk(id1, qVars2, qVars2Exp, combinedSnap, condition2, condition2Exp, permSum, permSumExp, invs2, singletonArgs2, singletonArgs2Exp, hints2), snapEq) - case _ => - None + private def mergeChunks(fr1: FunctionRecorder, chunk1: Chunk, chunk2: Chunk, qvars: Seq[Var], v: Verifier): Option[(FunctionRecorder, Chunk, Term)] = { + val result = (chunk1, chunk2) match { + case (BasicChunk(rid1, id1, args1, args1Exp, snap1, snap1Exp, perm1, perm1Exp), BasicChunk(_, _, _, _, snap2, _, perm2, perm2Exp)) => + val (fr2, combinedSnap, snapEq) = combineSnapshots(fr1, snap1, snap2, perm1, perm2, qvars, v) + val newExp = perm1Exp.map(p1 => ast.PermAdd(p1, perm2Exp.get)()) + Some(fr2, BasicChunk(rid1, id1, args1, args1Exp, combinedSnap, snap1Exp, PermPlus(perm1, perm2), newExp), snapEq) + case (l@QuantifiedFieldChunk(id1, fvf1, condition1, condition1Exp, perm1, perm1Exp, invs1, singletonRcvr1, singletonRcvr1Exp, hints1), + r@QuantifiedFieldChunk(_, fvf2, _, _, perm2, perm2Exp, _, _, _, hints2)) => + assert(l.quantifiedVars == Seq(`?r`)) + assert(r.quantifiedVars == Seq(`?r`)) + // We need to use l.perm/r.perm here instead of perm1 and perm2 since the permission amount might be dependent on the condition/domain + val (fr2, combinedSnap, snapEq) = quantifiedChunkSupporter.combineFieldSnapshotMaps(fr1, id1.name, fvf1, fvf2, l.perm, r.perm, v) + val permSum = PermPlus(perm1, perm2) + val permSumExp = perm1Exp.map(p1 => ast.PermAdd(p1, perm2Exp.get)()) + val bestHints = if (hints1.nonEmpty) hints1 else hints2 + Some(fr2, QuantifiedFieldChunk(id1, combinedSnap, condition1, condition1Exp, permSum, permSumExp, invs1, singletonRcvr1, singletonRcvr1Exp, bestHints), snapEq) + case (l@QuantifiedPredicateChunk(id1, qVars1, qVars1Exp, psf1, _, _, perm1, perm1Exp, _, _, _, _), + r@QuantifiedPredicateChunk(_, qVars2, qVars2Exp, psf2, condition2, condition2Exp, perm2, perm2Exp, invs2, singletonArgs2, singletonArgs2Exp, hints2)) => + val (fr2, combinedSnap, snapEq) = quantifiedChunkSupporter.combinePredicateSnapshotMaps(fr1, id1.name, qVars2, psf1, psf2, l.perm.replace(qVars1, qVars2), r.perm, v) + + val permSum = PermPlus(perm1.replace(qVars1, qVars2), perm2) + val permSumExp = perm1Exp.map(p1 => ast.PermAdd(p1.replace(qVars1Exp.get.zip(qVars2Exp.get).toMap), perm2Exp.get)()) + Some(fr2, QuantifiedPredicateChunk(id1, qVars2, qVars2Exp, combinedSnap, condition2, condition2Exp, permSum, permSumExp, invs2, singletonArgs2, singletonArgs2Exp, hints2), snapEq) + case _ => + None + } + if(result.isDefined){ + val (_, newChunk, _) = result.get + v.decider.assumptionAnalyzer.addChunkNode(Set(chunk1, chunk2), new ChunkGroupNode("mergeChunks", newChunk, AssumptionType.Implicit)) + } + result } /** Merge the snapshots of two chunks that denote the same location, i.e. whose ids and arguments diff --git a/src/main/scala/state/Chunks.scala b/src/main/scala/state/Chunks.scala index 638be4288..6d1f5b764 100644 --- a/src/main/scala/state/Chunks.scala +++ b/src/main/scala/state/Chunks.scala @@ -10,6 +10,7 @@ import viper.silver.ast import viper.silicon.interfaces.state._ import viper.silicon.resources._ import viper.silicon.rules.InverseFunctions +import viper.silicon.state.BasicChunk.createDerivedChunk import viper.silicon.state.terms._ import viper.silicon.state.terms.predef.`?r` import viper.silicon.verifier.Verifier @@ -28,7 +29,35 @@ case class BasicChunkIdentifier(name: String) extends ChunkIdentifer { override def toString = name } -case class BasicChunk(resourceID: BaseID, +object BasicChunk { + def apply(resourceID: BaseID, + id: BasicChunkIdentifier, + args: Seq[Term], + argsExp: Option[Seq[ast.Exp]], + snap: Term, + snapExp: Option[ast.Exp], + perm: Term, + permExp: Option[ast.Exp]): BasicChunk = { + new BasicChunk(resourceID, id, args, argsExp, snap, snapExp, perm, permExp) + // TODO ake: add to assumption graph + } + + def createDerivedChunk(oldChunk: BasicChunk, + resourceID: BaseID, + id: BasicChunkIdentifier, + args: Seq[Term], + argsExp: Option[Seq[ast.Exp]], + snap: Term, + snapExp: Option[ast.Exp], + perm: Term, + permExp: Option[ast.Exp]): BasicChunk = { + val newChunk = apply(resourceID, id, args, argsExp, snap, snapExp, perm, permExp) + // TODO ake: add edge + newChunk + } +} + +case class BasicChunk private (resourceID: BaseID, id: BasicChunkIdentifier, args: Seq[Term], argsExp: Option[Seq[ast.Exp]], @@ -50,8 +79,8 @@ case class BasicChunk(resourceID: BaseID, withPerm(PermMinus(perm, newPerm), newPermExp.map(npe => ast.PermSub(permExp.get, npe)())) override def permPlus(newPerm: Term, newPermExp: Option[ast.Exp]) = withPerm(PermPlus(perm, newPerm), newPermExp.map(npe => ast.PermAdd(permExp.get, npe)())) - override def withPerm(newPerm: Term, newPermExp: Option[ast.Exp]) = BasicChunk(resourceID, id, args, argsExp, snap, snapExp, newPerm, newPermExp) - override def withSnap(newSnap: Term, newSnapExp: Option[ast.Exp]) = BasicChunk(resourceID, id, args, argsExp, newSnap, newSnapExp, perm, permExp) + override def withPerm(newPerm: Term, newPermExp: Option[ast.Exp]) = createDerivedChunk(this, resourceID, id, args, argsExp, snap, snapExp, newPerm, newPermExp) + override def withSnap(newSnap: Term, newSnapExp: Option[ast.Exp]) = createDerivedChunk(this, resourceID, id, args, argsExp, newSnap, newSnapExp, perm, permExp) override lazy val toString = resourceID match { case FieldID => s"${args.head}.$id -> $snap # $perm" @@ -75,7 +104,7 @@ sealed trait QuantifiedBasicChunk extends QuantifiedChunk { * to potentially multiple locations, consider using regular, non-quantified * chunks instead. */ -case class QuantifiedFieldChunk(id: BasicChunkIdentifier, +case class QuantifiedFieldChunk private(id: BasicChunkIdentifier, fvf: Term, condition: Term, conditionExp: Option[ast.Exp], From 5ad3c25bf65210887d11f97b9f50e02f28157672 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 6 May 2025 13:45:52 +0200 Subject: [PATCH 015/474] create one graph per method --- src/main/scala/decider/Decider.scala | 9 +- src/main/scala/interfaces/Verification.scala | 2 + .../scala/supporters/MethodSupporter.scala | 7 +- .../scala/verifier/DefaultMainVerifier.scala | 6 + src/test/resources/andrea/framing.vpr | 1 + src/test/resources/andrea/manyMethods.vpr | 113 ++++++++++++++++++ 6 files changed, 133 insertions(+), 5 deletions(-) create mode 100644 src/test/resources/andrea/manyMethods.vpr diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 7bf7fe65c..cbb0f2247 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -103,7 +103,7 @@ trait Decider { def statistics(): Map[String, String] - val assumptionAnalyzer: AssumptionAnalyzer + var assumptionAnalyzer: AssumptionAnalyzer // TODO ake } /* @@ -133,7 +133,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => private var _proverResetOptions: Map[String, String] = Map.empty private val _debuggerAssumedTerms: mutable.Set[Term] = mutable.Set.empty - val assumptionAnalyzer: AssumptionAnalyzer = new DefaultAssumptionAnalyzer() // TODO ake: only if analysis is enabled + var assumptionAnalyzer: AssumptionAnalyzer = new NoAssumptionAnalyzer() def functionDecls: Set[FunctionDecl] = _declaredFreshFunctions def macroDecls: Vector[MacroDecl] = _declaredFreshMacros @@ -406,9 +406,10 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => val assertRecord = new DeciderAssertRecord(t, timeout) val sepIdentifier = symbExLog.openScope(assertRecord) - assumptionAnalyzer.addAssertion(t) - val asserted = if(Verifier.config.enableAssumptionAnalysis()) false else isKnownToBeTrue(t) + + val asserted = if(Verifier.config.enableAssumptionAnalysis()) t.equals(True) else isKnownToBeTrue(t) + if(!asserted) assumptionAnalyzer.addAssertion(t) val result = asserted || proverAssert(t, timeout) symbExLog.closeScope(sepIdentifier) diff --git a/src/main/scala/interfaces/Verification.scala b/src/main/scala/interfaces/Verification.scala index ad52d3bf2..e78bc26f5 100644 --- a/src/main/scala/interfaces/Verification.scala +++ b/src/main/scala/interfaces/Verification.scala @@ -6,6 +6,7 @@ package viper.silicon.interfaces +import viper.silicon.assumptionAnalysis.{AssumptionAnalyzer, NoAssumptionAnalyzer} import viper.silicon.debugger.{DebugAxiom, DebugExp, DebugExpPrintConfiguration} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.interfaces.state.Chunk @@ -30,6 +31,7 @@ sealed abstract class VerificationResult { var previous: Vector[VerificationResult] = Vector() //Sets had problems with equality val continueVerification: Boolean = true var isReported: Boolean = false + var assumptionAnalyzer: AssumptionAnalyzer = new NoAssumptionAnalyzer() // TODO ake def isFatal: Boolean def &&(other: => VerificationResult): VerificationResult diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index 9b483ff4a..8eefc6109 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -7,6 +7,7 @@ package viper.silicon.supporters import com.typesafe.scalalogging.Logger +import viper.silicon.assumptionAnalysis.{DefaultAssumptionAnalyzer, NoAssumptionAnalyzer} import viper.silver.ast import viper.silver.components.StatefulComponent import viper.silver.verifier.errors._ @@ -19,7 +20,7 @@ import viper.silicon.state.State.OldHeaps import viper.silicon.verifier.{Verifier, VerifierComponent} import viper.silicon.utils.freshSnap import viper.silver.reporter.AnnotationWarning -import viper.silicon.{Map, toMap} +import viper.silicon.{Config, Map, toMap} /* TODO: Consider changing the DefaultMethodVerificationUnitProvider into a SymbolicExecutionRule */ @@ -46,6 +47,8 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif logger.debug("\n\n" + "-" * 10 + " METHOD " + method.name + "-" * 10 + "\n") decider.prover.comment("%s %s %s".format("-" * 10, method.name, "-" * 10)) + v.decider.assumptionAnalyzer = if(Verifier.config.enableAssumptionAnalysis()) new DefaultAssumptionAnalyzer(method) else new NoAssumptionAnalyzer() + val proverOptions: Map[String, String] = method.info.getUniqueInfo[ast.AnnotationInfo] match { case Some(ai) if ai.values.contains("proverArgs") => toMap(ai.values("proverArgs").flatMap(o => { @@ -114,6 +117,8 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif consumes(s4, posts, false, postViolated, v4)((_, _, _) => Success()))}) } )})}) + result.assumptionAnalyzer = v.decider.assumptionAnalyzer + v.decider.assumptionAnalyzer = new NoAssumptionAnalyzer() v.decider.resetProverOptions() symbExLog.closeMemberScope() diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index d59001540..6325afbef 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -17,6 +17,7 @@ import scala.util.Random import viper.silver.ast import viper.silver.components.StatefulComponent import viper.silicon._ +import viper.silicon.assumptionAnalysis.DefaultAssumptionAnalyzer import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.decider.SMTLib2PreambleReader import viper.silicon.extensions.ConditionalPermissionRewriter @@ -311,6 +312,11 @@ class DefaultMainVerifier(config: Config, debugger.startDebugger() } + if(Verifier.config.enableAssumptionAnalysis()){ + val assumptionAnalyzers = verificationResults.filter(_.assumptionAnalyzer.isInstanceOf[DefaultAssumptionAnalyzer]).map(_.assumptionAnalyzer) + logger debug s"assumption analyzers ${assumptionAnalyzers.mkString(", ")}" + } + verificationResults } diff --git a/src/test/resources/andrea/framing.vpr b/src/test/resources/andrea/framing.vpr index c61503725..bc028121e 100644 --- a/src/test/resources/andrea/framing.vpr +++ b/src/test/resources/andrea/framing.vpr @@ -11,6 +11,7 @@ method foo(a: Ref) returns(n:Int) method framing1(a: Ref) requires acc(a.f) && a.f > 0 + ensures acc(a.f) { var n: Int n := foo(a) diff --git a/src/test/resources/andrea/manyMethods.vpr b/src/test/resources/andrea/manyMethods.vpr new file mode 100644 index 000000000..563ea86b1 --- /dev/null +++ b/src/test/resources/andrea/manyMethods.vpr @@ -0,0 +1,113 @@ + +field f: Int + +method foo1(n: Int) + requires n > 0 +{ + var a : Bool := n >= 0 + assert a +} + + +method foo2(n: Int) + requires n < 0 +{ + var a : Bool := n >= 0 + assert !a +} + +method foo3(n: Int) + requires n != 0 +{ + var a : Bool := n > 0 || n < 0 + assert a +} + +method foo4(n: Int, x: Ref) + requires n != 0 + requires acc(x.f) + ensures acc(x.f) +{ + var a : Bool := n > 0 + if(a){ + x.f := n + }else{ + x.f := -n + } + + assert x.f >= n +} + +method foo5(n: Int, x: Ref) + requires n != 0 + requires acc(x.f) + ensures acc(x.f) +{ + var a : Bool := n > 0 + if(a){ + x.f := n + }else{ + x.f := -n + } + + assert x.f > 0 +} + + +method foo6(n: Int, x: Ref) + requires acc(x.f) + ensures acc(x.f) +{ + var a : Bool := n > 0 + if(a){ + x.f := n + }else{ + x.f := -n + } + + assert x.f >= 0 +} + + +method foo7(n: Int, x: Ref) + requires acc(x.f) && x.f > 0 + ensures acc(x.f) && x.f <= 0 +{ + var a : Bool := n > 0 + if(a){ + x.f := -n + }else{ + x.f := n + } + + assert x.f <= n +} + +method foo8(n: Int, x: Ref) + requires acc(x.f) && x.f > 0 + ensures acc(x.f) && x.f < 0 +{ + var a : Bool := n > 0 + if(a){ + x.f := -n + }else{ + x.f := n - 1 + } + + assert x.f < n +} + +method foo9(n: Int, x: Ref) + requires n != 0 + requires acc(x.f) && x.f > 0 + ensures acc(x.f) && x.f < 0 +{ + var a : Bool := n > 0 + if(a){ + x.f := -n + }else{ + x.f := n + } + + assert x.f <= n +} \ No newline at end of file From c3b8d186b4fc7c9ebe4a56d8dd2a2645392bcf73 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 8 May 2025 09:17:39 +0200 Subject: [PATCH 016/474] parse unsat core and add edges to assumption graph --- .../AssumptionAnalysisGraph.scala | 43 ++++++++++---- .../AssumptionAnalyzer.scala | 35 +++++++++--- src/main/scala/decider/CVC5ProverStdIO.scala | 8 +-- src/main/scala/decider/Decider.scala | 56 +++++++++++++------ src/main/scala/decider/ProverStdIO.scala | 11 +++- src/main/scala/decider/Z3ProverAPI.scala | 5 ++ src/main/scala/decider/Z3ProverStdIO.scala | 5 +- .../scala/interfaces/decider/Prover.scala | 3 + .../scala/supporters/MethodSupporter.scala | 4 +- .../scala/verifier/DefaultMainVerifier.scala | 10 ++-- 10 files changed, 128 insertions(+), 52 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index 9ccd2646e..eff0da2e5 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -13,6 +13,22 @@ object AssumptionType extends Enumeration { } import AssumptionType._ +// TODO ake: make sure that the string does not contain invalid characters +class AssumptionLabel(description: String, id: Option[Int], offset: Int = 0) { + override def toString: String = + if(id.isDefined) description.trim + "_" + id.get + "_" + offset + else "" +} + + +object AssumptionAnalysisGraphHelper { + private val idCounter: AtomicInteger = new AtomicInteger(0) + + def nextId(): Int = { + idCounter.getAndIncrement() + } +} + trait AssumptionAnalysisGraph { var nodes: Seq[AssumptionAnalysisNode] @@ -23,8 +39,8 @@ trait AssumptionAnalysisGraph { def addNode(node: AssumptionAnalysisNode): Unit def addNodes(nodes: Set[AssumptionAnalysisNode]): Unit - def addEdges(source: Int, targets: Set[Int]): Unit - def addEdges(sources: Set[Int], target: Int): Unit + def addEdges(source: Int, targets: Iterable[Int]): Unit + def addEdges(sources: Iterable[Int], target: Int): Unit // def findDependentAssumptions(assertion, enableTransitivity=false) // def findDependentAssertions(assumption, enableTransitivity=false) @@ -32,13 +48,6 @@ trait AssumptionAnalysisGraph { // def mergeIdenticalNodes() } -object AssumptionAnalysisGraphHelper { - private val idCounter: AtomicInteger = new AtomicInteger(0) - - def nextId(): Int = { - idCounter.getAndIncrement() - } -} class DefaultAssumptionAnalysisGraph extends AssumptionAnalysisGraph { override var nodes: Seq[AssumptionAnalysisNode] = Seq() @@ -53,12 +62,12 @@ class DefaultAssumptionAnalysisGraph extends AssumptionAnalysisGraph { nodes foreach addNode } - override def addEdges(source: Int, targets: Set[Int]): Unit = { + override def addEdges(source: Int, targets: Iterable[Int]): Unit = { val oldTargets = edges.getOrElse(source, Set.empty) edges.update(source, oldTargets ++ targets) } - override def addEdges(sources: Set[Int], target: Int): Unit = { + override def addEdges(sources: Iterable[Int], target: Int): Unit = { sources.foreach(addEdges(_, Set(target))) } } @@ -103,6 +112,18 @@ class SimpleAssumptionNode(assumption: ast.Exp, val assumptionType: AssumptionTy } } +// TODO ake: remove this +class StringAssumptionNode(description: String, val assumptionType: AssumptionType = Unknown) extends AssumptionAnalysisNode { + + override def toString: String = super.toString + ": " + description + + def getAssumption: String = description + + override def equals(other: AssumptionAnalysisNode): Boolean = { + other.id == this.id + } +} + // TODO ake: ast.Exp instead of Term class SimpleAssertionNode(assertion: Term) extends AssumptionAnalysisNode { override val assumptionType: AssumptionType = AssumptionType.Explicit diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 1dacaef72..efb43a8dc 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -10,8 +10,9 @@ trait AssumptionAnalyzer { // def pushScope(stmt: ast.Stmt): Unit // def closeScope(): Unit def addAssumptionNode(assumption: AssumptionAnalysisNode): Unit - def addAssumptions(assumptions: Iterable[DebugExp]): Unit - def addAssertion(assertion: Term): Unit + def addSingleAssumption(assumption: DebugExp): Option[Int] + def addAssumptions(assumptions: Iterable[DebugExp]): Seq[Int] + def addAssertion(assertion: Term): Option[Int] def addChunkNode(oldChunks: Set[Chunk], newChunkNode: AssumptionAnalysisNode) def addDependency(dep: String): Unit @@ -39,19 +40,32 @@ class DefaultAssumptionAnalyzer(method: ast.Method) extends AssumptionAnalyzer { // isScopeOpen = false // } - override def addAssumptions(assumptions: Iterable[DebugExp]): Unit = { + override def addSingleAssumption(assumption: DebugExp): Option[Int] = { + val node = if(assumption.originalExp.isDefined) new SimpleAssumptionNode(assumption.originalExp.get) + else new StringAssumptionNode(assumption.description.getOrElse("unknown")) + assumptionGraph.addNode(node) + Some(node.id) + } + + override def addAssumptions(assumptions: Iterable[DebugExp]): Seq[Int] = { val newNodes = assumptions.filter(_.originalExp.isDefined) .map(a => new SimpleAssumptionNode(a.originalExp.get)) assumptionGraph.addNodes(newNodes.toSet) + newNodes.map(_.id).toSeq } - override def addAssertion(assertion: Term): Unit = { + override def addAssertion(assertion: Term): Option[Int] = { val newNode = new SimpleAssertionNode(assertion) assumptionGraph.addNode(newNode) + Some(newNode.id) } override def addDependency(dep: String): Unit = { - val assumptions = dep.split(" ") + val assumptionLabels = dep.replace("(", "").replace(")", "").split(" ") + if(assumptionLabels.size < 2) return + val ids: Iterable[Int] = assumptionLabels map (l => l.split("_").tail.head.toInt) + val maxId: Int = ids.reduce[Int]{case (a, b) => math.max(a, b)} + assumptionGraph.addEdges(ids.filter(_ != maxId), maxId) } override def addAssumptionNode(assumption: AssumptionAnalysisNode): Unit = { @@ -67,14 +81,17 @@ class DefaultAssumptionAnalyzer(method: ast.Method) extends AssumptionAnalyzer { } override def getMethod: Option[ast.Method] = Some(method) + } class NoAssumptionAnalyzer extends AssumptionAnalyzer { - override def addAssumptions(assumptions: Iterable[DebugExp]): Unit = { + override def addAssumptions(assumptions: Iterable[DebugExp]): Seq[Int] = { + Seq.empty } - override def addAssertion(assertion: Term): Unit = { + override def addAssertion(assertion: Term): Option[Int] = { + None } override def addDependency(dep: String): Unit = { @@ -87,4 +104,8 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { } override def getMethod: Option[ast.Method] = None + + override def addSingleAssumption(assumption: DebugExp): Option[Int] = { + None + } } \ No newline at end of file diff --git a/src/main/scala/decider/CVC5ProverStdIO.scala b/src/main/scala/decider/CVC5ProverStdIO.scala index c2cdc0e91..ba4029361 100644 --- a/src/main/scala/decider/CVC5ProverStdIO.scala +++ b/src/main/scala/decider/CVC5ProverStdIO.scala @@ -6,7 +6,8 @@ package viper.silicon.decider -import viper.silicon.assumptionAnalysis.AssumptionAnalyzer +import viper.silicon.assumptionAnalysis.{AssumptionAnalyzer, NoAssumptionAnalyzer} + import java.nio.file.{Path, Paths} import viper.silicon.state.IdentifierFactory import viper.silicon.verifier.Verifier @@ -28,9 +29,8 @@ object Cvc5ProverStdIO { class Cvc5ProverStdIO(uniqueId: String, termConverter: TermToSMTLib2Converter, identifierFactory: IdentifierFactory, - reporter: Reporter, - assumptionAnalyzer: AssumptionAnalyzer) - extends ProverStdIO(uniqueId, termConverter, identifierFactory, reporter, assumptionAnalyzer) { + reporter: Reporter) + extends ProverStdIO(uniqueId, termConverter, identifierFactory, reporter) { val name: String = Cvc5ProverStdIO.name val minVersion: Version = Cvc5ProverStdIO.minVersion diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index cbb0f2247..6ac5a3127 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -6,7 +6,7 @@ package viper.silicon.decider -import viper.silicon.assumptionAnalysis.{AssumptionAnalyzer, DefaultAssumptionAnalyzer, NoAssumptionAnalyzer} +import viper.silicon.assumptionAnalysis.{AssumptionAnalysisGraphHelper, AssumptionAnalyzer, AssumptionLabel, DefaultAssumptionAnalyzer, NoAssumptionAnalyzer} import com.typesafe.scalalogging.Logger import viper.silicon.debugger.DebugExp import viper.silicon._ @@ -104,6 +104,8 @@ trait Decider { def statistics(): Map[String, String] var assumptionAnalyzer: AssumptionAnalyzer // TODO ake + def initAssumptionAnalyzer(method: ast.Method): Unit + def removeAssumptionAnalyzer(): Unit } /* @@ -134,6 +136,18 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => private val _debuggerAssumedTerms: mutable.Set[Term] = mutable.Set.empty var assumptionAnalyzer: AssumptionAnalyzer = new NoAssumptionAnalyzer() + + override def initAssumptionAnalyzer(method: ast.Method): Unit = { + if(Verifier.config.enableAssumptionAnalysis()){ + assumptionAnalyzer = new DefaultAssumptionAnalyzer(method) + prover.setAssumptionAnalyzer(assumptionAnalyzer) + } + } + + override def removeAssumptionAnalyzer(): Unit = { + assumptionAnalyzer = new NoAssumptionAnalyzer + prover.setAssumptionAnalyzer(assumptionAnalyzer) + } def functionDecls: Set[FunctionDecl] = _declaredFreshFunctions def macroDecls: Vector[MacroDecl] = _declaredFreshMacros @@ -159,8 +173,8 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } private def getProver(prover: String): Prover = prover match { - case Z3ProverStdIO.name => new Z3ProverStdIO(uniqueId, termConverter, identifierFactory, reporter, assumptionAnalyzer) - case Cvc5ProverStdIO.name => new Cvc5ProverStdIO(uniqueId, termConverter, identifierFactory, reporter, assumptionAnalyzer) + case Z3ProverStdIO.name => new Z3ProverStdIO(uniqueId, termConverter, identifierFactory, reporter) + case Cvc5ProverStdIO.name => new Cvc5ProverStdIO(uniqueId, termConverter, identifierFactory, reporter) case Z3ProverAPI.name => new Z3ProverAPI(uniqueId, new TermToZ3APIConverter(), identifierFactory, reporter, triggerGenerator) case prover => val msg1 = s"Unknown prover '$prover' provided. Defaulting to ${Z3ProverStdIO.name}." @@ -312,21 +326,25 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => if (debugMode) { filteredAssumptions foreach (a => addDebugExp(a._2.get.withTerm(a._1))) - assumptionAnalyzer.addAssumptions(filteredAssumptions filter(t => t._2.isDefined) map(t => t._2.get.withTerm(t._1))) } + val filteredAssumptionsWithLabels = filteredAssumptions map{case (t, de) => + val assumptionId: Option[Int] = if(de.isDefined) assumptionAnalyzer.addSingleAssumption(de.get) else None + (t, new AssumptionLabel("a", assumptionId).toString) + } if (filteredAssumptions.nonEmpty){ - val d = filteredAssumptions.head._2 // FIXME ake - val labelPrefix = if (d.isDefined) "debugExp_" + d.get.id else "" - assumeWithoutSmokeChecks(filteredAssumptions map (_._1), isDefinition=isDefinition, labelPrefix=labelPrefix) + assumeWithoutSmokeChecks(filteredAssumptionsWithLabels, isDefinition=isDefinition) } } def assume(assumptions: Seq[Term], debugExps: Option[Seq[DebugExp]]): Unit = { - if(debugExps.isDefined) assumptionAnalyzer.addAssumptions(debugExps.get) - val labelPrefix = if(debugExps.isDefined) "debugExp_" + debugExps.get.head.id else "" // FIXME ake - assumeWithoutSmokeChecks(InsertionOrderedSet(assumptions), labelPrefix=labelPrefix) + val assumptionIds = if(debugExps.isDefined) assumptionAnalyzer.addAssumptions(debugExps.get) else Seq.empty + + val assumptionsWithLabels = + if(assumptions.size == assumptionIds.size) assumptions.zip(assumptionIds).map{case (t, id) => (t, new AssumptionLabel("a", Some(id)).toString)} + else assumptions map (t => (t, "")) + assumeWithoutSmokeChecks(InsertionOrderedSet(assumptionsWithLabels)) if (debugMode) { debugExps.get foreach (e => addDebugExp(e)) } @@ -338,13 +356,16 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => if (enforceAssumption) terms else terms filterNot isKnownToBeTrue - if (debugMode && filteredTerms.nonEmpty) { + if(filteredTerms.isEmpty) return + + if (debugMode) { addDebugExp(debugExp.get.withTerm(And(filteredTerms))) - if(debugExp.isDefined) assumptionAnalyzer.addAssumptions(Set(debugExp.get.withTerm(And(filteredTerms)))) + val assumptionId: Option[Int] = if(debugExp.isDefined) assumptionAnalyzer.addSingleAssumption(debugExp.get.withTerm(And(filteredTerms))) else None + val termsWithLabel = filteredTerms.zipWithIndex.iterator.map {case (t, idx) => (t, new AssumptionLabel("a", assumptionId, idx).toString)}.toSeq + assumeWithoutSmokeChecks(InsertionOrderedSet(termsWithLabel)) + }else{ + assumeWithoutSmokeChecks(InsertionOrderedSet(filteredTerms.map ((_, "")))) } - - val labelPrefix = if(debugExp.isDefined) "debugExp_" + debugExp.get.id else "" // FIXME ake - if (filteredTerms.nonEmpty) assumeWithoutSmokeChecks(InsertionOrderedSet(filteredTerms), labelPrefix=labelPrefix) } def debuggerAssume(terms: Iterable[Term], de: DebugExp) = { @@ -357,7 +378,8 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => }) } - private def assumeWithoutSmokeChecks(terms: InsertionOrderedSet[Term], isDefinition: Boolean = false, labelPrefix: String = "") = { + private def assumeWithoutSmokeChecks(termsWithLabel: InsertionOrderedSet[(Term, String)], isDefinition: Boolean = false) = { + val terms = termsWithLabel map (_._1) val assumeRecord = new DeciderAssumeRecord(terms) val sepIdentifier = symbExLog.openScope(assumeRecord) @@ -369,7 +391,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } /* Add terms to the prover's assumptions */ - terms.zipWithIndex.iterator.foreach{case (t, tid) => prover.assume(t, if(labelPrefix.isEmpty) "" else labelPrefix + "_" + tid)} + termsWithLabel foreach{case (t, label) => prover.assume(t, label)} symbExLog.closeScope(sepIdentifier) None diff --git a/src/main/scala/decider/ProverStdIO.scala b/src/main/scala/decider/ProverStdIO.scala index 63b09780f..7518d291f 100644 --- a/src/main/scala/decider/ProverStdIO.scala +++ b/src/main/scala/decider/ProverStdIO.scala @@ -6,7 +6,8 @@ package viper.silicon.decider -import viper.silicon.assumptionAnalysis.AssumptionAnalyzer +import viper.silicon.assumptionAnalysis.{AssumptionAnalyzer, NoAssumptionAnalyzer} + import java.io._ import java.nio.file.Path import java.util.concurrent.TimeUnit @@ -27,8 +28,7 @@ import scala.collection.mutable abstract class ProverStdIO(uniqueId: String, termConverter: TermToSMTLib2Converter, identifierFactory: IdentifierFactory, - reporter: Reporter, - assumptionAnalyzer: AssumptionAnalyzer) + reporter: Reporter) extends Prover with LazyLogging { @@ -42,6 +42,7 @@ abstract class ProverStdIO(uniqueId: String, protected var allDecls: Seq[Decl] = Seq() protected var allEmits: Seq[String] = Seq() protected var proverLabelId: Int = 0 + var assumptionAnalyzer: AssumptionAnalyzer = new NoAssumptionAnalyzer() var proverPath: Path = _ var lastReasonUnknown : String = _ @@ -54,6 +55,10 @@ abstract class ProverStdIO(uniqueId: String, protected def setTimeout(timeout: Option[Int]): Unit protected def getProverPath: Path + override def setAssumptionAnalyzer(assumptionAnalyzer: AssumptionAnalyzer): Unit = { + this.assumptionAnalyzer = assumptionAnalyzer + } + @inline private def readLineFromInput(): String = { val line = input.readLine() diff --git a/src/main/scala/decider/Z3ProverAPI.scala b/src/main/scala/decider/Z3ProverAPI.scala index 686d22425..7dcc11393 100644 --- a/src/main/scala/decider/Z3ProverAPI.scala +++ b/src/main/scala/decider/Z3ProverAPI.scala @@ -20,6 +20,7 @@ import java.nio.file.Path import scala.collection.mutable import com.microsoft.z3._ import com.microsoft.z3.enumerations.Z3_param_kind +import viper.silicon.assumptionAnalysis.AssumptionAnalyzer import viper.silicon.reporting.ExternalToolError import viper.silicon.reporting.ProverInteractionFailed @@ -562,4 +563,8 @@ class Z3ProverAPI(uniqueId: String, lazy val randomizeSeedsOptions: Seq[String] = { Seq(Z3ProverAPI.randomizeSeedsSetting) } + + override def setAssumptionAnalyzer(assumptionAnalyzer: AssumptionAnalyzer): Unit = { + // TODO ake + } } diff --git a/src/main/scala/decider/Z3ProverStdIO.scala b/src/main/scala/decider/Z3ProverStdIO.scala index 50ac84bdb..94d3677e6 100644 --- a/src/main/scala/decider/Z3ProverStdIO.scala +++ b/src/main/scala/decider/Z3ProverStdIO.scala @@ -29,9 +29,8 @@ object Z3ProverStdIO { class Z3ProverStdIO(uniqueId: String, termConverter: TermToSMTLib2Converter, identifierFactory: IdentifierFactory, - reporter: Reporter, - assumptionAnalyzer: AssumptionAnalyzer) - extends ProverStdIO(uniqueId, termConverter, identifierFactory, reporter, assumptionAnalyzer) { + reporter: Reporter) + extends ProverStdIO(uniqueId, termConverter, identifierFactory, reporter) { val name = Z3ProverStdIO.name val minVersion = Z3ProverStdIO.minVersion diff --git a/src/main/scala/interfaces/decider/Prover.scala b/src/main/scala/interfaces/decider/Prover.scala index 084529d71..1455def47 100644 --- a/src/main/scala/interfaces/decider/Prover.scala +++ b/src/main/scala/interfaces/decider/Prover.scala @@ -6,6 +6,7 @@ package viper.silicon.interfaces.decider +import viper.silicon.assumptionAnalysis.AssumptionAnalyzer import viper.silicon.debugger.DebugAxiom import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.common.config.Version @@ -68,4 +69,6 @@ trait Prover extends ProverLike with StatefulComponent { def getAllDecls(): Seq[Decl] def getAllEmits(): Seq[String] + + def setAssumptionAnalyzer(assumptionAnalyzer: AssumptionAnalyzer): Unit } diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index 8eefc6109..1bf735147 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -47,7 +47,7 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif logger.debug("\n\n" + "-" * 10 + " METHOD " + method.name + "-" * 10 + "\n") decider.prover.comment("%s %s %s".format("-" * 10, method.name, "-" * 10)) - v.decider.assumptionAnalyzer = if(Verifier.config.enableAssumptionAnalysis()) new DefaultAssumptionAnalyzer(method) else new NoAssumptionAnalyzer() + v.decider.initAssumptionAnalyzer(method) val proverOptions: Map[String, String] = method.info.getUniqueInfo[ast.AnnotationInfo] match { case Some(ai) if ai.values.contains("proverArgs") => @@ -118,7 +118,7 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif Success()))}) } )})}) result.assumptionAnalyzer = v.decider.assumptionAnalyzer - v.decider.assumptionAnalyzer = new NoAssumptionAnalyzer() + v.decider.removeAssumptionAnalyzer() v.decider.resetProverOptions() symbExLog.closeMemberScope() diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index 6325afbef..679a39cc4 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -307,16 +307,16 @@ class DefaultMainVerifier(config: Config, ++ predicateVerificationResults ++ methodVerificationResults) - if (Verifier.config.enableDebugging()){ - val debugger = new SiliconDebugger(verificationResults, identifierFactory, reporter, FrontendStateCache.resolver, FrontendStateCache.pprogram, FrontendStateCache.translator, this) - debugger.startDebugger() - } - if(Verifier.config.enableAssumptionAnalysis()){ val assumptionAnalyzers = verificationResults.filter(_.assumptionAnalyzer.isInstanceOf[DefaultAssumptionAnalyzer]).map(_.assumptionAnalyzer) logger debug s"assumption analyzers ${assumptionAnalyzers.mkString(", ")}" } + if (Verifier.config.enableDebugging()){ + val debugger = new SiliconDebugger(verificationResults, identifierFactory, reporter, FrontendStateCache.resolver, FrontendStateCache.pprogram, FrontendStateCache.translator, this) + debugger.startDebugger() + } + verificationResults } From c721b553e37e38334f2fbf037e3e32eeaf3fe716 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 8 May 2025 16:47:51 +0200 Subject: [PATCH 017/474] refactor assumption graph nodes --- .../AnalysisSourceInfo.scala | 49 +++++++++ .../AssumptionAnalysisGraph.scala | 101 +++--------------- .../AssumptionAnalyzer.scala | 56 +++++++--- src/main/scala/decider/Decider.scala | 22 ++-- src/main/scala/decider/ProverStdIO.scala | 14 +-- src/main/scala/decider/Z3ProverAPI.scala | 3 +- .../scala/interfaces/decider/Prover.scala | 2 +- src/main/scala/rules/ChunkSupporter.scala | 6 +- src/main/scala/rules/Executor.scala | 10 +- src/main/scala/rules/MagicWandSupporter.scala | 3 +- .../rules/MoreCompleteExhaleSupporter.scala | 7 +- src/main/scala/rules/PredicateSupporter.scala | 7 +- src/main/scala/rules/Producer.scala | 14 +-- src/main/scala/rules/StateConsolidator.scala | 10 +- src/main/scala/state/Chunks.scala | 66 ++++++++---- 15 files changed, 198 insertions(+), 172 deletions(-) create mode 100644 src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala diff --git a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala new file mode 100644 index 000000000..5b0031c27 --- /dev/null +++ b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala @@ -0,0 +1,49 @@ +package viper.silicon.assumptionAnalysis + +import viper.silver.ast +import viper.silver.ast.Position + + +abstract class AnalysisSourceInfo { + def getPosition: Position +} + +case class ExpAnalysisSourceInfo(source: ast.Exp) extends AnalysisSourceInfo { + override def toString: String = source.toString + + override def getPosition: Position = source.pos + + override def equals(obj: Any): Boolean = { + obj match { + case info: ExpAnalysisSourceInfo => + info.source.equals(this.source) && info.getPosition.equals(this.getPosition) + case _ => false + } + } +} + +case class StmtAnalysisSourceInfo(source: ast.Stmt) extends AnalysisSourceInfo { + override def toString: String = source.toString() + + override def getPosition: Position = source.pos + + override def equals(obj: Any): Boolean = { + obj match { + case info: StmtAnalysisSourceInfo => + info.source.equals(this.source) && info.getPosition.equals(this.getPosition) + case _ => false + } + } +} + +case class StringAnalysisSourceInfo(description: String, position: Position) extends AnalysisSourceInfo { + override def toString: String = description + + override def getPosition: Position = position +} + +case class CombinedAnalysisSourceInfo(mainSource: AnalysisSourceInfo, sndSource: AnalysisSourceInfo) extends AnalysisSourceInfo { + override def toString: String = mainSource.toString + " and " + sndSource.toString + + override def getPosition: Position = mainSource.getPosition +} diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index eff0da2e5..41096360b 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -9,16 +9,9 @@ import scala.collection.mutable object AssumptionType extends Enumeration { type AssumptionType = Value - val Explicit, PathCondition, Implicit, Unknown = Value -} -import AssumptionType._ - -// TODO ake: make sure that the string does not contain invalid characters -class AssumptionLabel(description: String, id: Option[Int], offset: Int = 0) { - override def toString: String = - if(id.isDefined) description.trim + "_" + id.get + "_" + offset - else "" + val Explicit, PathCondition, Internal, Implicit, Unknown = Value } +import viper.silicon.assumptionAnalysis.AssumptionType._ object AssumptionAnalysisGraphHelper { @@ -31,16 +24,13 @@ object AssumptionAnalysisGraphHelper { trait AssumptionAnalysisGraph { var nodes: Seq[AssumptionAnalysisNode] - - // TODO ake - // var groups: mutable.Map[GroupNode, Set[Int]] // e.g. statements -> assumptions/assertions - - var edges: mutable.Map[Int, Set[Int]] // e.g. assertion -> assumptions + var edges: mutable.Map[Int, Set[Int]] def addNode(node: AssumptionAnalysisNode): Unit def addNodes(nodes: Set[AssumptionAnalysisNode]): Unit def addEdges(source: Int, targets: Iterable[Int]): Unit def addEdges(sources: Iterable[Int], target: Int): Unit + def addEdges(sources: Iterable[Int], targets: Iterable[Int]): Unit // def findDependentAssumptions(assertion, enableTransitivity=false) // def findDependentAssertions(assumption, enableTransitivity=false) @@ -68,21 +58,25 @@ class DefaultAssumptionAnalysisGraph extends AssumptionAnalysisGraph { } override def addEdges(sources: Iterable[Int], target: Int): Unit = { - sources.foreach(addEdges(_, Set(target))) + addEdges(sources, Set(target)) + } + + override def addEdges(sources: Iterable[Int], targets: Iterable[Int]): Unit = { + sources foreach (addEdges(_, targets)) } } trait AssumptionAnalysisNode { val id: Int = AssumptionAnalysisGraphHelper.nextId() + val sourceInfo: AnalysisSourceInfo val assumptionType: AssumptionType - def equals(other: AssumptionAnalysisNode): Boolean - - override def toString: String = id.toString + override def toString: String = id.toString + " " + sourceInfo.toString def isIncludedInAnalysis: Boolean = assumptionType match { case Explicit => true case PathCondition => true + case Internal => true case Implicit => true case Unknown => true } @@ -97,91 +91,28 @@ trait ChunkAnalysisInfo { def getChunk: Chunk = chunk } -class SimpleAssumptionNode(assumption: ast.Exp, val assumptionType: AssumptionType = Unknown) extends AssumptionAnalysisNode { +case class SimpleAssumptionNode(assumption: ast.Exp, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Unknown) extends AssumptionAnalysisNode { override def toString: String = super.toString + ": " + assumption.toString - def getAssumption: ast.Exp = assumption - - override def equals(other: AssumptionAnalysisNode): Boolean = { - other match { - case node: SimpleAssumptionNode => - node.getAssumption.equals(this.assumption) && node.getAssumption.pos.equals(this.assumption.pos) - case _ => false - } - } } -// TODO ake: remove this -class StringAssumptionNode(description: String, val assumptionType: AssumptionType = Unknown) extends AssumptionAnalysisNode { +case class StringAssumptionNode(description: String, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Unknown) extends AssumptionAnalysisNode { override def toString: String = super.toString + ": " + description - def getAssumption: String = description - - override def equals(other: AssumptionAnalysisNode): Boolean = { - other.id == this.id - } } // TODO ake: ast.Exp instead of Term -class SimpleAssertionNode(assertion: Term) extends AssumptionAnalysisNode { +case class SimpleAssertionNode(assertion: Term, isAsserted: Boolean, sourceInfo: AnalysisSourceInfo) extends AssumptionAnalysisNode { override val assumptionType: AssumptionType = AssumptionType.Explicit override def toString: String = super.toString + ": " + assertion.toString - def getAssertion: Term = assertion - override def equals(other: AssumptionAnalysisNode): Boolean = { - other match { - case node: SimpleAssertionNode => node.getAssertion.equals(this.assertion) - case _ => false - } - } } -class PermissionAssumptionNode(assumption: ast.Exp, val chunk: Chunk, assumptionType: AssumptionType = Unknown) extends SimpleAssumptionNode(assumption, assumptionType) with ChunkAnalysisInfo { - override def equals(other: AssumptionAnalysisNode): Boolean = { - other match { - case node: PermissionAssumptionNode => - node.getChunk.equals(this.chunk) && super.equals(other) - case _ => false - } - } +case class PermissionInhaleNode(chunk: Chunk, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Unknown) extends AssumptionAnalysisNode with ChunkAnalysisInfo { } -class PermissionAssertionNode(assertion: Term, val chunk: Chunk, assumptionType: AssumptionType = Unknown) extends SimpleAssertionNode(assertion) with ChunkAnalysisInfo { - override def equals(other: AssumptionAnalysisNode): Boolean = { - other match { - case node: PermissionAssertionNode => - node.getChunk.equals(this.chunk) && super.equals(other) - case _ => false - } - } -} -class ChunkGroupNode(description: String, val chunk: Chunk, val assumptionType: AssumptionType = Unknown) extends AssumptionAnalysisNode with ChunkAnalysisInfo { - override def toString: String = description + " - " + super.toString - - - override def equals(other: AssumptionAnalysisNode): Boolean = { - other match { - case node: ChunkAnalysisInfo => - node.getChunk.equals(this.chunk) - case _ => false - } - } -} -//class StatementGroupNode(stmt: ast.Stmt, nodes: Set[AssumptionAnalysisNode], val assumptionType: AssumptionType) extends AssumptionAnalysisNode { -// override def toString: String = super.toString + ": " + stmt.toString + " --> " + nodes.mkString(" && ") -// -// def getStmt: ast.Stmt = stmt -// -// override def equals(other: AssumptionAnalysisNode): Boolean = { -// other match { -// case node: StatementGroupNode => -// node.getStmt.equals(this.stmt) && node.getStmt.pos.equals(this.stmt.pos) -// case _ => false -// } -// } -//} diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index efb43a8dc..c62fa6d9e 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -4,6 +4,7 @@ import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.state.Chunk import viper.silicon.state.terms.Term import viper.silver.ast +import viper.silver.ast.NoPosition trait AssumptionAnalyzer { @@ -12,9 +13,9 @@ trait AssumptionAnalyzer { def addAssumptionNode(assumption: AssumptionAnalysisNode): Unit def addSingleAssumption(assumption: DebugExp): Option[Int] def addAssumptions(assumptions: Iterable[DebugExp]): Seq[Int] - def addAssertion(assertion: Term): Option[Int] - def addChunkNode(oldChunks: Set[Chunk], newChunkNode: AssumptionAnalysisNode) - def addDependency(dep: String): Unit + def addAssertion(assertion: Term, isAsserted: Boolean, sourceInfo: AnalysisSourceInfo): Option[Int] + def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNode: AssumptionAnalysisNode): Unit + def processUnsatCore(dep: String): Unit val assumptionGraph: AssumptionAnalysisGraph = new DefaultAssumptionAnalysisGraph() @@ -22,6 +23,29 @@ trait AssumptionAnalyzer { } +object AssumptionAnalyzer { + def createAssumptionLabel(id: Option[Int], offset: Int = 0): String = { + createLabel("assumption", id, offset) + } + + def createAssertionLabel(id: Option[Int], offset: Int = 0): String = { + createLabel("assertion", id, offset) + } + + private def createLabel(description: String, id: Option[Int], offset: Int = 0): String = { + if (id.isDefined) description + "_" + id.get + "_" + offset + else "" + } + + def getIdFromLabel(label: String): Int = { + label.split("_")(1).toInt + } + + def isAssertionLabel(label: String): Boolean = label.startsWith("assertion_") + + def isAssumptionLabel(label: String): Boolean = label.startsWith("assumption_") +} + class DefaultAssumptionAnalyzer(method: ast.Method) extends AssumptionAnalyzer { // private var scope : mutable.Set[AssumptionAnalysisNode] = mutable.Set.empty // private var isScopeOpen: Boolean = false @@ -41,38 +65,38 @@ class DefaultAssumptionAnalyzer(method: ast.Method) extends AssumptionAnalyzer { // } override def addSingleAssumption(assumption: DebugExp): Option[Int] = { - val node = if(assumption.originalExp.isDefined) new SimpleAssumptionNode(assumption.originalExp.get) - else new StringAssumptionNode(assumption.description.getOrElse("unknown")) + val node = if(assumption.originalExp.isDefined) SimpleAssumptionNode(assumption.originalExp.get, ExpAnalysisSourceInfo(assumption.originalExp.get), AssumptionType.Unknown) + else StringAssumptionNode(assumption.description.getOrElse("unknown"), StringAnalysisSourceInfo(assumption.description.getOrElse("unknown"), NoPosition), AssumptionType.Unknown) assumptionGraph.addNode(node) Some(node.id) } override def addAssumptions(assumptions: Iterable[DebugExp]): Seq[Int] = { val newNodes = assumptions.filter(_.originalExp.isDefined) - .map(a => new SimpleAssumptionNode(a.originalExp.get)) + .map(a => SimpleAssumptionNode(a.originalExp.get, ExpAnalysisSourceInfo(a.originalExp.get), AssumptionType.Unknown)) assumptionGraph.addNodes(newNodes.toSet) newNodes.map(_.id).toSeq } - override def addAssertion(assertion: Term): Option[Int] = { - val newNode = new SimpleAssertionNode(assertion) + override def addAssertion(assertion: Term, isAsserted: Boolean, sourceInfo: AnalysisSourceInfo): Option[Int] = { + val newNode = SimpleAssertionNode(assertion, isAsserted, sourceInfo) assumptionGraph.addNode(newNode) Some(newNode.id) } - override def addDependency(dep: String): Unit = { + override def processUnsatCore(dep: String): Unit = { val assumptionLabels = dep.replace("(", "").replace(")", "").split(" ") if(assumptionLabels.size < 2) return - val ids: Iterable[Int] = assumptionLabels map (l => l.split("_").tail.head.toInt) - val maxId: Int = ids.reduce[Int]{case (a, b) => math.max(a, b)} - assumptionGraph.addEdges(ids.filter(_ != maxId), maxId) + val assumptionIds = assumptionLabels.filter(AssumptionAnalyzer.isAssumptionLabel).map(AssumptionAnalyzer.getIdFromLabel) + val assertionIds = assumptionLabels.filter(AssumptionAnalyzer.isAssertionLabel).map(AssumptionAnalyzer.getIdFromLabel) + assumptionGraph.addEdges(assumptionIds, assertionIds) } override def addAssumptionNode(assumption: AssumptionAnalysisNode): Unit = { assumptionGraph.addNode(assumption) } - override def addChunkNode(oldChunks: Set[Chunk], newChunkNode: AssumptionAnalysisNode): Unit = { + override def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNode: AssumptionAnalysisNode): Unit = { val analysisChunks = assumptionGraph.nodes .filter(c => c.isInstanceOf[ChunkAnalysisInfo] && oldChunks.contains(c.asInstanceOf[ChunkAnalysisInfo].getChunk)) .map(_.id).toSet @@ -90,17 +114,17 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { Seq.empty } - override def addAssertion(assertion: Term): Option[Int] = { + override def addAssertion(assertion: Term, isAsserted: Boolean, sourceInfo: AnalysisSourceInfo): Option[Int] = { None } - override def addDependency(dep: String): Unit = { + override def processUnsatCore(dep: String): Unit = { } override def addAssumptionNode(assumption: AssumptionAnalysisNode): Unit = { } - override def addChunkNode(oldChunks: Set[Chunk], newChunkNode: AssumptionAnalysisNode): Unit = { + override def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNode: AssumptionAnalysisNode): Unit = { } override def getMethod: Option[ast.Method] = None diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 6ac5a3127..c2442f634 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -6,11 +6,11 @@ package viper.silicon.decider -import viper.silicon.assumptionAnalysis.{AssumptionAnalysisGraphHelper, AssumptionAnalyzer, AssumptionLabel, DefaultAssumptionAnalyzer, NoAssumptionAnalyzer} import com.typesafe.scalalogging.Logger -import viper.silicon.debugger.DebugExp import viper.silicon._ +import viper.silicon.assumptionAnalysis.{AssumptionAnalyzer, DefaultAssumptionAnalyzer, NoAssumptionAnalyzer, StringAnalysisSourceInfo} import viper.silicon.common.collections.immutable.InsertionOrderedSet +import viper.silicon.debugger.DebugExp import viper.silicon.interfaces._ import viper.silicon.interfaces.decider._ import viper.silicon.logger.records.data.{DeciderAssertRecord, DeciderAssumeRecord, ProverAssertRecord} @@ -26,8 +26,8 @@ import viper.silver.reporter.{ConfigurationConfirmation, InternalWarningMessage} import viper.silver.verifier.{DependencyNotFoundError, Model} import scala.collection.immutable.HashSet -import scala.reflect.{ClassTag, classTag} import scala.collection.mutable +import scala.reflect.{ClassTag, classTag} /* * Interfaces @@ -330,7 +330,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => val filteredAssumptionsWithLabels = filteredAssumptions map{case (t, de) => val assumptionId: Option[Int] = if(de.isDefined) assumptionAnalyzer.addSingleAssumption(de.get) else None - (t, new AssumptionLabel("a", assumptionId).toString) + (t, AssumptionAnalyzer.createAssumptionLabel(assumptionId)) } if (filteredAssumptions.nonEmpty){ @@ -342,7 +342,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => val assumptionIds = if(debugExps.isDefined) assumptionAnalyzer.addAssumptions(debugExps.get) else Seq.empty val assumptionsWithLabels = - if(assumptions.size == assumptionIds.size) assumptions.zip(assumptionIds).map{case (t, id) => (t, new AssumptionLabel("a", Some(id)).toString)} + if(assumptions.size == assumptionIds.size) assumptions.zip(assumptionIds).map{case (t, id) => (t, AssumptionAnalyzer.createAssumptionLabel(Some(id)))} else assumptions map (t => (t, "")) assumeWithoutSmokeChecks(InsertionOrderedSet(assumptionsWithLabels)) if (debugMode) { @@ -361,7 +361,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => if (debugMode) { addDebugExp(debugExp.get.withTerm(And(filteredTerms))) val assumptionId: Option[Int] = if(debugExp.isDefined) assumptionAnalyzer.addSingleAssumption(debugExp.get.withTerm(And(filteredTerms))) else None - val termsWithLabel = filteredTerms.zipWithIndex.iterator.map {case (t, idx) => (t, new AssumptionLabel("a", assumptionId, idx).toString)}.toSeq + val termsWithLabel = filteredTerms.zipWithIndex.iterator.map {case (t, idx) => (t, AssumptionAnalyzer.createAssumptionLabel(assumptionId, idx))}.toSeq assumeWithoutSmokeChecks(InsertionOrderedSet(termsWithLabel)) }else{ assumeWithoutSmokeChecks(InsertionOrderedSet(filteredTerms.map ((_, "")))) @@ -431,8 +431,8 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => val asserted = if(Verifier.config.enableAssumptionAnalysis()) t.equals(True) else isKnownToBeTrue(t) - if(!asserted) assumptionAnalyzer.addAssertion(t) - val result = asserted || proverAssert(t, timeout) + val assertionId: Option[Int] = if(!asserted) assumptionAnalyzer.addAssertion(t, false, StringAnalysisSourceInfo("deciderAssert", NoPosition)) else None + val result = asserted || proverAssert(t, timeout, AssumptionAnalyzer.createAssertionLabel(assertionId)) symbExLog.closeScope(sepIdentifier) result @@ -452,11 +452,11 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } } - private def proverAssert(t: Term, timeout: Option[Int]) = { + private def proverAssert(t: Term, timeout: Option[Int], label: String) = { val assertRecord = new ProverAssertRecord(t, timeout) val sepIdentifier = symbExLog.openScope(assertRecord) - val result = prover.assert(t, timeout) + val result = prover.assert(t, timeout, label) symbExLog.whenEnabled { assertRecord.statistics = Some(symbExLog.deltaStatistics(prover.statistics())) @@ -574,7 +574,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => def statistics(): Map[String, String] = prover.statistics() - override def generateModel(): Unit = proverAssert(False, Verifier.config.assertTimeout.toOption) + override def generateModel(): Unit = proverAssert(False, Verifier.config.assertTimeout.toOption, "") override def getModel(): Model = prover.getModel() diff --git a/src/main/scala/decider/ProverStdIO.scala b/src/main/scala/decider/ProverStdIO.scala index 7518d291f..5b073fda4 100644 --- a/src/main/scala/decider/ProverStdIO.scala +++ b/src/main/scala/decider/ProverStdIO.scala @@ -261,15 +261,15 @@ abstract class ProverStdIO(uniqueId: String, readSuccess() } - def assert(goal: Term, timeout: Option[Int] = None): Boolean = - assert(termConverter.convert(goal), timeout) + def assert(goal: Term, timeout: Option[Int] = None, label: String = ""): Boolean = + assert(termConverter.convert(goal), timeout, label) - def assert(goal: String, timeout: Option[Int]): Boolean = { + def assert(goal: String, timeout: Option[Int], label: String): Boolean = { // bookkeeper.assertionCounter += 1 val (result, duration) = Verifier.config.assertionMode() match { case Config.AssertionMode.SoftConstraints => assertUsingSoftConstraints(goal, timeout) - case Config.AssertionMode.PushPop => assertUsingPushPop(goal, timeout) + case Config.AssertionMode.PushPop => assertUsingPushPop(goal, timeout, label) } comment(s"${viper.silver.reporter.format.formatMillisReadably(duration)}") @@ -278,11 +278,11 @@ abstract class ProverStdIO(uniqueId: String, result } - protected def assertUsingPushPop(goal: String, timeout: Option[Int]): (Boolean, Long) = { + protected def assertUsingPushPop(goal: String, timeout: Option[Int], label: String): (Boolean, Long) = { push() setTimeout(timeout) - writeLine("(assert (not " + goal + "))") + writeLine("(assert (! (not " + goal + ") :named " + label + "))") readSuccess() val startTime = System.currentTimeMillis() @@ -295,7 +295,7 @@ abstract class ProverStdIO(uniqueId: String, retrieveReasonUnknown() }else{ val unsatCore = extractUnsatCore() - assumptionAnalyzer.addDependency(unsatCore) + assumptionAnalyzer.processUnsatCore(unsatCore) } pop() diff --git a/src/main/scala/decider/Z3ProverAPI.scala b/src/main/scala/decider/Z3ProverAPI.scala index 7dcc11393..292cb7e31 100644 --- a/src/main/scala/decider/Z3ProverAPI.scala +++ b/src/main/scala/decider/Z3ProverAPI.scala @@ -299,7 +299,8 @@ class Z3ProverAPI(uniqueId: String, cleanTerm } - def assert(goal: Term, timeout: Option[Int]): Boolean = { + // TODO ake: label + def assert(goal: Term, timeout: Option[Int], label: String = ""): Boolean = { endPreamblePhase() try { diff --git a/src/main/scala/interfaces/decider/Prover.scala b/src/main/scala/interfaces/decider/Prover.scala index 1455def47..67ee216ac 100644 --- a/src/main/scala/interfaces/decider/Prover.scala +++ b/src/main/scala/interfaces/decider/Prover.scala @@ -44,7 +44,7 @@ trait ProverLike { trait Prover extends ProverLike with StatefulComponent { def start(userArgsString: Option[String]): Unit - def assert(goal: Term, timeout: Option[Int] = None): Boolean + def assert(goal: Term, timeout: Option[Int] = None, label: String = ""): Boolean def check(timeout: Option[Int] = None): Result def fresh(id: String, argSorts: Seq[Sort], resultSort: Sort): Function def statistics(): Map[String, String] diff --git a/src/main/scala/rules/ChunkSupporter.scala b/src/main/scala/rules/ChunkSupporter.scala index 58393107b..e6c4eccea 100644 --- a/src/main/scala/rules/ChunkSupporter.scala +++ b/src/main/scala/rules/ChunkSupporter.scala @@ -6,7 +6,7 @@ package viper.silicon.rules -import viper.silicon.assumptionAnalysis.{AssumptionType, PermissionAssumptionNode} +import viper.silicon.assumptionAnalysis.{AssumptionType, PermissionInhaleNode} import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.state._ import viper.silicon.interfaces.{Success, VerificationResult} @@ -182,8 +182,6 @@ object chunkSupporter extends ChunkSupportRules { val newPermExp = permsExp.map(pe => ast.PermSub(ch.permExp.get, toTakeExp.get)(pe.pos, pe.info, pe.errT)) val newChunk = ch.withPerm(PermMinus(ch.perm, toTake), newPermExp) val takenChunk = Some(ch.withPerm(toTake, toTakeExp)) - Option.when(withExp)(v.decider.assumptionAnalyzer.addChunkNode(Set(ch), new PermissionAssumptionNode(permsExp.get, newChunk, AssumptionType.Unknown))) - Option.when(withExp)(v.decider.assumptionAnalyzer.addChunkNode(Set(ch), new PermissionAssumptionNode(permsExp.get, takenChunk.get, AssumptionType.Unknown))) var newHeap = h - ch if (!v.decider.check(newChunk.perm === NoPerm, Verifier.config.checkTimeout())) { newHeap = newHeap + newChunk @@ -198,8 +196,6 @@ object chunkSupporter extends ChunkSupportRules { val newPermExp = permsExp.map(pe => ast.PermSub(ch.permExp.get, pe)(pe.pos, pe.info, pe.errT)) val newChunk = ch.withPerm(PermMinus(ch.perm, perms), newPermExp) val takenChunk = ch.withPerm(perms, permsExp) - Option.when(withExp)(v.decider.assumptionAnalyzer.addChunkNode(Set(ch), new PermissionAssumptionNode(permsExp.get, newChunk, AssumptionType.Unknown))) - Option.when(withExp)(v.decider.assumptionAnalyzer.addChunkNode(Set(ch), new PermissionAssumptionNode(permsExp.get, takenChunk, AssumptionType.Unknown))) val newHeap = h - ch + newChunk assumeProperties(newChunk, newHeap) (Complete(), s, newHeap, Some(takenChunk)) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index ce0fd4e6a..f5e1532e1 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -9,7 +9,7 @@ package viper.silicon.rules import viper.silicon.debugger.DebugExp import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.Config.JoinMode -import viper.silicon.assumptionAnalysis.{AssumptionType, PermissionAssumptionNode} +import viper.silicon.assumptionAnalysis.{AssumptionType, ExpAnalysisSourceInfo, PermissionInhaleNode} import scala.annotation.unused import viper.silver.cfg.silver.SilverCfg @@ -440,8 +440,8 @@ object executor extends ExecutionRules { chunkSupporter.consume(s2, s2.h, resource, Seq(tRcvr), eRcvrNew.map(Seq(_)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), false, ve, v2, description)((s3, h3, _, v3) => { val (tSnap, _) = ssaifyRhs(tRhs, rhs, rhsNew, field.name, field.typ, v3, s3) val id = BasicChunkIdentifier(field.name) - val newChunk = BasicChunk(FieldID, id, Seq(tRcvr), eRcvrNew.map(Seq(_)), tSnap, rhsNew, FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT))) - v3.decider.assumptionAnalyzer.addAssumptionNode(new PermissionAssumptionNode(eRcvr, newChunk, AssumptionType.Implicit)) + val newChunk = BasicChunk(FieldID, id, Seq(tRcvr), eRcvrNew.map(Seq(_)), tSnap, rhsNew, FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), + v3, ExpAnalysisSourceInfo(fa), AssumptionType.Implicit) chunkSupporter.produce(s3, h3, newChunk, v3)((s4, h4, v4) => { val s5 = s4.copy(h = h4) val (debugHeapName, _) = v4.getDebugOldLabel(s5, fa.pos) @@ -471,8 +471,8 @@ object executor extends ExecutionRules { quantifiedChunkSupporter.createSingletonQuantifiedChunk(Seq(`?r`), Option.when(withExp)(Seq(ast.LocalVarDecl("r", ast.Ref)(stmt.pos, stmt.info, stmt.errT))), field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), p, pExp, sm, s.program) } else { - val newChunk = BasicChunk(FieldID, BasicChunkIdentifier(field.name), Seq(tRcvr), Option.when(withExp)(Seq(x)), snap, snapExp, p, pExp) - Option.when(withExp)(v.decider.assumptionAnalyzer.addAssumptionNode(new PermissionAssumptionNode(pExp.get, newChunk, AssumptionType.Explicit))) + val newChunk = BasicChunk(FieldID, BasicChunkIdentifier(field.name), Seq(tRcvr), Option.when(withExp)(Seq(x)), snap, snapExp, p, pExp, + v, ExpAnalysisSourceInfo(x), AssumptionType.Explicit) newChunk } }) diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index c62df9d9b..3c6ed6b2e 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -8,7 +8,7 @@ package viper.silicon.rules import viper.silicon.debugger.DebugExp import viper.silicon._ -import viper.silicon.assumptionAnalysis.{AssumptionType, PermissionAssumptionNode} +import viper.silicon.assumptionAnalysis.{AssumptionType, PermissionInhaleNode} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.decider.RecordedPathConditions import viper.silicon.interfaces._ @@ -96,7 +96,6 @@ object magicWandSupporter extends SymbolicExecutionRules { evaluateWandArguments(s, wand, pve, v)((s1, ts, esNew, v1) => { val newChunk = MagicWandChunk(MagicWandIdentifier(wand, s.program), s1.g.values, ts, esNew, snap, FullPerm, Option.when(withExp)(ast.FullPerm()(wand.pos, wand.info, wand.errT))) - v.decider.assumptionAnalyzer.addAssumptionNode(new PermissionAssumptionNode(wand, newChunk, AssumptionType.Explicit)) Q(s1, newChunk, v1) }) } diff --git a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala index ad37d85f7..cc43cf9a9 100644 --- a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala +++ b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala @@ -6,17 +6,16 @@ package viper.silicon.rules -import viper.silicon.assumptionAnalysis.{AssumptionType, PermissionAssumptionNode} import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.state._ import viper.silicon.interfaces.{Success, VerificationResult} import viper.silicon.resources.{FieldID, NonQuantifiedPropertyInterpreter, Resources} -import viper.silicon.rules.chunkSupporter.{findChunksWithID, withExp} +import viper.silicon.rules.chunkSupporter.findChunksWithID import viper.silicon.state._ import viper.silicon.state.terms._ import viper.silicon.state.terms.perms.{IsNonPositive, IsPositive} import viper.silicon.supporters.functions.NoopFunctionRecorder -import viper.silicon.utils.ast.{BigAnd, buildMinExp, removeKnownToBeTrueExp, replaceVarsInExp, simplifyVariableName} +import viper.silicon.utils.ast._ import viper.silicon.verifier.Verifier import viper.silicon.{MList, MMap} import viper.silver.ast @@ -361,7 +360,6 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { pSumExp = eqExp.map(eq => ast.PermAdd(pSumExp.get, ast.CondExp(eq, ch.permExp.get, ast.NoPerm()())(eq.pos, eq.info, eq.errT))()) val newChunk = ch.withPerm(PermMinus(ch.perm, pTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, pTakenExp.get)(pe.pos, pe.info, pe.errT))) - Option.when(withExp)(v.decider.assumptionAnalyzer.addChunkNode(Set(ch), new PermissionAssumptionNode(permsExp.get, newChunk, AssumptionType.Unknown))) pNeeded = PermMinus(pNeeded, pTaken) pNeededExp = permsExp.map(pe => ast.PermSub(pNeededExp.get, pTakenExp.get)(pe.pos, pe.info, pe.errT)) @@ -475,7 +473,6 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { newFr = newFr.recordPathSymbol(permTaken.applicable.asInstanceOf[Function]).recordConstraint(constraint) val newChunk = ch.withPerm(PermMinus(ch.perm, permTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, permTakenExp.get)(pe.pos, pe.info, pe.errT))) - Option.when(withExp)(v.decider.assumptionAnalyzer.addChunkNode(Set(ch), new PermissionAssumptionNode(permsExp.get, newChunk, AssumptionType.Unknown))) newChunk }) diff --git a/src/main/scala/rules/PredicateSupporter.scala b/src/main/scala/rules/PredicateSupporter.scala index 654ef457c..53722e94b 100644 --- a/src/main/scala/rules/PredicateSupporter.scala +++ b/src/main/scala/rules/PredicateSupporter.scala @@ -6,7 +6,7 @@ package viper.silicon.rules -import viper.silicon.assumptionAnalysis.{AssumptionType, PermissionAssumptionNode} +import viper.silicon.assumptionAnalysis.{AssumptionType, ExpAnalysisSourceInfo, PermissionInhaleNode} import viper.silicon.debugger.DebugExp import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.interfaces.VerificationResult @@ -16,6 +16,7 @@ import viper.silicon.state.terms._ import viper.silicon.utils.toSf import viper.silicon.verifier.Verifier import viper.silver.ast +import viper.silver.ast.{NoInfo, NoPosition, NoTrafos, PredicateAccess} import viper.silver.verifier.PartialVerificationError import viper.silver.verifier.reasons.InsufficientPermission @@ -114,8 +115,8 @@ object predicateSupporter extends PredicateSupportRules { functionRecorder = s2.functionRecorder.recordFvfAndDomain(smDef)) Q(s3, v1) } else { - val ch = BasicChunk(PredicateID, BasicChunkIdentifier(predicate.name), tArgs, eArgs, snap.get.convert(sorts.Snap), None, tPerm, ePerm) - Option.when(withExp)(v1.decider.assumptionAnalyzer.addAssumptionNode(new PermissionAssumptionNode(ePerm.get, ch, AssumptionType.Unknown))) + val ch = BasicChunk(PredicateID, BasicChunkIdentifier(predicate.name), tArgs, eArgs, snap.get.convert(sorts.Snap), None, tPerm, ePerm, + v1, ExpAnalysisSourceInfo(PredicateAccess(Seq(), predicate)(NoPosition, NoInfo, NoTrafos)), AssumptionType.Unknown) val s3 = s2.copy(g = s.g, smDomainNeeded = s.smDomainNeeded, permissionScalingFactor = s.permissionScalingFactor, diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index 4777e80ae..7d9c5c011 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -8,7 +8,7 @@ package viper.silicon.rules import viper.silicon.debugger.DebugExp import viper.silicon.Config.JoinMode -import viper.silicon.assumptionAnalysis.{AssumptionType, PermissionAssumptionNode} +import viper.silicon.assumptionAnalysis.{AssumptionType, ExpAnalysisSourceInfo, PermissionInhaleNode} import scala.collection.mutable import viper.silver.ast @@ -319,7 +319,7 @@ object producer extends ProductionRules { letSupporter.handle[ast.Exp](s, let, pve, v)((s1, g1, body, v1) => produceR(s1.copy(g = s1.g + g1), sf, body, pve, v1)(Q)) - case accPred@ast.FieldAccessPredicate(ast.FieldAccess(eRcvr, field), _) => + case accPred@ast.FieldAccessPredicate(fa @ ast.FieldAccess(eRcvr, field), _) => val perm = accPred.perm eval(s, eRcvr, pve, v)((s1, tRcvr, eRcvrNew, v1) => eval(s1, perm, pve, v1)((s2, tPerm, ePermNew, v2) => @@ -338,15 +338,15 @@ object producer extends ProductionRules { } else { val (debugHeapName, debugLabel) = v3.getDebugOldLabel(s3, accPred.pos) val snapExp = Option.when(withExp)(ast.DebugLabelledOld(ast.FieldAccess(eRcvrNew.get, field)(), debugLabel)(accPred.pos, accPred.info, accPred.errT)) - val ch = BasicChunk(FieldID, BasicChunkIdentifier(field.name), Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), snap, snapExp, gain, gainExp) - Option.when(withExp)(v1.decider.assumptionAnalyzer.addAssumptionNode(new PermissionAssumptionNode(gainExp.get, ch, AssumptionType.Unknown))) + val ch = BasicChunk(FieldID, BasicChunkIdentifier(field.name), Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), snap, snapExp, gain, gainExp, + v1, ExpAnalysisSourceInfo(fa), AssumptionType.Unknown) chunkSupporter.produce(s3, s3.h, ch, v3)((s4, h4, v4) => { val s5 = s4.copy(h = h4) val s6 = if (withExp) s5.copy(oldHeaps = s5.oldHeaps + (debugHeapName -> magicWandSupporter.getEvalHeap(s4))) else s5 Q(s6, v4) })}}))) - case accPred @ ast.PredicateAccessPredicate(ast.PredicateAccess(eArgs, predicateName), _) => + case accPred @ ast.PredicateAccessPredicate(pa @ ast.PredicateAccess(eArgs, predicateName), _) => val predicate = s.program.findPredicate(predicateName) val perm = accPred.perm evals(s, eArgs, _ => pve, v)((s1, tArgs, eArgsNew, v1) => @@ -366,8 +366,8 @@ object producer extends ProductionRules { s2, predicate, formalArgs, Option.when(withExp)(predicate.formalArgs), tArgs, eArgsNew, snap, gain, gainExp, trigger, v2)(Q) } else { val snap1 = snap.convert(sorts.Snap) - val ch = BasicChunk(PredicateID, BasicChunkIdentifier(predicate.name), tArgs, eArgsNew, snap1, None, gain, gainExp) - Option.when(withExp)(v1.decider.assumptionAnalyzer.addAssumptionNode(new PermissionAssumptionNode(gainExp.get, ch, AssumptionType.Unknown))) + val ch = BasicChunk(PredicateID, BasicChunkIdentifier(predicate.name), tArgs, eArgsNew, snap1, None, gain, gainExp, + v1, ExpAnalysisSourceInfo(pa), AssumptionType.Unknown) chunkSupporter.produce(s2, s2.h, ch, v2)((s3, h3, v3) => { if (Verifier.config.enablePredicateTriggersOnInhale() && s3.functionRecorder == NoopFunctionRecorder && !Verifier.config.disableFunctionUnfoldTrigger()) { diff --git a/src/main/scala/rules/StateConsolidator.scala b/src/main/scala/rules/StateConsolidator.scala index 1e0a69038..109b0d54b 100644 --- a/src/main/scala/rules/StateConsolidator.scala +++ b/src/main/scala/rules/StateConsolidator.scala @@ -6,14 +6,13 @@ package viper.silicon.rules -import viper.silicon.debugger.DebugExp import viper.silicon.Config -import viper.silicon.assumptionAnalysis.{AssumptionType, ChunkGroupNode, PermissionAssumptionNode} +import viper.silicon.assumptionAnalysis.{AssumptionType, PermissionInhaleNode, StringAnalysisSourceInfo} import viper.silicon.common.collections.immutable.InsertionOrderedSet +import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.state._ import viper.silicon.logger.records.data.{CommentRecord, SingleMergeRecord} import viper.silicon.resources.{NonQuantifiedPropertyInterpreter, Resources} -import viper.silicon.rules.predicateSupporter.withExp import viper.silicon.state._ import viper.silicon.state.terms._ import viper.silicon.state.terms.perms._ @@ -21,7 +20,7 @@ import viper.silicon.state.terms.predef.`?r` import viper.silicon.supporters.functions.FunctionRecorder import viper.silicon.verifier.Verifier import viper.silver.ast -import viper.silver.parser.PUnknown +import viper.silver.ast.NoPosition import scala.annotation.unused @@ -227,7 +226,8 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol } if(result.isDefined){ val (_, newChunk, _) = result.get - v.decider.assumptionAnalyzer.addChunkNode(Set(chunk1, chunk2), new ChunkGroupNode("mergeChunks", newChunk, AssumptionType.Implicit)) + val newChunkNode = PermissionInhaleNode(newChunk, StringAnalysisSourceInfo("state consolidation", NoPosition), AssumptionType.Internal) + v.decider.assumptionAnalyzer.addPermissionDependencies(Set(chunk1, chunk2), newChunkNode) } result } diff --git a/src/main/scala/state/Chunks.scala b/src/main/scala/state/Chunks.scala index 6d1f5b764..33c7680a5 100644 --- a/src/main/scala/state/Chunks.scala +++ b/src/main/scala/state/Chunks.scala @@ -6,14 +6,15 @@ package viper.silicon.state -import viper.silver.ast +import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType +import viper.silicon.assumptionAnalysis.{AnalysisSourceInfo, PermissionInhaleNode} import viper.silicon.interfaces.state._ import viper.silicon.resources._ import viper.silicon.rules.InverseFunctions -import viper.silicon.state.BasicChunk.createDerivedChunk import viper.silicon.state.terms._ import viper.silicon.state.terms.predef.`?r` import viper.silicon.verifier.Verifier +import viper.silver.ast object ChunkIdentifier { def apply(from: ast.Resource, program: ast.Program): ChunkIdentifer = { @@ -30,19 +31,26 @@ case class BasicChunkIdentifier(name: String) extends ChunkIdentifer { } object BasicChunk { - def apply(resourceID: BaseID, - id: BasicChunkIdentifier, - args: Seq[Term], - argsExp: Option[Seq[ast.Exp]], - snap: Term, - snapExp: Option[ast.Exp], - perm: Term, - permExp: Option[ast.Exp]): BasicChunk = { + // TODO ake: remove once every apply was adapted to the new version + def apply(resourceID: BaseID, id: BasicChunkIdentifier, + args: Seq[Term], argsExp: Option[Seq[ast.Exp]], + snap: Term, snapExp: Option[ast.Exp], + perm: Term, permExp: Option[ast.Exp]): BasicChunk = { new BasicChunk(resourceID, id, args, argsExp, snap, snapExp, perm, permExp) - // TODO ake: add to assumption graph } - def createDerivedChunk(oldChunk: BasicChunk, + def apply(resourceID: BaseID, id: BasicChunkIdentifier, + args: Seq[Term], argsExp: Option[Seq[ast.Exp]], + snap: Term, snapExp: Option[ast.Exp], + perm: Term, permExp: Option[ast.Exp], + v: Verifier, + sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): BasicChunk = { + val chunk = new BasicChunk(resourceID, id, args, argsExp, snap, snapExp, perm, permExp) + v.decider.assumptionAnalyzer.addAssumptionNode(PermissionInhaleNode(chunk, sourceInfo, assumptionType)) + chunk + } + + def createDerivedChunk(oldChunks: Set[Chunk], resourceID: BaseID, id: BasicChunkIdentifier, args: Seq[Term], @@ -50,9 +58,29 @@ object BasicChunk { snap: Term, snapExp: Option[ast.Exp], perm: Term, - permExp: Option[ast.Exp]): BasicChunk = { + permExp: Option[ast.Exp], v: Verifier, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): BasicChunk = { val newChunk = apply(resourceID, id, args, argsExp, snap, snapExp, perm, permExp) - // TODO ake: add edge + v.decider.assumptionAnalyzer.addPermissionDependencies(oldChunks, PermissionInhaleNode(newChunk, sourceInfo, assumptionType)) + newChunk + } +} + +object GeneralChunk { + def applyCondition(chunk: GeneralChunk, newCond: Term, newCondExp: Option[ast.Exp], v: Verifier, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): GeneralChunk = { + val newChunk = chunk.applyCondition(newCond, newCondExp) + v.decider.assumptionAnalyzer.addPermissionDependencies(Set(chunk), PermissionInhaleNode(newChunk, sourceInfo, assumptionType)) + newChunk + } + + def permMinus(chunk: GeneralChunk, newPerm: Term, newPermExp: Option[ast.Exp], v: Verifier, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): GeneralChunk = { + val newChunk = chunk.permMinus(newPerm, newPermExp) + v.decider.assumptionAnalyzer.addPermissionDependencies(Set(chunk), PermissionInhaleNode(newChunk, sourceInfo, assumptionType)) + newChunk + } + + def permPlus(chunk: GeneralChunk, newPerm: Term, newPermExp: Option[ast.Exp], v: Verifier, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): GeneralChunk = { + val newChunk = chunk.permPlus(newPerm, newPermExp) + v.decider.assumptionAnalyzer.addPermissionDependencies(Set(chunk), PermissionInhaleNode(newChunk, sourceInfo, assumptionType)) newChunk } } @@ -73,14 +101,14 @@ case class BasicChunk private (resourceID: BaseID, case PredicateID => require(snap.sort == sorts.Snap, s"A predicate chunk's snapshot ($snap) is expected to be of sort Snap, but found ${snap.sort}") } - override def applyCondition(newCond: Term, newCondExp: Option[ast.Exp]) = + override def applyCondition(newCond: Term, newCondExp: Option[ast.Exp]): BasicChunk = withPerm(Ite(newCond, perm, NoPerm), newCondExp.map(nce => ast.CondExp(nce, permExp.get, ast.NoPerm()())())) - override def permMinus(newPerm: Term, newPermExp: Option[ast.Exp]) = + override def permMinus(newPerm: Term, newPermExp: Option[ast.Exp]): BasicChunk = withPerm(PermMinus(perm, newPerm), newPermExp.map(npe => ast.PermSub(permExp.get, npe)())) - override def permPlus(newPerm: Term, newPermExp: Option[ast.Exp]) = + override def permPlus(newPerm: Term, newPermExp: Option[ast.Exp]): BasicChunk = withPerm(PermPlus(perm, newPerm), newPermExp.map(npe => ast.PermAdd(permExp.get, npe)())) - override def withPerm(newPerm: Term, newPermExp: Option[ast.Exp]) = createDerivedChunk(this, resourceID, id, args, argsExp, snap, snapExp, newPerm, newPermExp) - override def withSnap(newSnap: Term, newSnapExp: Option[ast.Exp]) = createDerivedChunk(this, resourceID, id, args, argsExp, newSnap, newSnapExp, perm, permExp) + override def withPerm(newPerm: Term, newPermExp: Option[ast.Exp]): BasicChunk = BasicChunk(resourceID, id, args, argsExp, snap, snapExp, newPerm, newPermExp) + override def withSnap(newSnap: Term, newSnapExp: Option[ast.Exp]): BasicChunk = BasicChunk(resourceID, id, args, argsExp, newSnap, newSnapExp, perm, permExp) override lazy val toString = resourceID match { case FieldID => s"${args.head}.$id -> $snap # $perm" From e9248289e10d7c9aa9fe63f72bfcad087a1fe84b Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 8 May 2025 16:48:13 +0200 Subject: [PATCH 018/474] update test examplest --- src/test/resources/andrea/branches.vpr | 13 ++++--------- src/test/resources/andrea/permissions.vpr | 8 ++++++-- src/test/resources/andrea/quickTest.vpr | 20 ++++++-------------- 3 files changed, 16 insertions(+), 25 deletions(-) diff --git a/src/test/resources/andrea/branches.vpr b/src/test/resources/andrea/branches.vpr index 00ebb755a..44740ac3f 100644 --- a/src/test/resources/andrea/branches.vpr +++ b/src/test/resources/andrea/branches.vpr @@ -15,17 +15,12 @@ method foo(a: Ref, b: Ref) n := b.f } - var x : Int := a.f - - if(x > n){ - x := 2*x + var x: Int := 0 + if(a.f > n){ + x := a.f + b.f }else{ - x := 2*n - b.f := 10 + x := n assert x >= 1 } - - n := b.f - assert x > 0 } \ No newline at end of file diff --git a/src/test/resources/andrea/permissions.vpr b/src/test/resources/andrea/permissions.vpr index 90898b88d..077487328 100644 --- a/src/test/resources/andrea/permissions.vpr +++ b/src/test/resources/andrea/permissions.vpr @@ -2,7 +2,7 @@ field f: Int method foo(a: Ref) - requires acc(a.f, 1/2) + requires acc(a.f, 1/2) && a.f >= 0 ensures acc(a.f, 1/2) @@ -11,9 +11,13 @@ method copyAndInc(x: Ref, y: Ref, z: Ref, p: Perm) requires acc(x.f) && acc(y.f, p) && acc(z.f, 1/2) ensures acc(x.f) && acc(y.f, p) { - assume p > 1/2 + assume p < 1/2 + assume y.f == 1 x.f := y.f + 1 assume y == z foo(x) + foo(x) assert x.f == z.f + 1 + x.f := 5 + assert x.f > 0 } \ No newline at end of file diff --git a/src/test/resources/andrea/quickTest.vpr b/src/test/resources/andrea/quickTest.vpr index 90898b88d..0af6666e6 100644 --- a/src/test/resources/andrea/quickTest.vpr +++ b/src/test/resources/andrea/quickTest.vpr @@ -1,19 +1,11 @@ field f: Int +function id(n: Int): Int + ensures n == result -method foo(a: Ref) - requires acc(a.f, 1/2) - ensures acc(a.f, 1/2) - - -method copyAndInc(x: Ref, y: Ref, z: Ref, p: Perm) - requires p > none - requires acc(x.f) && acc(y.f, p) && acc(z.f, 1/2) - ensures acc(x.f) && acc(y.f, p) +method foo(x: Ref, n: Int) { - assume p > 1/2 - x.f := y.f + 1 - assume y == z - foo(x) - assert x.f == z.f + 1 + var a: Int + a := id(5) + assert a > 0 } \ No newline at end of file From 3435cee0f9941a21b895d67c44684613f0669e8d Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 8 May 2025 17:52:35 +0200 Subject: [PATCH 019/474] prepare assume methods --- .../AssumptionAnalyzer.scala | 64 ++++++++++++------- src/main/scala/decider/Decider.scala | 53 ++++++++++----- src/main/scala/rules/Executor.scala | 4 +- src/main/scala/state/Chunks.scala | 2 +- 4 files changed, 80 insertions(+), 43 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index c62fa6d9e..064da3ec0 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -1,5 +1,6 @@ package viper.silicon.assumptionAnalysis +import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.state.Chunk import viper.silicon.state.terms.Term @@ -10,9 +11,12 @@ import viper.silver.ast.NoPosition trait AssumptionAnalyzer { // def pushScope(stmt: ast.Stmt): Unit // def closeScope(): Unit - def addAssumptionNode(assumption: AssumptionAnalysisNode): Unit - def addSingleAssumption(assumption: DebugExp): Option[Int] - def addAssumptions(assumptions: Iterable[DebugExp]): Seq[Int] + def addNode(assumption: AssumptionAnalysisNode): Unit + def addSingleAssumption(assumption: ast.Exp, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] + def addSingleAssumption(description: String, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] + def addSingleAssumption(assumption: DebugExp, assumptionType: AssumptionType = AssumptionType.Unknown): Option[Int] + def addSingleAssumption(assumption: DebugExp, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] + def addAssumptions(assumptions: Iterable[DebugExp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Seq[Int] def addAssertion(assertion: Term, isAsserted: Boolean, sourceInfo: AnalysisSourceInfo): Option[Int] def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNode: AssumptionAnalysisNode): Unit def processUnsatCore(dep: String): Unit @@ -64,23 +68,40 @@ class DefaultAssumptionAnalyzer(method: ast.Method) extends AssumptionAnalyzer { // isScopeOpen = false // } - override def addSingleAssumption(assumption: DebugExp): Option[Int] = { - val node = if(assumption.originalExp.isDefined) SimpleAssumptionNode(assumption.originalExp.get, ExpAnalysisSourceInfo(assumption.originalExp.get), AssumptionType.Unknown) - else StringAssumptionNode(assumption.description.getOrElse("unknown"), StringAnalysisSourceInfo(assumption.description.getOrElse("unknown"), NoPosition), AssumptionType.Unknown) + override def addSingleAssumption(assumption: ast.Exp, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { + val node = SimpleAssumptionNode(assumption, sourceInfo, assumptionType) assumptionGraph.addNode(node) Some(node.id) } - override def addAssumptions(assumptions: Iterable[DebugExp]): Seq[Int] = { + override def addSingleAssumption(description: String, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { + val node = StringAssumptionNode(description, sourceInfo, assumptionType) + addNode(node) + Some(node.id) + } + + override def addSingleAssumption(assumption: DebugExp, assumptionType: AssumptionType = AssumptionType.Unknown): Option[Int] = { + if(assumption.originalExp.isDefined) addSingleAssumption(assumption, ExpAnalysisSourceInfo(assumption.originalExp.get), assumptionType) + else addSingleAssumption(assumption, StringAnalysisSourceInfo(assumption.description.getOrElse("unknown"), NoPosition), assumptionType) + } + + override def addSingleAssumption(assumption: DebugExp, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { + val node = if(assumption.originalExp.isDefined) SimpleAssumptionNode(assumption.originalExp.get, sourceInfo, assumptionType) + else StringAssumptionNode(assumption.description.getOrElse("unknown"), sourceInfo, assumptionType) + addNode(node) + Some(node.id) + } + + override def addAssumptions(assumptions: Iterable[DebugExp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Seq[Int] = { val newNodes = assumptions.filter(_.originalExp.isDefined) - .map(a => SimpleAssumptionNode(a.originalExp.get, ExpAnalysisSourceInfo(a.originalExp.get), AssumptionType.Unknown)) - assumptionGraph.addNodes(newNodes.toSet) + .map(a => SimpleAssumptionNode(a.originalExp.get, sourceInfo, assumptionType)) + newNodes foreach addNode newNodes.map(_.id).toSeq } override def addAssertion(assertion: Term, isAsserted: Boolean, sourceInfo: AnalysisSourceInfo): Option[Int] = { val newNode = SimpleAssertionNode(assertion, isAsserted, sourceInfo) - assumptionGraph.addNode(newNode) + addNode(newNode) Some(newNode.id) } @@ -92,15 +113,15 @@ class DefaultAssumptionAnalyzer(method: ast.Method) extends AssumptionAnalyzer { assumptionGraph.addEdges(assumptionIds, assertionIds) } - override def addAssumptionNode(assumption: AssumptionAnalysisNode): Unit = { - assumptionGraph.addNode(assumption) + override def addNode(node: AssumptionAnalysisNode): Unit = { + assumptionGraph.addNode(node) } override def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNode: AssumptionAnalysisNode): Unit = { val analysisChunks = assumptionGraph.nodes .filter(c => c.isInstanceOf[ChunkAnalysisInfo] && oldChunks.contains(c.asInstanceOf[ChunkAnalysisInfo].getChunk)) .map(_.id).toSet - addAssumptionNode(newChunkNode) + addNode(newChunkNode) assumptionGraph.addEdges(analysisChunks, newChunkNode.id) } @@ -110,18 +131,14 @@ class DefaultAssumptionAnalyzer(method: ast.Method) extends AssumptionAnalyzer { class NoAssumptionAnalyzer extends AssumptionAnalyzer { - override def addAssumptions(assumptions: Iterable[DebugExp]): Seq[Int] = { - Seq.empty - } + override def addAssumptions(assumptions: Iterable[DebugExp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Seq[Int] = Seq.empty - override def addAssertion(assertion: Term, isAsserted: Boolean, sourceInfo: AnalysisSourceInfo): Option[Int] = { - None - } + override def addAssertion(assertion: Term, isAsserted: Boolean, sourceInfo: AnalysisSourceInfo): Option[Int] = None override def processUnsatCore(dep: String): Unit = { } - override def addAssumptionNode(assumption: AssumptionAnalysisNode): Unit = { + override def addNode(assumption: AssumptionAnalysisNode): Unit = { } override def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNode: AssumptionAnalysisNode): Unit = { @@ -129,7 +146,8 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { override def getMethod: Option[ast.Method] = None - override def addSingleAssumption(assumption: DebugExp): Option[Int] = { - None - } + override def addSingleAssumption(assumption: DebugExp, assumptionType: AssumptionType = AssumptionType.Unknown): Option[Int] = None + override def addSingleAssumption(assumption: DebugExp, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None + override def addSingleAssumption(assumption: ast.Exp, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None + override def addSingleAssumption(description: String, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None } \ No newline at end of file diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index c2442f634..3a7ac2e02 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -8,7 +8,8 @@ package viper.silicon.decider import com.typesafe.scalalogging.Logger import viper.silicon._ -import viper.silicon.assumptionAnalysis.{AssumptionAnalyzer, DefaultAssumptionAnalyzer, NoAssumptionAnalyzer, StringAnalysisSourceInfo} +import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType +import viper.silicon.assumptionAnalysis.{AnalysisSourceInfo, AssumptionAnalyzer, AssumptionType, DefaultAssumptionAnalyzer, NoAssumptionAnalyzer, StringAnalysisSourceInfo} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.interfaces._ @@ -59,14 +60,16 @@ trait Decider { def startDebugSubExp(): Unit - def assume(t: Term, e: ast.Exp, finalExp: ast.Exp): Unit def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp]): Unit + def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Unit def assume(t: Term, debugExp: Option[DebugExp]): Unit + def assume(t: Term, debugExp: Option[DebugExp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Unit def assume(terms: Seq[Term], debugExps: Option[Seq[DebugExp]]): Unit + def assume(terms: Seq[Term], debugExps: Option[Seq[DebugExp]], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Unit def assumeDefinition(t: Term, debugExp: Option[DebugExp]): Unit - def assume(assumptions: Iterable[(Term, Option[DebugExp])]): Unit - def assume(assumptions: InsertionOrderedSet[(Term, Option[DebugExp])], enforceAssumption: Boolean = false, isDefinition: Boolean = false): Unit + def assumeDefinition(t: Term, debugExp: Option[DebugExp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Unit def assume(terms: Iterable[Term], debugExp: Option[DebugExp], enforceAssumption: Boolean): Unit + def assume(terms: Iterable[Term], debugExp: Option[DebugExp], enforceAssumption: Boolean, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Unit def check(t: Term, timeout: Int): Boolean @@ -295,30 +298,38 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } } - def assume(t: Term, e : ast.Exp, finalExp : ast.Exp): Unit = { - assume(assumptions=InsertionOrderedSet((t, Some(DebugExp.createInstance(e, finalExp)))), false, false) + def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp]): Unit = { + assume(t, e, finalExp, StringAnalysisSourceInfo("unknown", NoPosition), AssumptionType.Unknown) } - def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp]): Unit = { + def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Unit = { if (finalExp.isDefined) { - assume(assumptions=InsertionOrderedSet((t, Some(DebugExp.createInstance(e.get, finalExp.get)))), false, false) + assume(assumptions=InsertionOrderedSet((t, Some(DebugExp.createInstance(e.get, finalExp.get)))), sourceInfo, assumptionType, false, false) } else { - assume(assumptions=InsertionOrderedSet((t, None)), false, false) + assume(assumptions=InsertionOrderedSet((t, None)), sourceInfo, assumptionType, false, false) } } def assume(t: Term, debugExp: Option[DebugExp]): Unit = { - assume(InsertionOrderedSet(Seq((t, debugExp))), false) + assume(t, debugExp, StringAnalysisSourceInfo("unknown", NoPosition), AssumptionType.Unknown) + } + + def assume(t: Term, debugExp: Option[DebugExp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Unit = { + assume(InsertionOrderedSet(Seq((t, debugExp))), sourceInfo, assumptionType, false, false) } def assumeDefinition(t: Term, debugExp: Option[DebugExp]): Unit = { - assume(InsertionOrderedSet(Seq((t, debugExp))), enforceAssumption=false, isDefinition=true) + assumeDefinition(t, debugExp, StringAnalysisSourceInfo("unknown", NoPosition), AssumptionType.Unknown) + } + + def assumeDefinition(t: Term, debugExp: Option[DebugExp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Unit = { + assume(InsertionOrderedSet(Seq((t, debugExp))), sourceInfo, assumptionType, enforceAssumption=false, isDefinition=true) } - def assume(assumptions: Iterable[(Term, Option[DebugExp])]): Unit = - assume(InsertionOrderedSet(assumptions), false) + def assume(assumptions: Iterable[(Term, Option[DebugExp])], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Unit = + assume(InsertionOrderedSet(assumptions), sourceInfo, assumptionType, false, false) - def assume(assumptions: InsertionOrderedSet[(Term, Option[DebugExp])], enforceAssumption: Boolean = false, isDefinition: Boolean = false): Unit = { + def assume(assumptions: InsertionOrderedSet[(Term, Option[DebugExp])], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, enforceAssumption: Boolean, isDefinition: Boolean): Unit = { val filteredAssumptions = if (enforceAssumption) assumptions else assumptions filterNot (a => isKnownToBeTrue(a._1)) @@ -329,7 +340,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } val filteredAssumptionsWithLabels = filteredAssumptions map{case (t, de) => - val assumptionId: Option[Int] = if(de.isDefined) assumptionAnalyzer.addSingleAssumption(de.get) else None + val assumptionId: Option[Int] = if(de.isDefined) assumptionAnalyzer.addSingleAssumption(de.get, sourceInfo, assumptionType) else None (t, AssumptionAnalyzer.createAssumptionLabel(assumptionId)) } @@ -339,7 +350,11 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } def assume(assumptions: Seq[Term], debugExps: Option[Seq[DebugExp]]): Unit = { - val assumptionIds = if(debugExps.isDefined) assumptionAnalyzer.addAssumptions(debugExps.get) else Seq.empty + assume(assumptions, debugExps, StringAnalysisSourceInfo("unknown", NoPosition), AssumptionType.Unknown) + } + + def assume(assumptions: Seq[Term], debugExps: Option[Seq[DebugExp]], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Unit = { + val assumptionIds = if(debugExps.isDefined) assumptionAnalyzer.addAssumptions(debugExps.get, sourceInfo, assumptionType) else Seq.empty val assumptionsWithLabels = if(assumptions.size == assumptionIds.size) assumptions.zip(assumptionIds).map{case (t, id) => (t, AssumptionAnalyzer.createAssumptionLabel(Some(id)))} @@ -351,6 +366,10 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } def assume(terms: Iterable[Term], debugExp: Option[DebugExp], enforceAssumption: Boolean): Unit = { + assume(terms, debugExp, enforceAssumption, StringAnalysisSourceInfo("unknown", NoPosition), AssumptionType.Unknown) + } + + def assume(terms: Iterable[Term], debugExp: Option[DebugExp], enforceAssumption: Boolean, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Unit = { val filteredTerms = if (enforceAssumption) terms @@ -360,7 +379,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => if (debugMode) { addDebugExp(debugExp.get.withTerm(And(filteredTerms))) - val assumptionId: Option[Int] = if(debugExp.isDefined) assumptionAnalyzer.addSingleAssumption(debugExp.get.withTerm(And(filteredTerms))) else None + val assumptionId: Option[Int] = if(debugExp.isDefined) assumptionAnalyzer.addSingleAssumption(debugExp.get.withTerm(And(filteredTerms)), sourceInfo, assumptionType) else None val termsWithLabel = filteredTerms.zipWithIndex.iterator.map {case (t, idx) => (t, AssumptionAnalyzer.createAssumptionLabel(assumptionId, idx))}.toSeq assumeWithoutSmokeChecks(InsertionOrderedSet(termsWithLabel)) }else{ diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index f5e1532e1..885d4de1c 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -9,7 +9,7 @@ package viper.silicon.rules import viper.silicon.debugger.DebugExp import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.Config.JoinMode -import viper.silicon.assumptionAnalysis.{AssumptionType, ExpAnalysisSourceInfo, PermissionInhaleNode} +import viper.silicon.assumptionAnalysis.{AssumptionType, ExpAnalysisSourceInfo, PermissionInhaleNode, StmtAnalysisSourceInfo} import scala.annotation.unused import viper.silver.cfg.silver.SilverCfg @@ -457,7 +457,7 @@ object executor extends ExecutionRules { val debugExp = Option.when(withExp)(ast.NeCmp(x, ast.NullLit()())()) val debugExpSubst = Option.when(withExp)(ast.NeCmp(eRcvrNew.get, ast.NullLit()())()) val (debugHeapName, debugLabel) = v.getDebugOldLabel(s, stmt.pos) - v.decider.assume(tRcvr !== Null, debugExp, debugExpSubst) + v.decider.assume(tRcvr !== Null, debugExp, debugExpSubst, StmtAnalysisSourceInfo(stmt), AssumptionType.Implicit) val newChunks = fields map (field => { val p = FullPerm val pExp = Option.when(withExp)(ast.FullPerm()(stmt.pos, stmt.info, stmt.errT)) diff --git a/src/main/scala/state/Chunks.scala b/src/main/scala/state/Chunks.scala index 33c7680a5..e75b2891a 100644 --- a/src/main/scala/state/Chunks.scala +++ b/src/main/scala/state/Chunks.scala @@ -46,7 +46,7 @@ object BasicChunk { v: Verifier, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): BasicChunk = { val chunk = new BasicChunk(resourceID, id, args, argsExp, snap, snapExp, perm, permExp) - v.decider.assumptionAnalyzer.addAssumptionNode(PermissionInhaleNode(chunk, sourceInfo, assumptionType)) + v.decider.assumptionAnalyzer.addNode(PermissionInhaleNode(chunk, sourceInfo, assumptionType)) chunk } From a4b9c310a0029e60888dcb5e3dd3c144d11db637 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 8 May 2025 18:05:36 +0200 Subject: [PATCH 020/474] refactoring --- .../AssumptionAnalyzer.scala | 68 +++++++++++-------- src/main/scala/decider/ProverStdIO.scala | 2 +- src/main/scala/state/Chunks.scala | 2 +- 3 files changed, 42 insertions(+), 30 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 064da3ec0..791572e92 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -9,22 +9,29 @@ import viper.silver.ast.NoPosition trait AssumptionAnalyzer { -// def pushScope(stmt: ast.Stmt): Unit -// def closeScope(): Unit - def addNode(assumption: AssumptionAnalysisNode): Unit + // def pushScope(stmt: ast.Stmt): Unit + // def closeScope(): Unit + def addPermissionNode(chunk: Chunk, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] + def addSingleAssumption(assumption: ast.Exp, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] + def addSingleAssumption(description: String, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] + def addSingleAssumption(assumption: DebugExp, assumptionType: AssumptionType = AssumptionType.Unknown): Option[Int] + def addSingleAssumption(assumption: DebugExp, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] + def addAssumptions(assumptions: Iterable[DebugExp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Seq[Int] + def addAssertion(assertion: Term, isAsserted: Boolean, sourceInfo: AnalysisSourceInfo): Option[Int] + def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNode: AssumptionAnalysisNode): Unit - def processUnsatCore(dep: String): Unit + + def processUnsatCoreAndAddDependencies(dep: String): Unit val assumptionGraph: AssumptionAnalysisGraph = new DefaultAssumptionAnalysisGraph() def getMethod: Option[ast.Method] - } object AssumptionAnalyzer { @@ -51,26 +58,30 @@ object AssumptionAnalyzer { } class DefaultAssumptionAnalyzer(method: ast.Method) extends AssumptionAnalyzer { -// private var scope : mutable.Set[AssumptionAnalysisNode] = mutable.Set.empty -// private var isScopeOpen: Boolean = false -// private var scopeStmt: ast.Stmt = ??? - - -// override def pushScope(stmt: ast.Stmt): Unit = { -//// scopeStmt = stmt -// scope = mutable.Set.empty -// isScopeOpen = true -// } -// -// override def closeScope(): Unit = { -//// assumptionGraph.addNode(new StatementGroupNode(scopeStmt, scope.toSet)) -// scope = mutable.Set.empty -// isScopeOpen = false -// } + // private var scope : mutable.Set[AssumptionAnalysisNode] = mutable.Set.empty + // private var isScopeOpen: Boolean = false + // private var scopeStmt: ast.Stmt = ??? + + + // override def pushScope(stmt: ast.Stmt): Unit = { + //// scopeStmt = stmt + // scope = mutable.Set.empty + // isScopeOpen = true + // } + // + // override def closeScope(): Unit = { + //// assumptionGraph.addNode(new StatementGroupNode(scopeStmt, scope.toSet)) + // scope = mutable.Set.empty + // isScopeOpen = false + // } + + def addNode(node: AssumptionAnalysisNode): Unit = { + assumptionGraph.addNode(node) + } override def addSingleAssumption(assumption: ast.Exp, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { val node = SimpleAssumptionNode(assumption, sourceInfo, assumptionType) - assumptionGraph.addNode(node) + addNode(node) Some(node.id) } @@ -105,7 +116,7 @@ class DefaultAssumptionAnalyzer(method: ast.Method) extends AssumptionAnalyzer { Some(newNode.id) } - override def processUnsatCore(dep: String): Unit = { + override def processUnsatCoreAndAddDependencies(dep: String): Unit = { val assumptionLabels = dep.replace("(", "").replace(")", "").split(" ") if(assumptionLabels.size < 2) return val assumptionIds = assumptionLabels.filter(AssumptionAnalyzer.isAssumptionLabel).map(AssumptionAnalyzer.getIdFromLabel) @@ -113,8 +124,10 @@ class DefaultAssumptionAnalyzer(method: ast.Method) extends AssumptionAnalyzer { assumptionGraph.addEdges(assumptionIds, assertionIds) } - override def addNode(node: AssumptionAnalysisNode): Unit = { - assumptionGraph.addNode(node) + override def addPermissionNode(chunk: Chunk, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { + val node = PermissionInhaleNode(chunk, sourceInfo, assumptionType) + addNode(node) + Some(node.id) } override def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNode: AssumptionAnalysisNode): Unit = { @@ -135,11 +148,10 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { override def addAssertion(assertion: Term, isAsserted: Boolean, sourceInfo: AnalysisSourceInfo): Option[Int] = None - override def processUnsatCore(dep: String): Unit = { + override def processUnsatCoreAndAddDependencies(dep: String): Unit = { } - override def addNode(assumption: AssumptionAnalysisNode): Unit = { - } + override def addPermissionNode(chunk: Chunk, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None override def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNode: AssumptionAnalysisNode): Unit = { } diff --git a/src/main/scala/decider/ProverStdIO.scala b/src/main/scala/decider/ProverStdIO.scala index 5b073fda4..48f2f0754 100644 --- a/src/main/scala/decider/ProverStdIO.scala +++ b/src/main/scala/decider/ProverStdIO.scala @@ -295,7 +295,7 @@ abstract class ProverStdIO(uniqueId: String, retrieveReasonUnknown() }else{ val unsatCore = extractUnsatCore() - assumptionAnalyzer.processUnsatCore(unsatCore) + assumptionAnalyzer.processUnsatCoreAndAddDependencies(unsatCore) } pop() diff --git a/src/main/scala/state/Chunks.scala b/src/main/scala/state/Chunks.scala index e75b2891a..0ddca1f23 100644 --- a/src/main/scala/state/Chunks.scala +++ b/src/main/scala/state/Chunks.scala @@ -46,7 +46,7 @@ object BasicChunk { v: Verifier, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): BasicChunk = { val chunk = new BasicChunk(resourceID, id, args, argsExp, snap, snapExp, perm, permExp) - v.decider.assumptionAnalyzer.addNode(PermissionInhaleNode(chunk, sourceInfo, assumptionType)) + v.decider.assumptionAnalyzer.addPermissionNode(chunk, sourceInfo, assumptionType) chunk } From 27aa069cf751b8fc3aac8136024dd6ddb4cbf1e2 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 9 May 2025 08:54:14 +0200 Subject: [PATCH 021/474] BasicChunk applyCondition --- .../assumptionAnalysis/AnalysisInfo.scala | 9 ++++++ src/main/scala/rules/Consumer.scala | 2 ++ src/main/scala/rules/Executor.scala | 6 ++-- src/main/scala/rules/Joiner.scala | 8 +++-- src/main/scala/rules/PredicateSupporter.scala | 4 +-- src/main/scala/rules/Producer.scala | 6 ++-- src/main/scala/state/Chunks.scala | 23 +++++++------- src/main/scala/state/State.scala | 30 ++++++++++--------- 8 files changed, 51 insertions(+), 37 deletions(-) create mode 100644 src/main/scala/assumptionAnalysis/AnalysisInfo.scala diff --git a/src/main/scala/assumptionAnalysis/AnalysisInfo.scala b/src/main/scala/assumptionAnalysis/AnalysisInfo.scala new file mode 100644 index 000000000..3f4581524 --- /dev/null +++ b/src/main/scala/assumptionAnalysis/AnalysisInfo.scala @@ -0,0 +1,9 @@ +package viper.silicon.assumptionAnalysis + +import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType +import viper.silicon.verifier.Verifier + +case class AnalysisInfo(v: Verifier, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType) { + + def getAssumptionAnalyzer: AssumptionAnalyzer = v.decider.assumptionAnalyzer +} diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index d15f1b882..5e9aa39cc 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -8,6 +8,7 @@ package viper.silicon.rules import viper.silicon.debugger.DebugExp import viper.silicon.Config.JoinMode +import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType, StringAnalysisSourceInfo} import scala.collection.mutable import viper.silver.ast @@ -570,6 +571,7 @@ object consumer extends ConsumptionRules { State.mergeHeap( entry1.data._1, And(entry1.pathConditions.branchConditions), Option.when(withExp)(BigAnd(entry1.pathConditions.branchConditionExps.map(_._2.get))), entry2.data._1, And(entry2.pathConditions.branchConditions), Option.when(withExp)(BigAnd(entry2.pathConditions.branchConditionExps.map(_._2.get))), + AnalysisInfo(v, StringAnalysisSourceInfo("conditional join", e0.pos), AssumptionType.Implicit) ), // Assume that entry1.pcs is inverse of entry2.pcs (entry1.data._2, entry2.data._2) match { diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 885d4de1c..128451182 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -9,7 +9,7 @@ package viper.silicon.rules import viper.silicon.debugger.DebugExp import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.Config.JoinMode -import viper.silicon.assumptionAnalysis.{AssumptionType, ExpAnalysisSourceInfo, PermissionInhaleNode, StmtAnalysisSourceInfo} +import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType, ExpAnalysisSourceInfo, PermissionInhaleNode, StmtAnalysisSourceInfo} import scala.annotation.unused import viper.silver.cfg.silver.SilverCfg @@ -441,7 +441,7 @@ object executor extends ExecutionRules { val (tSnap, _) = ssaifyRhs(tRhs, rhs, rhsNew, field.name, field.typ, v3, s3) val id = BasicChunkIdentifier(field.name) val newChunk = BasicChunk(FieldID, id, Seq(tRcvr), eRcvrNew.map(Seq(_)), tSnap, rhsNew, FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), - v3, ExpAnalysisSourceInfo(fa), AssumptionType.Implicit) + AnalysisInfo(v3, ExpAnalysisSourceInfo(fa), AssumptionType.Implicit)) chunkSupporter.produce(s3, h3, newChunk, v3)((s4, h4, v4) => { val s5 = s4.copy(h = h4) val (debugHeapName, _) = v4.getDebugOldLabel(s5, fa.pos) @@ -472,7 +472,7 @@ object executor extends ExecutionRules { field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), p, pExp, sm, s.program) } else { val newChunk = BasicChunk(FieldID, BasicChunkIdentifier(field.name), Seq(tRcvr), Option.when(withExp)(Seq(x)), snap, snapExp, p, pExp, - v, ExpAnalysisSourceInfo(x), AssumptionType.Explicit) + AnalysisInfo(v, ExpAnalysisSourceInfo(ast.FieldAccess(x, field)(field.pos, field.info, field.errT)), AssumptionType.Explicit)) newChunk } }) diff --git a/src/main/scala/rules/Joiner.scala b/src/main/scala/rules/Joiner.scala index c2dbdb6a9..27ffff96c 100644 --- a/src/main/scala/rules/Joiner.scala +++ b/src/main/scala/rules/Joiner.scala @@ -6,6 +6,7 @@ package viper.silicon.rules +import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType, StringAnalysisSourceInfo} import viper.silicon.debugger.DebugExp import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.decider.RecordedPathConditions @@ -16,6 +17,7 @@ import viper.silicon.state.terms.{And, Or, Term} import viper.silicon.utils.ast.{BigAnd, BigOr} import viper.silicon.verifier.Verifier import viper.silver.ast +import viper.silver.ast.NoPosition import scala.annotation.unused @@ -24,12 +26,12 @@ case class JoinDataEntry[D](s: State, data: D, pathConditions: RecordedPathCondi // we can directly merge JoinDataEntries to obtain new States, // and the join data entries themselves provide information about the path conditions to State.merge. def pathConditionAwareMerge(other: JoinDataEntry[D], v: Verifier): State = { - val res = State.merge(this.s, this.pathConditions, other.s, other.pathConditions) + val res = State.merge(this.s, this.pathConditions, other.s, other.pathConditions, AnalysisInfo(v, StringAnalysisSourceInfo("merge", NoPosition), AssumptionType.Implicit)) v.stateConsolidator(s).consolidate(res, v) } - def pathConditionAwareMergeWithoutConsolidation(other: JoinDataEntry[D], @unused v: Verifier): State = { - State.merge(this.s, this.pathConditions, other.s, other.pathConditions) + def pathConditionAwareMergeWithoutConsolidation(other: JoinDataEntry[D], v: Verifier): State = { + State.merge(this.s, this.pathConditions, other.s, other.pathConditions, AnalysisInfo(v, StringAnalysisSourceInfo("merge", NoPosition), AssumptionType.Implicit)) } } diff --git a/src/main/scala/rules/PredicateSupporter.scala b/src/main/scala/rules/PredicateSupporter.scala index 53722e94b..644315ed5 100644 --- a/src/main/scala/rules/PredicateSupporter.scala +++ b/src/main/scala/rules/PredicateSupporter.scala @@ -6,7 +6,7 @@ package viper.silicon.rules -import viper.silicon.assumptionAnalysis.{AssumptionType, ExpAnalysisSourceInfo, PermissionInhaleNode} +import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType, ExpAnalysisSourceInfo, PermissionInhaleNode} import viper.silicon.debugger.DebugExp import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.interfaces.VerificationResult @@ -116,7 +116,7 @@ object predicateSupporter extends PredicateSupportRules { Q(s3, v1) } else { val ch = BasicChunk(PredicateID, BasicChunkIdentifier(predicate.name), tArgs, eArgs, snap.get.convert(sorts.Snap), None, tPerm, ePerm, - v1, ExpAnalysisSourceInfo(PredicateAccess(Seq(), predicate)(NoPosition, NoInfo, NoTrafos)), AssumptionType.Unknown) + AnalysisInfo(v1, ExpAnalysisSourceInfo(PredicateAccess(Seq(), predicate)(NoPosition, NoInfo, NoTrafos)), AssumptionType.Unknown)) val s3 = s2.copy(g = s.g, smDomainNeeded = s.smDomainNeeded, permissionScalingFactor = s.permissionScalingFactor, diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index 7d9c5c011..c6871348c 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -8,7 +8,7 @@ package viper.silicon.rules import viper.silicon.debugger.DebugExp import viper.silicon.Config.JoinMode -import viper.silicon.assumptionAnalysis.{AssumptionType, ExpAnalysisSourceInfo, PermissionInhaleNode} +import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType, ExpAnalysisSourceInfo, PermissionInhaleNode} import scala.collection.mutable import viper.silver.ast @@ -339,7 +339,7 @@ object producer extends ProductionRules { val (debugHeapName, debugLabel) = v3.getDebugOldLabel(s3, accPred.pos) val snapExp = Option.when(withExp)(ast.DebugLabelledOld(ast.FieldAccess(eRcvrNew.get, field)(), debugLabel)(accPred.pos, accPred.info, accPred.errT)) val ch = BasicChunk(FieldID, BasicChunkIdentifier(field.name), Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), snap, snapExp, gain, gainExp, - v1, ExpAnalysisSourceInfo(fa), AssumptionType.Unknown) + AnalysisInfo(v1, ExpAnalysisSourceInfo(fa), AssumptionType.Unknown)) chunkSupporter.produce(s3, s3.h, ch, v3)((s4, h4, v4) => { val s5 = s4.copy(h = h4) val s6 = if (withExp) s5.copy(oldHeaps = s5.oldHeaps + (debugHeapName -> magicWandSupporter.getEvalHeap(s4))) else s5 @@ -367,7 +367,7 @@ object producer extends ProductionRules { } else { val snap1 = snap.convert(sorts.Snap) val ch = BasicChunk(PredicateID, BasicChunkIdentifier(predicate.name), tArgs, eArgsNew, snap1, None, gain, gainExp, - v1, ExpAnalysisSourceInfo(pa), AssumptionType.Unknown) + AnalysisInfo(v1, ExpAnalysisSourceInfo(pa), AssumptionType.Unknown)) chunkSupporter.produce(s2, s2.h, ch, v2)((s3, h3, v3) => { if (Verifier.config.enablePredicateTriggersOnInhale() && s3.functionRecorder == NoopFunctionRecorder && !Verifier.config.disableFunctionUnfoldTrigger()) { diff --git a/src/main/scala/state/Chunks.scala b/src/main/scala/state/Chunks.scala index 0ddca1f23..07da06ad8 100644 --- a/src/main/scala/state/Chunks.scala +++ b/src/main/scala/state/Chunks.scala @@ -7,7 +7,7 @@ package viper.silicon.state import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType -import viper.silicon.assumptionAnalysis.{AnalysisSourceInfo, PermissionInhaleNode} +import viper.silicon.assumptionAnalysis.{AnalysisInfo, AnalysisSourceInfo, PermissionInhaleNode} import viper.silicon.interfaces.state._ import viper.silicon.resources._ import viper.silicon.rules.InverseFunctions @@ -43,10 +43,9 @@ object BasicChunk { args: Seq[Term], argsExp: Option[Seq[ast.Exp]], snap: Term, snapExp: Option[ast.Exp], perm: Term, permExp: Option[ast.Exp], - v: Verifier, - sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): BasicChunk = { + analysisInfo: AnalysisInfo): BasicChunk = { val chunk = new BasicChunk(resourceID, id, args, argsExp, snap, snapExp, perm, permExp) - v.decider.assumptionAnalyzer.addPermissionNode(chunk, sourceInfo, assumptionType) + analysisInfo.getAssumptionAnalyzer.addPermissionNode(chunk, analysisInfo.sourceInfo, analysisInfo.assumptionType) chunk } @@ -58,29 +57,29 @@ object BasicChunk { snap: Term, snapExp: Option[ast.Exp], perm: Term, - permExp: Option[ast.Exp], v: Verifier, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): BasicChunk = { + permExp: Option[ast.Exp], analysisInfo: AnalysisInfo): BasicChunk = { val newChunk = apply(resourceID, id, args, argsExp, snap, snapExp, perm, permExp) - v.decider.assumptionAnalyzer.addPermissionDependencies(oldChunks, PermissionInhaleNode(newChunk, sourceInfo, assumptionType)) + analysisInfo.getAssumptionAnalyzer.addPermissionDependencies(oldChunks, PermissionInhaleNode(newChunk, analysisInfo.sourceInfo, analysisInfo.assumptionType)) newChunk } } object GeneralChunk { - def applyCondition(chunk: GeneralChunk, newCond: Term, newCondExp: Option[ast.Exp], v: Verifier, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): GeneralChunk = { + def applyCondition(chunk: GeneralChunk, newCond: Term, newCondExp: Option[ast.Exp], analysisInfo: AnalysisInfo): GeneralChunk = { val newChunk = chunk.applyCondition(newCond, newCondExp) - v.decider.assumptionAnalyzer.addPermissionDependencies(Set(chunk), PermissionInhaleNode(newChunk, sourceInfo, assumptionType)) + analysisInfo.getAssumptionAnalyzer.addPermissionDependencies(Set(chunk), PermissionInhaleNode(newChunk, analysisInfo.sourceInfo, analysisInfo.assumptionType)) newChunk } - def permMinus(chunk: GeneralChunk, newPerm: Term, newPermExp: Option[ast.Exp], v: Verifier, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): GeneralChunk = { + def permMinus(chunk: GeneralChunk, newPerm: Term, newPermExp: Option[ast.Exp], analysisInfo: AnalysisInfo): GeneralChunk = { val newChunk = chunk.permMinus(newPerm, newPermExp) - v.decider.assumptionAnalyzer.addPermissionDependencies(Set(chunk), PermissionInhaleNode(newChunk, sourceInfo, assumptionType)) + analysisInfo.getAssumptionAnalyzer.addPermissionDependencies(Set(chunk), PermissionInhaleNode(newChunk, analysisInfo.sourceInfo, analysisInfo.assumptionType)) newChunk } - def permPlus(chunk: GeneralChunk, newPerm: Term, newPermExp: Option[ast.Exp], v: Verifier, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): GeneralChunk = { + def permPlus(chunk: GeneralChunk, newPerm: Term, newPermExp: Option[ast.Exp], analysisInfo: AnalysisInfo): GeneralChunk = { val newChunk = chunk.permPlus(newPerm, newPermExp) - v.decider.assumptionAnalyzer.addPermissionDependencies(Set(chunk), PermissionInhaleNode(newChunk, sourceInfo, assumptionType)) + analysisInfo.getAssumptionAnalyzer.addPermissionDependencies(Set(chunk), PermissionInhaleNode(newChunk, analysisInfo.sourceInfo, analysisInfo.assumptionType)) newChunk } } diff --git a/src/main/scala/state/State.scala b/src/main/scala/state/State.scala index 3256ff451..6b8b0fe6a 100644 --- a/src/main/scala/state/State.scala +++ b/src/main/scala/state/State.scala @@ -8,6 +8,7 @@ package viper.silicon.state import viper.silicon.Config.JoinMode import viper.silicon.Config.JoinMode.JoinMode +import viper.silicon.assumptionAnalysis.AnalysisInfo import viper.silver.ast import viper.silver.cfg.silver.SilverCfg import viper.silicon.common.Mergeable @@ -287,33 +288,33 @@ object State { } // Puts a collection of chunks under a condition. - private def conditionalizeChunks(h: Iterable[Chunk], cond: Term, condExp: Option[ast.Exp]): Iterable[Chunk] = { + private def conditionalizeChunks(h: Iterable[Chunk], cond: Term, condExp: Option[ast.Exp], analysisInfo: AnalysisInfo): Iterable[Chunk] = { h map (c => { c match { case c: GeneralChunk => - c.applyCondition(cond, condExp) + GeneralChunk.applyCondition(c, cond, condExp, analysisInfo) case _ => sys.error("Chunk type not conditionalizable.") } }) } // Puts a heap under a condition. - private def conditionalizeHeap(h: Heap, cond: Term, condExp: Option[ast.Exp]): Heap = { - Heap(conditionalizeChunks(h.values, cond, condExp)) + private def conditionalizeHeap(h: Heap, cond: Term, condExp: Option[ast.Exp], analysisInfo: AnalysisInfo): Heap = { + Heap(conditionalizeChunks(h.values, cond, condExp, analysisInfo)) } // Merges two heaps together, by putting h1 under condition cond1, // and h2 under cond2. // Assumes that cond1 is the negation of cond2. - def mergeHeap(h1: Heap, cond1: Term, cond1Exp: Option[ast.Exp], h2: Heap, cond2: Term, cond2Exp: Option[ast.Exp]): Heap = { + def mergeHeap(h1: Heap, cond1: Term, cond1Exp: Option[ast.Exp], h2: Heap, cond2: Term, cond2Exp: Option[ast.Exp], analysisInfo: AnalysisInfo): Heap = { val (unconditionalHeapChunks, h1HeapChunksToConditionalize) = h1.values.partition(c1 => h2.values.exists(_ == c1)) val h2HeapChunksToConditionalize = h2.values.filter(c2 => !unconditionalHeapChunks.exists(_ == c2)) - val h1ConditionalizedHeapChunks = conditionalizeChunks(h1HeapChunksToConditionalize, cond1, cond1Exp) - val h2ConditionalizedHeapChunks = conditionalizeChunks(h2HeapChunksToConditionalize, cond2, cond2Exp) + val h1ConditionalizedHeapChunks = conditionalizeChunks(h1HeapChunksToConditionalize, cond1, cond1Exp, analysisInfo) + val h2ConditionalizedHeapChunks = conditionalizeChunks(h2HeapChunksToConditionalize, cond2, cond2Exp, analysisInfo) Heap(unconditionalHeapChunks) + Heap(h1ConditionalizedHeapChunks) + Heap(h2ConditionalizedHeapChunks) } - def merge(s1: State, pc1: RecordedPathConditions, s2: State, pc2: RecordedPathConditions): State = { + def merge(s1: State, pc1: RecordedPathConditions, s2: State, pc2: RecordedPathConditions, analysisInfo: AnalysisInfo): State = { s1 match { /* Decompose state s1 */ case State(g1, h1, program, member, @@ -394,15 +395,16 @@ object State { val g3 = mergeStore(g1, g2) - val h3 = mergeHeap(h1, conditions1, conditions1Exp, h2, conditions2, conditions2Exp) + val h3 = mergeHeap(h1, conditions1, conditions1Exp, h2, conditions2, conditions2Exp, analysisInfo) val partiallyConsumedHeap3 = (partiallyConsumedHeap1, partiallyConsumedHeap2) match { case (None, None) => None - case (Some(pch1), None) => Some(conditionalizeHeap(pch1, conditions1, conditions1Exp)) - case (None, Some(pch2)) => Some(conditionalizeHeap(pch2, conditions2, conditions2Exp)) + case (Some(pch1), None) => Some(conditionalizeHeap(pch1, conditions1, conditions1Exp, analysisInfo)) + case (None, Some(pch2)) => Some(conditionalizeHeap(pch2, conditions2, conditions2Exp, analysisInfo)) case (Some(pch1), Some(pch2)) => Some(mergeHeap( pch1, conditions1, conditions1Exp, pch2, conditions2, conditions2Exp, + analysisInfo )) } @@ -411,18 +413,18 @@ object State { None }) ((heap1, cond1, heap2, cond2) => { - Some(mergeHeap(heap1, cond1._1, cond1._2, heap2, cond2._1, cond2._2)) + Some(mergeHeap(heap1, cond1._1, cond1._2, heap2, cond2._1, cond2._2, analysisInfo)) })) assert(invariantContexts1.length == invariantContexts2.length) val invariantContexts3 = invariantContexts1 .zip(invariantContexts2) - .map({case (h1, h2) => mergeHeap(h1, conditions1, conditions1Exp, h2, conditions2, conditions2Exp)}) + .map({case (h1, h2) => mergeHeap(h1, conditions1, conditions1Exp, h2, conditions2, conditions2Exp, analysisInfo)}) assert(reserveHeaps1.length == reserveHeaps2.length) val reserveHeaps3 = reserveHeaps1 .zip(reserveHeaps2) - .map({case (h1, h2) => mergeHeap(h1, conditions1, conditions1Exp, h2, conditions2, conditions2Exp)}) + .map({case (h1, h2) => mergeHeap(h1, conditions1, conditions1Exp, h2, conditions2, conditions2Exp, analysisInfo)}) assert(conservedPcs1.length == conservedPcs2.length) From 4f129502c9a0b5fe05b4e53fcc038a47fb58d1b5 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 9 May 2025 10:03:33 +0200 Subject: [PATCH 022/474] BasicChunk perm minus --- .../AssumptionAnalysisGraph.scala | 2 +- src/main/scala/rules/Consumer.scala | 70 +++++++++++-------- src/main/scala/rules/Evaluator.scala | 7 +- src/main/scala/rules/Executor.scala | 23 +++--- src/main/scala/rules/MagicWandSupporter.scala | 8 +-- src/main/scala/rules/PredicateSupporter.scala | 11 +-- .../scala/rules/QuantifiedChunkSupport.scala | 30 +++++--- .../scala/supporters/MethodSupporter.scala | 4 +- .../functions/FunctionVerificationUnit.scala | 3 +- 9 files changed, 91 insertions(+), 67 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index 41096360b..7623a5340 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -9,7 +9,7 @@ import scala.collection.mutable object AssumptionType extends Enumeration { type AssumptionType = Value - val Explicit, PathCondition, Internal, Implicit, Unknown = Value + val Explicit, PathCondition, Internal, Implicit, Assertion, Unknown = Value } import viper.silicon.assumptionAnalysis.AssumptionType._ diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index 5e9aa39cc..e269649bc 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -38,7 +38,7 @@ trait ConsumptionRules extends SymbolicExecutionRules { * consumed partial heap. * @return The result of the continuation. */ - def consume(s: State, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier) + def consume(s: State, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, analysisInfo: AnalysisInfo) (Q: (State, Option[Term], Verifier) => VerificationResult) : VerificationResult @@ -61,7 +61,7 @@ trait ConsumptionRules extends SymbolicExecutionRules { as: Seq[ast.Exp], returnSnap: Boolean, pvef: ast.Exp => PartialVerificationError, - v: Verifier) + v: Verifier, analysisInfo: AnalysisInfo) (Q: (State, Option[Term], Verifier) => VerificationResult) : VerificationResult } @@ -75,11 +75,11 @@ object consumer extends ConsumptionRules { */ /** @inheritdoc */ - def consume(s: State, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier) + def consume(s: State, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, analysisInfo: AnalysisInfo) (Q: (State, Option[Term], Verifier) => VerificationResult) : VerificationResult = { - consumeR(s, s.h, a.whenExhaling, returnSnap, pve, v)((s1, h1, snap, v1) => { + consumeR(s, s.h, a.whenExhaling, returnSnap, pve, v, analysisInfo)((s1, h1, snap, v1) => { val s2 = s1.copy(h = h1, partiallyConsumedHeap = s.partiallyConsumedHeap) Q(s2, snap, v1)}) @@ -90,7 +90,8 @@ object consumer extends ConsumptionRules { as: Seq[ast.Exp], returnSnap: Boolean, pvef: ast.Exp => PartialVerificationError, - v: Verifier) + v: Verifier, + analysisInfo: AnalysisInfo) (Q: (State, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -105,7 +106,7 @@ object consumer extends ConsumptionRules { allPves ++= pves }) - consumeTlcs(s, s.h, allTlcs.result(), returnSnap, allPves.result(), v)((s1, h1, snap1, v1) => { + consumeTlcs(s, s.h, allTlcs.result(), returnSnap, allPves.result(), v, analysisInfo)((s1, h1, snap1, v1) => { val s2 = s1.copy(h = h1, partiallyConsumedHeap = s.partiallyConsumedHeap) Q(s2, snap1, v1) @@ -117,7 +118,8 @@ object consumer extends ConsumptionRules { tlcs: Seq[ast.Exp], returnSnap: Boolean, pves: Seq[PartialVerificationError], - v: Verifier) + v: Verifier, + analysisInfo: AnalysisInfo) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -128,10 +130,10 @@ object consumer extends ConsumptionRules { val pve = pves.head if (tlcs.tail.isEmpty) - wrappedConsumeTlc(s, h, a, returnSnap, pve, v)(Q) + wrappedConsumeTlc(s, h, a, returnSnap, pve, v, analysisInfo)(Q) else - wrappedConsumeTlc(s, h, a, returnSnap, pve, v)((s1, h1, snap1, v1) => { - consumeTlcs(s1, h1, tlcs.tail, returnSnap, pves.tail, v1)((s2, h2, snap2, v2) => + wrappedConsumeTlc(s, h, a, returnSnap, pve, v, analysisInfo)((s1, h1, snap1, v1) => { + consumeTlcs(s1, h1, tlcs.tail, returnSnap, pves.tail, v1, analysisInfo)((s2, h2, snap2, v2) => (snap1, snap2) match { case (Some(sn1), Some(sn2)) if returnSnap => Q(s2, h2, Some(Combine(sn1, sn2)), v2) @@ -142,14 +144,14 @@ object consumer extends ConsumptionRules { } } - private def consumeR(s: State, h: Heap, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier) + private def consumeR(s: State, h: Heap, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, analysisInfo: AnalysisInfo) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { val tlcs = a.topLevelConjuncts val pves = Seq.fill(tlcs.length)(pve) - consumeTlcs(s, h, tlcs, returnSnap, pves, v)(Q) + consumeTlcs(s, h, tlcs, returnSnap, pves, v, analysisInfo)(Q) } /** Wrapper/decorator for consume that injects the following operations: @@ -161,7 +163,8 @@ object consumer extends ConsumptionRules { a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, - v: Verifier) + v: Verifier, + analysisInfo: AnalysisInfo) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -176,13 +179,13 @@ object consumer extends ConsumptionRules { val sepIdentifier = v1.symbExLog.openScope(new ConsumeRecord(a, s1, v.decider.pcs)) - consumeTlc(s1, h0, a, returnSnap, pve, v1)((s2, h2, snap2, v2) => { + consumeTlc(s1, h0, a, returnSnap, pve, v1, analysisInfo)((s2, h2, snap2, v2) => { v2.symbExLog.closeScope(sepIdentifier) QS(s2, h2, snap2, v2)}) })(Q) } - private def consumeTlc(s: State, h: Heap, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier) + private def consumeTlc(s: State, h: Heap, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, analysisInfo: AnalysisInfo) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -203,7 +206,7 @@ object consumer extends ConsumptionRules { case imp @ ast.Implies(e0, a0) if !a.isPure && s.moreJoins.id >= JoinMode.Impure.id => val impliesRecord = new ImpliesRecord(imp, s, v.decider.pcs, "consume") val uidImplies = v.symbExLog.openScope(impliesRecord) - consumeConditionalTlcMoreJoins(s, h, e0, a0, None, uidImplies, returnSnap, pve, v)(Q) + consumeConditionalTlcMoreJoins(s, h, e0, a0, None, uidImplies, returnSnap, pve, v, analysisInfo)(Q) case imp @ ast.Implies(e0, a0) if !a.isPure => val impliesRecord = new ImpliesRecord(imp, s, v.decider.pcs, "consume") @@ -211,7 +214,7 @@ object consumer extends ConsumptionRules { evaluator.eval(s, e0, pve, v)((s1, t0, e0New, v1) => branch(s1, t0, (e0, e0New), v1)( - (s2, v2) => consumeR(s2, h, a0, returnSnap, pve, v2)((s3, h1, t1, v3) => { + (s2, v2) => consumeR(s2, h, a0, returnSnap, pve, v2, analysisInfo)((s3, h1, t1, v3) => { v3.symbExLog.closeScope(uidImplies) Q(s3, h1, t1, v3) }), @@ -223,7 +226,7 @@ object consumer extends ConsumptionRules { case ite @ ast.CondExp(e0, a1, a2) if !a.isPure && s.moreJoins.id >= JoinMode.Impure.id => val condExpRecord = new CondExpRecord(ite, s, v.decider.pcs, "consume") val uidCondExp = v.symbExLog.openScope(condExpRecord) - consumeConditionalTlcMoreJoins(s, h, e0, a1, Some(a2), uidCondExp, returnSnap, pve, v)(Q) + consumeConditionalTlcMoreJoins(s, h, e0, a1, Some(a2), uidCondExp, returnSnap, pve, v, analysisInfo)(Q) case ite @ ast.CondExp(e0, a1, a2) if !a.isPure => val condExpRecord = new CondExpRecord(ite, s, v.decider.pcs, "consume") @@ -231,11 +234,11 @@ object consumer extends ConsumptionRules { eval(s, e0, pve, v)((s1, t0, e0New, v1) => branch(s1, t0, (e0, e0New), v1)( - (s2, v2) => consumeR(s2, h, a1, returnSnap, pve, v2)((s3, h1, t1, v3) => { + (s2, v2) => consumeR(s2, h, a1, returnSnap, pve, v2, analysisInfo)((s3, h1, t1, v3) => { v3.symbExLog.closeScope(uidCondExp) Q(s3, h1, t1, v3) }), - (s2, v2) => consumeR(s2, h, a2, returnSnap, pve, v2)((s3, h1, t1, v3) => { + (s2, v2) => consumeR(s2, h, a2, returnSnap, pve, v2, analysisInfo)((s3, h1, t1, v3) => { v3.symbExLog.closeScope(uidCondExp) Q(s3, h1, t1, v3) }))) @@ -278,7 +281,8 @@ object consumer extends ConsumptionRules { negativePermissionReason = NegativePermission(acc.perm), notInjectiveReason = QPAssertionNotInjective(acc.loc), insufficientPermissionReason = InsufficientPermission(acc.loc), - v1)((s2, h2, snap, v2) => Q(s2.copy(constrainableARPs = s.constrainableARPs), h2, snap, v2)) + v1, + analysisInfo)((s2, h2, snap, v2) => Q(s2.copy(constrainableARPs = s.constrainableARPs), h2, snap, v2)) case (s1, _, _, _, _, None, v1) => Q(s1, h, if (returnSnap) Some(Unit) else None, v1) } @@ -324,7 +328,8 @@ object consumer extends ConsumptionRules { negativePermissionReason = NegativePermission(acc.perm), notInjectiveReason = QPAssertionNotInjective(acc.loc), insufficientPermissionReason = InsufficientPermission(acc.loc), - v1)((s2, h2, snap, v2) => Q(s2.copy(constrainableARPs = s.constrainableARPs), h2, snap, v2)) + v1, + analysisInfo)((s2, h2, snap, v2) => Q(s2.copy(constrainableARPs = s.constrainableARPs), h2, snap, v2)) case (s1, _, _, _, _, None, v1) => Q(s1, h, if (returnSnap) Some(Unit) else None, v1) } @@ -366,7 +371,8 @@ object consumer extends ConsumptionRules { negativePermissionReason = NegativePermission(ePerm), notInjectiveReason = sys.error("Quantified wand not injective"), /*ReceiverNotInjective(...)*/ insufficientPermissionReason = MagicWandChunkNotFound(wand), /*InsufficientPermission(...)*/ - v1)((s2, h2, snap, v2) => Q(s2.copy(constrainableARPs = s.constrainableARPs), h2, snap, v2)) + v1, + analysisInfo)((s2, h2, snap, v2) => Q(s2.copy(constrainableARPs = s.constrainableARPs), h2, snap, v2)) case (s1, _, _, _, _, None, v1) => Q(s1.copy(constrainableARPs = s.constrainableARPs), h, if (returnSnap) Some(Unit) else None, v1) } @@ -406,7 +412,8 @@ object consumer extends ConsumptionRules { returnSnap, None, pve, - v2 + v2, + analysisInfo )((s3, h3, snap, v3) => { val s4 = s3.copy(constrainableARPs = s1.constrainableARPs, partiallyConsumedHeap = Some(h3)) @@ -451,7 +458,8 @@ object consumer extends ConsumptionRules { returnSnap, None, pve, - v2 + v2, + analysisInfo )((s3, h3, snap, v3) => { val s4 = s3.copy(constrainableARPs = s1.constrainableARPs, partiallyConsumedHeap = Some(h3)) @@ -460,7 +468,7 @@ object consumer extends ConsumptionRules { case let: ast.Let if !let.isPure => letSupporter.handle[ast.Exp](s, let, pve, v)((s1, g1, body, v1) => { val s2 = s1.copy(g = s1.g + g1) - consumeR(s2, h, body, returnSnap, pve, v1)(Q)}) + consumeR(s2, h, body, returnSnap, pve, v1, analysisInfo)(Q)}) case ast.AccessPredicate(locacc: ast.LocationAccess, perm) => eval(s, perm, pve, v)((s1, tPerm, permNew, v1) => @@ -517,7 +525,8 @@ object consumer extends ConsumptionRules { returnSnap, None, pve, - v1 + v1, + analysisInfo )((s3, h3, snap, v3) => { val s4 = s3.copy(constrainableARPs = s1.constrainableARPs, partiallyConsumedHeap = Some(h3)) @@ -541,20 +550,21 @@ object consumer extends ConsumptionRules { private def consumeConditionalTlcMoreJoins(s: State, h: Heap, e0: ast.Exp, a1: ast.Exp, a2: Option[ast.Exp], scopeUid: Int, returnSnap: Boolean, - pve: PartialVerificationError, v: Verifier) + pve: PartialVerificationError, v: Verifier, + analysisInfo: AnalysisInfo) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { eval(s, e0, pve, v)((s1, t0, e0New, v1) => joiner.join[(Heap, Option[Term]), (Heap, Option[Term])](s1, v1, resetState = false)((s1, v1, QB) => { branch(s1.copy(parallelizeBranches = false), t0, (e0, e0New), v1)( (s2, v2) => - consumeR(s2.copy(parallelizeBranches = s1.parallelizeBranches), h, a1, returnSnap, pve, v2)((s3, h1, t1, v3) => { + consumeR(s2.copy(parallelizeBranches = s1.parallelizeBranches), h, a1, returnSnap, pve, v2, analysisInfo)((s3, h1, t1, v3) => { v3.symbExLog.closeScope(scopeUid) QB(s3, (h1, t1), v3) }), (s2, v2) => a2 match { - case Some(a2) => consumeR(s2.copy(parallelizeBranches = s1.parallelizeBranches), h, a2, returnSnap, pve, v2)((s3, h1, t1, v3) => { + case Some(a2) => consumeR(s2.copy(parallelizeBranches = s1.parallelizeBranches), h, a2, returnSnap, pve, v2, analysisInfo)((s3, h1, t1, v3) => { v3.symbExLog.closeScope(scopeUid) QB(s3, (h1, t1), v3) }) diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 8697ba3f5..1b2e9950b 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -8,6 +8,7 @@ package viper.silicon.rules import viper.silicon.debugger.DebugExp import viper.silicon.Config.JoinMode +import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType, ExpAnalysisSourceInfo} import viper.silver.ast import viper.silver.verifier.{CounterexampleTransformer, PartialVerificationError, VerifierWarning} import viper.silver.verifier.errors.{ErrorWrapperWithExampleTransformer, PreconditionInAppFalse} @@ -929,7 +930,7 @@ object evaluator extends EvaluationRules { moreJoins = JoinMode.Off, assertReadAccessOnly = if (Verifier.config.respectFunctionPrePermAmounts()) s2.assertReadAccessOnly /* should currently always be false */ else true) - consumes(s3, pres, true, _ => pvePre, v2)((s4, snap, v3) => { + consumes(s3, pres, true, _ => pvePre, v2, AnalysisInfo(v2, ExpAnalysisSourceInfo(fapp), AssumptionType.Assertion))((s4, snap, v3) => { val snap1 = snap.get.convert(sorts.Snap) val preFApp = App(functionSupporter.preconditionVersion(v3.symbolConverter.toFunction(func)), snap1 :: tArgs) val preExp = Option.when(withExp)({ @@ -991,7 +992,7 @@ object evaluator extends EvaluationRules { // val c4 = c3.decCycleCounter(predicate) // eval(σ1, eIn, pve, c4)((tIn, c5) => // QB(tIn, c5))}) - consume(s4, acc, true, pve, v3)((s5, snap, v4) => { + consume(s4, acc, true, pve, v3, AnalysisInfo(v3, ExpAnalysisSourceInfo(acc), AssumptionType.Implicit))((s5, snap, v4) => { val fr6 = s5.functionRecorder.recordSnapshot(pa, v4.decider.pcs.branchConditions, snap.get) .changeDepthBy(+1) @@ -1045,7 +1046,7 @@ object evaluator extends EvaluationRules { => Q(s4, r4._1, r4._2, v4)) case ast.Asserting(eAss, eIn) => - consume(s, eAss, false, pve, v)((s2, _, v2) => { + consume(s, eAss, false, pve, v, AnalysisInfo(v, ExpAnalysisSourceInfo(eAss), AssumptionType.Assertion))((s2, _, v2) => { val s3 = s2.copy(g = s.g, h = s.h) eval(s3, eIn, pve, v2)(Q) }) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 128451182..f8744a554 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -269,7 +269,7 @@ object executor extends ExecutionRules { })}) combine executionFlowController.locally(s, v)((s0, v0) => { v0.decider.prover.comment("Loop head block: Establish invariant") - consumes(s0, invs, false, LoopInvariantNotEstablished, v0)((sLeftover, _, v1) => { + consumes(s0, invs, false, LoopInvariantNotEstablished, v0, AnalysisInfo(v0, ExpAnalysisSourceInfo(invs.head), AssumptionType.Assertion))((sLeftover, _, v1) => { v1.decider.prover.comment("Loop head block: Execute statements of loop head block (in invariant state)") phase1data.foldLeft(Success(): VerificationResult) { case (result, _) if !result.continueVerification => result @@ -303,7 +303,7 @@ object executor extends ExecutionRules { * attempting to re-establish the invariant. */ v.decider.prover.comment("Loop head block: Re-establish invariant") - consumes(s, invs, false, e => LoopInvariantNotPreserved(e), v)((_, _, _) => + consumes(s, invs, false, e => LoopInvariantNotPreserved(e), v, AnalysisInfo(v, ExpAnalysisSourceInfo(invs.head), AssumptionType.Assertion))((_, _, _) => Success()) } } @@ -399,15 +399,16 @@ object executor extends ExecutionRules { s2p, relevantChunks, Seq(`?r`), - Option.when(withExp)(Seq(ast.LocalVarDecl(`?r`.id.name, ast.Ref)())), + Option.when(withExp)(Seq(ast.LocalVarDecl(`?r`.id.name, ast.Ref)(eRcvr.pos, eRcvr.info, eRcvr.errT))), `?r` === tRcvr, - eRcvrNew.map(r => ast.EqCmp(ast.LocalVar(`?r`.id.name, ast.Ref)(), r)()), + eRcvrNew.map(r => ast.EqCmp(ast.LocalVar(`?r`.id.name, ast.Ref)(), r)(eRcvr.pos, eRcvr.info, eRcvr.errT)), Some(Seq(tRcvr)), field, FullPerm, Option.when(withExp)(ast.FullPerm()()), chunkOrderHeuristics, - v2 + v2, + AnalysisInfo(v2, ExpAnalysisSourceInfo(fa), AssumptionType.Implicit) ) result match { case (Complete(), s3, remainingChunks) => @@ -495,7 +496,7 @@ object executor extends ExecutionRules { case exhale @ ast.Exhale(a) => val pve = ExhaleFailed(exhale) - consume(s, a, false, pve, v)((s1, _, v1) => + consume(s, a, false, pve, v, AnalysisInfo(v, ExpAnalysisSourceInfo(a), AssumptionType.Assertion))((s1, _, v1) => Q(s1, v1)) case assert @ ast.Assert(a: ast.FalseLit) if !s.isInPackage => @@ -509,7 +510,7 @@ object executor extends ExecutionRules { case assert @ ast.Assert(a) if Verifier.config.disableSubsumption() => val r = - consume(s, a, false, AssertFailed(assert), v)((_, _, _) => + consume(s, a, false, AssertFailed(assert), v, AnalysisInfo(v, ExpAnalysisSourceInfo(a), AssumptionType.Assertion))((_, _, _) => Success()) r combine Q(s, v) @@ -525,11 +526,11 @@ object executor extends ExecutionRules { * hUsed (reserveHeaps.head) instead of consuming them. hUsed is later discarded and replaced * by s.h. By copying hUsed to s.h the contained permissions remain available inside the wand. */ - consume(s, a, false, pve, v)((s2, _, v1) => { + consume(s, a, false, pve, v, AnalysisInfo(v, ExpAnalysisSourceInfo(a), AssumptionType.Assertion))((s2, _, v1) => { Q(s2.copy(h = s2.reserveHeaps.head), v1) }) } else - consume(s, a, false, pve, v)((s1, _, v1) => { + consume(s, a, false, pve, v, AnalysisInfo(v, ExpAnalysisSourceInfo(a), AssumptionType.Assertion))((s1, _, v1) => { val s2 = s1.copy(h = s.h, reserveHeaps = s.reserveHeaps) Q(s2, v1)}) @@ -587,7 +588,7 @@ object executor extends ExecutionRules { tArgs zip Seq.fill(tArgs.size)(None) val s2 = s1.copy(g = Store(fargs.zip(argsWithExp)), recordVisited = true) - consumes(s2, meth.pres, false, _ => pvePre, v1)((s3, _, v2) => { + consumes(s2, meth.pres, false, _ => pvePre, v1, AnalysisInfo(v1, StmtAnalysisSourceInfo(call), AssumptionType.Assertion))((s3, _, v2) => { v2.symbExLog.closeScope(preCondId) val postCondLog = new CommentRecord("Postcondition", s3, v2.decider.pcs) val postCondId = v2.symbExLog.openScope(postCondLog) @@ -615,7 +616,7 @@ object executor extends ExecutionRules { eval(s1, ePerm, pve, v1)((s2, tPerm, ePermNew, v2) => permissionSupporter.assertPositive(s2, tPerm, if (withExp) ePermNew.get else ePerm, pve, v2)((s3, v3) => { val wildcards = s3.constrainableARPs -- s1.constrainableARPs - predicateSupporter.fold(s3, predicate, tArgs, eArgsNew, tPerm, ePermNew, wildcards, pve, v3)((s4, v4) => { + predicateSupporter.fold(s3, predicate, tArgs, eArgsNew, tPerm, ePermNew, wildcards, pve, v3, AnalysisInfo(v3, ExpAnalysisSourceInfo(predAcc), AssumptionType.Unknown))((s4, v4) => { v3.decider.finishDebugSubExp(s"folded ${predAcc.toString}") Q(s4, v4) } diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 3c6ed6b2e..8555a8624 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -8,7 +8,7 @@ package viper.silicon.rules import viper.silicon.debugger.DebugExp import viper.silicon._ -import viper.silicon.assumptionAnalysis.{AssumptionType, PermissionInhaleNode} +import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType, ExpAnalysisSourceInfo, PermissionInhaleNode} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.decider.RecordedPathConditions import viper.silicon.interfaces._ @@ -400,7 +400,7 @@ object magicWandSupporter extends SymbolicExecutionRules { // This part indirectly calls the methods `this.transfer` and `this.consumeFromMultipleHeaps`. consume( proofScriptState.copy(oldHeaps = s2.oldHeaps, reserveCfgs = proofScriptState.reserveCfgs.tail), - wand.right, true, pve, proofScriptVerifier + wand.right, true, pve, proofScriptVerifier, AnalysisInfo(proofScriptVerifier, ExpAnalysisSourceInfo(wand.right), AssumptionType.Assertion) )((s3, snapRhs, v3) => { createWandChunkAndRecordResults(s3.copy(exhaleExt = false, oldHeaps = s.oldHeaps), freshSnapRoot, snapRhs.get, v3) @@ -459,9 +459,9 @@ object magicWandSupporter extends SymbolicExecutionRules { (Q: (State, Verifier) => VerificationResult) : VerificationResult = { // Consume the magic wand instance "A --* B". - consume(s, wand, true, pve, v)((s1, snapWand, v1) => { + consume(s, wand, true, pve, v, AnalysisInfo(v, ExpAnalysisSourceInfo(wand), AssumptionType.Assertion))((s1, snapWand, v1) => { // Consume the wand's LHS "A". - consume(s1, wand.left, true, pve, v1)((s2, snapLhs, v2) => { + consume(s1, wand.left, true, pve, v1, AnalysisInfo(v1, ExpAnalysisSourceInfo(wand.left), AssumptionType.Assertion))((s2, snapLhs, v2) => { /* It is assumed that snap and MagicWandSnapshot.abstractLhs are structurally the same. * Equating the two snapshots is sound iff a wand is applied only once. * The old solution in this case did use this assumption: diff --git a/src/main/scala/rules/PredicateSupporter.scala b/src/main/scala/rules/PredicateSupporter.scala index 644315ed5..af8ff8d98 100644 --- a/src/main/scala/rules/PredicateSupporter.scala +++ b/src/main/scala/rules/PredicateSupporter.scala @@ -29,7 +29,8 @@ trait PredicateSupportRules extends SymbolicExecutionRules { ePerm: Option[ast.Exp], constrainableWildcards: InsertionOrderedSet[Var], pve: PartialVerificationError, - v: Verifier) + v: Verifier, + analysisInfo: AnalysisInfo) (Q: (State, Verifier) => VerificationResult) : VerificationResult @@ -59,7 +60,8 @@ object predicateSupporter extends PredicateSupportRules { ePerm: Option[ast.Exp], constrainableWildcards: InsertionOrderedSet[Var], pve: PartialVerificationError, - v: Verifier) + v: Verifier, + analysisInfo: AnalysisInfo) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -72,7 +74,7 @@ object predicateSupporter extends PredicateSupportRules { val s1 = s.copy(g = gIns, smDomainNeeded = true) .scalePermissionFactor(tPerm, ePerm) - consume(s1, body, true, pve, v)((s1a, snap, v1) => { + consume(s1, body, true, pve, v, AnalysisInfo(v, analysisInfo.sourceInfo, AssumptionType.Assertion))((s1a, snap, v1) => { if (!Verifier.config.disableFunctionUnfoldTrigger()) { val predTrigger = App(s1a.predicateData(predicate).triggerFunction, snap.get.convert(terms.sorts.Snap) +: tArgs) @@ -162,7 +164,8 @@ object predicateSupporter extends PredicateSupportRules { true, None, pve, - v + v, + AnalysisInfo(v, ExpAnalysisSourceInfo(pa), AssumptionType.Assertion) )((s2, h2, snap, v1) => { val s3 = s2.copy(g = gIns, h = h2) .setConstrainable(constrainableWildcards, false) diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 3637b49e4..838075d52 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -6,9 +6,10 @@ package viper.silicon.rules -import viper.silicon.debugger.DebugExp import viper.silicon.Map +import viper.silicon.assumptionAnalysis.AnalysisInfo import viper.silicon.common.collections.immutable.InsertionOrderedSet +import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.VerificationResult import viper.silicon.interfaces.state._ import viper.silicon.logger.records.data.CommentRecord @@ -20,8 +21,8 @@ import viper.silicon.state.terms.predef.`?r` import viper.silicon.state.terms.utils.consumeExactRead import viper.silicon.supporters.functions.{FunctionRecorder, NoopFunctionRecorder} import viper.silicon.utils.ast.{BigAnd, buildMinExp} -import viper.silicon.utils.notNothing.NotNothing import viper.silicon.utils.freshSnap +import viper.silicon.utils.notNothing.NotNothing import viper.silicon.verifier.Verifier import viper.silver.ast import viper.silver.parser.PUnknown @@ -1158,7 +1159,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { negativePermissionReason: => ErrorReason, notInjectiveReason: => ErrorReason, insufficientPermissionReason: => ErrorReason, - v: Verifier) + v: Verifier, + analysisInfo: AnalysisInfo) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -1306,7 +1308,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { rPerm, rPermExp, chunkOrderHeuristics, - v2) + v2, + analysisInfo) val optSmDomainDefinitionCondition2 = if (s3.smDomainNeeded) Some(And(condOfInvOfLoc, IsPositive(lossOfInvOfLoc), And(imagesOfFormalQVars))) else None @@ -1371,7 +1374,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { lossOfInvOfLoc, lossExp, chunkOrderHeuristics, - v + v, + analysisInfo ) permissionRemovalResult match { case (Complete(), s2, remainingChunks) => @@ -1413,7 +1417,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { returnSnap: Boolean, optChunkOrderHeuristic: Option[Seq[QuantifiedBasicChunk] => Seq[QuantifiedBasicChunk]], pve: PartialVerificationError, - v: Verifier) + v: Verifier, + analysisInfo: AnalysisInfo) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -1449,7 +1454,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { rPerm, rPermExp, chunkOrderHeuristics, - v + v, + analysisInfo ) val h2 = Heap(remainingChunks ++ otherChunks) val (smDef1, smCache1) = @@ -1498,7 +1504,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { permissions, permissionsExp, chunkOrderHeuristics, - v + v, + analysisInfo ) result match { case (Complete(), s1, remainingChunks) => @@ -1576,7 +1583,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { perms: Term, // p(rs) permsExp: Option[ast.Exp], // p(rs) chunkOrderHeuristic: Seq[QuantifiedBasicChunk] => Seq[QuantifiedBasicChunk], - v: Verifier) + v: Verifier, + analysisInfo: AnalysisInfo) : (ConsumptionResult, State, Seq[QuantifiedBasicChunk]) = { val rmPermRecord = new CommentRecord("removePermissions", s, v.decider.pcs) @@ -1659,7 +1667,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.assume(permissionConstraint, permissionConstraintExp, permissionConstraintExp) remainingChunks = - remainingChunks :+ ithChunk.permMinus(ithPTaken, ithPTakenExp) + remainingChunks :+ GeneralChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, analysisInfo) } else { v.decider.prover.comment(s"Chunk depleted?") val chunkDepleted = v.decider.check(depletedCheck, Verifier.config.splitTimeout()) @@ -1670,7 +1678,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { remainingChunks = remainingChunks :+ ithChunk } else { remainingChunks = - remainingChunks :+ ithChunk.permMinus(ithPTaken, ithPTakenExp) + remainingChunks :+ GeneralChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, analysisInfo) } } } diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index 1bf735147..dca6fb4a0 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -7,7 +7,7 @@ package viper.silicon.supporters import com.typesafe.scalalogging.Logger -import viper.silicon.assumptionAnalysis.{DefaultAssumptionAnalyzer, NoAssumptionAnalyzer} +import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType, DefaultAssumptionAnalyzer, ExpAnalysisSourceInfo, NoAssumptionAnalyzer, StringAnalysisSourceInfo} import viper.silver.ast import viper.silver.components.StatefulComponent import viper.silver.verifier.errors._ @@ -114,7 +114,7 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif && { executionFlowController.locally(s2a, v2)((s3, v3) => { exec(s3, body, v3)((s4, v4) => - consumes(s4, posts, false, postViolated, v4)((_, _, _) => + consumes(s4, posts, false, postViolated, v4, AnalysisInfo(v, if(posts.isEmpty) StringAnalysisSourceInfo("no postcondition", ast.NoPosition) else ExpAnalysisSourceInfo(posts.head), AssumptionType.Assertion))((_, _, _) => Success()))}) } )})}) result.assumptionAnalyzer = v.decider.assumptionAnalyzer diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index 8e83c0973..543a7c694 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -7,6 +7,7 @@ package viper.silicon.supporters.functions import com.typesafe.scalalogging.Logger +import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType, ExpAnalysisSourceInfo, StringAnalysisSourceInfo} import viper.silicon.debugger.DebugExp import viper.silver.ast import viper.silver.ast.utility.Functions @@ -266,7 +267,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver Some(DebugExp.createInstance(e, eNew)) } else { None } decider.assume(BuiltinEquals(data.formalResult, tBody), debugExp) - consumes(s2, posts, false, postconditionViolated, v)((s3, _, _) => { + consumes(s2, posts, false, postconditionViolated, v, AnalysisInfo(v, if(posts.isEmpty) StringAnalysisSourceInfo("no postcondition", ast.NoPosition) else ExpAnalysisSourceInfo(posts.head), AssumptionType.Assertion))((s3, _, _) => { recorders :+= s3.functionRecorder Success()})})})} From e4fcc564b84c85f01dfed35d5eb8dfdca7e6daec Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 9 May 2025 10:22:38 +0200 Subject: [PATCH 023/474] make chunk operations protected --- src/main/scala/interfaces/state/Chunks.scala | 39 ++++++++--- .../scala/rules/QuantifiedChunkSupport.scala | 4 +- src/main/scala/state/Chunks.scala | 66 ++++++++----------- 3 files changed, 60 insertions(+), 49 deletions(-) diff --git a/src/main/scala/interfaces/state/Chunks.scala b/src/main/scala/interfaces/state/Chunks.scala index bb1a2b9e2..2f0a1ebf3 100644 --- a/src/main/scala/interfaces/state/Chunks.scala +++ b/src/main/scala/interfaces/state/Chunks.scala @@ -6,6 +6,7 @@ package viper.silicon.interfaces.state +import viper.silicon.assumptionAnalysis.{AnalysisInfo, PermissionInhaleNode} import viper.silicon.resources.ResourceID import viper.silicon.state.terms.{Term, Var} import viper.silver.ast @@ -18,20 +19,40 @@ trait GeneralChunk extends Chunk { val resourceID: ResourceID val id: ChunkIdentifer val perm: Term - def applyCondition(newCond: Term, newCondExp: Option[ast.Exp]): GeneralChunk - def permMinus(perm: Term, permExp: Option[ast.Exp]): GeneralChunk - def permPlus(perm: Term, permExp: Option[ast.Exp]): GeneralChunk + protected def applyCondition(newCond: Term, newCondExp: Option[ast.Exp]): GeneralChunk + protected def permMinus(perm: Term, permExp: Option[ast.Exp]): GeneralChunk + protected def permPlus(perm: Term, permExp: Option[ast.Exp]): GeneralChunk val permExp: Option[ast.Exp] } +object GeneralChunk { + def applyCondition(chunk: GeneralChunk, newCond: Term, newCondExp: Option[ast.Exp], analysisInfo: AnalysisInfo): GeneralChunk = { + val newChunk = chunk.applyCondition(newCond, newCondExp) + analysisInfo.getAssumptionAnalyzer.addPermissionDependencies(Set(chunk), PermissionInhaleNode(newChunk, analysisInfo.sourceInfo, analysisInfo.assumptionType)) + newChunk + } + + def permMinus(chunk: GeneralChunk, newPerm: Term, newPermExp: Option[ast.Exp], analysisInfo: AnalysisInfo): GeneralChunk = { + val newChunk = chunk.permMinus(newPerm, newPermExp) + analysisInfo.getAssumptionAnalyzer.addPermissionDependencies(Set(chunk), PermissionInhaleNode(newChunk, analysisInfo.sourceInfo, analysisInfo.assumptionType)) + newChunk + } + + def permPlus(chunk: GeneralChunk, newPerm: Term, newPermExp: Option[ast.Exp], analysisInfo: AnalysisInfo): GeneralChunk = { + val newChunk = chunk.permPlus(newPerm, newPermExp) + analysisInfo.getAssumptionAnalyzer.addPermissionDependencies(Set(chunk), PermissionInhaleNode(newChunk, analysisInfo.sourceInfo, analysisInfo.assumptionType)) + newChunk + } +} + trait NonQuantifiedChunk extends GeneralChunk { val args: Seq[Term] val argsExp: Option[Seq[ast.Exp]] val snap: Term - override def applyCondition(newCond: Term, newCondExp: Option[ast.Exp]): NonQuantifiedChunk - override def permMinus(perm: Term, permExp: Option[ast.Exp]): NonQuantifiedChunk - override def permPlus(perm: Term, permExp: Option[ast.Exp]): NonQuantifiedChunk + override protected def applyCondition(newCond: Term, newCondExp: Option[ast.Exp]): NonQuantifiedChunk + override protected def permMinus(perm: Term, permExp: Option[ast.Exp]): NonQuantifiedChunk + override protected def permPlus(perm: Term, permExp: Option[ast.Exp]): NonQuantifiedChunk def withPerm(perm: Term, permExp: Option[ast.Exp]): NonQuantifiedChunk def withSnap(snap: Term, snapExp: Option[ast.Exp]): NonQuantifiedChunk } @@ -42,8 +63,8 @@ trait QuantifiedChunk extends GeneralChunk { def snapshotMap: Term def valueAt(arguments: Seq[Term]): Term - override def applyCondition(newCond: Term, newCondExp: Option[ast.Exp]): QuantifiedChunk - override def permMinus(perm: Term, permExp: Option[ast.Exp]): QuantifiedChunk - override def permPlus(perm: Term, permExp: Option[ast.Exp]): QuantifiedChunk + override protected def applyCondition(newCond: Term, newCondExp: Option[ast.Exp]): QuantifiedChunk + override protected def permMinus(perm: Term, permExp: Option[ast.Exp]): QuantifiedChunk + override protected def permPlus(perm: Term, permExp: Option[ast.Exp]): QuantifiedChunk def withSnapshotMap(snap: Term): QuantifiedChunk } \ No newline at end of file diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 838075d52..0aaaa728e 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -1667,7 +1667,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.assume(permissionConstraint, permissionConstraintExp, permissionConstraintExp) remainingChunks = - remainingChunks :+ GeneralChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, analysisInfo) + remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, analysisInfo) } else { v.decider.prover.comment(s"Chunk depleted?") val chunkDepleted = v.decider.check(depletedCheck, Verifier.config.splitTimeout()) @@ -1678,7 +1678,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { remainingChunks = remainingChunks :+ ithChunk } else { remainingChunks = - remainingChunks :+ GeneralChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, analysisInfo) + remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, analysisInfo) } } } diff --git a/src/main/scala/state/Chunks.scala b/src/main/scala/state/Chunks.scala index 07da06ad8..0dbb81e6e 100644 --- a/src/main/scala/state/Chunks.scala +++ b/src/main/scala/state/Chunks.scala @@ -64,26 +64,6 @@ object BasicChunk { } } -object GeneralChunk { - def applyCondition(chunk: GeneralChunk, newCond: Term, newCondExp: Option[ast.Exp], analysisInfo: AnalysisInfo): GeneralChunk = { - val newChunk = chunk.applyCondition(newCond, newCondExp) - analysisInfo.getAssumptionAnalyzer.addPermissionDependencies(Set(chunk), PermissionInhaleNode(newChunk, analysisInfo.sourceInfo, analysisInfo.assumptionType)) - newChunk - } - - def permMinus(chunk: GeneralChunk, newPerm: Term, newPermExp: Option[ast.Exp], analysisInfo: AnalysisInfo): GeneralChunk = { - val newChunk = chunk.permMinus(newPerm, newPermExp) - analysisInfo.getAssumptionAnalyzer.addPermissionDependencies(Set(chunk), PermissionInhaleNode(newChunk, analysisInfo.sourceInfo, analysisInfo.assumptionType)) - newChunk - } - - def permPlus(chunk: GeneralChunk, newPerm: Term, newPermExp: Option[ast.Exp], analysisInfo: AnalysisInfo): GeneralChunk = { - val newChunk = chunk.permPlus(newPerm, newPermExp) - analysisInfo.getAssumptionAnalyzer.addPermissionDependencies(Set(chunk), PermissionInhaleNode(newChunk, analysisInfo.sourceInfo, analysisInfo.assumptionType)) - newChunk - } -} - case class BasicChunk private (resourceID: BaseID, id: BasicChunkIdentifier, args: Seq[Term], @@ -100,11 +80,11 @@ case class BasicChunk private (resourceID: BaseID, case PredicateID => require(snap.sort == sorts.Snap, s"A predicate chunk's snapshot ($snap) is expected to be of sort Snap, but found ${snap.sort}") } - override def applyCondition(newCond: Term, newCondExp: Option[ast.Exp]): BasicChunk = + override protected def applyCondition(newCond: Term, newCondExp: Option[ast.Exp]): BasicChunk = withPerm(Ite(newCond, perm, NoPerm), newCondExp.map(nce => ast.CondExp(nce, permExp.get, ast.NoPerm()())())) - override def permMinus(newPerm: Term, newPermExp: Option[ast.Exp]): BasicChunk = + override protected def permMinus(newPerm: Term, newPermExp: Option[ast.Exp]): BasicChunk = withPerm(PermMinus(perm, newPerm), newPermExp.map(npe => ast.PermSub(permExp.get, npe)())) - override def permPlus(newPerm: Term, newPermExp: Option[ast.Exp]): BasicChunk = + override protected def permPlus(newPerm: Term, newPermExp: Option[ast.Exp]): BasicChunk = withPerm(PermPlus(perm, newPerm), newPermExp.map(npe => ast.PermAdd(permExp.get, npe)())) override def withPerm(newPerm: Term, newPermExp: Option[ast.Exp]): BasicChunk = BasicChunk(resourceID, id, args, argsExp, snap, snapExp, newPerm, newPermExp) override def withSnap(newSnap: Term, newSnapExp: Option[ast.Exp]): BasicChunk = BasicChunk(resourceID, id, args, argsExp, newSnap, newSnapExp, perm, permExp) @@ -115,11 +95,21 @@ case class BasicChunk private (resourceID: BaseID, } } +object QuantifiedBasicChunk { + def applyCondition(chunk: QuantifiedBasicChunk, newCond: Term, newCondExp: Option[ast.Exp], analysisInfo: AnalysisInfo): QuantifiedBasicChunk = { + GeneralChunk.applyCondition(chunk, newCond, newCondExp, analysisInfo).asInstanceOf[QuantifiedBasicChunk] + } + def permMinus(chunk: QuantifiedBasicChunk, perm: Term, permExp: Option[ast.Exp], analysisInfo: AnalysisInfo): QuantifiedBasicChunk = + GeneralChunk.permMinus(chunk, perm, permExp, analysisInfo).asInstanceOf[QuantifiedBasicChunk] + def permPlus(chunk: QuantifiedBasicChunk, perm: Term, permExp: Option[ast.Exp], analysisInfo: AnalysisInfo): QuantifiedBasicChunk = + GeneralChunk.permPlus(chunk, perm, permExp, analysisInfo).asInstanceOf[QuantifiedBasicChunk] +} + sealed trait QuantifiedBasicChunk extends QuantifiedChunk { override val id: ChunkIdentifer - override def applyCondition(newCond: Term, newCondExp: Option[ast.Exp]): QuantifiedBasicChunk - override def permMinus(perm: Term, permExp: Option[ast.Exp]): QuantifiedBasicChunk - override def permPlus(perm: Term, permExp: Option[ast.Exp]): QuantifiedBasicChunk + override protected def applyCondition(newCond: Term, newCondExp: Option[ast.Exp]): QuantifiedBasicChunk + override protected def permMinus(perm: Term, permExp: Option[ast.Exp]): QuantifiedBasicChunk + override protected def permPlus(perm: Term, permExp: Option[ast.Exp]): QuantifiedBasicChunk override def withSnapshotMap(snap: Term): QuantifiedBasicChunk def singletonArguments: Option[Seq[Term]] def singletonArgumentExps: Option[Seq[ast.Exp]] @@ -168,11 +158,11 @@ case class QuantifiedFieldChunk private(id: BasicChunkIdentifier, def withPerm(newPerm: Term, newPermExp: Option[ast.Exp]) = QuantifiedFieldChunk(id, fvf, condition, conditionExp, newPerm, newPermExp, invs, singletonRcvr, singletonRcvrExp, hints) - override def applyCondition(newCond: Term, newCondExp: Option[ast.Exp]) = + override protected def applyCondition(newCond: Term, newCondExp: Option[ast.Exp]) = QuantifiedFieldChunk(id, fvf, terms.And(newCond, condition), newCondExp.map(nce => ast.And(nce, conditionExp.get)()), permValue, permValueExp, invs, singletonRcvr, singletonRcvrExp, hints) - override def permMinus(newPerm: Term, newPermExp: Option[ast.Exp]) = + override protected def permMinus(newPerm: Term, newPermExp: Option[ast.Exp]) = withPerm(PermMinus(permValue, newPerm), newPermExp.map(npe => ast.PermSub(permValueExp.get, npe)())) - override def permPlus(newPerm: Term, newPermExp: Option[ast.Exp]) = + override protected def permPlus(newPerm: Term, newPermExp: Option[ast.Exp]) = withPerm(PermPlus(permValue, newPerm), newPermExp.map(npe => ast.PermAdd(permValueExp.get, npe)())) override def withSnapshotMap(newFvf: Term) = QuantifiedFieldChunk(id, newFvf, condition, conditionExp, permValue, permValueExp, invs, singletonRcvr, singletonRcvrExp, hints) @@ -209,11 +199,11 @@ case class QuantifiedPredicateChunk(id: BasicChunkIdentifier, def withPerm(newPerm: Term, newPermExp: Option[ast.Exp]) = QuantifiedPredicateChunk(id, quantifiedVars, quantifiedVarExps, psf, condition, conditionExp, newPerm, newPermExp, invs, singletonArgs, singletonArgExps, hints) - override def applyCondition(newCond: Term, newCondExp: Option[ast.Exp]) = + override protected def applyCondition(newCond: Term, newCondExp: Option[ast.Exp]) = QuantifiedPredicateChunk(id, quantifiedVars, quantifiedVarExps, psf, terms.And(newCond, condition), newCondExp.map(nce => ast.And(nce, conditionExp.get)()), permValue, permValueExp, invs, singletonArgs, singletonArgExps, hints) - override def permMinus(newPerm: Term, newPermExp: Option[ast.Exp]) = + override protected def permMinus(newPerm: Term, newPermExp: Option[ast.Exp]) = withPerm(PermMinus(permValue, newPerm), newPermExp.map(npe => ast.PermSub(permValueExp.get, npe)())) - override def permPlus(newPerm: Term, newPermExp: Option[ast.Exp]) = + override protected def permPlus(newPerm: Term, newPermExp: Option[ast.Exp]) = withPerm(PermPlus(permValue, newPerm), newPermExp.map(npe => ast.PermAdd(permValueExp.get, npe)())) override def withSnapshotMap(newPsf: Term) = QuantifiedPredicateChunk(id, quantifiedVars, quantifiedVarExps, newPsf, condition, conditionExp, permValue, permValueExp, invs, singletonArgs, singletonArgExps, hints) @@ -244,11 +234,11 @@ case class QuantifiedMagicWandChunk(id: MagicWandIdentifier, override def valueAt(args: Seq[Term]) = PredicateLookup(id.toString, wsf, args) - override def applyCondition(newCond: Term, newCondExp: Option[ast.Exp]) = + override protected def applyCondition(newCond: Term, newCondExp: Option[ast.Exp]) = withPerm(Ite(newCond, perm, NoPerm), newCondExp.map(nce => ast.CondExp(nce, permExp.get, ast.NoPerm()())())) - override def permMinus(newPerm: Term, newPermExp: Option[ast.Exp]) = + override protected def permMinus(newPerm: Term, newPermExp: Option[ast.Exp]) = withPerm(PermMinus(perm, newPerm), newPermExp.map(npe => ast.PermSub(permExp.get, npe)())) - override def permPlus(newPerm: Term, newPermExp: Option[ast.Exp]) = + override protected def permPlus(newPerm: Term, newPermExp: Option[ast.Exp]) = withPerm(PermPlus(perm, newPerm), newPermExp.map(npe => ast.PermAdd(permExp.get, npe)())) def withPerm(newPerm: Term, newPermExp: Option[ast.Exp]) = QuantifiedMagicWandChunk(id, quantifiedVars, quantifiedVarExps, wsf, newPerm, newPermExp, invs, singletonArgs, singletonArgExps, hints) @@ -288,11 +278,11 @@ case class MagicWandChunk(id: MagicWandIdentifier, override val resourceID = MagicWandID - override def applyCondition(newCond: Term, newCondExp: Option[ast.Exp]) = + override protected def applyCondition(newCond: Term, newCondExp: Option[ast.Exp]) = withPerm(Ite(newCond, perm, NoPerm), newCondExp.map(nce => ast.CondExp(nce, permExp.get, ast.NoPerm()())())) - override def permMinus(newPerm: Term, newPermExp: Option[ast.Exp]) = + override protected def permMinus(newPerm: Term, newPermExp: Option[ast.Exp]) = withPerm(PermMinus(perm, newPerm), newPermExp.map(npe => ast.PermSub(permExp.get, npe)())) - override def permPlus(newPerm: Term, newPermExp: Option[ast.Exp]) = + override protected def permPlus(newPerm: Term, newPermExp: Option[ast.Exp]) = withPerm(PermPlus(perm, newPerm), newPermExp.map(npe => ast.PermAdd(permExp.get, npe)())) override def withPerm(newPerm: Term, newPermExp: Option[ast.Exp]) = MagicWandChunk(id, bindings, args, argsExp, snap, newPerm, newPermExp) From 094639c9c4099ff6dae224803ea705f108fa0c19 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 9 May 2025 10:43:55 +0200 Subject: [PATCH 024/474] chunks with perm --- src/main/scala/interfaces/state/Chunks.scala | 9 +++++- src/main/scala/rules/ChunkSupporter.scala | 30 +++++++++++-------- src/main/scala/rules/Consumer.scala | 4 +-- src/main/scala/rules/Executor.scala | 3 +- .../rules/MoreCompleteExhaleSupporter.scala | 18 ++++++----- src/main/scala/rules/PredicateSupporter.scala | 6 ++-- src/main/scala/state/Chunks.scala | 10 +++---- 7 files changed, 48 insertions(+), 32 deletions(-) diff --git a/src/main/scala/interfaces/state/Chunks.scala b/src/main/scala/interfaces/state/Chunks.scala index 2f0a1ebf3..3ff77c4c2 100644 --- a/src/main/scala/interfaces/state/Chunks.scala +++ b/src/main/scala/interfaces/state/Chunks.scala @@ -22,6 +22,7 @@ trait GeneralChunk extends Chunk { protected def applyCondition(newCond: Term, newCondExp: Option[ast.Exp]): GeneralChunk protected def permMinus(perm: Term, permExp: Option[ast.Exp]): GeneralChunk protected def permPlus(perm: Term, permExp: Option[ast.Exp]): GeneralChunk + protected def withPerm(newPerm: Term, newPermExp: Option[ast.Exp]): GeneralChunk val permExp: Option[ast.Exp] } @@ -44,6 +45,12 @@ object GeneralChunk { analysisInfo.getAssumptionAnalyzer.addPermissionDependencies(Set(chunk), PermissionInhaleNode(newChunk, analysisInfo.sourceInfo, analysisInfo.assumptionType)) newChunk } + + def withPerm(chunk: GeneralChunk, newPerm: Term, newPermExp: Option[ast.Exp], analysisInfo: AnalysisInfo): GeneralChunk = { + val newChunk = chunk.withPerm(newPerm, newPermExp) + analysisInfo.getAssumptionAnalyzer.addPermissionDependencies(Set(chunk), PermissionInhaleNode(newChunk, analysisInfo.sourceInfo, analysisInfo.assumptionType)) + newChunk + } } trait NonQuantifiedChunk extends GeneralChunk { @@ -53,7 +60,7 @@ trait NonQuantifiedChunk extends GeneralChunk { override protected def applyCondition(newCond: Term, newCondExp: Option[ast.Exp]): NonQuantifiedChunk override protected def permMinus(perm: Term, permExp: Option[ast.Exp]): NonQuantifiedChunk override protected def permPlus(perm: Term, permExp: Option[ast.Exp]): NonQuantifiedChunk - def withPerm(perm: Term, permExp: Option[ast.Exp]): NonQuantifiedChunk + override protected def withPerm(perm: Term, permExp: Option[ast.Exp]): NonQuantifiedChunk def withSnap(snap: Term, snapExp: Option[ast.Exp]): NonQuantifiedChunk } diff --git a/src/main/scala/rules/ChunkSupporter.scala b/src/main/scala/rules/ChunkSupporter.scala index e6c4eccea..2fdd077f3 100644 --- a/src/main/scala/rules/ChunkSupporter.scala +++ b/src/main/scala/rules/ChunkSupporter.scala @@ -6,7 +6,7 @@ package viper.silicon.rules -import viper.silicon.assumptionAnalysis.{AssumptionType, PermissionInhaleNode} +import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType, PermissionInhaleNode} import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.state._ import viper.silicon.interfaces.{Success, VerificationResult} @@ -33,7 +33,8 @@ trait ChunkSupportRules extends SymbolicExecutionRules { returnSnap: Boolean, ve: VerificationError, v: Verifier, - description: String) + description: String, + analysisInfo: AnalysisInfo) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult @@ -76,11 +77,12 @@ object chunkSupporter extends ChunkSupportRules { returnSnap: Boolean, ve: VerificationError, v: Verifier, - description: String) + description: String, + analysisInfo: AnalysisInfo) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { - consume2(s, h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v)((s2, h2, optSnap, v2) => + consume2(s, h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, analysisInfo)((s2, h2, optSnap, v2) => optSnap match { case Some(snap) => Q(s2, h2, Some(snap.convert(sorts.Snap)), v2) @@ -108,14 +110,15 @@ object chunkSupporter extends ChunkSupportRules { permsExp: Option[ast.Exp], returnSnap: Boolean, ve: VerificationError, - v: Verifier) + v: Verifier, + analysisInfo: AnalysisInfo) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { val id = ChunkIdentifier(resource, s.program) if (s.exhaleExt) { val failure = createFailure(ve, v, s, "chunk consume in package") - magicWandSupporter.transfer(s, perms, permsExp, failure, Seq(), v)(consumeGreedy(_, _, id, args, _, _, _))((s1, optCh, v1) => + magicWandSupporter.transfer(s, perms, permsExp, failure, Seq(), v)(consumeGreedy(_, _, id, args, _, _, _, analysisInfo))((s1, optCh, v1) => if (returnSnap){ Q(s1, h, optCh.flatMap(ch => Some(ch.snap)), v1) } else { @@ -124,11 +127,11 @@ object chunkSupporter extends ChunkSupportRules { } else { executionFlowController.tryOrFail2[Heap, Option[Term]](s.copy(h = h), v)((s1, v1, QS) => if (s1.moreCompleteExhale) { - moreCompleteExhaleSupporter.consumeComplete(s1, s1.h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v1)((s2, h2, snap2, v2) => { + moreCompleteExhaleSupporter.consumeComplete(s1, s1.h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v1, analysisInfo)((s2, h2, snap2, v2) => { QS(s2.copy(h = s.h), h2, snap2, v2) }) } else { - consumeGreedy(s1, s1.h, id, args, perms, permsExp, v1) match { + consumeGreedy(s1, s1.h, id, args, perms, permsExp, v1, analysisInfo) match { case (Complete(), s2, h2, optCh2) => val snap = optCh2 match { case Some(ch) if returnSnap => @@ -156,7 +159,8 @@ object chunkSupporter extends ChunkSupportRules { args: Seq[Term], perms: Term, permsExp: Option[ast.Exp], - v: Verifier) + v: Verifier, + analysisInfo: AnalysisInfo) : (ConsumptionResult, State, Heap, Option[NonQuantifiedChunk]) = { val consumeExact = terms.utils.consumeExactRead(perms, s.constrainableARPs) @@ -180,8 +184,8 @@ object chunkSupporter extends ChunkSupportRules { val toTake = PermMin(ch.perm, perms) val toTakeExp = permsExp.map(pe => buildMinExp(Seq(ch.permExp.get, pe), ast.Perm)) val newPermExp = permsExp.map(pe => ast.PermSub(ch.permExp.get, toTakeExp.get)(pe.pos, pe.info, pe.errT)) - val newChunk = ch.withPerm(PermMinus(ch.perm, toTake), newPermExp) - val takenChunk = Some(ch.withPerm(toTake, toTakeExp)) + val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, toTake), newPermExp, analysisInfo).asInstanceOf[NonQuantifiedChunk] + val takenChunk = Some(GeneralChunk.withPerm(ch, toTake, toTakeExp, analysisInfo).asInstanceOf[NonQuantifiedChunk]) var newHeap = h - ch if (!v.decider.check(newChunk.perm === NoPerm, Verifier.config.checkTimeout())) { newHeap = newHeap + newChunk @@ -194,8 +198,8 @@ object chunkSupporter extends ChunkSupportRules { val constraintExp = permsExp.map(pe => ast.PermLtCmp(pe, ch.permExp.get)(pe.pos, pe.info, pe.errT)) v.decider.assume(PermLess(perms, ch.perm), Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp))) val newPermExp = permsExp.map(pe => ast.PermSub(ch.permExp.get, pe)(pe.pos, pe.info, pe.errT)) - val newChunk = ch.withPerm(PermMinus(ch.perm, perms), newPermExp) - val takenChunk = ch.withPerm(perms, permsExp) + val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, perms), newPermExp, analysisInfo).asInstanceOf[NonQuantifiedChunk] + val takenChunk = GeneralChunk.withPerm(ch, perms, permsExp, analysisInfo).asInstanceOf[NonQuantifiedChunk] val newHeap = h - ch + newChunk assumeProperties(newChunk, newHeap) (Complete(), s, newHeap, Some(takenChunk)) diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index e269649bc..7d6fcce5d 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -482,7 +482,7 @@ object consumer extends ConsumptionRules { val lossExp = permNew.map(p => ast.PermMul(p, s3.permissionScalingFactorExp.get)(p.pos, p.info, p.errT)) val ve = pve dueTo InsufficientPermission(locacc) val description = s"consume ${a.pos}: $a" - chunkSupporter.consume(s3, h, resource, tArgs, eArgs, loss, lossExp, returnSnap, ve, v3, description)((s4, h1, snap1, v4) => { + chunkSupporter.consume(s3, h, resource, tArgs, eArgs, loss, lossExp, returnSnap, ve, v3, description, analysisInfo)((s4, h1, snap1, v4) => { val s5 = s4.copy(partiallyConsumedHeap = Some(h1), constrainableARPs = s.constrainableARPs) Q(s5, h1, snap1, v4)})}))) @@ -536,7 +536,7 @@ object consumer extends ConsumptionRules { magicWandSupporter.evaluateWandArguments(s, wand, pve, v)((s1, tArgs, eArgs, v1) => { val ve = pve dueTo MagicWandChunkNotFound(wand) val description = s"consume wand $wand" - chunkSupporter.consume(s1, h, wand, tArgs, eArgs, FullPerm, Option.when(withExp)(ast.FullPerm()(wand.pos, wand.info, wand.errT)), returnSnap, ve, v1, description)(Q) + chunkSupporter.consume(s1, h, wand, tArgs, eArgs, FullPerm, Option.when(withExp)(ast.FullPerm()(wand.pos, wand.info, wand.errT)), returnSnap, ve, v1, description, analysisInfo)(Q) }) case _ => diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index f8744a554..5e97af1f9 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -438,7 +438,8 @@ object executor extends ExecutionRules { val resource = fa.res(s.program) val ve = pve dueTo InsufficientPermission(fa) val description = s"consume ${ass.pos}: $ass" - chunkSupporter.consume(s2, s2.h, resource, Seq(tRcvr), eRcvrNew.map(Seq(_)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), false, ve, v2, description)((s3, h3, _, v3) => { + val analysisInfo = AnalysisInfo(v2, ExpAnalysisSourceInfo(fa), AssumptionType.Assertion) + chunkSupporter.consume(s2, s2.h, resource, Seq(tRcvr), eRcvrNew.map(Seq(_)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), false, ve, v2, description, analysisInfo)((s3, h3, _, v3) => { val (tSnap, _) = ssaifyRhs(tRhs, rhs, rhsNew, field.name, field.typ, v3, s3) val id = BasicChunkIdentifier(field.name) val newChunk = BasicChunk(FieldID, id, Seq(tRcvr), eRcvrNew.map(Seq(_)), tSnap, rhsNew, FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), diff --git a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala index cc43cf9a9..84ea882c6 100644 --- a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala +++ b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala @@ -6,6 +6,7 @@ package viper.silicon.rules +import viper.silicon.assumptionAnalysis.AnalysisInfo import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.state._ import viper.silicon.interfaces.{Success, VerificationResult} @@ -233,12 +234,13 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { permsExp: Option[ast.Exp], returnSnap: Boolean, ve: VerificationError, - v: Verifier) + v: Verifier, + analysisInfo: AnalysisInfo) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { if (!s.assertReadAccessOnly) - actualConsumeComplete(s, h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v)(Q) + actualConsumeComplete(s, h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, analysisInfo)(Q) else summariseHeapAndAssertReadAccess(s, h, resource, perms, args, argsExp, returnSnap, ve, v)(Q) } @@ -286,7 +288,8 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { permsExp: Option[ast.Exp], returnSnap: Boolean, ve: VerificationError, - v: Verifier) + v: Verifier, + analysisInfo: AnalysisInfo) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -306,7 +309,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { } } else { if (!terms.utils.consumeExactRead(perms, s.constrainableARPs)) { - actualConsumeCompleteConstrainable(s, relevantChunks, resource, args, argsExp, perms, permsExp, returnSnap, ve, v)((s1, updatedChunks, optSnap, v2) => { + actualConsumeCompleteConstrainable(s, relevantChunks, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, analysisInfo)((s1, updatedChunks, optSnap, v2) => { Q(s1, Heap(updatedChunks ++ otherChunks), optSnap, v2) }) } else { @@ -359,7 +362,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { pSum = PermPlus(pSum, Ite(eq, ch.perm, NoPerm)) pSumExp = eqExp.map(eq => ast.PermAdd(pSumExp.get, ast.CondExp(eq, ch.permExp.get, ast.NoPerm()())(eq.pos, eq.info, eq.errT))()) - val newChunk = ch.withPerm(PermMinus(ch.perm, pTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, pTakenExp.get)(pe.pos, pe.info, pe.errT))) + val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, pTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, pTakenExp.get)(pe.pos, pe.info, pe.errT)), analysisInfo).asInstanceOf[NonQuantifiedChunk] pNeeded = PermMinus(pNeeded, pTaken) pNeededExp = permsExp.map(pe => ast.PermSub(pNeededExp.get, pTakenExp.get)(pe.pos, pe.info, pe.errT)) @@ -431,7 +434,8 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { permsExp: Option[ast.Exp], returnSnap: Boolean, ve: VerificationError, - v: Verifier) + v: Verifier, + analysisInfo: AnalysisInfo) (Q: (State, ListBuffer[NonQuantifiedChunk], Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -472,7 +476,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { newFr = newFr.recordPathSymbol(permTaken.applicable.asInstanceOf[Function]).recordConstraint(constraint) - val newChunk = ch.withPerm(PermMinus(ch.perm, permTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, permTakenExp.get)(pe.pos, pe.info, pe.errT))) + val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, permTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, permTakenExp.get)(pe.pos, pe.info, pe.errT)), analysisInfo).asInstanceOf[NonQuantifiedChunk] newChunk }) diff --git a/src/main/scala/rules/PredicateSupporter.scala b/src/main/scala/rules/PredicateSupporter.scala index af8ff8d98..9d4934733 100644 --- a/src/main/scala/rules/PredicateSupporter.scala +++ b/src/main/scala/rules/PredicateSupporter.scala @@ -141,7 +141,7 @@ object predicateSupporter extends PredicateSupportRules { pa: ast.PredicateAccess) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { - + val analysisInfo = AnalysisInfo(v, ExpAnalysisSourceInfo(pa), AssumptionType.Assertion) // TODO ake: check that this is valid val tArgsWithE = if (withExp) tArgs zip eArgs.get.map(Some(_)) else @@ -165,7 +165,7 @@ object predicateSupporter extends PredicateSupportRules { None, pve, v, - AnalysisInfo(v, ExpAnalysisSourceInfo(pa), AssumptionType.Assertion) + analysisInfo )((s2, h2, snap, v1) => { val s3 = s2.copy(g = gIns, h = h2) .setConstrainable(constrainableWildcards, false) @@ -186,7 +186,7 @@ object predicateSupporter extends PredicateSupportRules { } else { val ve = pve dueTo InsufficientPermission(pa) val description = s"consume ${pa.pos}: $pa" - chunkSupporter.consume(s1, s1.h, predicate, tArgs, eArgs, s1.permissionScalingFactor, s1.permissionScalingFactorExp, true, ve, v, description)((s2, h1, snap, v1) => { + chunkSupporter.consume(s1, s1.h, predicate, tArgs, eArgs, s1.permissionScalingFactor, s1.permissionScalingFactorExp, true, ve, v, description, analysisInfo)((s2, h1, snap, v1) => { val s3 = s2.copy(g = gIns, h = h1) .setConstrainable(constrainableWildcards, false) produce(s3, toSf(snap.get), body, pve, v1)((s4, v2) => { diff --git a/src/main/scala/state/Chunks.scala b/src/main/scala/state/Chunks.scala index 0dbb81e6e..68fbbcac6 100644 --- a/src/main/scala/state/Chunks.scala +++ b/src/main/scala/state/Chunks.scala @@ -86,7 +86,7 @@ case class BasicChunk private (resourceID: BaseID, withPerm(PermMinus(perm, newPerm), newPermExp.map(npe => ast.PermSub(permExp.get, npe)())) override protected def permPlus(newPerm: Term, newPermExp: Option[ast.Exp]): BasicChunk = withPerm(PermPlus(perm, newPerm), newPermExp.map(npe => ast.PermAdd(permExp.get, npe)())) - override def withPerm(newPerm: Term, newPermExp: Option[ast.Exp]): BasicChunk = BasicChunk(resourceID, id, args, argsExp, snap, snapExp, newPerm, newPermExp) + override protected def withPerm(newPerm: Term, newPermExp: Option[ast.Exp]): BasicChunk = BasicChunk(resourceID, id, args, argsExp, snap, snapExp, newPerm, newPermExp) override def withSnap(newSnap: Term, newSnapExp: Option[ast.Exp]): BasicChunk = BasicChunk(resourceID, id, args, argsExp, newSnap, newSnapExp, perm, permExp) override lazy val toString = resourceID match { @@ -156,7 +156,7 @@ case class QuantifiedFieldChunk private(id: BasicChunkIdentifier, Lookup(id.name, fvf, arguments.head) } - def withPerm(newPerm: Term, newPermExp: Option[ast.Exp]) = + override protected def withPerm(newPerm: Term, newPermExp: Option[ast.Exp]) = QuantifiedFieldChunk(id, fvf, condition, conditionExp, newPerm, newPermExp, invs, singletonRcvr, singletonRcvrExp, hints) override protected def applyCondition(newCond: Term, newCondExp: Option[ast.Exp]) = QuantifiedFieldChunk(id, fvf, terms.And(newCond, condition), newCondExp.map(nce => ast.And(nce, conditionExp.get)()), permValue, permValueExp, invs, singletonRcvr, singletonRcvrExp, hints) @@ -197,7 +197,7 @@ case class QuantifiedPredicateChunk(id: BasicChunkIdentifier, override def valueAt(args: Seq[Term]) = PredicateLookup(id.name, psf, args) - def withPerm(newPerm: Term, newPermExp: Option[ast.Exp]) = + override protected def withPerm(newPerm: Term, newPermExp: Option[ast.Exp]) = QuantifiedPredicateChunk(id, quantifiedVars, quantifiedVarExps, psf, condition, conditionExp, newPerm, newPermExp, invs, singletonArgs, singletonArgExps, hints) override protected def applyCondition(newCond: Term, newCondExp: Option[ast.Exp]) = QuantifiedPredicateChunk(id, quantifiedVars, quantifiedVarExps, psf, terms.And(newCond, condition), newCondExp.map(nce => ast.And(nce, conditionExp.get)()), permValue, permValueExp, invs, singletonArgs, singletonArgExps, hints) @@ -240,7 +240,7 @@ case class QuantifiedMagicWandChunk(id: MagicWandIdentifier, withPerm(PermMinus(perm, newPerm), newPermExp.map(npe => ast.PermSub(permExp.get, npe)())) override protected def permPlus(newPerm: Term, newPermExp: Option[ast.Exp]) = withPerm(PermPlus(perm, newPerm), newPermExp.map(npe => ast.PermAdd(permExp.get, npe)())) - def withPerm(newPerm: Term, newPermExp: Option[ast.Exp]) = + override protected def withPerm(newPerm: Term, newPermExp: Option[ast.Exp]) = QuantifiedMagicWandChunk(id, quantifiedVars, quantifiedVarExps, wsf, newPerm, newPermExp, invs, singletonArgs, singletonArgExps, hints) override def withSnapshotMap(newWsf: Term) = QuantifiedMagicWandChunk(id, quantifiedVars, quantifiedVarExps, newWsf, perm, permExp, invs, singletonArgs, singletonArgExps, hints) @@ -284,7 +284,7 @@ case class MagicWandChunk(id: MagicWandIdentifier, withPerm(PermMinus(perm, newPerm), newPermExp.map(npe => ast.PermSub(permExp.get, npe)())) override protected def permPlus(newPerm: Term, newPermExp: Option[ast.Exp]) = withPerm(PermPlus(perm, newPerm), newPermExp.map(npe => ast.PermAdd(permExp.get, npe)())) - override def withPerm(newPerm: Term, newPermExp: Option[ast.Exp]) = MagicWandChunk(id, bindings, args, argsExp, snap, newPerm, newPermExp) + override protected def withPerm(newPerm: Term, newPermExp: Option[ast.Exp]) = MagicWandChunk(id, bindings, args, argsExp, snap, newPerm, newPermExp) override def withSnap(newSnap: Term, newSnapExp: Option[ast.Exp]) = { assert(newSnapExp.isEmpty) From 5ed74c8d3f82b6a9d5686a9b22c968578cb4d663 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 9 May 2025 10:55:30 +0200 Subject: [PATCH 025/474] chunks with snap --- src/main/scala/interfaces/state/Chunks.scala | 10 +++++++++- src/main/scala/rules/Executor.scala | 5 +++-- src/main/scala/rules/HavocSupporter.scala | 16 +++++++++------- src/main/scala/state/Chunks.scala | 4 ++-- 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/main/scala/interfaces/state/Chunks.scala b/src/main/scala/interfaces/state/Chunks.scala index 3ff77c4c2..4ead53c50 100644 --- a/src/main/scala/interfaces/state/Chunks.scala +++ b/src/main/scala/interfaces/state/Chunks.scala @@ -61,7 +61,15 @@ trait NonQuantifiedChunk extends GeneralChunk { override protected def permMinus(perm: Term, permExp: Option[ast.Exp]): NonQuantifiedChunk override protected def permPlus(perm: Term, permExp: Option[ast.Exp]): NonQuantifiedChunk override protected def withPerm(perm: Term, permExp: Option[ast.Exp]): NonQuantifiedChunk - def withSnap(snap: Term, snapExp: Option[ast.Exp]): NonQuantifiedChunk + protected def withSnap(snap: Term, snapExp: Option[ast.Exp]): NonQuantifiedChunk +} + +object NonQuantifiedChunk { + def withSnap(chunk: NonQuantifiedChunk, snap: Term, snapExp: Option[ast.Exp], analysisInfo: AnalysisInfo): NonQuantifiedChunk = { + val newChunk = chunk.withSnap(snap, snapExp) + analysisInfo.getAssumptionAnalyzer.addPermissionDependencies(Set(chunk), PermissionInhaleNode(newChunk, analysisInfo.sourceInfo, analysisInfo.assumptionType)) + newChunk + } } trait QuantifiedChunk extends GeneralChunk { diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 5e97af1f9..3d30ebe23 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -20,6 +20,7 @@ import viper.silver.verifier.reasons._ import viper.silver.{ast, cfg} import viper.silicon.decider.RecordedPathConditions import viper.silicon.interfaces._ +import viper.silicon.interfaces.state.NonQuantifiedChunk import viper.silicon.logger.records.data.{CommentRecord, ConditionalEdgeRecord, ExecuteRecord, MethodCallRecord} import viper.silicon.resources.FieldID import viper.silicon.state._ @@ -537,7 +538,7 @@ object executor extends ExecutionRules { // Calling hack407_R() results in Silicon efficiently havocking all instances of resource R. // See also Silicon issue #407. - case ast.MethodCall(methodName, _, _) + case methCall @ ast.MethodCall(methodName, _, _) if !Verifier.config.disableHavocHack407() && methodName.startsWith(hack407_method_name_prefix) => val resourceName = methodName.stripPrefix(hack407_method_name_prefix) @@ -547,7 +548,7 @@ object executor extends ExecutionRules { }.getOrElse(sys.error(s"Found $methodName, but no matching field or predicate $resourceName")) val h1 = Heap(s.h.values.map { case bc: BasicChunk if bc.id.name == member.name => - bc.withSnap(freshSnap(bc.snap.sort, v), None) + NonQuantifiedChunk.withSnap(bc, freshSnap(bc.snap.sort, v), None, AnalysisInfo(v, StmtAnalysisSourceInfo(methCall), AssumptionType.Unknown)) case qfc: QuantifiedFieldChunk if qfc.id.name == member.name => qfc.withSnapshotMap(freshSnap(qfc.fvf.sort, v)) case qpc: QuantifiedPredicateChunk if qpc.id.name == member.name => diff --git a/src/main/scala/rules/HavocSupporter.scala b/src/main/scala/rules/HavocSupporter.scala index b36339812..cbf934225 100644 --- a/src/main/scala/rules/HavocSupporter.scala +++ b/src/main/scala/rules/HavocSupporter.scala @@ -8,6 +8,7 @@ package viper.silicon.rules import viper.silicon.debugger.DebugExp import viper.silicon.Map +import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType, StmtAnalysisSourceInfo} import viper.silicon.interfaces.VerificationResult import viper.silicon.interfaces.state.{Chunk, NonQuantifiedChunk} import viper.silicon.rules.evaluator.{eval, evalQuantified, evals} @@ -42,7 +43,7 @@ object havocSupporter extends SymbolicExecutionRules { s: State) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { - + val analysisInfo = AnalysisInfo(v, StmtAnalysisSourceInfo(havoc), AssumptionType.Explicit) val pve = QuasihavocFailed(havoc) // If there is no havoc condition, use True as the condition @@ -59,7 +60,7 @@ object havocSupporter extends SymbolicExecutionRules { if (usesQPChunks(s1, resource)) havocQuantifiedResource(s1, lhsTerm, resource, HavocOneData(tRcvrs), v1) else - havocNonQuantifiedResource(s1, lhsTerm, resource, HavocOneData(tRcvrs), v1) + havocNonQuantifiedResource(s1, lhsTerm, resource, HavocOneData(tRcvrs), v1, analysisInfo) Q(s1.copy(h = Heap(newChunks)), v1) }) @@ -82,7 +83,7 @@ object havocSupporter extends SymbolicExecutionRules { s: State) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { - + val analysisInfo = AnalysisInfo(v, StmtAnalysisSourceInfo(havocall), AssumptionType.Explicit) val pve = HavocallFailed(havocall) val ast.Quasihavocall(vars, lhs, eRsc) = havocall val qid = resourceName(s, eRsc) @@ -158,7 +159,7 @@ object havocSupporter extends SymbolicExecutionRules { if (usesQPChunks(s1, resource)) havocQuantifiedResource(s1, tCond, resource, HavocallData(inverseFunctions, codomainQVars, imagesOfCodomain), v1) else - havocNonQuantifiedResource(s1, tCond, resource, HavocallData(inverseFunctions, codomainQVars, imagesOfCodomain), v1) + havocNonQuantifiedResource(s1, tCond, resource, HavocallData(inverseFunctions, codomainQVars, imagesOfCodomain), v1, analysisInfo) Q(s1.copy(h = Heap(newChunks)), v1) } @@ -183,7 +184,8 @@ object havocSupporter extends SymbolicExecutionRules { lhs: Term, resource: ast.Resource, condInfo: HavocHelperData, - v: Verifier) + v: Verifier, + analysisInfo: AnalysisInfo) : Seq[Chunk] = { val id = ChunkIdentifier(resource, s.program) @@ -194,12 +196,12 @@ object havocSupporter extends SymbolicExecutionRules { val havockedSnap = v.decider.fresh("mwsf", sorts.MagicWandSnapFunction, Option.when(withExp)(PUnknown())) val cond = replacementCond(lhs, ch.args, condInfo) val magicWandSnapshot = MagicWandSnapshot(Ite(cond, havockedSnap, ch.snap.mwsf)) - ch.withSnap(magicWandSnapshot, None) + NonQuantifiedChunk.withSnap(ch, magicWandSnapshot, None, analysisInfo) case ch => val havockedSnap = freshSnap(ch.snap.sort, v) val cond = replacementCond(lhs, ch.args, condInfo) - ch.withSnap(Ite(cond, havockedSnap, ch.snap), None) + NonQuantifiedChunk.withSnap(ch, Ite(cond, havockedSnap, ch.snap), None, analysisInfo) } otherChunks ++ newChunks } diff --git a/src/main/scala/state/Chunks.scala b/src/main/scala/state/Chunks.scala index 68fbbcac6..bc3cbc725 100644 --- a/src/main/scala/state/Chunks.scala +++ b/src/main/scala/state/Chunks.scala @@ -87,7 +87,7 @@ case class BasicChunk private (resourceID: BaseID, override protected def permPlus(newPerm: Term, newPermExp: Option[ast.Exp]): BasicChunk = withPerm(PermPlus(perm, newPerm), newPermExp.map(npe => ast.PermAdd(permExp.get, npe)())) override protected def withPerm(newPerm: Term, newPermExp: Option[ast.Exp]): BasicChunk = BasicChunk(resourceID, id, args, argsExp, snap, snapExp, newPerm, newPermExp) - override def withSnap(newSnap: Term, newSnapExp: Option[ast.Exp]): BasicChunk = BasicChunk(resourceID, id, args, argsExp, newSnap, newSnapExp, perm, permExp) + override protected def withSnap(newSnap: Term, newSnapExp: Option[ast.Exp]): BasicChunk = BasicChunk(resourceID, id, args, argsExp, newSnap, newSnapExp, perm, permExp) override lazy val toString = resourceID match { case FieldID => s"${args.head}.$id -> $snap # $perm" @@ -286,7 +286,7 @@ case class MagicWandChunk(id: MagicWandIdentifier, withPerm(PermPlus(perm, newPerm), newPermExp.map(npe => ast.PermAdd(permExp.get, npe)())) override protected def withPerm(newPerm: Term, newPermExp: Option[ast.Exp]) = MagicWandChunk(id, bindings, args, argsExp, snap, newPerm, newPermExp) - override def withSnap(newSnap: Term, newSnapExp: Option[ast.Exp]) = { + override protected def withSnap(newSnap: Term, newSnapExp: Option[ast.Exp]) = { assert(newSnapExp.isEmpty) newSnap match { case s: MagicWandSnapshot => MagicWandChunk(id, bindings, args, argsExp, s, perm, permExp) From 1c2d80b97d101727035276c1987abc640e5da223 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 9 May 2025 11:15:25 +0200 Subject: [PATCH 026/474] chunks with snapshot map --- src/main/scala/interfaces/state/Chunks.scala | 10 +++++++++- src/main/scala/rules/Executor.scala | 9 +++++---- src/main/scala/rules/HavocSupporter.scala | 11 ++++++----- src/main/scala/state/Chunks.scala | 8 ++++---- 4 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/main/scala/interfaces/state/Chunks.scala b/src/main/scala/interfaces/state/Chunks.scala index 4ead53c50..1117692c9 100644 --- a/src/main/scala/interfaces/state/Chunks.scala +++ b/src/main/scala/interfaces/state/Chunks.scala @@ -81,5 +81,13 @@ trait QuantifiedChunk extends GeneralChunk { override protected def applyCondition(newCond: Term, newCondExp: Option[ast.Exp]): QuantifiedChunk override protected def permMinus(perm: Term, permExp: Option[ast.Exp]): QuantifiedChunk override protected def permPlus(perm: Term, permExp: Option[ast.Exp]): QuantifiedChunk - def withSnapshotMap(snap: Term): QuantifiedChunk + protected def withSnapshotMap(snap: Term): QuantifiedChunk +} + +object QuantifiedChunk { + def withSnapshotMap(chunk: QuantifiedChunk, snap: Term, analysisInfo: AnalysisInfo): QuantifiedChunk = { + val newChunk = chunk.withSnapshotMap(snap) + analysisInfo.getAssumptionAnalyzer.addPermissionDependencies(Set(chunk), PermissionInhaleNode(newChunk, analysisInfo.sourceInfo, analysisInfo.assumptionType)) + newChunk + } } \ No newline at end of file diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 3d30ebe23..27857aa89 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -20,7 +20,7 @@ import viper.silver.verifier.reasons._ import viper.silver.{ast, cfg} import viper.silicon.decider.RecordedPathConditions import viper.silicon.interfaces._ -import viper.silicon.interfaces.state.NonQuantifiedChunk +import viper.silicon.interfaces.state.{NonQuantifiedChunk, QuantifiedChunk} import viper.silicon.logger.records.data.{CommentRecord, ConditionalEdgeRecord, ExecuteRecord, MethodCallRecord} import viper.silicon.resources.FieldID import viper.silicon.state._ @@ -541,6 +541,7 @@ object executor extends ExecutionRules { case methCall @ ast.MethodCall(methodName, _, _) if !Verifier.config.disableHavocHack407() && methodName.startsWith(hack407_method_name_prefix) => + val analysisInfo = AnalysisInfo(v, StmtAnalysisSourceInfo(methCall), AssumptionType.Unknown) val resourceName = methodName.stripPrefix(hack407_method_name_prefix) val member = s.program.collectFirst { case m: ast.Field if m.name == resourceName => m @@ -548,11 +549,11 @@ object executor extends ExecutionRules { }.getOrElse(sys.error(s"Found $methodName, but no matching field or predicate $resourceName")) val h1 = Heap(s.h.values.map { case bc: BasicChunk if bc.id.name == member.name => - NonQuantifiedChunk.withSnap(bc, freshSnap(bc.snap.sort, v), None, AnalysisInfo(v, StmtAnalysisSourceInfo(methCall), AssumptionType.Unknown)) + NonQuantifiedChunk.withSnap(bc, freshSnap(bc.snap.sort, v), None, analysisInfo) case qfc: QuantifiedFieldChunk if qfc.id.name == member.name => - qfc.withSnapshotMap(freshSnap(qfc.fvf.sort, v)) + QuantifiedChunk.withSnapshotMap(qfc,freshSnap(qfc.fvf.sort, v), analysisInfo) case qpc: QuantifiedPredicateChunk if qpc.id.name == member.name => - qpc.withSnapshotMap(freshSnap(qpc.psf.sort, v)) + QuantifiedChunk.withSnapshotMap(qpc, freshSnap(qpc.psf.sort, v), analysisInfo) case other => other}) Q(s.copy(h = h1), v) diff --git a/src/main/scala/rules/HavocSupporter.scala b/src/main/scala/rules/HavocSupporter.scala index cbf934225..2bda2298d 100644 --- a/src/main/scala/rules/HavocSupporter.scala +++ b/src/main/scala/rules/HavocSupporter.scala @@ -10,7 +10,7 @@ import viper.silicon.debugger.DebugExp import viper.silicon.Map import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType, StmtAnalysisSourceInfo} import viper.silicon.interfaces.VerificationResult -import viper.silicon.interfaces.state.{Chunk, NonQuantifiedChunk} +import viper.silicon.interfaces.state.{Chunk, NonQuantifiedChunk, QuantifiedChunk} import viper.silicon.rules.evaluator.{eval, evalQuantified, evals} import viper.silicon.rules.quantifiedChunkSupporter.freshSnapshotMap import viper.silicon.state._ @@ -58,7 +58,7 @@ object havocSupporter extends SymbolicExecutionRules { // the HavocHelperData inside of a HavocOneData case (as opposed to HavocAllData). val newChunks = if (usesQPChunks(s1, resource)) - havocQuantifiedResource(s1, lhsTerm, resource, HavocOneData(tRcvrs), v1) + havocQuantifiedResource(s1, lhsTerm, resource, HavocOneData(tRcvrs), v1, analysisInfo) else havocNonQuantifiedResource(s1, lhsTerm, resource, HavocOneData(tRcvrs), v1, analysisInfo) @@ -157,7 +157,7 @@ object havocSupporter extends SymbolicExecutionRules { // the HavocHelperData inside of a HavocAllData case. val newChunks = if (usesQPChunks(s1, resource)) - havocQuantifiedResource(s1, tCond, resource, HavocallData(inverseFunctions, codomainQVars, imagesOfCodomain), v1) + havocQuantifiedResource(s1, tCond, resource, HavocallData(inverseFunctions, codomainQVars, imagesOfCodomain), v1, analysisInfo) else havocNonQuantifiedResource(s1, tCond, resource, HavocallData(inverseFunctions, codomainQVars, imagesOfCodomain), v1, analysisInfo) @@ -227,7 +227,8 @@ object havocSupporter extends SymbolicExecutionRules { lhs: Term, resource: ast.Resource, condInfo: HavocHelperData, - v: Verifier) + v: Verifier, + analysisInfo: AnalysisInfo) : Seq[Chunk] = { // Quantified field chunks are of the form R(r; sm, pm). @@ -284,7 +285,7 @@ object havocSupporter extends SymbolicExecutionRules { val debugExp = Option.when(withExp)(DebugExp.createInstance("havoc new axiom", isInternal_ = true)) v.decider.assume(newAxiom, debugExp) - ch.withSnapshotMap(newSm) + QuantifiedChunk.withSnapshotMap(ch, newSm, analysisInfo) } newChunks ++ otherChunks } diff --git a/src/main/scala/state/Chunks.scala b/src/main/scala/state/Chunks.scala index bc3cbc725..0c8589fad 100644 --- a/src/main/scala/state/Chunks.scala +++ b/src/main/scala/state/Chunks.scala @@ -110,7 +110,7 @@ sealed trait QuantifiedBasicChunk extends QuantifiedChunk { override protected def applyCondition(newCond: Term, newCondExp: Option[ast.Exp]): QuantifiedBasicChunk override protected def permMinus(perm: Term, permExp: Option[ast.Exp]): QuantifiedBasicChunk override protected def permPlus(perm: Term, permExp: Option[ast.Exp]): QuantifiedBasicChunk - override def withSnapshotMap(snap: Term): QuantifiedBasicChunk + override protected def withSnapshotMap(snap: Term): QuantifiedBasicChunk def singletonArguments: Option[Seq[Term]] def singletonArgumentExps: Option[Seq[ast.Exp]] def hints: Seq[Term] @@ -164,7 +164,7 @@ case class QuantifiedFieldChunk private(id: BasicChunkIdentifier, withPerm(PermMinus(permValue, newPerm), newPermExp.map(npe => ast.PermSub(permValueExp.get, npe)())) override protected def permPlus(newPerm: Term, newPermExp: Option[ast.Exp]) = withPerm(PermPlus(permValue, newPerm), newPermExp.map(npe => ast.PermAdd(permValueExp.get, npe)())) - override def withSnapshotMap(newFvf: Term) = + override protected def withSnapshotMap(newFvf: Term) = QuantifiedFieldChunk(id, newFvf, condition, conditionExp, permValue, permValueExp, invs, singletonRcvr, singletonRcvrExp, hints) override lazy val toString = s"${terms.Forall} ${`?r`} :: ${`?r`}.$id -> $fvf # $perm" @@ -205,7 +205,7 @@ case class QuantifiedPredicateChunk(id: BasicChunkIdentifier, withPerm(PermMinus(permValue, newPerm), newPermExp.map(npe => ast.PermSub(permValueExp.get, npe)())) override protected def permPlus(newPerm: Term, newPermExp: Option[ast.Exp]) = withPerm(PermPlus(permValue, newPerm), newPermExp.map(npe => ast.PermAdd(permValueExp.get, npe)())) - override def withSnapshotMap(newPsf: Term) = + override protected def withSnapshotMap(newPsf: Term) = QuantifiedPredicateChunk(id, quantifiedVars, quantifiedVarExps, newPsf, condition, conditionExp, permValue, permValueExp, invs, singletonArgs, singletonArgExps, hints) override lazy val toString = s"${terms.Forall} ${quantifiedVars.mkString(",")} :: $id(${quantifiedVars.mkString(",")}) -> $psf # $perm" @@ -242,7 +242,7 @@ case class QuantifiedMagicWandChunk(id: MagicWandIdentifier, withPerm(PermPlus(perm, newPerm), newPermExp.map(npe => ast.PermAdd(permExp.get, npe)())) override protected def withPerm(newPerm: Term, newPermExp: Option[ast.Exp]) = QuantifiedMagicWandChunk(id, quantifiedVars, quantifiedVarExps, wsf, newPerm, newPermExp, invs, singletonArgs, singletonArgExps, hints) - override def withSnapshotMap(newWsf: Term) = + override protected def withSnapshotMap(newWsf: Term) = QuantifiedMagicWandChunk(id, quantifiedVars, quantifiedVarExps, newWsf, perm, permExp, invs, singletonArgs, singletonArgExps, hints) override lazy val toString = s"${terms.Forall} ${quantifiedVars.mkString(",")} :: $id(${quantifiedVars.mkString(",")}) -> $wsf # $perm" From 9912be1614b3106bbe560a34775241d74e32c9c4 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 9 May 2025 11:43:48 +0200 Subject: [PATCH 027/474] add assumption analyzer for function and predicate verification --- .../scala/assumptionAnalysis/AssumptionAnalyzer.scala | 4 ++-- src/main/scala/decider/Decider.scala | 8 ++++---- .../scala/supporters/PredicateVerificationUnit.scala | 3 +++ .../functions/FunctionVerificationUnit.scala | 7 +++++-- src/test/resources/andrea/quantified-perm.vpr | 11 +++++++++++ 5 files changed, 25 insertions(+), 8 deletions(-) create mode 100644 src/test/resources/andrea/quantified-perm.vpr diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 791572e92..0cebf18e1 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -5,7 +5,7 @@ import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.state.Chunk import viper.silicon.state.terms.Term import viper.silver.ast -import viper.silver.ast.NoPosition +import viper.silver.ast.{Member, NoPosition} trait AssumptionAnalyzer { @@ -57,7 +57,7 @@ object AssumptionAnalyzer { def isAssumptionLabel(label: String): Boolean = label.startsWith("assumption_") } -class DefaultAssumptionAnalyzer(method: ast.Method) extends AssumptionAnalyzer { +class DefaultAssumptionAnalyzer(member: Member) extends AssumptionAnalyzer { // private var scope : mutable.Set[AssumptionAnalysisNode] = mutable.Set.empty // private var isScopeOpen: Boolean = false // private var scopeStmt: ast.Stmt = ??? diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 3a7ac2e02..e97b487e1 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -20,7 +20,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.{LocalVarWithVersion, Member, NoPosition} import viper.silver.components.StatefulComponent import viper.silver.parser.{PKw, PPrimitiv, PReserved, PType} import viper.silver.reporter.{ConfigurationConfirmation, InternalWarningMessage} @@ -107,7 +107,7 @@ trait Decider { def statistics(): Map[String, String] var assumptionAnalyzer: AssumptionAnalyzer // TODO ake - def initAssumptionAnalyzer(method: ast.Method): Unit + def initAssumptionAnalyzer(member: Member): Unit def removeAssumptionAnalyzer(): Unit } @@ -140,9 +140,9 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => var assumptionAnalyzer: AssumptionAnalyzer = new NoAssumptionAnalyzer() - override def initAssumptionAnalyzer(method: ast.Method): Unit = { + override def initAssumptionAnalyzer(member: Member): Unit = { if(Verifier.config.enableAssumptionAnalysis()){ - assumptionAnalyzer = new DefaultAssumptionAnalyzer(method) + assumptionAnalyzer = new DefaultAssumptionAnalyzer(member) prover.setAssumptionAnalyzer(assumptionAnalyzer) } } diff --git a/src/main/scala/supporters/PredicateVerificationUnit.scala b/src/main/scala/supporters/PredicateVerificationUnit.scala index 388c54709..e5ddb9dc9 100644 --- a/src/main/scala/supporters/PredicateVerificationUnit.scala +++ b/src/main/scala/supporters/PredicateVerificationUnit.scala @@ -82,6 +82,7 @@ trait DefaultPredicateVerificationUnitProvider extends VerifierComponent { v: Ve def verify(sInit: State, predicate: ast.Predicate): Seq[VerificationResult] = { logger.debug("\n\n" + "-" * 10 + " PREDICATE " + predicate.name + "-" * 10 + "\n") + v.decider.initAssumptionAnalyzer(predicate) decider.prover.comment("%s %s %s".format("-" * 10, predicate.name, "-" * 10)) openSymbExLogger(predicate) @@ -104,6 +105,8 @@ trait DefaultPredicateVerificationUnitProvider extends VerifierComponent { v: Ve } symbExLog.closeMemberScope() + result.assumptionAnalyzer = v.decider.assumptionAnalyzer + v.decider.removeAssumptionAnalyzer() Seq(result) } diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index 543a7c694..fe58ba61f 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -148,6 +148,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver def verify(sInit: State, function: ast.Function): Seq[VerificationResult] = { val comment = ("-" * 10) + " FUNCTION " + function.name + ("-" * 10) + v.decider.initAssumptionAnalyzer(function) logger.debug(s"\n\n$comment\n") decider.prover.comment(comment) @@ -157,9 +158,11 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver data.formalArgs.values foreach (v => decider.prover.declare(ConstDecl(v))) decider.prover.declare(ConstDecl(data.formalResult)) - val res = Seq(handleFunction(sInit, function)) + val res = handleFunction(sInit, function) symbExLog.closeMemberScope() - res + res.assumptionAnalyzer = v.decider.assumptionAnalyzer + v.decider.removeAssumptionAnalyzer() + Seq(res) } private def handleFunction(sInit: State, function: ast.Function): VerificationResult = { diff --git a/src/test/resources/andrea/quantified-perm.vpr b/src/test/resources/andrea/quantified-perm.vpr new file mode 100644 index 000000000..76bbe0c48 --- /dev/null +++ b/src/test/resources/andrea/quantified-perm.vpr @@ -0,0 +1,11 @@ +field f: Int + +method foo(xs: Seq[Ref]) { + assume |xs| > 5 + inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) + + var a : Ref := xs[0] + a.f := 0 + + assert xs[2].f > 0 +} \ No newline at end of file From 8d38e9a34d2b6487531113a6014760a2741819b4 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 9 May 2025 12:23:49 +0200 Subject: [PATCH 028/474] fix --- src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 0cebf18e1..44fc675bd 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -31,7 +31,7 @@ trait AssumptionAnalyzer { val assumptionGraph: AssumptionAnalysisGraph = new DefaultAssumptionAnalysisGraph() - def getMethod: Option[ast.Method] + def getMember: Option[Member] } object AssumptionAnalyzer { @@ -138,7 +138,7 @@ class DefaultAssumptionAnalyzer(member: Member) extends AssumptionAnalyzer { assumptionGraph.addEdges(analysisChunks, newChunkNode.id) } - override def getMethod: Option[ast.Method] = Some(method) + override def getMember: Option[Member] = Some(member) } @@ -156,7 +156,7 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { override def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNode: AssumptionAnalysisNode): Unit = { } - override def getMethod: Option[ast.Method] = None + override def getMember: Option[Member] = None override def addSingleAssumption(assumption: DebugExp, assumptionType: AssumptionType = AssumptionType.Unknown): Option[Int] = None override def addSingleAssumption(assumption: DebugExp, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None From cec252dd4cc800a688d35506ccbf0ba7b051b0cd Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 9 May 2025 13:55:20 +0200 Subject: [PATCH 029/474] collect source info on statement level --- .../assumptionAnalysis/AnalysisInfo.scala | 9 ++- .../AnalysisSourceInfo.scala | 14 ++-- .../AssumptionAnalysisGraph.scala | 13 ++-- .../AssumptionAnalyzer.scala | 64 +++++++++++++------ src/main/scala/decider/Decider.scala | 54 ++++++++-------- src/main/scala/interfaces/state/Chunks.scala | 12 ++-- src/main/scala/rules/Consumer.scala | 2 +- src/main/scala/rules/Evaluator.scala | 6 +- src/main/scala/rules/Executor.scala | 56 +++++++++------- src/main/scala/rules/HavocSupporter.scala | 4 +- src/main/scala/rules/Joiner.scala | 4 +- src/main/scala/rules/MagicWandSupporter.scala | 6 +- src/main/scala/rules/PredicateSupporter.scala | 6 +- src/main/scala/rules/Producer.scala | 4 +- src/main/scala/state/Chunks.scala | 4 +- .../scala/supporters/MethodSupporter.scala | 2 +- .../functions/FunctionVerificationUnit.scala | 2 +- 17 files changed, 151 insertions(+), 111 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AnalysisInfo.scala b/src/main/scala/assumptionAnalysis/AnalysisInfo.scala index 3f4581524..2f4f22a2d 100644 --- a/src/main/scala/assumptionAnalysis/AnalysisInfo.scala +++ b/src/main/scala/assumptionAnalysis/AnalysisInfo.scala @@ -3,7 +3,12 @@ package viper.silicon.assumptionAnalysis import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType import viper.silicon.verifier.Verifier -case class AnalysisInfo(v: Verifier, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType) { +case class AnalysisInfo(assumptionAnalyzer: AssumptionAnalyzer, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType) { + + def withAssumptionType(at: AssumptionType): AnalysisInfo = AnalysisInfo(assumptionAnalyzer, sourceInfo, at) + def withSourceInfo(si: AnalysisSourceInfo): AnalysisInfo = AnalysisInfo(assumptionAnalyzer, si, assumptionType) +} + +class NoAnalysisInfo extends AnalysisInfo(AssumptionAnalyzer.noAssumptionAnalyzerSingelton, NoAnalysisSourceInfo(), AssumptionType.Unknown) { - def getAssumptionAnalyzer: AssumptionAnalyzer = v.decider.assumptionAnalyzer } diff --git a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala index 5b0031c27..50006973f 100644 --- a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala +++ b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala @@ -1,15 +1,19 @@ package viper.silicon.assumptionAnalysis import viper.silver.ast -import viper.silver.ast.Position +import viper.silver.ast.{NoPosition, Position} abstract class AnalysisSourceInfo { + override def toString: String = getPosition.toString def getPosition: Position } +case class NoAnalysisSourceInfo() extends AnalysisSourceInfo { + override def getPosition: Position = NoPosition +} + case class ExpAnalysisSourceInfo(source: ast.Exp) extends AnalysisSourceInfo { - override def toString: String = source.toString override def getPosition: Position = source.pos @@ -23,8 +27,6 @@ case class ExpAnalysisSourceInfo(source: ast.Exp) extends AnalysisSourceInfo { } case class StmtAnalysisSourceInfo(source: ast.Stmt) extends AnalysisSourceInfo { - override def toString: String = source.toString() - override def getPosition: Position = source.pos override def equals(obj: Any): Boolean = { @@ -37,13 +39,9 @@ case class StmtAnalysisSourceInfo(source: ast.Stmt) extends AnalysisSourceInfo { } case class StringAnalysisSourceInfo(description: String, position: Position) extends AnalysisSourceInfo { - override def toString: String = description - override def getPosition: Position = position } case class CombinedAnalysisSourceInfo(mainSource: AnalysisSourceInfo, sndSource: AnalysisSourceInfo) extends AnalysisSourceInfo { - override def toString: String = mainSource.toString + " and " + sndSource.toString - override def getPosition: Position = mainSource.getPosition } diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index 7623a5340..143a9054d 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -71,7 +71,9 @@ trait AssumptionAnalysisNode { val sourceInfo: AnalysisSourceInfo val assumptionType: AssumptionType - override def toString: String = id.toString + " " + sourceInfo.toString + override def toString: String = id.toString + ": " + getNodeString + " at " + sourceInfo.toString + + def getNodeString: String def isIncludedInAnalysis: Boolean = assumptionType match { case Explicit => true @@ -86,32 +88,31 @@ trait AssumptionAnalysisNode { trait ChunkAnalysisInfo { val chunk: Chunk - override def toString: String = super.toString + " with chunk " + chunk.toString - def getChunk: Chunk = chunk } case class SimpleAssumptionNode(assumption: ast.Exp, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Unknown) extends AssumptionAnalysisNode { - override def toString: String = super.toString + ": " + assumption.toString + override def getNodeString: String ="assumed " + assumption.toString } case class StringAssumptionNode(description: String, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Unknown) extends AssumptionAnalysisNode { - override def toString: String = super.toString + ": " + description + override def getNodeString: String = "assumed " + description } // TODO ake: ast.Exp instead of Term case class SimpleAssertionNode(assertion: Term, isAsserted: Boolean, sourceInfo: AnalysisSourceInfo) extends AssumptionAnalysisNode { override val assumptionType: AssumptionType = AssumptionType.Explicit - override def toString: String = super.toString + ": " + assertion.toString + override def getNodeString: String = "asserted " + assertion.toString } case class PermissionInhaleNode(chunk: Chunk, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Unknown) extends AssumptionAnalysisNode with ChunkAnalysisInfo { + override def getNodeString: String = "inhaled " + chunk.toString } diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 44fc675bd..f497ec790 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -13,15 +13,15 @@ trait AssumptionAnalyzer { // def closeScope(): Unit def addPermissionNode(chunk: Chunk, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] - def addSingleAssumption(assumption: ast.Exp, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] + def addSingleAssumption(assumption: ast.Exp, analysisInfo: AnalysisInfo): Option[Int] - def addSingleAssumption(description: String, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] + def addSingleAssumption(description: String, analysisInfo: AnalysisInfo): Option[Int] def addSingleAssumption(assumption: DebugExp, assumptionType: AssumptionType = AssumptionType.Unknown): Option[Int] - def addSingleAssumption(assumption: DebugExp, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] + def addSingleAssumption(assumption: DebugExp, analysisInfo: AnalysisInfo): Option[Int] - def addAssumptions(assumptions: Iterable[DebugExp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Seq[Int] + def addAssumptions(assumptions: Iterable[DebugExp], analysisInfo: AnalysisInfo): Seq[Int] def addAssertion(assertion: Term, isAsserted: Boolean, sourceInfo: AnalysisSourceInfo): Option[Int] @@ -31,10 +31,36 @@ trait AssumptionAnalyzer { val assumptionGraph: AssumptionAnalysisGraph = new DefaultAssumptionAnalysisGraph() + var currentAnalysisInfo: AnalysisInfo = new NoAnalysisInfo() + + def setCurrentAnalysisInfo(analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): AnalysisInfo = { + currentAnalysisInfo = AnalysisInfo(this, analysisSourceInfo, assumptionType) + currentAnalysisInfo + } + + def clearCurrentAnalysisInfo(): AnalysisInfo = { + currentAnalysisInfo = new NoAnalysisInfo() + currentAnalysisInfo + } + + def updateCurrentAnalysisInfo(at: AssumptionType): AnalysisInfo = { + setCurrentAnalysisInfo(currentAnalysisInfo.sourceInfo, at) + } + + def updateCurrentAnalysisInfo(si: AnalysisSourceInfo): AnalysisInfo = { + setCurrentAnalysisInfo(si, currentAnalysisInfo.assumptionType) + } + + def updateCurrentAnalysisInfo(si: AnalysisSourceInfo, at: AssumptionType): AnalysisInfo = { + setCurrentAnalysisInfo(si, at) + } + def getMember: Option[Member] } object AssumptionAnalyzer { + val noAssumptionAnalyzerSingelton = new NoAssumptionAnalyzer() + def createAssumptionLabel(id: Option[Int], offset: Int = 0): String = { createLabel("assumption", id, offset) } @@ -79,33 +105,33 @@ class DefaultAssumptionAnalyzer(member: Member) extends AssumptionAnalyzer { assumptionGraph.addNode(node) } - override def addSingleAssumption(assumption: ast.Exp, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { - val node = SimpleAssumptionNode(assumption, sourceInfo, assumptionType) + override def addSingleAssumption(assumption: ast.Exp, analysisInfo: AnalysisInfo): Option[Int] = { + val node = SimpleAssumptionNode(assumption, analysisInfo.sourceInfo, analysisInfo.assumptionType) addNode(node) Some(node.id) } - override def addSingleAssumption(description: String, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { - val node = StringAssumptionNode(description, sourceInfo, assumptionType) + override def addSingleAssumption(description: String, analysisInfo: AnalysisInfo): Option[Int] = { + val node = StringAssumptionNode(description, analysisInfo.sourceInfo, analysisInfo.assumptionType) addNode(node) Some(node.id) } override def addSingleAssumption(assumption: DebugExp, assumptionType: AssumptionType = AssumptionType.Unknown): Option[Int] = { - if(assumption.originalExp.isDefined) addSingleAssumption(assumption, ExpAnalysisSourceInfo(assumption.originalExp.get), assumptionType) - else addSingleAssumption(assumption, StringAnalysisSourceInfo(assumption.description.getOrElse("unknown"), NoPosition), assumptionType) + if(assumption.originalExp.isDefined) addSingleAssumption(assumption, AnalysisInfo(this, ExpAnalysisSourceInfo(assumption.originalExp.get), assumptionType)) + else addSingleAssumption(assumption, AnalysisInfo(this, StringAnalysisSourceInfo(assumption.description.getOrElse("unknown"), NoPosition), assumptionType)) } - override def addSingleAssumption(assumption: DebugExp, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { - val node = if(assumption.originalExp.isDefined) SimpleAssumptionNode(assumption.originalExp.get, sourceInfo, assumptionType) - else StringAssumptionNode(assumption.description.getOrElse("unknown"), sourceInfo, assumptionType) + override def addSingleAssumption(assumption: DebugExp, analysisInfo: AnalysisInfo): Option[Int] = { + val node = if(assumption.originalExp.isDefined) SimpleAssumptionNode(assumption.originalExp.get, analysisInfo.sourceInfo, analysisInfo.assumptionType) + else StringAssumptionNode(assumption.description.getOrElse("unknown"), analysisInfo.sourceInfo, analysisInfo.assumptionType) addNode(node) Some(node.id) } - override def addAssumptions(assumptions: Iterable[DebugExp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Seq[Int] = { + override def addAssumptions(assumptions: Iterable[DebugExp], analysisInfo: AnalysisInfo): Seq[Int] = { val newNodes = assumptions.filter(_.originalExp.isDefined) - .map(a => SimpleAssumptionNode(a.originalExp.get, sourceInfo, assumptionType)) + .map(a => SimpleAssumptionNode(a.originalExp.get, analysisInfo.sourceInfo, analysisInfo.assumptionType)) newNodes foreach addNode newNodes.map(_.id).toSeq } @@ -144,7 +170,7 @@ class DefaultAssumptionAnalyzer(member: Member) extends AssumptionAnalyzer { class NoAssumptionAnalyzer extends AssumptionAnalyzer { - override def addAssumptions(assumptions: Iterable[DebugExp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Seq[Int] = Seq.empty + override def addAssumptions(assumptions: Iterable[DebugExp], analysisInfo: AnalysisInfo): Seq[Int] = Seq.empty override def addAssertion(assertion: Term, isAsserted: Boolean, sourceInfo: AnalysisSourceInfo): Option[Int] = None @@ -159,7 +185,7 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { override def getMember: Option[Member] = None override def addSingleAssumption(assumption: DebugExp, assumptionType: AssumptionType = AssumptionType.Unknown): Option[Int] = None - override def addSingleAssumption(assumption: DebugExp, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None - override def addSingleAssumption(assumption: ast.Exp, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None - override def addSingleAssumption(description: String, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None + override def addSingleAssumption(assumption: DebugExp, analysisInfo: AnalysisInfo): Option[Int] = None + override def addSingleAssumption(assumption: ast.Exp, analysisInfo: AnalysisInfo): Option[Int] = None + override def addSingleAssumption(description: String, analysisInfo: AnalysisInfo): Option[Int] = None } \ No newline at end of file diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index e97b487e1..2b779443d 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -9,7 +9,7 @@ package viper.silicon.decider import com.typesafe.scalalogging.Logger import viper.silicon._ import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType -import viper.silicon.assumptionAnalysis.{AnalysisSourceInfo, AssumptionAnalyzer, AssumptionType, DefaultAssumptionAnalyzer, NoAssumptionAnalyzer, StringAnalysisSourceInfo} +import viper.silicon.assumptionAnalysis.{AnalysisInfo, AnalysisSourceInfo, AssumptionAnalyzer, AssumptionType, DefaultAssumptionAnalyzer, NoAssumptionAnalyzer, StringAnalysisSourceInfo} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.interfaces._ @@ -61,15 +61,15 @@ trait Decider { def startDebugSubExp(): Unit def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp]): Unit - def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Unit + def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp], analysisInfo: AnalysisInfo): Unit def assume(t: Term, debugExp: Option[DebugExp]): Unit - def assume(t: Term, debugExp: Option[DebugExp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Unit + def assume(t: Term, debugExp: Option[DebugExp], analysisInfo: AnalysisInfo): Unit def assume(terms: Seq[Term], debugExps: Option[Seq[DebugExp]]): Unit - def assume(terms: Seq[Term], debugExps: Option[Seq[DebugExp]], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Unit + def assume(terms: Seq[Term], debugExps: Option[Seq[DebugExp]], analysisInfo: AnalysisInfo): Unit def assumeDefinition(t: Term, debugExp: Option[DebugExp]): Unit - def assumeDefinition(t: Term, debugExp: Option[DebugExp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Unit + def assumeDefinition(t: Term, debugExp: Option[DebugExp], analysisInfo: AnalysisInfo): Unit def assume(terms: Iterable[Term], debugExp: Option[DebugExp], enforceAssumption: Boolean): Unit - def assume(terms: Iterable[Term], debugExp: Option[DebugExp], enforceAssumption: Boolean, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Unit + def assume(terms: Iterable[Term], debugExp: Option[DebugExp], enforceAssumption: Boolean, analysisInfo: AnalysisInfo): Unit def check(t: Term, timeout: Int): Boolean @@ -299,37 +299,37 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp]): Unit = { - assume(t, e, finalExp, StringAnalysisSourceInfo("unknown", NoPosition), AssumptionType.Unknown) + assume(t, e, finalExp, decider.assumptionAnalyzer.currentAnalysisInfo) } - def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Unit = { + def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp], analysisInfo: AnalysisInfo): Unit = { if (finalExp.isDefined) { - assume(assumptions=InsertionOrderedSet((t, Some(DebugExp.createInstance(e.get, finalExp.get)))), sourceInfo, assumptionType, false, false) + assume(assumptions=InsertionOrderedSet((t, Some(DebugExp.createInstance(e.get, finalExp.get)))), analysisInfo, false, false) } else { - assume(assumptions=InsertionOrderedSet((t, None)), sourceInfo, assumptionType, false, false) + assume(assumptions=InsertionOrderedSet((t, None)), analysisInfo, false, false) } } def assume(t: Term, debugExp: Option[DebugExp]): Unit = { - assume(t, debugExp, StringAnalysisSourceInfo("unknown", NoPosition), AssumptionType.Unknown) + assume(t, debugExp, assumptionAnalyzer.currentAnalysisInfo) } - def assume(t: Term, debugExp: Option[DebugExp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Unit = { - assume(InsertionOrderedSet(Seq((t, debugExp))), sourceInfo, assumptionType, false, false) + def assume(t: Term, debugExp: Option[DebugExp], analysisInfo: AnalysisInfo): Unit = { + assume(InsertionOrderedSet(Seq((t, debugExp))), analysisInfo, false, false) } def assumeDefinition(t: Term, debugExp: Option[DebugExp]): Unit = { - assumeDefinition(t, debugExp, StringAnalysisSourceInfo("unknown", NoPosition), AssumptionType.Unknown) + assumeDefinition(t, debugExp, assumptionAnalyzer.currentAnalysisInfo) } - def assumeDefinition(t: Term, debugExp: Option[DebugExp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Unit = { - assume(InsertionOrderedSet(Seq((t, debugExp))), sourceInfo, assumptionType, enforceAssumption=false, isDefinition=true) + def assumeDefinition(t: Term, debugExp: Option[DebugExp], analysisInfo: AnalysisInfo): Unit = { + assume(InsertionOrderedSet(Seq((t, debugExp))), analysisInfo, enforceAssumption=false, isDefinition=true) } - def assume(assumptions: Iterable[(Term, Option[DebugExp])], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Unit = - assume(InsertionOrderedSet(assumptions), sourceInfo, assumptionType, false, false) + def assume(assumptions: Iterable[(Term, Option[DebugExp])], analysisInfo: AnalysisInfo): Unit = + assume(InsertionOrderedSet(assumptions), analysisInfo, false, false) - def assume(assumptions: InsertionOrderedSet[(Term, Option[DebugExp])], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, enforceAssumption: Boolean, isDefinition: Boolean): Unit = { + def assume(assumptions: InsertionOrderedSet[(Term, Option[DebugExp])], analysisInfo: AnalysisInfo, enforceAssumption: Boolean, isDefinition: Boolean): Unit = { val filteredAssumptions = if (enforceAssumption) assumptions else assumptions filterNot (a => isKnownToBeTrue(a._1)) @@ -340,7 +340,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } val filteredAssumptionsWithLabels = filteredAssumptions map{case (t, de) => - val assumptionId: Option[Int] = if(de.isDefined) assumptionAnalyzer.addSingleAssumption(de.get, sourceInfo, assumptionType) else None + val assumptionId: Option[Int] = if(de.isDefined) assumptionAnalyzer.addSingleAssumption(de.get, analysisInfo) else None (t, AssumptionAnalyzer.createAssumptionLabel(assumptionId)) } @@ -350,11 +350,11 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } def assume(assumptions: Seq[Term], debugExps: Option[Seq[DebugExp]]): Unit = { - assume(assumptions, debugExps, StringAnalysisSourceInfo("unknown", NoPosition), AssumptionType.Unknown) + assume(assumptions, debugExps, assumptionAnalyzer.currentAnalysisInfo) } - def assume(assumptions: Seq[Term], debugExps: Option[Seq[DebugExp]], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Unit = { - val assumptionIds = if(debugExps.isDefined) assumptionAnalyzer.addAssumptions(debugExps.get, sourceInfo, assumptionType) else Seq.empty + def assume(assumptions: Seq[Term], debugExps: Option[Seq[DebugExp]], analysisInfo: AnalysisInfo): Unit = { + val assumptionIds = if(debugExps.isDefined) assumptionAnalyzer.addAssumptions(debugExps.get, analysisInfo) else Seq.empty val assumptionsWithLabels = if(assumptions.size == assumptionIds.size) assumptions.zip(assumptionIds).map{case (t, id) => (t, AssumptionAnalyzer.createAssumptionLabel(Some(id)))} @@ -366,10 +366,10 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } def assume(terms: Iterable[Term], debugExp: Option[DebugExp], enforceAssumption: Boolean): Unit = { - assume(terms, debugExp, enforceAssumption, StringAnalysisSourceInfo("unknown", NoPosition), AssumptionType.Unknown) + assume(terms, debugExp, enforceAssumption, assumptionAnalyzer.currentAnalysisInfo) } - def assume(terms: Iterable[Term], debugExp: Option[DebugExp], enforceAssumption: Boolean, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Unit = { + def assume(terms: Iterable[Term], debugExp: Option[DebugExp], enforceAssumption: Boolean, analysisInfo: AnalysisInfo): Unit = { val filteredTerms = if (enforceAssumption) terms @@ -379,7 +379,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => if (debugMode) { addDebugExp(debugExp.get.withTerm(And(filteredTerms))) - val assumptionId: Option[Int] = if(debugExp.isDefined) assumptionAnalyzer.addSingleAssumption(debugExp.get.withTerm(And(filteredTerms)), sourceInfo, assumptionType) else None + val assumptionId: Option[Int] = if(debugExp.isDefined) assumptionAnalyzer.addSingleAssumption(debugExp.get.withTerm(And(filteredTerms)), analysisInfo) else None val termsWithLabel = filteredTerms.zipWithIndex.iterator.map {case (t, idx) => (t, AssumptionAnalyzer.createAssumptionLabel(assumptionId, idx))}.toSeq assumeWithoutSmokeChecks(InsertionOrderedSet(termsWithLabel)) }else{ @@ -450,7 +450,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => val asserted = if(Verifier.config.enableAssumptionAnalysis()) t.equals(True) else isKnownToBeTrue(t) - val assertionId: Option[Int] = if(!asserted) assumptionAnalyzer.addAssertion(t, false, StringAnalysisSourceInfo("deciderAssert", NoPosition)) else None + val assertionId: Option[Int] = if(!asserted) assumptionAnalyzer.addAssertion(t, false, decider.assumptionAnalyzer.currentAnalysisInfo.sourceInfo) else None val result = asserted || proverAssert(t, timeout, AssumptionAnalyzer.createAssertionLabel(assertionId)) symbExLog.closeScope(sepIdentifier) diff --git a/src/main/scala/interfaces/state/Chunks.scala b/src/main/scala/interfaces/state/Chunks.scala index 1117692c9..c213a8a18 100644 --- a/src/main/scala/interfaces/state/Chunks.scala +++ b/src/main/scala/interfaces/state/Chunks.scala @@ -30,25 +30,25 @@ trait GeneralChunk extends Chunk { object GeneralChunk { def applyCondition(chunk: GeneralChunk, newCond: Term, newCondExp: Option[ast.Exp], analysisInfo: AnalysisInfo): GeneralChunk = { val newChunk = chunk.applyCondition(newCond, newCondExp) - analysisInfo.getAssumptionAnalyzer.addPermissionDependencies(Set(chunk), PermissionInhaleNode(newChunk, analysisInfo.sourceInfo, analysisInfo.assumptionType)) + analysisInfo.assumptionAnalyzer.addPermissionDependencies(Set(chunk), PermissionInhaleNode(newChunk, analysisInfo.sourceInfo, analysisInfo.assumptionType)) newChunk } def permMinus(chunk: GeneralChunk, newPerm: Term, newPermExp: Option[ast.Exp], analysisInfo: AnalysisInfo): GeneralChunk = { val newChunk = chunk.permMinus(newPerm, newPermExp) - analysisInfo.getAssumptionAnalyzer.addPermissionDependencies(Set(chunk), PermissionInhaleNode(newChunk, analysisInfo.sourceInfo, analysisInfo.assumptionType)) + analysisInfo.assumptionAnalyzer.addPermissionDependencies(Set(chunk), PermissionInhaleNode(newChunk, analysisInfo.sourceInfo, analysisInfo.assumptionType)) newChunk } def permPlus(chunk: GeneralChunk, newPerm: Term, newPermExp: Option[ast.Exp], analysisInfo: AnalysisInfo): GeneralChunk = { val newChunk = chunk.permPlus(newPerm, newPermExp) - analysisInfo.getAssumptionAnalyzer.addPermissionDependencies(Set(chunk), PermissionInhaleNode(newChunk, analysisInfo.sourceInfo, analysisInfo.assumptionType)) + analysisInfo.assumptionAnalyzer.addPermissionDependencies(Set(chunk), PermissionInhaleNode(newChunk, analysisInfo.sourceInfo, analysisInfo.assumptionType)) newChunk } def withPerm(chunk: GeneralChunk, newPerm: Term, newPermExp: Option[ast.Exp], analysisInfo: AnalysisInfo): GeneralChunk = { val newChunk = chunk.withPerm(newPerm, newPermExp) - analysisInfo.getAssumptionAnalyzer.addPermissionDependencies(Set(chunk), PermissionInhaleNode(newChunk, analysisInfo.sourceInfo, analysisInfo.assumptionType)) + analysisInfo.assumptionAnalyzer.addPermissionDependencies(Set(chunk), PermissionInhaleNode(newChunk, analysisInfo.sourceInfo, analysisInfo.assumptionType)) newChunk } } @@ -67,7 +67,7 @@ trait NonQuantifiedChunk extends GeneralChunk { object NonQuantifiedChunk { def withSnap(chunk: NonQuantifiedChunk, snap: Term, snapExp: Option[ast.Exp], analysisInfo: AnalysisInfo): NonQuantifiedChunk = { val newChunk = chunk.withSnap(snap, snapExp) - analysisInfo.getAssumptionAnalyzer.addPermissionDependencies(Set(chunk), PermissionInhaleNode(newChunk, analysisInfo.sourceInfo, analysisInfo.assumptionType)) + analysisInfo.assumptionAnalyzer.addPermissionDependencies(Set(chunk), PermissionInhaleNode(newChunk, analysisInfo.sourceInfo, analysisInfo.assumptionType)) newChunk } } @@ -87,7 +87,7 @@ trait QuantifiedChunk extends GeneralChunk { object QuantifiedChunk { def withSnapshotMap(chunk: QuantifiedChunk, snap: Term, analysisInfo: AnalysisInfo): QuantifiedChunk = { val newChunk = chunk.withSnapshotMap(snap) - analysisInfo.getAssumptionAnalyzer.addPermissionDependencies(Set(chunk), PermissionInhaleNode(newChunk, analysisInfo.sourceInfo, analysisInfo.assumptionType)) + analysisInfo.assumptionAnalyzer.addPermissionDependencies(Set(chunk), PermissionInhaleNode(newChunk, analysisInfo.sourceInfo, analysisInfo.assumptionType)) newChunk } } \ No newline at end of file diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index 7d6fcce5d..fbaac2cf6 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -581,7 +581,7 @@ object consumer extends ConsumptionRules { State.mergeHeap( entry1.data._1, And(entry1.pathConditions.branchConditions), Option.when(withExp)(BigAnd(entry1.pathConditions.branchConditionExps.map(_._2.get))), entry2.data._1, And(entry2.pathConditions.branchConditions), Option.when(withExp)(BigAnd(entry2.pathConditions.branchConditionExps.map(_._2.get))), - AnalysisInfo(v, StringAnalysisSourceInfo("conditional join", e0.pos), AssumptionType.Implicit) + AnalysisInfo(v.decider.assumptionAnalyzer, StringAnalysisSourceInfo("conditional join", e0.pos), AssumptionType.Implicit) ), // Assume that entry1.pcs is inverse of entry2.pcs (entry1.data._2, entry2.data._2) match { diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 1b2e9950b..285115aac 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -930,7 +930,7 @@ object evaluator extends EvaluationRules { moreJoins = JoinMode.Off, assertReadAccessOnly = if (Verifier.config.respectFunctionPrePermAmounts()) s2.assertReadAccessOnly /* should currently always be false */ else true) - consumes(s3, pres, true, _ => pvePre, v2, AnalysisInfo(v2, ExpAnalysisSourceInfo(fapp), AssumptionType.Assertion))((s4, snap, v3) => { + consumes(s3, pres, true, _ => pvePre, v2, AnalysisInfo(v2.decider.assumptionAnalyzer, ExpAnalysisSourceInfo(fapp), AssumptionType.Assertion))((s4, snap, v3) => { val snap1 = snap.get.convert(sorts.Snap) val preFApp = App(functionSupporter.preconditionVersion(v3.symbolConverter.toFunction(func)), snap1 :: tArgs) val preExp = Option.when(withExp)({ @@ -992,7 +992,7 @@ object evaluator extends EvaluationRules { // val c4 = c3.decCycleCounter(predicate) // eval(σ1, eIn, pve, c4)((tIn, c5) => // QB(tIn, c5))}) - consume(s4, acc, true, pve, v3, AnalysisInfo(v3, ExpAnalysisSourceInfo(acc), AssumptionType.Implicit))((s5, snap, v4) => { + consume(s4, acc, true, pve, v3, AnalysisInfo(v3.decider.assumptionAnalyzer, ExpAnalysisSourceInfo(acc), AssumptionType.Implicit))((s5, snap, v4) => { val fr6 = s5.functionRecorder.recordSnapshot(pa, v4.decider.pcs.branchConditions, snap.get) .changeDepthBy(+1) @@ -1046,7 +1046,7 @@ object evaluator extends EvaluationRules { => Q(s4, r4._1, r4._2, v4)) case ast.Asserting(eAss, eIn) => - consume(s, eAss, false, pve, v, AnalysisInfo(v, ExpAnalysisSourceInfo(eAss), AssumptionType.Assertion))((s2, _, v2) => { + consume(s, eAss, false, pve, v, AnalysisInfo(v.decider.assumptionAnalyzer, ExpAnalysisSourceInfo(eAss), AssumptionType.Assertion))((s2, _, v2) => { val s3 = s2.copy(g = s.g, h = s.h) eval(s3, eIn, pve, v2)(Q) }) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 27857aa89..b46f1b4be 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -270,7 +270,7 @@ object executor extends ExecutionRules { })}) combine executionFlowController.locally(s, v)((s0, v0) => { v0.decider.prover.comment("Loop head block: Establish invariant") - consumes(s0, invs, false, LoopInvariantNotEstablished, v0, AnalysisInfo(v0, ExpAnalysisSourceInfo(invs.head), AssumptionType.Assertion))((sLeftover, _, v1) => { + consumes(s0, invs, false, LoopInvariantNotEstablished, v0, AnalysisInfo(v0.decider.assumptionAnalyzer, ExpAnalysisSourceInfo(invs.head), AssumptionType.Assertion))((sLeftover, _, v1) => { v1.decider.prover.comment("Loop head block: Execute statements of loop head block (in invariant state)") phase1data.foldLeft(Success(): VerificationResult) { case (result, _) if !result.continueVerification => result @@ -304,7 +304,7 @@ object executor extends ExecutionRules { * attempting to re-establish the invariant. */ v.decider.prover.comment("Loop head block: Re-establish invariant") - consumes(s, invs, false, e => LoopInvariantNotPreserved(e), v, AnalysisInfo(v, ExpAnalysisSourceInfo(invs.head), AssumptionType.Assertion))((_, _, _) => + consumes(s, invs, false, e => LoopInvariantNotPreserved(e), v, AnalysisInfo(v.decider.assumptionAnalyzer, ExpAnalysisSourceInfo(invs.head), AssumptionType.Assertion))((_, _, _) => Success()) } } @@ -324,8 +324,10 @@ object executor extends ExecutionRules { (Q: (State, Verifier) => VerificationResult) : VerificationResult = { val sepIdentifier = v.symbExLog.openScope(new ExecuteRecord(stmt, s, v.decider.pcs)) + v.decider.assumptionAnalyzer.setCurrentAnalysisInfo(StmtAnalysisSourceInfo(stmt), AssumptionType.Implicit) exec2(s, stmt, v)((s1, v1) => { v1.symbExLog.closeScope(sepIdentifier) + v.decider.assumptionAnalyzer.clearCurrentAnalysisInfo() Q(s1, v1)}) } @@ -409,7 +411,7 @@ object executor extends ExecutionRules { Option.when(withExp)(ast.FullPerm()()), chunkOrderHeuristics, v2, - AnalysisInfo(v2, ExpAnalysisSourceInfo(fa), AssumptionType.Implicit) + AnalysisInfo(v2.decider.assumptionAnalyzer, ExpAnalysisSourceInfo(fa), AssumptionType.Implicit) ) result match { case (Complete(), s3, remainingChunks) => @@ -439,12 +441,12 @@ object executor extends ExecutionRules { val resource = fa.res(s.program) val ve = pve dueTo InsufficientPermission(fa) val description = s"consume ${ass.pos}: $ass" - val analysisInfo = AnalysisInfo(v2, ExpAnalysisSourceInfo(fa), AssumptionType.Assertion) + val analysisInfo = AnalysisInfo(v2.decider.assumptionAnalyzer, ExpAnalysisSourceInfo(fa), AssumptionType.Assertion) chunkSupporter.consume(s2, s2.h, resource, Seq(tRcvr), eRcvrNew.map(Seq(_)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), false, ve, v2, description, analysisInfo)((s3, h3, _, v3) => { val (tSnap, _) = ssaifyRhs(tRhs, rhs, rhsNew, field.name, field.typ, v3, s3) val id = BasicChunkIdentifier(field.name) val newChunk = BasicChunk(FieldID, id, Seq(tRcvr), eRcvrNew.map(Seq(_)), tSnap, rhsNew, FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), - AnalysisInfo(v3, ExpAnalysisSourceInfo(fa), AssumptionType.Implicit)) + AnalysisInfo(v3.decider.assumptionAnalyzer, ExpAnalysisSourceInfo(fa), AssumptionType.Implicit)) chunkSupporter.produce(s3, h3, newChunk, v3)((s4, h4, v4) => { val s5 = s4.copy(h = h4) val (debugHeapName, _) = v4.getDebugOldLabel(s5, fa.pos) @@ -460,7 +462,7 @@ object executor extends ExecutionRules { val debugExp = Option.when(withExp)(ast.NeCmp(x, ast.NullLit()())()) val debugExpSubst = Option.when(withExp)(ast.NeCmp(eRcvrNew.get, ast.NullLit()())()) val (debugHeapName, debugLabel) = v.getDebugOldLabel(s, stmt.pos) - v.decider.assume(tRcvr !== Null, debugExp, debugExpSubst, StmtAnalysisSourceInfo(stmt), AssumptionType.Implicit) + v.decider.assume(tRcvr !== Null, debugExp, debugExpSubst) val newChunks = fields map (field => { val p = FullPerm val pExp = Option.when(withExp)(ast.FullPerm()(stmt.pos, stmt.info, stmt.errT)) @@ -475,7 +477,7 @@ object executor extends ExecutionRules { field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), p, pExp, sm, s.program) } else { val newChunk = BasicChunk(FieldID, BasicChunkIdentifier(field.name), Seq(tRcvr), Option.when(withExp)(Seq(x)), snap, snapExp, p, pExp, - AnalysisInfo(v, ExpAnalysisSourceInfo(ast.FieldAccess(x, field)(field.pos, field.info, field.errT)), AssumptionType.Explicit)) + AnalysisInfo(v.decider.assumptionAnalyzer, ExpAnalysisSourceInfo(ast.FieldAccess(x, field)(field.pos, field.info, field.errT)), AssumptionType.Explicit)) newChunk } }) @@ -486,23 +488,28 @@ object executor extends ExecutionRules { v.decider.assume(ts, Option.when(withExp)(DebugExp.createInstance(Some("Reference Disjointness"), esNew, esNew, InsertionOrderedSet.empty)), enforceAssumption = false) Q(s2, v) - case inhale @ ast.Inhale(a) => a match { - case _: ast.FalseLit => - /* We're done */ - Success() - case _ => - produce(s, freshSnap, a, InhaleFailed(inhale), v)((s1, v1) => { - v1.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterInhale) - Q(s1, v1)}) - } + case inhale @ ast.Inhale(a) => + v.decider.assumptionAnalyzer.setCurrentAnalysisInfo(ExpAnalysisSourceInfo(a), AssumptionType.Explicit) + a match { + case _: ast.FalseLit => + /* We're done */ + Success() + case _ => + produce(s, freshSnap, a, InhaleFailed(inhale), v)((s1, v1) => { + v1.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterInhale) + Q(s1, v1)}) + } + case exhale @ ast.Exhale(a) => + val analysisInfo = v.decider.assumptionAnalyzer.setCurrentAnalysisInfo(ExpAnalysisSourceInfo(a), AssumptionType.Assertion) val pve = ExhaleFailed(exhale) - consume(s, a, false, pve, v, AnalysisInfo(v, ExpAnalysisSourceInfo(a), AssumptionType.Assertion))((s1, _, v1) => + consume(s, a, false, pve, v, analysisInfo)((s1, _, v1) => Q(s1, v1)) case assert @ ast.Assert(a: ast.FalseLit) if !s.isInPackage => /* "assert false" triggers a smoke check. If successful, we backtrack. */ + v.decider.assumptionAnalyzer.setCurrentAnalysisInfo(ExpAnalysisSourceInfo(a), AssumptionType.Assertion) executionFlowController.tryOrFail0(s.copy(h = magicWandSupporter.getEvalHeap(s)), v)((s1, v1, QS) => { if (v1.decider.checkSmoke(true)) QS(s1.copy(h = s.h), v1) @@ -511,13 +518,15 @@ object executor extends ExecutionRules { })((_, _) => Success()) case assert @ ast.Assert(a) if Verifier.config.disableSubsumption() => + val analysisInfo = v.decider.assumptionAnalyzer.setCurrentAnalysisInfo(ExpAnalysisSourceInfo(a), AssumptionType.Assertion) val r = - consume(s, a, false, AssertFailed(assert), v, AnalysisInfo(v, ExpAnalysisSourceInfo(a), AssumptionType.Assertion))((_, _, _) => + consume(s, a, false, AssertFailed(assert), v, analysisInfo)((_, _, _) => Success()) r combine Q(s, v) case assert @ ast.Assert(a) => + val analysisInfo = v.decider.assumptionAnalyzer.setCurrentAnalysisInfo(ExpAnalysisSourceInfo(a), AssumptionType.Assertion) val pve = AssertFailed(assert) if (s.exhaleExt) { @@ -528,11 +537,11 @@ object executor extends ExecutionRules { * hUsed (reserveHeaps.head) instead of consuming them. hUsed is later discarded and replaced * by s.h. By copying hUsed to s.h the contained permissions remain available inside the wand. */ - consume(s, a, false, pve, v, AnalysisInfo(v, ExpAnalysisSourceInfo(a), AssumptionType.Assertion))((s2, _, v1) => { + consume(s, a, false, pve, v, analysisInfo)((s2, _, v1) => { Q(s2.copy(h = s2.reserveHeaps.head), v1) }) } else - consume(s, a, false, pve, v, AnalysisInfo(v, ExpAnalysisSourceInfo(a), AssumptionType.Assertion))((s1, _, v1) => { + consume(s, a, false, pve, v, analysisInfo)((s1, _, v1) => { val s2 = s1.copy(h = s.h, reserveHeaps = s.reserveHeaps) Q(s2, v1)}) @@ -541,7 +550,7 @@ object executor extends ExecutionRules { case methCall @ ast.MethodCall(methodName, _, _) if !Verifier.config.disableHavocHack407() && methodName.startsWith(hack407_method_name_prefix) => - val analysisInfo = AnalysisInfo(v, StmtAnalysisSourceInfo(methCall), AssumptionType.Unknown) + val analysisInfo = v.decider.assumptionAnalyzer.updateCurrentAnalysisInfo(AssumptionType.Explicit) val resourceName = methodName.stripPrefix(hack407_method_name_prefix) val member = s.program.collectFirst { case m: ast.Field if m.name == resourceName => m @@ -591,13 +600,14 @@ object executor extends ExecutionRules { tArgs zip Seq.fill(tArgs.size)(None) val s2 = s1.copy(g = Store(fargs.zip(argsWithExp)), recordVisited = true) - consumes(s2, meth.pres, false, _ => pvePre, v1, AnalysisInfo(v1, StmtAnalysisSourceInfo(call), AssumptionType.Assertion))((s3, _, v2) => { + consumes(s2, meth.pres, false, _ => pvePre, v1, v1.decider.assumptionAnalyzer.currentAnalysisInfo.withAssumptionType(AssumptionType.Assertion))((s3, _, v2) => { v2.symbExLog.closeScope(preCondId) val postCondLog = new CommentRecord("Postcondition", s3, v2.decider.pcs) val postCondId = v2.symbExLog.openScope(postCondLog) val outs = meth.formalReturns.map(_.localVar) val gOuts = Store(outs.map(x => (x, v2.decider.fresh(x))).toMap) val s4 = s3.copy(g = s3.g + gOuts, oldHeaps = s3.oldHeaps + (Verifier.PRE_STATE_LABEL -> magicWandSupporter.getEvalHeap(s1))) + v2.decider.assumptionAnalyzer.updateCurrentAnalysisInfo(AssumptionType.Explicit) produces(s4, freshSnap, meth.posts, _ => pveCallTransformed, v2)((s5, v3) => { v3.symbExLog.closeScope(postCondId) v3.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) @@ -619,7 +629,7 @@ object executor extends ExecutionRules { eval(s1, ePerm, pve, v1)((s2, tPerm, ePermNew, v2) => permissionSupporter.assertPositive(s2, tPerm, if (withExp) ePermNew.get else ePerm, pve, v2)((s3, v3) => { val wildcards = s3.constrainableARPs -- s1.constrainableARPs - predicateSupporter.fold(s3, predicate, tArgs, eArgsNew, tPerm, ePermNew, wildcards, pve, v3, AnalysisInfo(v3, ExpAnalysisSourceInfo(predAcc), AssumptionType.Unknown))((s4, v4) => { + predicateSupporter.fold(s3, predicate, tArgs, eArgsNew, tPerm, ePermNew, wildcards, pve, v3, AnalysisInfo(v3.decider.assumptionAnalyzer, ExpAnalysisSourceInfo(predAcc), AssumptionType.Unknown))((s4, v4) => { v3.decider.finishDebugSubExp(s"folded ${predAcc.toString}") Q(s4, v4) } diff --git a/src/main/scala/rules/HavocSupporter.scala b/src/main/scala/rules/HavocSupporter.scala index 2bda2298d..f8eb1c386 100644 --- a/src/main/scala/rules/HavocSupporter.scala +++ b/src/main/scala/rules/HavocSupporter.scala @@ -43,7 +43,7 @@ object havocSupporter extends SymbolicExecutionRules { s: State) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { - val analysisInfo = AnalysisInfo(v, StmtAnalysisSourceInfo(havoc), AssumptionType.Explicit) + val analysisInfo = AnalysisInfo(v.decider.assumptionAnalyzer, StmtAnalysisSourceInfo(havoc), AssumptionType.Explicit) val pve = QuasihavocFailed(havoc) // If there is no havoc condition, use True as the condition @@ -83,7 +83,7 @@ object havocSupporter extends SymbolicExecutionRules { s: State) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { - val analysisInfo = AnalysisInfo(v, StmtAnalysisSourceInfo(havocall), AssumptionType.Explicit) + val analysisInfo = AnalysisInfo(v.decider.assumptionAnalyzer, StmtAnalysisSourceInfo(havocall), AssumptionType.Explicit) val pve = HavocallFailed(havocall) val ast.Quasihavocall(vars, lhs, eRsc) = havocall val qid = resourceName(s, eRsc) diff --git a/src/main/scala/rules/Joiner.scala b/src/main/scala/rules/Joiner.scala index 27ffff96c..c5012c018 100644 --- a/src/main/scala/rules/Joiner.scala +++ b/src/main/scala/rules/Joiner.scala @@ -26,12 +26,12 @@ case class JoinDataEntry[D](s: State, data: D, pathConditions: RecordedPathCondi // we can directly merge JoinDataEntries to obtain new States, // and the join data entries themselves provide information about the path conditions to State.merge. def pathConditionAwareMerge(other: JoinDataEntry[D], v: Verifier): State = { - val res = State.merge(this.s, this.pathConditions, other.s, other.pathConditions, AnalysisInfo(v, StringAnalysisSourceInfo("merge", NoPosition), AssumptionType.Implicit)) + val res = State.merge(this.s, this.pathConditions, other.s, other.pathConditions, AnalysisInfo(v.decider.assumptionAnalyzer, StringAnalysisSourceInfo("merge", NoPosition), AssumptionType.Implicit)) v.stateConsolidator(s).consolidate(res, v) } def pathConditionAwareMergeWithoutConsolidation(other: JoinDataEntry[D], v: Verifier): State = { - State.merge(this.s, this.pathConditions, other.s, other.pathConditions, AnalysisInfo(v, StringAnalysisSourceInfo("merge", NoPosition), AssumptionType.Implicit)) + State.merge(this.s, this.pathConditions, other.s, other.pathConditions, AnalysisInfo(v.decider.assumptionAnalyzer, StringAnalysisSourceInfo("merge", NoPosition), AssumptionType.Implicit)) } } diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 8555a8624..d32d9b268 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -400,7 +400,7 @@ object magicWandSupporter extends SymbolicExecutionRules { // This part indirectly calls the methods `this.transfer` and `this.consumeFromMultipleHeaps`. consume( proofScriptState.copy(oldHeaps = s2.oldHeaps, reserveCfgs = proofScriptState.reserveCfgs.tail), - wand.right, true, pve, proofScriptVerifier, AnalysisInfo(proofScriptVerifier, ExpAnalysisSourceInfo(wand.right), AssumptionType.Assertion) + wand.right, true, pve, proofScriptVerifier, AnalysisInfo(proofScriptVerifier.decider.assumptionAnalyzer, ExpAnalysisSourceInfo(wand.right), AssumptionType.Assertion) )((s3, snapRhs, v3) => { createWandChunkAndRecordResults(s3.copy(exhaleExt = false, oldHeaps = s.oldHeaps), freshSnapRoot, snapRhs.get, v3) @@ -459,9 +459,9 @@ object magicWandSupporter extends SymbolicExecutionRules { (Q: (State, Verifier) => VerificationResult) : VerificationResult = { // Consume the magic wand instance "A --* B". - consume(s, wand, true, pve, v, AnalysisInfo(v, ExpAnalysisSourceInfo(wand), AssumptionType.Assertion))((s1, snapWand, v1) => { + consume(s, wand, true, pve, v, AnalysisInfo(v.decider.assumptionAnalyzer, ExpAnalysisSourceInfo(wand), AssumptionType.Assertion))((s1, snapWand, v1) => { // Consume the wand's LHS "A". - consume(s1, wand.left, true, pve, v1, AnalysisInfo(v1, ExpAnalysisSourceInfo(wand.left), AssumptionType.Assertion))((s2, snapLhs, v2) => { + consume(s1, wand.left, true, pve, v1, AnalysisInfo(v1.decider.assumptionAnalyzer, ExpAnalysisSourceInfo(wand.left), AssumptionType.Assertion))((s2, snapLhs, v2) => { /* It is assumed that snap and MagicWandSnapshot.abstractLhs are structurally the same. * Equating the two snapshots is sound iff a wand is applied only once. * The old solution in this case did use this assumption: diff --git a/src/main/scala/rules/PredicateSupporter.scala b/src/main/scala/rules/PredicateSupporter.scala index 9d4934733..6b97c14f6 100644 --- a/src/main/scala/rules/PredicateSupporter.scala +++ b/src/main/scala/rules/PredicateSupporter.scala @@ -74,7 +74,7 @@ object predicateSupporter extends PredicateSupportRules { val s1 = s.copy(g = gIns, smDomainNeeded = true) .scalePermissionFactor(tPerm, ePerm) - consume(s1, body, true, pve, v, AnalysisInfo(v, analysisInfo.sourceInfo, AssumptionType.Assertion))((s1a, snap, v1) => { + consume(s1, body, true, pve, v, AnalysisInfo(v.decider.assumptionAnalyzer, analysisInfo.sourceInfo, AssumptionType.Assertion))((s1a, snap, v1) => { if (!Verifier.config.disableFunctionUnfoldTrigger()) { val predTrigger = App(s1a.predicateData(predicate).triggerFunction, snap.get.convert(terms.sorts.Snap) +: tArgs) @@ -118,7 +118,7 @@ object predicateSupporter extends PredicateSupportRules { Q(s3, v1) } else { val ch = BasicChunk(PredicateID, BasicChunkIdentifier(predicate.name), tArgs, eArgs, snap.get.convert(sorts.Snap), None, tPerm, ePerm, - AnalysisInfo(v1, ExpAnalysisSourceInfo(PredicateAccess(Seq(), predicate)(NoPosition, NoInfo, NoTrafos)), AssumptionType.Unknown)) + AnalysisInfo(v1.decider.assumptionAnalyzer, ExpAnalysisSourceInfo(PredicateAccess(Seq(), predicate)(NoPosition, NoInfo, NoTrafos)), AssumptionType.Unknown)) val s3 = s2.copy(g = s.g, smDomainNeeded = s.smDomainNeeded, permissionScalingFactor = s.permissionScalingFactor, @@ -141,7 +141,7 @@ object predicateSupporter extends PredicateSupportRules { pa: ast.PredicateAccess) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { - val analysisInfo = AnalysisInfo(v, ExpAnalysisSourceInfo(pa), AssumptionType.Assertion) // TODO ake: check that this is valid + val analysisInfo = AnalysisInfo(v.decider.assumptionAnalyzer, ExpAnalysisSourceInfo(pa), AssumptionType.Assertion) // TODO ake: check that this is valid val tArgsWithE = if (withExp) tArgs zip eArgs.get.map(Some(_)) else diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index c6871348c..a889ee39c 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -339,7 +339,7 @@ object producer extends ProductionRules { val (debugHeapName, debugLabel) = v3.getDebugOldLabel(s3, accPred.pos) val snapExp = Option.when(withExp)(ast.DebugLabelledOld(ast.FieldAccess(eRcvrNew.get, field)(), debugLabel)(accPred.pos, accPred.info, accPred.errT)) val ch = BasicChunk(FieldID, BasicChunkIdentifier(field.name), Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), snap, snapExp, gain, gainExp, - AnalysisInfo(v1, ExpAnalysisSourceInfo(fa), AssumptionType.Unknown)) + AnalysisInfo(v1.decider.assumptionAnalyzer, ExpAnalysisSourceInfo(fa), AssumptionType.Unknown)) chunkSupporter.produce(s3, s3.h, ch, v3)((s4, h4, v4) => { val s5 = s4.copy(h = h4) val s6 = if (withExp) s5.copy(oldHeaps = s5.oldHeaps + (debugHeapName -> magicWandSupporter.getEvalHeap(s4))) else s5 @@ -367,7 +367,7 @@ object producer extends ProductionRules { } else { val snap1 = snap.convert(sorts.Snap) val ch = BasicChunk(PredicateID, BasicChunkIdentifier(predicate.name), tArgs, eArgsNew, snap1, None, gain, gainExp, - AnalysisInfo(v1, ExpAnalysisSourceInfo(pa), AssumptionType.Unknown)) + AnalysisInfo(v1.decider.assumptionAnalyzer, ExpAnalysisSourceInfo(pa), AssumptionType.Unknown)) chunkSupporter.produce(s2, s2.h, ch, v2)((s3, h3, v3) => { if (Verifier.config.enablePredicateTriggersOnInhale() && s3.functionRecorder == NoopFunctionRecorder && !Verifier.config.disableFunctionUnfoldTrigger()) { diff --git a/src/main/scala/state/Chunks.scala b/src/main/scala/state/Chunks.scala index 0c8589fad..974f77aab 100644 --- a/src/main/scala/state/Chunks.scala +++ b/src/main/scala/state/Chunks.scala @@ -45,7 +45,7 @@ object BasicChunk { perm: Term, permExp: Option[ast.Exp], analysisInfo: AnalysisInfo): BasicChunk = { val chunk = new BasicChunk(resourceID, id, args, argsExp, snap, snapExp, perm, permExp) - analysisInfo.getAssumptionAnalyzer.addPermissionNode(chunk, analysisInfo.sourceInfo, analysisInfo.assumptionType) + analysisInfo.assumptionAnalyzer.addPermissionNode(chunk, analysisInfo.sourceInfo, analysisInfo.assumptionType) chunk } @@ -59,7 +59,7 @@ object BasicChunk { perm: Term, permExp: Option[ast.Exp], analysisInfo: AnalysisInfo): BasicChunk = { val newChunk = apply(resourceID, id, args, argsExp, snap, snapExp, perm, permExp) - analysisInfo.getAssumptionAnalyzer.addPermissionDependencies(oldChunks, PermissionInhaleNode(newChunk, analysisInfo.sourceInfo, analysisInfo.assumptionType)) + analysisInfo.assumptionAnalyzer.addPermissionDependencies(oldChunks, PermissionInhaleNode(newChunk, analysisInfo.sourceInfo, analysisInfo.assumptionType)) newChunk } } diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index dca6fb4a0..b27b5f7af 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -114,7 +114,7 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif && { executionFlowController.locally(s2a, v2)((s3, v3) => { exec(s3, body, v3)((s4, v4) => - consumes(s4, posts, false, postViolated, v4, AnalysisInfo(v, if(posts.isEmpty) StringAnalysisSourceInfo("no postcondition", ast.NoPosition) else ExpAnalysisSourceInfo(posts.head), AssumptionType.Assertion))((_, _, _) => + consumes(s4, posts, false, postViolated, v4, AnalysisInfo(v.decider.assumptionAnalyzer, if(posts.isEmpty) StringAnalysisSourceInfo("no postcondition", ast.NoPosition) else ExpAnalysisSourceInfo(posts.head), AssumptionType.Assertion))((_, _, _) => Success()))}) } )})}) result.assumptionAnalyzer = v.decider.assumptionAnalyzer diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index fe58ba61f..7c60e4c55 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -270,7 +270,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver Some(DebugExp.createInstance(e, eNew)) } else { None } decider.assume(BuiltinEquals(data.formalResult, tBody), debugExp) - consumes(s2, posts, false, postconditionViolated, v, AnalysisInfo(v, if(posts.isEmpty) StringAnalysisSourceInfo("no postcondition", ast.NoPosition) else ExpAnalysisSourceInfo(posts.head), AssumptionType.Assertion))((s3, _, _) => { + consumes(s2, posts, false, postconditionViolated, v, AnalysisInfo(v.decider.assumptionAnalyzer, if(posts.isEmpty) StringAnalysisSourceInfo("no postcondition", ast.NoPosition) else ExpAnalysisSourceInfo(posts.head), AssumptionType.Assertion))((s3, _, _) => { recorders :+= s3.functionRecorder Success()})})})} From a5b8ec21b346c247dac2adac2ed85e023c298731 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 12 May 2025 10:33:15 +0200 Subject: [PATCH 030/474] pass Viper expression to assert --- .../AssumptionAnalysisGraph.scala | 26 ++-- .../AssumptionAnalyzer.scala | 11 +- src/main/scala/decider/Decider.scala | 25 ++-- src/main/scala/rules/Consumer.scala | 2 +- src/main/scala/rules/Evaluator.scala | 116 +++++++++--------- src/main/scala/rules/HavocSupporter.scala | 6 +- .../rules/MoreCompleteExhaleSupporter.scala | 37 +++--- .../scala/rules/PermissionSupporter.scala | 9 +- .../scala/rules/QuantifiedChunkSupport.scala | 16 +-- 9 files changed, 136 insertions(+), 112 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index 143a9054d..23545d9ea 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -92,27 +92,31 @@ trait ChunkAnalysisInfo { } case class SimpleAssumptionNode(assumption: ast.Exp, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Unknown) extends AssumptionAnalysisNode { - - override def getNodeString: String ="assumed " + assumption.toString - + override def getNodeString: String ="assume " + assumption.toString } case class StringAssumptionNode(description: String, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Unknown) extends AssumptionAnalysisNode { + override def getNodeString: String = "assume " + description +} - override def getNodeString: String = "assumed " + description - +case class SimpleAssertionNode(assertion: ast.Exp, isAsserted: Boolean, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Explicit) extends AssumptionAnalysisNode { + override def getNodeString: String = "assert " + assertion.toString } -// TODO ake: ast.Exp instead of Term -case class SimpleAssertionNode(assertion: Term, isAsserted: Boolean, sourceInfo: AnalysisSourceInfo) extends AssumptionAnalysisNode { - override val assumptionType: AssumptionType = AssumptionType.Explicit - override def getNodeString: String = "asserted " + assertion.toString +case class StringAssertionNode(description: String, isAsserted: Boolean, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Explicit) extends AssumptionAnalysisNode { + override def getNodeString: String = "assert " + description +} +case class PermissionInhaleNode(chunk: Chunk, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Unknown) extends AssumptionAnalysisNode with ChunkAnalysisInfo { + override def getNodeString: String = "inhale " + chunk.toString +} +case class PermissionExhaleNode(chunk: Chunk, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Explicit) extends AssumptionAnalysisNode with ChunkAnalysisInfo { + override def getNodeString: String = "exhale " + chunk.toString } -case class PermissionInhaleNode(chunk: Chunk, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Unknown) extends AssumptionAnalysisNode with ChunkAnalysisInfo { - override def getNodeString: String = "inhaled " + chunk.toString +case class PermissionAssertNode(chunk: Chunk, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Explicit) extends AssumptionAnalysisNode with ChunkAnalysisInfo { + override def getNodeString: String = "assert " + chunk.toString } diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index f497ec790..6190a863d 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -23,7 +23,7 @@ trait AssumptionAnalyzer { def addAssumptions(assumptions: Iterable[DebugExp], analysisInfo: AnalysisInfo): Seq[Int] - def addAssertion(assertion: Term, isAsserted: Boolean, sourceInfo: AnalysisSourceInfo): Option[Int] + def addAssertion(assertion: Either[String, ast.Exp], isAsserted: Boolean, analysisInfo: AnalysisInfo): Option[Int] def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNode: AssumptionAnalysisNode): Unit @@ -136,8 +136,11 @@ class DefaultAssumptionAnalyzer(member: Member) extends AssumptionAnalyzer { newNodes.map(_.id).toSeq } - override def addAssertion(assertion: Term, isAsserted: Boolean, sourceInfo: AnalysisSourceInfo): Option[Int] = { - val newNode = SimpleAssertionNode(assertion, isAsserted, sourceInfo) + override def addAssertion(assertion: Either[String, ast.Exp], isAsserted: Boolean, analysisInfo: AnalysisInfo): Option[Int] = { + val newNode = assertion match { + case Left(description) => StringAssertionNode(description, isAsserted, analysisInfo.sourceInfo, analysisInfo.assumptionType) + case Right(exp) => SimpleAssertionNode(exp, isAsserted, analysisInfo.sourceInfo, analysisInfo.assumptionType) + } addNode(newNode) Some(newNode.id) } @@ -172,7 +175,7 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { override def addAssumptions(assumptions: Iterable[DebugExp], analysisInfo: AnalysisInfo): Seq[Int] = Seq.empty - override def addAssertion(assertion: Term, isAsserted: Boolean, sourceInfo: AnalysisSourceInfo): Option[Int] = None + override def addAssertion(assertion: Either[String, ast.Exp], isAsserted: Boolean, analysisInfo: AnalysisInfo): Option[Int] = None override def processUnsatCoreAndAddDependencies(dep: String): Unit = { } diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 2b779443d..7be648d0d 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -77,7 +77,8 @@ trait Decider { * 1. It passes State and Operations to the continuation * 2. The implementation reacts to a failing assertion by e.g. a state consolidation */ - def assert(t: Term, timeout: Option[Int] = None)(Q: Boolean => VerificationResult): VerificationResult + def assert(t: Term, description: String, timeout: Option[Int])(Q: Boolean => VerificationResult): VerificationResult + def assert(t: Term, e: Option[ast.Exp], timeout: Option[Int] = Verifier.config.assertTimeout.toOption)(Q: Boolean => VerificationResult): VerificationResult def fresh(id: String, sort: Sort, ptype: Option[PType]): Var def fresh(id: String, argSorts: Seq[Sort], resultSort: Sort): Function @@ -423,13 +424,19 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => prover.check(timeout) == Unsat } - def check(t: Term, timeout: Int): Boolean = deciderAssert(t, Some(timeout)) + def check(t: Term, timeout: Int): Boolean = deciderAssert(t, Left("check"), Some(timeout)) // TODO ake - def assert(t: Term, timeout: Option[Int] = Verifier.config.assertTimeout.toOption) - (Q: Boolean => VerificationResult) - : VerificationResult = { - val success = deciderAssert(t, timeout) + def assert(t: Term, description: String, timeout: Option[Int])(Q: Boolean => VerificationResult): VerificationResult = { + assert(t, Left(description), timeout)(Q) + } + + def assert(t: Term, e: Option[ast.Exp], timeout: Option[Int] = Verifier.config.assertTimeout.toOption)(Q: Boolean => VerificationResult): VerificationResult = { + assert(t, e.map(Right(_)).getOrElse(Left("unknown assertion")), timeout)(Q) + } + + private def assert(t: Term, e: Either[String, ast.Exp], timeout: Option[Int])(Q: Boolean => VerificationResult): VerificationResult = { + val success = deciderAssert(t, e, timeout) // If the SMT query was not successful, store it (possibly "overwriting" // any previously saved query), otherwise discard any query we had saved @@ -443,14 +450,12 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => Q(success) } - private def deciderAssert(t: Term, timeout: Option[Int]) = { + private def deciderAssert(t: Term, e: Either[String, ast.Exp], timeout: Option[Int]) = { val assertRecord = new DeciderAssertRecord(t, timeout) val sepIdentifier = symbExLog.openScope(assertRecord) - - val asserted = if(Verifier.config.enableAssumptionAnalysis()) t.equals(True) else isKnownToBeTrue(t) - val assertionId: Option[Int] = if(!asserted) assumptionAnalyzer.addAssertion(t, false, decider.assumptionAnalyzer.currentAnalysisInfo.sourceInfo) else None + val assertionId: Option[Int] = if(!asserted) assumptionAnalyzer.addAssertion(e, false, decider.assumptionAnalyzer.currentAnalysisInfo) else None val result = asserted || proverAssert(t, timeout, AssumptionAnalyzer.createAssertionLabel(assertionId)) symbExLog.closeScope(sepIdentifier) diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index fbaac2cf6..4d877cd5a 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -629,7 +629,7 @@ object consumer extends ConsumptionRules { Quantification(q, vars, Implies(transformed, body), trgs, name, isGlob, weight) case _ => t } - v2.decider.assert(termToAssert) { + v2.decider.assert(termToAssert, Some(e)) { case true => v2.decider.assume(t, Option.when(withExp)(e), eNew) QS(s3, v2) diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 285115aac..c00d110e5 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -235,9 +235,10 @@ object evaluator extends EvaluationRules { Q(s3, fvfLookup, newFa, v1) } else { val toAssert = IsPositive(totalPermissions.replace(`?r`, tRcvr)) - v1.decider.assert(toAssert) { + val toAssertExp = Option.when(withExp)(perms.IsPositive(ast.CurrentPerm(fa)())(fa.pos, fa.info, fa.errT)) + v1.decider.assert(toAssert, toAssertExp) { case false => - createFailure(pve dueTo InsufficientPermission(fa), v1, s1, toAssert, Option.when(withExp)(perms.IsPositive(ast.CurrentPerm(fa)())())) + createFailure(pve dueTo InsufficientPermission(fa), v1, s1, toAssert, toAssertExp) case true => val fvfLookup = Lookup(fa.field.name, fvfDef.sm, tRcvr) val fr1 = s1.functionRecorder.recordSnapshot(fa, v1.decider.pcs.branchConditions, fvfLookup).recordFvfAndDomain(fvfDef) @@ -259,15 +260,15 @@ object evaluator extends EvaluationRules { } val (permCheck, permCheckExp) = if (s1.triggerExp) { - (True, Option.when(withExp)(TrueLit()())) + (True, TrueLit()(fa.pos, fa.info, fa.errT)) } else { val permVal = relevantChunks.head.perm val totalPermissions = permVal.replace(relevantChunks.head.quantifiedVars, Seq(tRcvr)) - (IsPositive(totalPermissions), Option.when(withExp)(ast.PermGtCmp(ast.CurrentPerm(fa)(fa.pos, fa.info, fa.errT), ast.NoPerm()())(fa.pos, fa.info, fa.errT))) + (IsPositive(totalPermissions), ast.PermGtCmp(ast.CurrentPerm(fa)(fa.pos, fa.info, fa.errT), ast.NoPerm()())(fa.pos, fa.info, fa.errT)) } - v1.decider.assert(permCheck) { + v1.decider.assert(permCheck, Option.when(withExp)(permCheckExp)) { case false => - createFailure(pve dueTo InsufficientPermission(fa), v1, s1, permCheck, permCheckExp) + createFailure(pve dueTo InsufficientPermission(fa), v1, s1, permCheck, Option.when(withExp)(permCheckExp)) case true => val smLookup = Lookup(fa.field.name, relevantChunks.head.fvf, tRcvr) val fr2 = @@ -292,14 +293,14 @@ object evaluator extends EvaluationRules { } val (permCheck, permCheckExp) = if (s2.triggerExp) { - (True, Option.when(withExp)(TrueLit()())) + (True, TrueLit()(fa.pos, fa.info, fa.errT)) } else { val totalPermissions = PermLookup(fa.field.name, pmDef1.pm, tRcvr) - (IsPositive(totalPermissions), Option.when(withExp)(ast.PermGtCmp(ast.CurrentPerm(fa)(fa.pos, fa.info, fa.errT), ast.NoPerm()())(fa.pos, fa.info, fa.errT))) + (IsPositive(totalPermissions), ast.PermGtCmp(ast.CurrentPerm(fa)(fa.pos, fa.info, fa.errT), ast.NoPerm()())(fa.pos, fa.info, fa.errT)) } - v1.decider.assert(permCheck) { + v1.decider.assert(permCheck, Option.when(withExp)(permCheckExp)) { case false => - createFailure(pve dueTo InsufficientPermission(fa), v1, s2, permCheck, permCheckExp) + createFailure(pve dueTo InsufficientPermission(fa), v1, s2, permCheck, Option.when(withExp)(permCheckExp)) case true => val smLookup = Lookup(fa.field.name, smDef1.sm, tRcvr) val fr2 = @@ -969,16 +970,18 @@ object evaluator extends EvaluationRules { })(join(func.typ, s"joined_${func.name}", joinFunctionArgs, Option.when(withExp)(eArgs), v1))((s6, r, v4) => Q(s6, r._1, r._2, v4))}) - case ast.Unfolding( + case u @ ast.Unfolding( acc @ ast.PredicateAccessPredicate(pa @ ast.PredicateAccess(eArgs, predicateName), ePerm), eIn) => val predicate = s.program.findPredicate(predicateName) if (s.cycles(predicate) < Verifier.config.recursivePredicateUnfoldings()) { v.decider.startDebugSubExp() - evals(s, eArgs, _ => pve, v)((s1, tArgs, eArgsNew, v1) => - eval(s1, ePerm.getOrElse(ast.FullPerm()()), pve, v1)((s2, tPerm, ePermNew, v2) => - v2.decider.assert(IsPositive(tPerm)) { // TODO: Replace with permissionSupporter.assertNotNegative + evals(s, eArgs, _ => pve, v)((s1, tArgs, eArgsNew, v1) => { + val ePermOrFull = ePerm.getOrElse(ast.FullPerm()(u.pos, u.info, u.errT)) + eval(s1, ePermOrFull, pve, v1)((s2, tPerm, ePermNew, v2) => + // TODO: Replace with permissionSupporter.assertNotNegative + v2.decider.assert(IsPositive(tPerm), Option.when(withExp)(ast.PermGtCmp(ePermOrFull, ast.NoPerm()())(ePermOrFull.pos, ePermOrFull.info, ePermOrFull.errT))) { case true => joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s2, v2)((s3, v3, QB) => { val s4 = s3.incCycleCounter(predicate) @@ -1031,7 +1034,8 @@ object evaluator extends EvaluationRules { Q(s12, r12._1, r12._2, v7)}) case false => v2.decider.finishDebugSubExp(s"unfolded(${predicate.name})") - createFailure(pve dueTo NonPositivePermission(ePerm.get), v2, s2, IsPositive(tPerm), ePermNew.map(p => ast.PermGtCmp(p, ast.NoPerm()())(p.pos, p.info, p.errT)))})) + createFailure(pve dueTo NonPositivePermission(ePerm.get), v2, s2, IsPositive(tPerm), ePermNew.map(p => ast.PermGtCmp(p, ast.NoPerm()())(p.pos, p.info, p.errT)))}) + }) } else { val unknownValue = v.decider.appliedFresh("recunf", v.symbolConverter.toSort(eIn.typ), s.relevantQuantifiedVariables.map(_._1)) Q(s, unknownValue, Option.when(withExp)(ast.LocalVarWithVersion("unknownValue", eIn.typ)(eIn.pos, eIn.info, eIn.errT)), v) @@ -1057,41 +1061,41 @@ object evaluator extends EvaluationRules { Q(s1, t, e0New.map(e0p => ast.SeqContains(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) /* Note the reversed order of the arguments! */ - case ast.SeqIndex(e0, e1) => + case si @ ast.SeqIndex(e0, e1) => evals2(s, Seq(e0, e1), Nil, _ => pve, v)({case (s1, Seq(t0, t1), esNew, v1) => val eNew = esNew.map(es => ast.SeqIndex(es.head, es(1))(e.pos, e.info, e.errT)) if (s1.triggerExp) { Q(s1, SeqAt(t0, t1), eNew, v1) } else { - v1.decider.assert(AtLeast(t1, IntLiteral(0))) { + val idxGe0 = AtLeast(t1, IntLiteral(0)) + val idxGe0Exp = Option.when(withExp)(ast.GeCmp(e1, ast.IntLit(0)())(si.pos, si.info, si.errT)) + val idxGe0ExpNew = Option.when(withExp)(ast.GeCmp(esNew.get(1), ast.IntLit(0)())(si.pos, si.info, si.errT)) + val idxLtLength = Less(t1, SeqLength(t0)) + val idxLtLengthExp = Option.when(withExp)(ast.LtCmp(e1, ast.SeqLength(e0)())(si.pos, si.info, si.errT)) + val idxLtLengthExpNew = esNew.map(es => ast.LtCmp(es(1), ast.SeqLength(es.head)())(si.pos, si.info, si.errT)) + v1.decider.assert(idxGe0, idxGe0Exp) { case true => - v1.decider.assert(Less(t1, SeqLength(t0))) { + v1.decider.assert(idxLtLength, idxLtLengthExp) { case true => Q(s1, SeqAt(t0, t1), eNew, v1) case false => - val assertExp2 = Option.when(withExp)(ast.LtCmp(e1, ast.SeqLength(e0)())(e1.pos, e1.info, e1.errT)) - val failure = createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, Less(t1, SeqLength(t0)), assertExp2) + val failure = createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, idxLtLength, idxLtLengthExp) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { - val assertExp2 = Option.when(withExp)(ast.LeCmp(e1, ast.SeqLength(e0)())()) - val assertExp2New = esNew.map(es => ast.LeCmp(es(1), ast.SeqLength(es.head)())()) - v1.decider.assume(Less(t1, SeqLength(t0)), assertExp2, assertExp2New) + v1.decider.assume(idxLtLength, idxLtLengthExp, idxLtLengthExpNew) failure combine Q(s1, SeqAt(t0, t1), eNew, v1) } else failure} case false => - val assertExp1 = Option.when(withExp)(ast.GeCmp(e1, ast.IntLit(0)())(e1.pos, e1.info, e1.errT)) - val assertExp1New = Option.when(withExp)(ast.GeCmp(esNew.get(1), ast.IntLit(0)())(e1.pos, e1.info, e1.errT)) - val failure1 = createFailure(pve dueTo SeqIndexNegative(e0, e1), v1, s1, AtLeast(t1, IntLiteral(0)), assertExp1New) + + val failure1 = createFailure(pve dueTo SeqIndexNegative(e0, e1), v1, s1, idxGe0, idxGe0ExpNew) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { - v1.decider.assume(AtLeast(t1, IntLiteral(0)), assertExp1, assertExp1New) - val assertExp2 = Option.when(withExp)(ast.LtCmp(e1, ast.SeqLength(e0)())(e1.pos, e1.info, e1.errT)) - val assertExp2New = Option.when(withExp)(ast.LtCmp(esNew.get(1), ast.SeqLength(esNew.get(0))())(e1.pos, e1.info, e1.errT)) - v1.decider.assert(Less(t1, SeqLength(t0))) { + v1.decider.assume(idxGe0, idxGe0Exp, idxGe0ExpNew) + v1.decider.assert(idxLtLength, idxLtLengthExp) { case true => failure1 combine Q(s1, SeqAt(t0, t1), eNew, v1) case false => - val failure2 = failure1 combine createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, Less(t1, SeqLength(t0)), assertExp2New) + val failure2 = failure1 combine createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, idxLtLength, idxLtLengthExpNew) if (v1.reportFurtherErrors()) { - v1.decider.assume(Less(t1, SeqLength(t0)), assertExp2, assertExp2New) + v1.decider.assume(idxLtLength, idxLtLengthExp, idxLtLengthExpNew) failure2 combine Q(s1, SeqAt(t0, t1), eNew, v1) } else failure2} } else failure1}}}) @@ -1108,41 +1112,40 @@ object evaluator extends EvaluationRules { case ast.RangeSeq(e0, e1) => evalBinOp(s, e0, e1, SeqRanged, pve, v)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.RangeSeq(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) - case ast.SeqUpdate(e0, e1, e2) => + case seqUp @ ast.SeqUpdate(e0, e1, e2) => evals2(s, Seq(e0, e1, e2), Nil, _ => pve, v)({ case (s1, Seq(t0, t1, t2), esNew, v1) => val eNew = esNew.map(es => ast.SeqUpdate(es.head, es(1), es(2))(e.pos, e.info, e.errT)) if (s1.triggerExp) { Q(s1, SeqUpdate(t0, t1, t2), eNew, v1) } else { - val assertExp = Option.when(withExp)(ast.GeCmp(e1, ast.IntLit(0)())(e1.pos, e1.info, e1.errT)) - val assertExpNew = Option.when(withExp)(ast.GeCmp(esNew.get(1), ast.IntLit(0)())(e1.pos, e1.info, e1.errT)) - v1.decider.assert(AtLeast(t1, IntLiteral(0))) { + val idxGe0 = AtLeast(t1, IntLiteral(0)) + val idxGe0Exp = Option.when(withExp)(ast.GeCmp(e1, ast.IntLit(0)())(seqUp.pos, seqUp.info, seqUp.errT)) + val idxGe0ExpNew = Option.when(withExp)(ast.GeCmp(esNew.get(1), ast.IntLit(0)())(seqUp.pos, seqUp.info, seqUp.errT)) + val idxLtLength = Less(t1, SeqLength(t0)) + val idxLtLengthExp = Option.when(withExp)(ast.LtCmp(e1, ast.SeqLength(e0)())(seqUp.pos, seqUp.info, seqUp.errT)) + val idxLtLengthExpNew = Option.when(withExp)(ast.LtCmp(esNew.get(1), ast.SeqLength(esNew.get(0))())(seqUp.pos, seqUp.info, seqUp.errT)) + v1.decider.assert(idxGe0, idxGe0Exp) { case true => - val assertExp2New = Option.when(withExp)(ast.LtCmp(esNew.get(1), ast.SeqLength(esNew.get(0))())(e1.pos, e1.info, e1.errT)) - v1.decider.assert(Less(t1, SeqLength(t0))) { + v1.decider.assert(idxLtLength, idxLtLengthExp) { case true => Q(s1, SeqUpdate(t0, t1, t2), eNew, v1) case false => - val failure = createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, Less(t1, SeqLength(t0)), assertExp2New) + val failure = createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, idxLtLength, idxLtLengthExpNew) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { - val assertExp3 = Option.when(withExp)(ast.LeCmp(e1, ast.SeqLength(e0)())()) - val assertExp3New = Option.when(withExp)(ast.LeCmp(esNew.get(1), ast.SeqLength(esNew.get(0))())()) - v1.decider.assume(Less(t1, SeqLength(t0)), assertExp3, assertExp3New) + v1.decider.assume(idxLtLength, idxLtLengthExp, idxLtLengthExpNew) failure combine Q(s1, SeqUpdate(t0, t1, t2), eNew, v1)} else failure} case false => - val failure1 = createFailure(pve dueTo SeqIndexNegative(e0, e1), v1, s1, AtLeast(t1, IntLiteral(0)), assertExpNew) + val failure1 = createFailure(pve dueTo SeqIndexNegative(e0, e1), v1, s1, idxGe0, idxGe0ExpNew) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { - v1.decider.assume(AtLeast(t1, IntLiteral(0)), assertExp, assertExpNew) - val assertExp2 = Option.when(withExp)(ast.LtCmp(e1, ast.SeqLength(e0)())(e1.pos, e1.info, e1.errT)) - val assertExp2New = Option.when(withExp)(ast.LtCmp(esNew.get(1), ast.SeqLength(esNew.get(0))())(e1.pos, e1.info, e1.errT)) - v1.decider.assert(Less(t1, SeqLength(t0))) { + v1.decider.assume(idxGe0, idxGe0Exp, idxGe0ExpNew) + v1.decider.assert(idxLtLength, idxLtLengthExp) { case true => failure1 combine Q(s1, SeqUpdate(t0, t1, t2), eNew, v1) case false => - val failure2 = failure1 combine createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, Less(t1, SeqLength(t0)), assertExp2New) + val failure2 = failure1 combine createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, idxLtLength, idxLtLengthExpNew) if (v1.reportFurtherErrors()) { - v1.decider.assume(Less(t1, SeqLength(t0)), assertExp2, assertExp2New) + v1.decider.assume(idxLtLength, idxLtLengthExp, idxLtLengthExpNew) failure2 combine Q(s1, SeqUpdate(t0, t1, t2), eNew, v1) } else failure2} } else failure1}}}) @@ -1254,11 +1257,11 @@ object evaluator extends EvaluationRules { Q(s1, MapLookup(baseT, keyT), esNew.map(es => ast.MapLookup(es(0), es(1))(e.pos, e.info, e.errT)), v1) case (s1, Seq(baseT, keyT), esNew, v1) => val eNew = esNew.map(es => ast.MapLookup(es(0), es(1))(e.pos, e.info, e.errT)) - v1.decider.assert(SetIn(keyT, MapDomain(baseT))) { + val assertExp = Option.when(withExp)(ast.MapContains(key, base)(ml.pos, ml.info, ml.errT)) + val assertExpNew = Option.when(withExp)(ast.MapContains(esNew.get(1), esNew.get(0))(ml.pos, ml.info, ml.errT)) + v1.decider.assert(SetIn(keyT, MapDomain(baseT)), assertExp) { case true => Q(s1, MapLookup(baseT, keyT), eNew, v1) case false => - val assertExp = Option.when(withExp)(ast.MapContains(key, base)(ml.pos, ml.info, ml.errT)) - val assertExpNew = Option.when(withExp)(ast.MapContains(esNew.get(1), esNew.get(0))(ml.pos, ml.info, ml.errT)) val failure1 = createFailure(pve dueTo MapKeyNotContained(base, key), v1, s1, SetIn(keyT, MapDomain(baseT)), assertExpNew) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { v1.decider.assume(SetIn(keyT, MapDomain(baseT)), assertExp, assertExpNew) @@ -1507,13 +1510,12 @@ object evaluator extends EvaluationRules { v: Verifier) (Q: (State, Term, Verifier) => VerificationResult) : VerificationResult = { - - v.decider.assert(tDivisor !== tZero){ + val (notZeroExp, notZeroExpNew) = if (withExp) { + (Some(ast.NeCmp(eDivisor, ast.IntLit(0)())(eDivisor.pos, eDivisor.info, eDivisor.errT)), Some(ast.NeCmp(eDivisorNew.get, ast.IntLit(0)())(eDivisor.pos, eDivisor.info, eDivisor.errT))) + } else { (None, None) } + v.decider.assert(tDivisor !== tZero, notZeroExp){ case true => Q(s, t, v) case false => - val (notZeroExp, notZeroExpNew) = if (withExp) { - (Some(ast.NeCmp(eDivisor, ast.IntLit(0)())(eDivisor.pos, eDivisor.info, eDivisor.errT)), Some(ast.NeCmp(eDivisorNew.get, ast.IntLit(0)())(eDivisor.pos, eDivisor.info, eDivisor.errT))) - } else { (None, None) } val failure = createFailure(pve dueTo DivisionByZero(eDivisor), v, s, tDivisor !== tZero, notZeroExpNew) if (s.retryLevel == 0 && v.reportFurtherErrors()) { v.decider.assume(tDivisor !== tZero, notZeroExp, notZeroExpNew) diff --git a/src/main/scala/rules/HavocSupporter.scala b/src/main/scala/rules/HavocSupporter.scala index f8eb1c386..1597c6dde 100644 --- a/src/main/scala/rules/HavocSupporter.scala +++ b/src/main/scala/rules/HavocSupporter.scala @@ -127,10 +127,10 @@ object havocSupporter extends SymbolicExecutionRules { v.decider.prover.comment("Check havocall receiver injectivity") val notInjectiveReason = QuasihavocallNotInjective(havocall) - - val injectivityDebugExp = Option.when(withExp)(DebugExp.createInstance("QP receiver injectivity check is well-defined", true)) + val comment = "QP receiver injectivity check is well-defined" + val injectivityDebugExp = Option.when(withExp)(DebugExp.createInstance(comment, true)) v.decider.assume(FunctionPreconditionTransformer.transform(receiverInjectivityCheck, s.program), injectivityDebugExp) - v.decider.assert(receiverInjectivityCheck) { + v.decider.assert(receiverInjectivityCheck, comment, Verifier.config.checkTimeout.toOption) { case false => createFailure(pve dueTo notInjectiveReason, v, s1, receiverInjectivityCheck, "QP receiver injective") case true => // Generate the inverse axioms diff --git a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala index 84ea882c6..b6d5dab2c 100644 --- a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala +++ b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala @@ -215,13 +215,15 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { createFailure(ve, v, s, False, "branch is dead") } } else { - summarise(s, relevantChunks, resource, args, argsExp, None, v)((s1, snap, permSum, permSumExp, v1) => - v.decider.assert(IsPositive(permSum)) { + summarise(s, relevantChunks, resource, args, argsExp, None, v)((s1, snap, permSum, permSumExp, v1) => { + val assertExp = permSumExp.map(e => IsPositive(e)(e.pos, e.info, e.errT)) + v.decider.assert(IsPositive(permSum), assertExp) { case true => Q(s1, snap, v1) case false => - createFailure(ve, v, s1, IsPositive(permSum), permSumExp.map(IsPositive(_)())) - }) + createFailure(ve, v, s1, IsPositive(permSum), assertExp) + } + }) } } @@ -242,13 +244,14 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { if (!s.assertReadAccessOnly) actualConsumeComplete(s, h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, analysisInfo)(Q) else - summariseHeapAndAssertReadAccess(s, h, resource, perms, args, argsExp, returnSnap, ve, v)(Q) + summariseHeapAndAssertReadAccess(s, h, resource, perms, permsExp, args, argsExp, returnSnap, ve, v)(Q) } private def summariseHeapAndAssertReadAccess(s: State, h: Heap, resource: ast.Resource, perm: Term, + permExp: Option[ast.Exp], args: Seq[Term], argsExp: Option[Seq[ast.Exp]], returnSnap: Boolean, @@ -262,7 +265,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { if (returnSnap) { summarise(s, relevantChunks, resource, args, argsExp, None, v)((s1, snap, permSum, permSumExp, v1) => - v.decider.assert(Implies(IsPositive(perm), IsPositive(permSum))) { + v.decider.assert(Implies(IsPositive(perm), IsPositive(permSum)), Option.when(withExp)(ast.Implies(IsPositive(permExp.get)(), IsPositive(permSumExp.get)())(permExp.get.pos, permExp.get.info, permExp.get.errT))) { case true => Q(s1, h, Some(snap), v1) case false => @@ -270,7 +273,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { }) } else { val (s1, permSum, permSumExp) = permSummariseOnly(s, relevantChunks, resource, args, argsExp) - v.decider.assert(Implies(IsPositive(perm), IsPositive(permSum))) { + v.decider.assert(Implies(IsPositive(perm), IsPositive(permSum)), Option.when(withExp)(ast.Implies(IsPositive(permExp.get)(), IsPositive(permSumExp.get)())(permExp.get.pos, permExp.get.info, permExp.get.errT))) { case true => Q(s1, h, None, v) case false => @@ -302,10 +305,11 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { } if (relevantChunks.isEmpty) { + val assertExp = permsExp.map(pe => ast.EqCmp(pe, ast.NoPerm()())(pe.pos, pe.info, pe.errT)) // if no permission is exhaled, return none - v.decider.assert(perms === NoPerm) { + v.decider.assert(perms === NoPerm, assertExp) { case true => Q(s, h, None, v) - case false => createFailure(ve, v, s, perms === NoPerm, permsExp.map(pe => ast.EqCmp(pe, ast.NoPerm()())(pe.pos, pe.info, pe.errT))) + case false => createFailure(ve, v, s, perms === NoPerm, assertExp) } } else { if (!terms.utils.consumeExactRead(perms, s.constrainableARPs)) { @@ -401,11 +405,12 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { if (!moreNeeded) { Q(s1, newHeap, condSnap, v1) } else { - v1.decider.assert(pNeeded === NoPerm) { + val assertExp = pNeededExp.map(pn => ast.EqCmp(pn, ast.NoPerm()())(pn.pos, pn.info, pn.errT)) + v1.decider.assert(pNeeded === NoPerm, assertExp) { case true => Q(s1, newHeap, condSnap, v1) case false => - createFailure(ve, v1, s1, pNeeded === NoPerm, pNeededExp.map(pn => ast.EqCmp(pn, ast.NoPerm()())(pn.pos, pn.info, pn.errT))) + createFailure(ve, v1, s1, pNeeded === NoPerm, assertExp) } } }) @@ -413,11 +418,12 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { if (!moreNeeded) { Q(s0, newHeap, None, v) } else { - v.decider.assert(pNeeded === NoPerm) { + val assertExp = pNeededExp.map(pn => ast.EqCmp(pn, ast.NoPerm()())(pn.pos, pn.info, pn.errT)) + v.decider.assert(pNeeded === NoPerm, assertExp) { case true => Q(s0, newHeap, None, v) case false => - createFailure(ve, v, s0, pNeeded === NoPerm, pNeededExp.map(pn => ast.EqCmp(pn, ast.NoPerm()())(pn.pos, pn.info, pn.errT))) + createFailure(ve, v, s0, pNeeded === NoPerm, assertExp) } } } @@ -495,7 +501,8 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { val s1 = s.copy(functionRecorder = newFr) - v.decider.assert(Implies(PermLess(NoPerm, perms), totalPermTaken !== NoPerm)) { + val totalPermTakenImplExp = Option.when(withExp)(ast.Implies(ast.PermLtCmp(ast.NoPerm()(), permsExp.get)(), ast.NeCmp(totalPermTakenExp.get, ast.NoPerm()())())(permsExp.get.pos, permsExp.get.info, permsExp.get.errT)) + v.decider.assert(Implies(PermLess(NoPerm, perms), totalPermTaken !== NoPerm), totalPermTakenImplExp) { case true => val constraintExp = permsExp.map(pe => ast.EqCmp(pe, totalPermTakenExp.get)()) v.decider.assume(perms === totalPermTaken, Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp))) @@ -507,7 +514,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { } case false => v.decider.finishDebugSubExp(s"consume permissions for ${resource.toString()}") - createFailure(ve, v, s, totalPermTaken !== NoPerm, totalPermTakenExp.map(tpt => ast.NeCmp(tpt, ast.NoPerm()())())) + createFailure(ve, v, s, totalPermTaken !== NoPerm, totalPermTakenExp.map(tpt => ast.NeCmp(tpt, ast.NoPerm()())(tpt.pos, tpt.info, tpt.errT))) } } diff --git a/src/main/scala/rules/PermissionSupporter.scala b/src/main/scala/rules/PermissionSupporter.scala index 58c386fe9..b4d24bd64 100644 --- a/src/main/scala/rules/PermissionSupporter.scala +++ b/src/main/scala/rules/PermissionSupporter.scala @@ -23,10 +23,10 @@ object permissionSupporter extends SymbolicExecutionRules { case k: Var if s.constrainableARPs.contains(k) => Q(s, v) case _ => - v.decider.assert(perms.IsNonNegative(tPerm)) { + val assertExp = ePermNew.map(ep => perms.IsNonNegative(ep)(ep.pos, ep.info, ep.errT)) + v.decider.assert(perms.IsNonNegative(tPerm), assertExp) { case true => Q(s, v) case false => - val assertExp = ePermNew.map(ep => perms.IsNonNegative(ep)(ep.pos, ep.info, ep.errT)) createFailure(pve dueTo NegativePermission(ePerm), v, s, perms.IsNonNegative(tPerm), assertExp) } } @@ -40,9 +40,10 @@ object permissionSupporter extends SymbolicExecutionRules { case k: Var if s.constrainableARPs.contains(k) => Q(s, v) case _ => - v.decider.assert(perms.IsPositive(tPerm)) { + val assertExp = Option.when(withExp)(perms.IsPositive(ePerm)(ePerm.pos, ePerm.info, ePerm.errT)) + v.decider.assert(perms.IsPositive(tPerm), assertExp) { case true => Q(s, v) - case false => createFailure(pve dueTo NonPositivePermission(ePerm), v, s, perms.IsPositive(tPerm), Option.when(withExp)(perms.IsPositive(ePerm)())) + case false => createFailure(pve dueTo NonPositivePermission(ePerm), v, s, perms.IsPositive(tPerm), assertExp) } } } diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 0aaaa728e..5c0c81b89 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -978,8 +978,9 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val nonNegImplication = Implies(tCond, perms.IsNonNegative(tPerm)) val nonNegImplicationExp = eCond.map(c => ast.Implies(c, ast.PermGeCmp(ePerm.get, ast.NoPerm()())())(c.pos, c.info, c.errT)) val nonNegTerm = Forall(qvars, Implies(FunctionPreconditionTransformer.transform(nonNegImplication, s.program), nonNegImplication), Nil) + val nonNegExp = qvarExps.map(qv => ast.Forall(qv, Nil, nonNegImplicationExp.get)()) // TODO: Replace by QP-analogue of permissionSupporter.assertNotNegative - v.decider.assert(nonNegTerm) { + v.decider.assert(nonNegTerm, nonNegExp) { // FIXME ake: verify this case true => /* TODO: Can we omit/simplify the injectivity check in certain situations? */ @@ -999,9 +1000,9 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { } val comment = "Check receiver injectivity" v.decider.prover.comment(comment) - v.decider.assume(FunctionPreconditionTransformer.transform(receiverInjectivityCheck, s.program), - Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true))) - v.decider.assert(receiverInjectivityCheck) { + val debugExp = Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)) + v.decider.assume(FunctionPreconditionTransformer.transform(receiverInjectivityCheck, s.program), debugExp) + v.decider.assert(receiverInjectivityCheck, comment, Verifier.config.checkTimeout.toOption) { case true => val ax = inverseFunctions.axiomInversesOfInvertibles val inv = inverseFunctions.copy(axiomInversesOfInvertibles = Forall(ax.vars, ax.body, effectiveTriggers)) @@ -1216,7 +1217,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val nonNegTerm = Forall(qvars, Implies(FunctionPreconditionTransformer.transform(nonNegImplication, s.program), nonNegImplication), Nil) val nonNegExp = qvarExps.map(qv => ast.Forall(qv, Nil, nonNegImplicationExp.get)()) // TODO: Replace by QP-analogue of permissionSupporter.assertNotNegative - v.decider.assert(nonNegTerm) { + v.decider.assert(nonNegTerm, nonNegExp) { case true => val hints = quantifiedChunkSupporter.extractHints(Some(tCond), tArgs) val chunkOrderHeuristics = @@ -1254,8 +1255,9 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { qidPrefix = qid, program = s.program) v.decider.prover.comment("Check receiver injectivity") - v.decider.assume(FunctionPreconditionTransformer.transform(receiverInjectivityCheck, s.program), Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true))) - v.decider.assert(receiverInjectivityCheck) { + val debugExp = Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)) + v.decider.assume(FunctionPreconditionTransformer.transform(receiverInjectivityCheck, s.program), debugExp) + v.decider.assert(receiverInjectivityCheck, comment, Verifier.config.checkTimeout.toOption) { case true => val qvarsToInvOfLoc = inverseFunctions.qvarsToInversesOf(formalQVars) val condOfInvOfLoc = tCond.replace(qvarsToInvOfLoc) From 6d52906395a2e6fb315b4b693ad770ec42ceccce Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 12 May 2025 14:11:36 +0200 Subject: [PATCH 031/474] cleanup --- .../AnalysisSourceInfo.scala | 3 ++ src/main/scala/decider/CVC5ProverStdIO.scala | 9 ++--- src/main/scala/decider/Decider.scala | 40 +++---------------- src/main/scala/decider/ProverStdIO.scala | 17 ++++++-- src/main/scala/decider/Z3ProverAPI.scala | 14 +++---- src/main/scala/decider/Z3ProverStdIO.scala | 8 ++-- src/main/scala/rules/Evaluator.scala | 25 ++++++------ src/main/scala/rules/Executor.scala | 21 +++++----- src/main/scala/rules/HavocSupporter.scala | 22 +++++----- src/main/scala/rules/MagicWandSupporter.scala | 10 ++--- .../rules/MoreCompleteExhaleSupporter.scala | 3 +- src/main/scala/rules/PredicateSupporter.scala | 10 ++--- src/main/scala/rules/Producer.scala | 8 ++-- src/main/scala/rules/StateConsolidator.scala | 18 +++++---- src/main/scala/state/Chunks.scala | 15 +++++++ 15 files changed, 107 insertions(+), 116 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala index 50006973f..cfe55bf1c 100644 --- a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala +++ b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala @@ -14,6 +14,7 @@ case class NoAnalysisSourceInfo() extends AnalysisSourceInfo { } case class ExpAnalysisSourceInfo(source: ast.Exp) extends AnalysisSourceInfo { + override def toString: String = source.toString + " (" + source.pos + ")" override def getPosition: Position = source.pos @@ -27,6 +28,7 @@ case class ExpAnalysisSourceInfo(source: ast.Exp) extends AnalysisSourceInfo { } case class StmtAnalysisSourceInfo(source: ast.Stmt) extends AnalysisSourceInfo { + override def toString: String = source.toString + " (" + source.pos + ")" override def getPosition: Position = source.pos override def equals(obj: Any): Boolean = { @@ -39,6 +41,7 @@ case class StmtAnalysisSourceInfo(source: ast.Stmt) extends AnalysisSourceInfo { } case class StringAnalysisSourceInfo(description: String, position: Position) extends AnalysisSourceInfo { + override def toString: String = description + " (" + position + ")" override def getPosition: Position = position } diff --git a/src/main/scala/decider/CVC5ProverStdIO.scala b/src/main/scala/decider/CVC5ProverStdIO.scala index ba4029361..26a302a9d 100644 --- a/src/main/scala/decider/CVC5ProverStdIO.scala +++ b/src/main/scala/decider/CVC5ProverStdIO.scala @@ -6,14 +6,13 @@ package viper.silicon.decider -import viper.silicon.assumptionAnalysis.{AssumptionAnalyzer, NoAssumptionAnalyzer} - -import java.nio.file.{Path, Paths} +import viper.silicon.common.config.Version import viper.silicon.state.IdentifierFactory import viper.silicon.verifier.Verifier -import viper.silver.verifier.{DefaultDependency => SilDefaultDependency} import viper.silver.reporter.Reporter -import viper.silicon.common.config.Version +import viper.silver.verifier.{DefaultDependency => SilDefaultDependency} + +import java.nio.file.{Path, Paths} object Cvc5ProverStdIO { val name = "cvc5" diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 7be648d0d..dc6bea603 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -61,15 +61,10 @@ trait Decider { def startDebugSubExp(): Unit def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp]): Unit - def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp], analysisInfo: AnalysisInfo): Unit def assume(t: Term, debugExp: Option[DebugExp]): Unit - def assume(t: Term, debugExp: Option[DebugExp], analysisInfo: AnalysisInfo): Unit def assume(terms: Seq[Term], debugExps: Option[Seq[DebugExp]]): Unit - def assume(terms: Seq[Term], debugExps: Option[Seq[DebugExp]], analysisInfo: AnalysisInfo): Unit def assumeDefinition(t: Term, debugExp: Option[DebugExp]): Unit - def assumeDefinition(t: Term, debugExp: Option[DebugExp], analysisInfo: AnalysisInfo): Unit def assume(terms: Iterable[Term], debugExp: Option[DebugExp], enforceAssumption: Boolean): Unit - def assume(terms: Iterable[Term], debugExp: Option[DebugExp], enforceAssumption: Boolean, analysisInfo: AnalysisInfo): Unit def check(t: Term, timeout: Int): Boolean @@ -300,36 +295,21 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp]): Unit = { - assume(t, e, finalExp, decider.assumptionAnalyzer.currentAnalysisInfo) - } - - def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp], analysisInfo: AnalysisInfo): Unit = { if (finalExp.isDefined) { - assume(assumptions=InsertionOrderedSet((t, Some(DebugExp.createInstance(e.get, finalExp.get)))), analysisInfo, false, false) + assume(assumptions=InsertionOrderedSet((t, Some(DebugExp.createInstance(e.get, finalExp.get)))), assumptionAnalyzer.currentAnalysisInfo, false, false) } else { - assume(assumptions=InsertionOrderedSet((t, None)), analysisInfo, false, false) + assume(assumptions=InsertionOrderedSet((t, None)), assumptionAnalyzer.currentAnalysisInfo, false, false) } } def assume(t: Term, debugExp: Option[DebugExp]): Unit = { - assume(t, debugExp, assumptionAnalyzer.currentAnalysisInfo) - } - - def assume(t: Term, debugExp: Option[DebugExp], analysisInfo: AnalysisInfo): Unit = { - assume(InsertionOrderedSet(Seq((t, debugExp))), analysisInfo, false, false) + assume(InsertionOrderedSet(Seq((t, debugExp))), assumptionAnalyzer.currentAnalysisInfo, false, false) } def assumeDefinition(t: Term, debugExp: Option[DebugExp]): Unit = { - assumeDefinition(t, debugExp, assumptionAnalyzer.currentAnalysisInfo) - } - - def assumeDefinition(t: Term, debugExp: Option[DebugExp], analysisInfo: AnalysisInfo): Unit = { - assume(InsertionOrderedSet(Seq((t, debugExp))), analysisInfo, enforceAssumption=false, isDefinition=true) + assume(InsertionOrderedSet(Seq((t, debugExp))), assumptionAnalyzer.currentAnalysisInfo, enforceAssumption=false, isDefinition=true) } - def assume(assumptions: Iterable[(Term, Option[DebugExp])], analysisInfo: AnalysisInfo): Unit = - assume(InsertionOrderedSet(assumptions), analysisInfo, false, false) - def assume(assumptions: InsertionOrderedSet[(Term, Option[DebugExp])], analysisInfo: AnalysisInfo, enforceAssumption: Boolean, isDefinition: Boolean): Unit = { val filteredAssumptions = if (enforceAssumption) assumptions @@ -351,11 +331,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } def assume(assumptions: Seq[Term], debugExps: Option[Seq[DebugExp]]): Unit = { - assume(assumptions, debugExps, assumptionAnalyzer.currentAnalysisInfo) - } - - def assume(assumptions: Seq[Term], debugExps: Option[Seq[DebugExp]], analysisInfo: AnalysisInfo): Unit = { - val assumptionIds = if(debugExps.isDefined) assumptionAnalyzer.addAssumptions(debugExps.get, analysisInfo) else Seq.empty + val assumptionIds = if(debugExps.isDefined) assumptionAnalyzer.addAssumptions(debugExps.get, assumptionAnalyzer.currentAnalysisInfo) else Seq.empty val assumptionsWithLabels = if(assumptions.size == assumptionIds.size) assumptions.zip(assumptionIds).map{case (t, id) => (t, AssumptionAnalyzer.createAssumptionLabel(Some(id)))} @@ -367,10 +343,6 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } def assume(terms: Iterable[Term], debugExp: Option[DebugExp], enforceAssumption: Boolean): Unit = { - assume(terms, debugExp, enforceAssumption, assumptionAnalyzer.currentAnalysisInfo) - } - - def assume(terms: Iterable[Term], debugExp: Option[DebugExp], enforceAssumption: Boolean, analysisInfo: AnalysisInfo): Unit = { val filteredTerms = if (enforceAssumption) terms @@ -380,7 +352,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => if (debugMode) { addDebugExp(debugExp.get.withTerm(And(filteredTerms))) - val assumptionId: Option[Int] = if(debugExp.isDefined) assumptionAnalyzer.addSingleAssumption(debugExp.get.withTerm(And(filteredTerms)), analysisInfo) else None + val assumptionId: Option[Int] = if(debugExp.isDefined) assumptionAnalyzer.addSingleAssumption(debugExp.get.withTerm(And(filteredTerms)), assumptionAnalyzer.currentAnalysisInfo) else None val termsWithLabel = filteredTerms.zipWithIndex.iterator.map {case (t, idx) => (t, AssumptionAnalyzer.createAssumptionLabel(assumptionId, idx))}.toSeq assumeWithoutSmokeChecks(InsertionOrderedSet(termsWithLabel)) }else{ diff --git a/src/main/scala/decider/ProverStdIO.scala b/src/main/scala/decider/ProverStdIO.scala index 48f2f0754..d8a79c353 100644 --- a/src/main/scala/decider/ProverStdIO.scala +++ b/src/main/scala/decider/ProverStdIO.scala @@ -227,6 +227,7 @@ abstract class ProverStdIO(uniqueId: String, // private val quantificationLogger = bookkeeper.logfiles("quantification-problems") + // TODO ake: we should always have a label? def assume(term: Term): Unit = { assume(term, "prover_" + proverLabelId) proverLabelId += 1 @@ -257,7 +258,12 @@ abstract class ProverStdIO(uniqueId: String, def assume(term: String, label: String): Unit = { // bookkeeper.assumptionCounter += 1 - writeLine("(assert (! " + term + " :named " + label + "))") + if(Verifier.config.enableAssumptionAnalysis() && label.nonEmpty){ + writeLine("(assert (! " + term + " :named " + label + "))") + }else{ + writeLine("(assert " + term + ")") + } + readSuccess() } @@ -282,7 +288,12 @@ abstract class ProverStdIO(uniqueId: String, push() setTimeout(timeout) - writeLine("(assert (! (not " + goal + ") :named " + label + "))") + if(Verifier.config.enableAssumptionAnalysis() && label.nonEmpty){ + writeLine("(assert (! (not " + goal + ") :named " + label + "))") + }else{ + writeLine("(assert (not " + goal + "))") + } + readSuccess() val startTime = System.currentTimeMillis() @@ -293,7 +304,7 @@ abstract class ProverStdIO(uniqueId: String, if (!result) { retrieveAndSaveModel() retrieveReasonUnknown() - }else{ + }else if(Verifier.config.enableAssumptionAnalysis()){ val unsatCore = extractUnsatCore() assumptionAnalyzer.processUnsatCoreAndAddDependencies(unsatCore) } diff --git a/src/main/scala/decider/Z3ProverAPI.scala b/src/main/scala/decider/Z3ProverAPI.scala index 292cb7e31..7f31fc561 100644 --- a/src/main/scala/decider/Z3ProverAPI.scala +++ b/src/main/scala/decider/Z3ProverAPI.scala @@ -6,24 +6,22 @@ package viper.silicon.decider +import com.microsoft.z3._ +import com.microsoft.z3.enumerations.Z3_param_kind import com.typesafe.scalalogging.LazyLogging +import viper.silicon.assumptionAnalysis.AssumptionAnalyzer import viper.silicon.common.config.Version -import viper.silicon.interfaces.decider.{Prover, Result, Sat, Unknown, Unsat} +import viper.silicon.interfaces.decider._ +import viper.silicon.reporting.{ExternalToolError, ProverInteractionFailed} import viper.silicon.state.IdentifierFactory import viper.silicon.state.terms.{App, Decl, Fun, FunctionDecl, Implies, MacroDecl, Not, Quantification, Sort, SortDecl, SortWrapperDecl, Term, TriggerGenerator, Var, sorts} -import viper.silicon.{Config, Map} import viper.silicon.verifier.Verifier +import viper.silicon.{Config, Map} import viper.silver.reporter.{InternalWarningMessage, Reporter} import viper.silver.verifier.{MapEntry, ModelEntry, ModelParser, ValueEntry, DefaultDependency => SilDefaultDependency, Model => ViperModel} import java.nio.file.Path import scala.collection.mutable -import com.microsoft.z3._ -import com.microsoft.z3.enumerations.Z3_param_kind -import viper.silicon.assumptionAnalysis.AssumptionAnalyzer -import viper.silicon.reporting.ExternalToolError -import viper.silicon.reporting.ProverInteractionFailed - import scala.jdk.CollectionConverters.MapHasAsJava import scala.util.Random diff --git a/src/main/scala/decider/Z3ProverStdIO.scala b/src/main/scala/decider/Z3ProverStdIO.scala index 94d3677e6..c5101c217 100644 --- a/src/main/scala/decider/Z3ProverStdIO.scala +++ b/src/main/scala/decider/Z3ProverStdIO.scala @@ -6,13 +6,13 @@ package viper.silicon.decider -import viper.silicon.assumptionAnalysis.AssumptionAnalyzer -import java.nio.file.{Path, Paths} +import viper.silicon.common.config.Version import viper.silicon.state.IdentifierFactory import viper.silicon.verifier.Verifier -import viper.silver.verifier.{DefaultDependency => SilDefaultDependency} import viper.silver.reporter.Reporter -import viper.silicon.common.config.Version +import viper.silver.verifier.{DefaultDependency => SilDefaultDependency} + +import java.nio.file.{Path, Paths} object Z3ProverStdIO { diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index c00d110e5..097ae1845 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -260,15 +260,15 @@ object evaluator extends EvaluationRules { } val (permCheck, permCheckExp) = if (s1.triggerExp) { - (True, TrueLit()(fa.pos, fa.info, fa.errT)) + (True, Option.when(withExp)(TrueLit()(fa.pos, fa.info, fa.errT))) } else { val permVal = relevantChunks.head.perm val totalPermissions = permVal.replace(relevantChunks.head.quantifiedVars, Seq(tRcvr)) - (IsPositive(totalPermissions), ast.PermGtCmp(ast.CurrentPerm(fa)(fa.pos, fa.info, fa.errT), ast.NoPerm()())(fa.pos, fa.info, fa.errT)) + (IsPositive(totalPermissions), Option.when(withExp)(ast.PermGtCmp(ast.CurrentPerm(fa)(fa.pos, fa.info, fa.errT), ast.NoPerm()())(fa.pos, fa.info, fa.errT))) } - v1.decider.assert(permCheck, Option.when(withExp)(permCheckExp)) { + v1.decider.assert(permCheck, permCheckExp) { case false => - createFailure(pve dueTo InsufficientPermission(fa), v1, s1, permCheck, Option.when(withExp)(permCheckExp)) + createFailure(pve dueTo InsufficientPermission(fa), v1, s1, permCheck, permCheckExp) case true => val smLookup = Lookup(fa.field.name, relevantChunks.head.fvf, tRcvr) val fr2 = @@ -293,14 +293,14 @@ object evaluator extends EvaluationRules { } val (permCheck, permCheckExp) = if (s2.triggerExp) { - (True, TrueLit()(fa.pos, fa.info, fa.errT)) + (True, Option.when(withExp)(TrueLit()(fa.pos, fa.info, fa.errT))) } else { val totalPermissions = PermLookup(fa.field.name, pmDef1.pm, tRcvr) - (IsPositive(totalPermissions), ast.PermGtCmp(ast.CurrentPerm(fa)(fa.pos, fa.info, fa.errT), ast.NoPerm()())(fa.pos, fa.info, fa.errT)) + (IsPositive(totalPermissions), Option.when(withExp)(ast.PermGtCmp(ast.CurrentPerm(fa)(fa.pos, fa.info, fa.errT), ast.NoPerm()())(fa.pos, fa.info, fa.errT))) } - v1.decider.assert(permCheck, Option.when(withExp)(permCheckExp)) { + v1.decider.assert(permCheck, permCheckExp) { case false => - createFailure(pve dueTo InsufficientPermission(fa), v1, s2, permCheck, Option.when(withExp)(permCheckExp)) + createFailure(pve dueTo InsufficientPermission(fa), v1, s2, permCheck, permCheckExp) case true => val smLookup = Lookup(fa.field.name, smDef1.sm, tRcvr) val fr2 = @@ -977,11 +977,10 @@ object evaluator extends EvaluationRules { val predicate = s.program.findPredicate(predicateName) if (s.cycles(predicate) < Verifier.config.recursivePredicateUnfoldings()) { v.decider.startDebugSubExp() - evals(s, eArgs, _ => pve, v)((s1, tArgs, eArgsNew, v1) => { - val ePermOrFull = ePerm.getOrElse(ast.FullPerm()(u.pos, u.info, u.errT)) - eval(s1, ePermOrFull, pve, v1)((s2, tPerm, ePermNew, v2) => + evals(s, eArgs, _ => pve, v)((s1, tArgs, eArgsNew, v1) => + eval(s1, ePerm.getOrElse(ast.FullPerm()(u.pos, u.info, u.errT)), pve, v1)((s2, tPerm, ePermNew, v2) => // TODO: Replace with permissionSupporter.assertNotNegative - v2.decider.assert(IsPositive(tPerm), Option.when(withExp)(ast.PermGtCmp(ePermOrFull, ast.NoPerm()())(ePermOrFull.pos, ePermOrFull.info, ePermOrFull.errT))) { + v2.decider.assert(IsPositive(tPerm), Option.when(withExp)(ast.PermGtCmp(ePerm.getOrElse(ast.FullPerm()()), ast.NoPerm()())(u.pos, u.info, u.errT))) { case true => joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s2, v2)((s3, v3, QB) => { val s4 = s3.incCycleCounter(predicate) @@ -1035,7 +1034,7 @@ object evaluator extends EvaluationRules { case false => v2.decider.finishDebugSubExp(s"unfolded(${predicate.name})") createFailure(pve dueTo NonPositivePermission(ePerm.get), v2, s2, IsPositive(tPerm), ePermNew.map(p => ast.PermGtCmp(p, ast.NoPerm()())(p.pos, p.info, p.errT)))}) - }) + ) } else { val unknownValue = v.decider.appliedFresh("recunf", v.symbolConverter.toSort(eIn.typ), s.relevantQuantifiedVariables.map(_._1)) Q(s, unknownValue, Option.when(withExp)(ast.LocalVarWithVersion("unknownValue", eIn.typ)(eIn.pos, eIn.info, eIn.errT)), v) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index b46f1b4be..3c69de4d1 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -489,12 +489,12 @@ object executor extends ExecutionRules { Q(s2, v) case inhale @ ast.Inhale(a) => - v.decider.assumptionAnalyzer.setCurrentAnalysisInfo(ExpAnalysisSourceInfo(a), AssumptionType.Explicit) a match { case _: ast.FalseLit => /* We're done */ Success() case _ => + v.decider.assumptionAnalyzer.setCurrentAnalysisInfo(ExpAnalysisSourceInfo(a), AssumptionType.Explicit) produce(s, freshSnap, a, InhaleFailed(inhale), v)((s1, v1) => { v1.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterInhale) Q(s1, v1)}) @@ -502,14 +502,12 @@ object executor extends ExecutionRules { case exhale @ ast.Exhale(a) => - val analysisInfo = v.decider.assumptionAnalyzer.setCurrentAnalysisInfo(ExpAnalysisSourceInfo(a), AssumptionType.Assertion) val pve = ExhaleFailed(exhale) - consume(s, a, false, pve, v, analysisInfo)((s1, _, v1) => + consume(s, a, false, pve, v, v.decider.assumptionAnalyzer.currentAnalysisInfo)((s1, _, v1) => Q(s1, v1)) case assert @ ast.Assert(a: ast.FalseLit) if !s.isInPackage => /* "assert false" triggers a smoke check. If successful, we backtrack. */ - v.decider.assumptionAnalyzer.setCurrentAnalysisInfo(ExpAnalysisSourceInfo(a), AssumptionType.Assertion) executionFlowController.tryOrFail0(s.copy(h = magicWandSupporter.getEvalHeap(s)), v)((s1, v1, QS) => { if (v1.decider.checkSmoke(true)) QS(s1.copy(h = s.h), v1) @@ -518,15 +516,13 @@ object executor extends ExecutionRules { })((_, _) => Success()) case assert @ ast.Assert(a) if Verifier.config.disableSubsumption() => - val analysisInfo = v.decider.assumptionAnalyzer.setCurrentAnalysisInfo(ExpAnalysisSourceInfo(a), AssumptionType.Assertion) val r = - consume(s, a, false, AssertFailed(assert), v, analysisInfo)((_, _, _) => + consume(s, a, false, AssertFailed(assert), v, v.decider.assumptionAnalyzer.currentAnalysisInfo)((_, _, _) => Success()) r combine Q(s, v) case assert @ ast.Assert(a) => - val analysisInfo = v.decider.assumptionAnalyzer.setCurrentAnalysisInfo(ExpAnalysisSourceInfo(a), AssumptionType.Assertion) val pve = AssertFailed(assert) if (s.exhaleExt) { @@ -537,17 +533,17 @@ object executor extends ExecutionRules { * hUsed (reserveHeaps.head) instead of consuming them. hUsed is later discarded and replaced * by s.h. By copying hUsed to s.h the contained permissions remain available inside the wand. */ - consume(s, a, false, pve, v, analysisInfo)((s2, _, v1) => { + consume(s, a, false, pve, v, v.decider.assumptionAnalyzer.currentAnalysisInfo)((s2, _, v1) => { Q(s2.copy(h = s2.reserveHeaps.head), v1) }) } else - consume(s, a, false, pve, v, analysisInfo)((s1, _, v1) => { + consume(s, a, false, pve, v, v.decider.assumptionAnalyzer.currentAnalysisInfo)((s1, _, v1) => { val s2 = s1.copy(h = s.h, reserveHeaps = s.reserveHeaps) Q(s2, v1)}) // Calling hack407_R() results in Silicon efficiently havocking all instances of resource R. // See also Silicon issue #407. - case methCall @ ast.MethodCall(methodName, _, _) + case ast.MethodCall(methodName, _, _) if !Verifier.config.disableHavocHack407() && methodName.startsWith(hack407_method_name_prefix) => val analysisInfo = v.decider.assumptionAnalyzer.updateCurrentAnalysisInfo(AssumptionType.Explicit) @@ -570,6 +566,7 @@ object executor extends ExecutionRules { // Calling hack510() triggers a state consolidation. // See also Silicon issue #510. case ast.MethodCall(`hack510_method_name`, _, _) => + v.decider.assumptionAnalyzer.updateCurrentAnalysisInfo(AssumptionType.Explicit) val s1 = v.stateConsolidator(s).consolidate(s, v) Q(s1, v) @@ -600,7 +597,7 @@ object executor extends ExecutionRules { tArgs zip Seq.fill(tArgs.size)(None) val s2 = s1.copy(g = Store(fargs.zip(argsWithExp)), recordVisited = true) - consumes(s2, meth.pres, false, _ => pvePre, v1, v1.decider.assumptionAnalyzer.currentAnalysisInfo.withAssumptionType(AssumptionType.Assertion))((s3, _, v2) => { + consumes(s2, meth.pres, false, _ => pvePre, v1, v1.decider.assumptionAnalyzer.currentAnalysisInfo)((s3, _, v2) => { v2.symbExLog.closeScope(preCondId) val postCondLog = new CommentRecord("Postcondition", s3, v2.decider.pcs) val postCondId = v2.symbExLog.openScope(postCondLog) @@ -629,7 +626,7 @@ object executor extends ExecutionRules { eval(s1, ePerm, pve, v1)((s2, tPerm, ePermNew, v2) => permissionSupporter.assertPositive(s2, tPerm, if (withExp) ePermNew.get else ePerm, pve, v2)((s3, v3) => { val wildcards = s3.constrainableARPs -- s1.constrainableARPs - predicateSupporter.fold(s3, predicate, tArgs, eArgsNew, tPerm, ePermNew, wildcards, pve, v3, AnalysisInfo(v3.decider.assumptionAnalyzer, ExpAnalysisSourceInfo(predAcc), AssumptionType.Unknown))((s4, v4) => { + predicateSupporter.fold(s3, predicate, tArgs, eArgsNew, tPerm, ePermNew, wildcards, pve, v3, v3.decider.assumptionAnalyzer.currentAnalysisInfo)((s4, v4) => { v3.decider.finishDebugSubExp(s"folded ${predAcc.toString}") Q(s4, v4) } diff --git a/src/main/scala/rules/HavocSupporter.scala b/src/main/scala/rules/HavocSupporter.scala index 1597c6dde..d9e4612e3 100644 --- a/src/main/scala/rules/HavocSupporter.scala +++ b/src/main/scala/rules/HavocSupporter.scala @@ -43,7 +43,6 @@ object havocSupporter extends SymbolicExecutionRules { s: State) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { - val analysisInfo = AnalysisInfo(v.decider.assumptionAnalyzer, StmtAnalysisSourceInfo(havoc), AssumptionType.Explicit) val pve = QuasihavocFailed(havoc) // If there is no havoc condition, use True as the condition @@ -58,9 +57,9 @@ object havocSupporter extends SymbolicExecutionRules { // the HavocHelperData inside of a HavocOneData case (as opposed to HavocAllData). val newChunks = if (usesQPChunks(s1, resource)) - havocQuantifiedResource(s1, lhsTerm, resource, HavocOneData(tRcvrs), v1, analysisInfo) + havocQuantifiedResource(s1, lhsTerm, resource, HavocOneData(tRcvrs), v1) else - havocNonQuantifiedResource(s1, lhsTerm, resource, HavocOneData(tRcvrs), v1, analysisInfo) + havocNonQuantifiedResource(s1, lhsTerm, resource, HavocOneData(tRcvrs), v1) Q(s1.copy(h = Heap(newChunks)), v1) }) @@ -83,7 +82,6 @@ object havocSupporter extends SymbolicExecutionRules { s: State) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { - val analysisInfo = AnalysisInfo(v.decider.assumptionAnalyzer, StmtAnalysisSourceInfo(havocall), AssumptionType.Explicit) val pve = HavocallFailed(havocall) val ast.Quasihavocall(vars, lhs, eRsc) = havocall val qid = resourceName(s, eRsc) @@ -157,9 +155,9 @@ object havocSupporter extends SymbolicExecutionRules { // the HavocHelperData inside of a HavocAllData case. val newChunks = if (usesQPChunks(s1, resource)) - havocQuantifiedResource(s1, tCond, resource, HavocallData(inverseFunctions, codomainQVars, imagesOfCodomain), v1, analysisInfo) + havocQuantifiedResource(s1, tCond, resource, HavocallData(inverseFunctions, codomainQVars, imagesOfCodomain), v1) else - havocNonQuantifiedResource(s1, tCond, resource, HavocallData(inverseFunctions, codomainQVars, imagesOfCodomain), v1, analysisInfo) + havocNonQuantifiedResource(s1, tCond, resource, HavocallData(inverseFunctions, codomainQVars, imagesOfCodomain), v1) Q(s1.copy(h = Heap(newChunks)), v1) } @@ -184,8 +182,7 @@ object havocSupporter extends SymbolicExecutionRules { lhs: Term, resource: ast.Resource, condInfo: HavocHelperData, - v: Verifier, - analysisInfo: AnalysisInfo) + v: Verifier) : Seq[Chunk] = { val id = ChunkIdentifier(resource, s.program) @@ -196,12 +193,12 @@ object havocSupporter extends SymbolicExecutionRules { val havockedSnap = v.decider.fresh("mwsf", sorts.MagicWandSnapFunction, Option.when(withExp)(PUnknown())) val cond = replacementCond(lhs, ch.args, condInfo) val magicWandSnapshot = MagicWandSnapshot(Ite(cond, havockedSnap, ch.snap.mwsf)) - NonQuantifiedChunk.withSnap(ch, magicWandSnapshot, None, analysisInfo) + NonQuantifiedChunk.withSnap(ch, magicWandSnapshot, None, v.decider.assumptionAnalyzer.currentAnalysisInfo) case ch => val havockedSnap = freshSnap(ch.snap.sort, v) val cond = replacementCond(lhs, ch.args, condInfo) - NonQuantifiedChunk.withSnap(ch, Ite(cond, havockedSnap, ch.snap), None, analysisInfo) + NonQuantifiedChunk.withSnap(ch, Ite(cond, havockedSnap, ch.snap), None, v.decider.assumptionAnalyzer.currentAnalysisInfo) } otherChunks ++ newChunks } @@ -227,8 +224,7 @@ object havocSupporter extends SymbolicExecutionRules { lhs: Term, resource: ast.Resource, condInfo: HavocHelperData, - v: Verifier, - analysisInfo: AnalysisInfo) + v: Verifier) : Seq[Chunk] = { // Quantified field chunks are of the form R(r; sm, pm). @@ -285,7 +281,7 @@ object havocSupporter extends SymbolicExecutionRules { val debugExp = Option.when(withExp)(DebugExp.createInstance("havoc new axiom", isInternal_ = true)) v.decider.assume(newAxiom, debugExp) - QuantifiedChunk.withSnapshotMap(ch, newSm, analysisInfo) + QuantifiedChunk.withSnapshotMap(ch, newSm, v.decider.assumptionAnalyzer.currentAnalysisInfo) } newChunks ++ otherChunks } diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index d32d9b268..7b1c9ba62 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -94,8 +94,8 @@ object magicWandSupporter extends SymbolicExecutionRules { (Q: (State, MagicWandChunk, Verifier) => VerificationResult) : VerificationResult = { evaluateWandArguments(s, wand, pve, v)((s1, ts, esNew, v1) => { - val newChunk = MagicWandChunk(MagicWandIdentifier(wand, s.program), s1.g.values, ts, esNew, snap, FullPerm, - Option.when(withExp)(ast.FullPerm()(wand.pos, wand.info, wand.errT))) + val newChunk = MagicWandChunk.apply(MagicWandIdentifier(wand, s.program), s1.g.values, ts, esNew, snap, FullPerm, + Option.when(withExp)(ast.FullPerm()(wand.pos, wand.info, wand.errT)), v.decider.assumptionAnalyzer.currentAnalysisInfo) Q(s1, newChunk, v1) }) } @@ -400,7 +400,7 @@ object magicWandSupporter extends SymbolicExecutionRules { // This part indirectly calls the methods `this.transfer` and `this.consumeFromMultipleHeaps`. consume( proofScriptState.copy(oldHeaps = s2.oldHeaps, reserveCfgs = proofScriptState.reserveCfgs.tail), - wand.right, true, pve, proofScriptVerifier, AnalysisInfo(proofScriptVerifier.decider.assumptionAnalyzer, ExpAnalysisSourceInfo(wand.right), AssumptionType.Assertion) + wand.right, true, pve, proofScriptVerifier, proofScriptVerifier.decider.assumptionAnalyzer.currentAnalysisInfo )((s3, snapRhs, v3) => { createWandChunkAndRecordResults(s3.copy(exhaleExt = false, oldHeaps = s.oldHeaps), freshSnapRoot, snapRhs.get, v3) @@ -459,9 +459,9 @@ object magicWandSupporter extends SymbolicExecutionRules { (Q: (State, Verifier) => VerificationResult) : VerificationResult = { // Consume the magic wand instance "A --* B". - consume(s, wand, true, pve, v, AnalysisInfo(v.decider.assumptionAnalyzer, ExpAnalysisSourceInfo(wand), AssumptionType.Assertion))((s1, snapWand, v1) => { + consume(s, wand, true, pve, v, v.decider.assumptionAnalyzer.currentAnalysisInfo)((s1, snapWand, v1) => { // Consume the wand's LHS "A". - consume(s1, wand.left, true, pve, v1, AnalysisInfo(v1.decider.assumptionAnalyzer, ExpAnalysisSourceInfo(wand.left), AssumptionType.Assertion))((s2, snapLhs, v2) => { + consume(s1, wand.left, true, pve, v1, v1.decider.assumptionAnalyzer.currentAnalysisInfo)((s2, snapLhs, v2) => { /* It is assumed that snap and MagicWandSnapshot.abstractLhs are structurally the same. * Equating the two snapshots is sound iff a wand is applied only once. * The old solution in this case did use this assumption: diff --git a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala index b6d5dab2c..107e8626c 100644 --- a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala +++ b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala @@ -482,8 +482,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { newFr = newFr.recordPathSymbol(permTaken.applicable.asInstanceOf[Function]).recordConstraint(constraint) - val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, permTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, permTakenExp.get)(pe.pos, pe.info, pe.errT)), analysisInfo).asInstanceOf[NonQuantifiedChunk] - newChunk + GeneralChunk.withPerm(ch, PermMinus(ch.perm, permTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, permTakenExp.get)(pe.pos, pe.info, pe.errT)), analysisInfo).asInstanceOf[NonQuantifiedChunk] }) val totalTakenBounds = diff --git a/src/main/scala/rules/PredicateSupporter.scala b/src/main/scala/rules/PredicateSupporter.scala index 6b97c14f6..a31bb0909 100644 --- a/src/main/scala/rules/PredicateSupporter.scala +++ b/src/main/scala/rules/PredicateSupporter.scala @@ -74,7 +74,7 @@ object predicateSupporter extends PredicateSupportRules { val s1 = s.copy(g = gIns, smDomainNeeded = true) .scalePermissionFactor(tPerm, ePerm) - consume(s1, body, true, pve, v, AnalysisInfo(v.decider.assumptionAnalyzer, analysisInfo.sourceInfo, AssumptionType.Assertion))((s1a, snap, v1) => { + consume(s1, body, true, pve, v, analysisInfo)((s1a, snap, v1) => { if (!Verifier.config.disableFunctionUnfoldTrigger()) { val predTrigger = App(s1a.predicateData(predicate).triggerFunction, snap.get.convert(terms.sorts.Snap) +: tArgs) @@ -117,8 +117,7 @@ object predicateSupporter extends PredicateSupportRules { functionRecorder = s2.functionRecorder.recordFvfAndDomain(smDef)) Q(s3, v1) } else { - val ch = BasicChunk(PredicateID, BasicChunkIdentifier(predicate.name), tArgs, eArgs, snap.get.convert(sorts.Snap), None, tPerm, ePerm, - AnalysisInfo(v1.decider.assumptionAnalyzer, ExpAnalysisSourceInfo(PredicateAccess(Seq(), predicate)(NoPosition, NoInfo, NoTrafos)), AssumptionType.Unknown)) + val ch = BasicChunk(PredicateID, BasicChunkIdentifier(predicate.name), tArgs, eArgs, snap.get.convert(sorts.Snap), None, tPerm, ePerm, v1.decider.assumptionAnalyzer.currentAnalysisInfo) val s3 = s2.copy(g = s.g, smDomainNeeded = s.smDomainNeeded, permissionScalingFactor = s.permissionScalingFactor, @@ -141,7 +140,6 @@ object predicateSupporter extends PredicateSupportRules { pa: ast.PredicateAccess) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { - val analysisInfo = AnalysisInfo(v.decider.assumptionAnalyzer, ExpAnalysisSourceInfo(pa), AssumptionType.Assertion) // TODO ake: check that this is valid val tArgsWithE = if (withExp) tArgs zip eArgs.get.map(Some(_)) else @@ -165,7 +163,7 @@ object predicateSupporter extends PredicateSupportRules { None, pve, v, - analysisInfo + v.decider.assumptionAnalyzer.currentAnalysisInfo )((s2, h2, snap, v1) => { val s3 = s2.copy(g = gIns, h = h2) .setConstrainable(constrainableWildcards, false) @@ -186,7 +184,7 @@ object predicateSupporter extends PredicateSupportRules { } else { val ve = pve dueTo InsufficientPermission(pa) val description = s"consume ${pa.pos}: $pa" - chunkSupporter.consume(s1, s1.h, predicate, tArgs, eArgs, s1.permissionScalingFactor, s1.permissionScalingFactorExp, true, ve, v, description, analysisInfo)((s2, h1, snap, v1) => { + chunkSupporter.consume(s1, s1.h, predicate, tArgs, eArgs, s1.permissionScalingFactor, s1.permissionScalingFactorExp, true, ve, v, description, v.decider.assumptionAnalyzer.currentAnalysisInfo)((s2, h1, snap, v1) => { val s3 = s2.copy(g = gIns, h = h1) .setConstrainable(constrainableWildcards, false) produce(s3, toSf(snap.get), body, pve, v1)((s4, v2) => { diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index a889ee39c..6110d8807 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -319,7 +319,7 @@ object producer extends ProductionRules { letSupporter.handle[ast.Exp](s, let, pve, v)((s1, g1, body, v1) => produceR(s1.copy(g = s1.g + g1), sf, body, pve, v1)(Q)) - case accPred@ast.FieldAccessPredicate(fa @ ast.FieldAccess(eRcvr, field), _) => + case accPred@ast.FieldAccessPredicate(ast.FieldAccess(eRcvr, field), _) => val perm = accPred.perm eval(s, eRcvr, pve, v)((s1, tRcvr, eRcvrNew, v1) => eval(s1, perm, pve, v1)((s2, tPerm, ePermNew, v2) => @@ -339,14 +339,14 @@ object producer extends ProductionRules { val (debugHeapName, debugLabel) = v3.getDebugOldLabel(s3, accPred.pos) val snapExp = Option.when(withExp)(ast.DebugLabelledOld(ast.FieldAccess(eRcvrNew.get, field)(), debugLabel)(accPred.pos, accPred.info, accPred.errT)) val ch = BasicChunk(FieldID, BasicChunkIdentifier(field.name), Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), snap, snapExp, gain, gainExp, - AnalysisInfo(v1.decider.assumptionAnalyzer, ExpAnalysisSourceInfo(fa), AssumptionType.Unknown)) + v1.decider.assumptionAnalyzer.currentAnalysisInfo) chunkSupporter.produce(s3, s3.h, ch, v3)((s4, h4, v4) => { val s5 = s4.copy(h = h4) val s6 = if (withExp) s5.copy(oldHeaps = s5.oldHeaps + (debugHeapName -> magicWandSupporter.getEvalHeap(s4))) else s5 Q(s6, v4) })}}))) - case accPred @ ast.PredicateAccessPredicate(pa @ ast.PredicateAccess(eArgs, predicateName), _) => + case accPred @ ast.PredicateAccessPredicate(ast.PredicateAccess(eArgs, predicateName), _) => val predicate = s.program.findPredicate(predicateName) val perm = accPred.perm evals(s, eArgs, _ => pve, v)((s1, tArgs, eArgsNew, v1) => @@ -367,7 +367,7 @@ object producer extends ProductionRules { } else { val snap1 = snap.convert(sorts.Snap) val ch = BasicChunk(PredicateID, BasicChunkIdentifier(predicate.name), tArgs, eArgsNew, snap1, None, gain, gainExp, - AnalysisInfo(v1.decider.assumptionAnalyzer, ExpAnalysisSourceInfo(pa), AssumptionType.Unknown)) + v1.decider.assumptionAnalyzer.currentAnalysisInfo) chunkSupporter.produce(s2, s2.h, ch, v2)((s3, h3, v3) => { if (Verifier.config.enablePredicateTriggersOnInhale() && s3.functionRecorder == NoopFunctionRecorder && !Verifier.config.disableFunctionUnfoldTrigger()) { diff --git a/src/main/scala/rules/StateConsolidator.scala b/src/main/scala/rules/StateConsolidator.scala index 109b0d54b..8f2588247 100644 --- a/src/main/scala/rules/StateConsolidator.scala +++ b/src/main/scala/rules/StateConsolidator.scala @@ -199,7 +199,17 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol // Merges two chunks that are aliases (i.e. that have the same id and the args are proven to be equal) // and returns the merged chunk or None, if the chunks could not be merged private def mergeChunks(fr1: FunctionRecorder, chunk1: Chunk, chunk2: Chunk, qvars: Seq[Var], v: Verifier): Option[(FunctionRecorder, Chunk, Term)] = { - val result = (chunk1, chunk2) match { + val result = mergeChunks1(fr1, chunk1, chunk2, qvars, v) + if(result.isDefined){ + val (_, newChunk, _) = result.get + val newChunkNode = PermissionInhaleNode(newChunk, StringAnalysisSourceInfo("state consolidation", NoPosition), AssumptionType.Internal) + v.decider.assumptionAnalyzer.addPermissionDependencies(Set(chunk1, chunk2), newChunkNode) + } + result + } + + private def mergeChunks1(fr1: FunctionRecorder, chunk1: Chunk, chunk2: Chunk, qvars: Seq[Var], v: Verifier): Option[(FunctionRecorder, Chunk, Term)] = { + (chunk1, chunk2) match { case (BasicChunk(rid1, id1, args1, args1Exp, snap1, snap1Exp, perm1, perm1Exp), BasicChunk(_, _, _, _, snap2, _, perm2, perm2Exp)) => val (fr2, combinedSnap, snapEq) = combineSnapshots(fr1, snap1, snap2, perm1, perm2, qvars, v) val newExp = perm1Exp.map(p1 => ast.PermAdd(p1, perm2Exp.get)()) @@ -224,12 +234,6 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol case _ => None } - if(result.isDefined){ - val (_, newChunk, _) = result.get - val newChunkNode = PermissionInhaleNode(newChunk, StringAnalysisSourceInfo("state consolidation", NoPosition), AssumptionType.Internal) - v.decider.assumptionAnalyzer.addPermissionDependencies(Set(chunk1, chunk2), newChunkNode) - } - result } /** Merge the snapshots of two chunks that denote the same location, i.e. whose ids and arguments diff --git a/src/main/scala/state/Chunks.scala b/src/main/scala/state/Chunks.scala index 974f77aab..722b1e02b 100644 --- a/src/main/scala/state/Chunks.scala +++ b/src/main/scala/state/Chunks.scala @@ -265,6 +265,21 @@ object MagicWandIdentifier { } } +object MagicWandChunk { + def apply(id: MagicWandIdentifier, + bindings: Map[ast.AbstractLocalVar, (Term, Option[ast.Exp])], + args: Seq[Term], + argsExp: Option[Seq[ast.Exp]], + snap: MagicWandSnapshot, + perm: Term, + permExp: Option[ast.Exp], + analysisInfo: AnalysisInfo): MagicWandChunk = { + val chunk = MagicWandChunk(id, bindings, args, argsExp, snap, perm, permExp) + analysisInfo.assumptionAnalyzer.addPermissionNode(chunk, analysisInfo.sourceInfo, analysisInfo.assumptionType) + chunk + } +} + case class MagicWandChunk(id: MagicWandIdentifier, bindings: Map[ast.AbstractLocalVar, (Term, Option[ast.Exp])], args: Seq[Term], From 950c8f407ded6b48a17f29fd0888f7eb15c471ff Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 12 May 2025 14:41:15 +0200 Subject: [PATCH 032/474] implement missing chunk factories --- src/main/scala/rules/Executor.scala | 4 +- src/main/scala/rules/MagicWandSupporter.scala | 2 +- src/main/scala/rules/PredicateSupporter.scala | 2 +- src/main/scala/rules/Producer.scala | 2 +- .../scala/rules/QuantifiedChunkSupport.scala | 28 ++++--- src/main/scala/rules/StateConsolidator.scala | 6 +- src/main/scala/state/Chunks.scala | 76 ++++++++++++++++--- 7 files changed, 92 insertions(+), 28 deletions(-) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 3c69de4d1..7f8ea6f6b 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -421,7 +421,7 @@ object executor extends ExecutionRules { val debugExp = Option.when(withExp)(DebugExp.createInstance("Definitional axioms for singleton-FVF's value", isInternal_ = true)) v1.decider.assumeDefinition(smValueDef, debugExp) val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(Seq(`?r`), Option.when(withExp)(Seq(ast.LocalVarDecl("r", ast.Ref)(ass.pos, ass.info, ass.errT))), - field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), sm, s.program) + field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), sm, s.program, v1) if (s3.heapDependentTriggers.contains(field)) { val debugExp2 = Option.when(withExp)(DebugExp.createInstance(s"FieldTrigger(${eRcvrNew.toString()}.${field.name})")) v1.decider.assume(FieldTrigger(field.name, sm, tRcvr), debugExp2) @@ -474,7 +474,7 @@ object executor extends ExecutionRules { val debugExp = Option.when(withExp)(DebugExp.createInstance("Definitional axioms for singleton-FVF's value", isInternal_ = true)) v.decider.assumeDefinition(smValueDef, debugExp) quantifiedChunkSupporter.createSingletonQuantifiedChunk(Seq(`?r`), Option.when(withExp)(Seq(ast.LocalVarDecl("r", ast.Ref)(stmt.pos, stmt.info, stmt.errT))), - field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), p, pExp, sm, s.program) + field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), p, pExp, sm, s.program, v) } else { val newChunk = BasicChunk(FieldID, BasicChunkIdentifier(field.name), Seq(tRcvr), Option.when(withExp)(Seq(x)), snap, snapExp, p, pExp, AnalysisInfo(v.decider.assumptionAnalyzer, ExpAnalysisSourceInfo(ast.FieldAccess(x, field)(field.pos, field.info, field.errT)), AssumptionType.Explicit)) diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 7b1c9ba62..02743f3af 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -326,7 +326,7 @@ object magicWandSupporter extends SymbolicExecutionRules { val debugExp = Option.when(withExp)(DebugExp.createInstance("Definitional axioms for singleton-SM's value", true)) v4.decider.assumeDefinition(smValueDef, debugExp) val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(formalVars, formalVarExps, wand, args, - Option.when(withExp)(bodyVars), FullPerm, Option.when(withExp)(ast.FullPerm()()), sm, s.program) + Option.when(withExp)(bodyVars), FullPerm, Option.when(withExp)(ast.FullPerm()()), sm, s.program, v4) val conservedPcs = s5.conservedPcs.head :+ v4.decider.pcs.after(preMark).definitionsOnly appendToResults(s5, ch, v4.decider.pcs.after(preMark), (conservedPcs.flatMap(_.conditionalized), Option.when(withExp)(conservedPcs.flatMap(_.conditionalizedExp))), v4) Success() diff --git a/src/main/scala/rules/PredicateSupporter.scala b/src/main/scala/rules/PredicateSupporter.scala index a31bb0909..95b49ace3 100644 --- a/src/main/scala/rules/PredicateSupporter.scala +++ b/src/main/scala/rules/PredicateSupporter.scala @@ -92,7 +92,7 @@ object predicateSupporter extends PredicateSupportRules { v1.decider.assumeDefinition(smValueDef, debugExp) val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk( - formalArgs, Option.when(withExp)(predicate.formalArgs), predicate, tArgs, eArgs, tPerm, ePerm, sm, s.program) + formalArgs, Option.when(withExp)(predicate.formalArgs), predicate, tArgs, eArgs, tPerm, ePerm, sm, s.program, v1) val h3 = s2.h + ch val smDef = SnapshotMapDefinition(predicate, sm, Seq(smValueDef), Seq()) val smCache = if (s2.heapDependentTriggers.contains(predicate)) { diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index 6110d8807..c955fe3d9 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -394,7 +394,7 @@ object producer extends ProductionRules { else s1.conservedPcs val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(formalVars, formalVarExps, wand, args, bodyVarsNew, - FullPerm, Option.when(withExp)(ast.FullPerm()(wand.pos, wand.info, wand.errT)), sm, s.program) + FullPerm, Option.when(withExp)(ast.FullPerm()(wand.pos, wand.info, wand.errT)), sm, s.program, v1) val h2 = s1.h + ch val smCache1 = if (s1.heapDependentTriggers.contains(MagicWandIdentifier(wand, s1.program))){ val (relevantChunks, _) = diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 5c0c81b89..372c2537e 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -177,7 +177,8 @@ trait QuantifiedChunkSupport extends SymbolicExecutionRules { permissions: Term, permissionsExp: Option[ast.Exp], sm: Term, - program: ast.Program) + program: ast.Program, + v: Verifier) : QuantifiedBasicChunk /** Creates a quantified chunk corresponding to the assertion @@ -269,7 +270,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { permissions: Term, permissionsExp: Option[ast.Exp], sm: Term, - program: ast.Program) + program: ast.Program, + v: Verifier) : QuantifiedBasicChunk = { val condition = @@ -297,7 +299,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { Some(arguments), argumentsExp, hints, - program) + program, + v) } /** @inheritdoc [[QuantifiedChunkSupport.createQuantifiedChunk]] */ @@ -358,7 +361,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { None, None, hints, - program) + program, + v) (ch, inverseFunctions) } @@ -400,7 +404,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optSingletonArguments: Option[Seq[Term]], optSingletonArgumentsExp: Option[Seq[ast.Exp]], hints: Seq[Term], - program: ast.Program) + program: ast.Program, + v: Verifier) : QuantifiedBasicChunk = { resource match { @@ -419,7 +424,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optInverseFunctions, optSingletonArguments.map(_.head), optSingletonArgumentsExp.map(_.head), - hints) + hints, + v.decider.assumptionAnalyzer.currentAnalysisInfo) case predicate: ast.Predicate => QuantifiedPredicateChunk( @@ -434,7 +440,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optInverseFunctions, optSingletonArguments, optSingletonArgumentsExp, - hints) + hints, + v.decider.assumptionAnalyzer.currentAnalysisInfo) case wand: ast.MagicWand => val conditionalizedPermissions = Ite(condition, permissions, NoPerm) @@ -449,7 +456,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optInverseFunctions, optSingletonArguments, optSingletonArgumentsExp, - hints) + hints, + v.decider.assumptionAnalyzer.currentAnalysisInfo) case other => sys.error(s"Found yet unsupported resource $other (${other.getClass.getSimpleName})") @@ -1102,7 +1110,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val conservedPcs = if (s.recordPcs) (s.conservedPcs.head :+ v.decider.pcs.after(definitionalAxiomMark)) +: s.conservedPcs.tail else s.conservedPcs - val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(formalQVars, formalQVarsExp, resource, tArgs, eArgs, tPerm, ePerm, sm, s.program) + val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(formalQVars, formalQVarsExp, resource, tArgs, eArgs, tPerm, ePerm, sm, s.program, v) val (fr1, h1) = v.stateConsolidator(s).merge(s.functionRecorder, s, s.h, Heap(Seq(ch)), v) val interpreter = new NonQuantifiedPropertyInterpreter(h1.values, v) @@ -1476,7 +1484,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { } val consumedChunk = quantifiedChunkSupporter.createSingletonQuantifiedChunk( - codomainQVars, codomainQVarsExp, resource, arguments, argumentsExp, permsTaken, permsTakenExp, smDef1.sm, s.program) + codomainQVars, codomainQVarsExp, resource, arguments, argumentsExp, permsTaken, permsTakenExp, smDef1.sm, s.program, v1) val s3 = s2.copy(functionRecorder = s2.functionRecorder.recordFvfAndDomain(smDef1), smCache = smCache1) (result, s3, h2, Some(consumedChunk)) diff --git a/src/main/scala/rules/StateConsolidator.scala b/src/main/scala/rules/StateConsolidator.scala index 8f2588247..87b82516a 100644 --- a/src/main/scala/rules/StateConsolidator.scala +++ b/src/main/scala/rules/StateConsolidator.scala @@ -213,7 +213,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol case (BasicChunk(rid1, id1, args1, args1Exp, snap1, snap1Exp, perm1, perm1Exp), BasicChunk(_, _, _, _, snap2, _, perm2, perm2Exp)) => val (fr2, combinedSnap, snapEq) = combineSnapshots(fr1, snap1, snap2, perm1, perm2, qvars, v) val newExp = perm1Exp.map(p1 => ast.PermAdd(p1, perm2Exp.get)()) - Some(fr2, BasicChunk(rid1, id1, args1, args1Exp, combinedSnap, snap1Exp, PermPlus(perm1, perm2), newExp), snapEq) + Some(fr2, BasicChunk(rid1, id1, args1, args1Exp, combinedSnap, snap1Exp, PermPlus(perm1, perm2), newExp, v.decider.assumptionAnalyzer.currentAnalysisInfo), snapEq) case (l@QuantifiedFieldChunk(id1, fvf1, condition1, condition1Exp, perm1, perm1Exp, invs1, singletonRcvr1, singletonRcvr1Exp, hints1), r@QuantifiedFieldChunk(_, fvf2, _, _, perm2, perm2Exp, _, _, _, hints2)) => assert(l.quantifiedVars == Seq(`?r`)) @@ -223,14 +223,14 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol val permSum = PermPlus(perm1, perm2) val permSumExp = perm1Exp.map(p1 => ast.PermAdd(p1, perm2Exp.get)()) val bestHints = if (hints1.nonEmpty) hints1 else hints2 - Some(fr2, QuantifiedFieldChunk(id1, combinedSnap, condition1, condition1Exp, permSum, permSumExp, invs1, singletonRcvr1, singletonRcvr1Exp, bestHints), snapEq) + Some(fr2, QuantifiedFieldChunk(id1, combinedSnap, condition1, condition1Exp, permSum, permSumExp, invs1, singletonRcvr1, singletonRcvr1Exp, bestHints, v.decider.assumptionAnalyzer.currentAnalysisInfo), snapEq) case (l@QuantifiedPredicateChunk(id1, qVars1, qVars1Exp, psf1, _, _, perm1, perm1Exp, _, _, _, _), r@QuantifiedPredicateChunk(_, qVars2, qVars2Exp, psf2, condition2, condition2Exp, perm2, perm2Exp, invs2, singletonArgs2, singletonArgs2Exp, hints2)) => val (fr2, combinedSnap, snapEq) = quantifiedChunkSupporter.combinePredicateSnapshotMaps(fr1, id1.name, qVars2, psf1, psf2, l.perm.replace(qVars1, qVars2), r.perm, v) val permSum = PermPlus(perm1.replace(qVars1, qVars2), perm2) val permSumExp = perm1Exp.map(p1 => ast.PermAdd(p1.replace(qVars1Exp.get.zip(qVars2Exp.get).toMap), perm2Exp.get)()) - Some(fr2, QuantifiedPredicateChunk(id1, qVars2, qVars2Exp, combinedSnap, condition2, condition2Exp, permSum, permSumExp, invs2, singletonArgs2, singletonArgs2Exp, hints2), snapEq) + Some(fr2, QuantifiedPredicateChunk(id1, qVars2, qVars2Exp, combinedSnap, condition2, condition2Exp, permSum, permSumExp, invs2, singletonArgs2, singletonArgs2Exp, hints2, v.decider.assumptionAnalyzer.currentAnalysisInfo), snapEq) case _ => None } diff --git a/src/main/scala/state/Chunks.scala b/src/main/scala/state/Chunks.scala index 722b1e02b..ea0d68ea3 100644 --- a/src/main/scala/state/Chunks.scala +++ b/src/main/scala/state/Chunks.scala @@ -6,8 +6,7 @@ package viper.silicon.state -import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType -import viper.silicon.assumptionAnalysis.{AnalysisInfo, AnalysisSourceInfo, PermissionInhaleNode} +import viper.silicon.assumptionAnalysis.{AnalysisInfo, PermissionInhaleNode} import viper.silicon.interfaces.state._ import viper.silicon.resources._ import viper.silicon.rules.InverseFunctions @@ -36,7 +35,7 @@ object BasicChunk { args: Seq[Term], argsExp: Option[Seq[ast.Exp]], snap: Term, snapExp: Option[ast.Exp], perm: Term, permExp: Option[ast.Exp]): BasicChunk = { - new BasicChunk(resourceID, id, args, argsExp, snap, snapExp, perm, permExp) + BasicChunk(resourceID, id, args, argsExp, snap, snapExp, perm, permExp) } def apply(resourceID: BaseID, id: BasicChunkIdentifier, @@ -44,7 +43,7 @@ object BasicChunk { snap: Term, snapExp: Option[ast.Exp], perm: Term, permExp: Option[ast.Exp], analysisInfo: AnalysisInfo): BasicChunk = { - val chunk = new BasicChunk(resourceID, id, args, argsExp, snap, snapExp, perm, permExp) + val chunk = BasicChunk(resourceID, id, args, argsExp, snap, snapExp, perm, permExp) analysisInfo.assumptionAnalyzer.addPermissionNode(chunk, analysisInfo.sourceInfo, analysisInfo.assumptionType) chunk } @@ -116,6 +115,24 @@ sealed trait QuantifiedBasicChunk extends QuantifiedChunk { def hints: Seq[Term] } +object QuantifiedFieldChunk { + def apply(id: BasicChunkIdentifier, + fvf: Term, + condition: Term, + conditionExp: Option[ast.Exp], + permValue: Term, + permValueExp: Option[ast.Exp], + invs: Option[InverseFunctions], + singletonRcvr: Option[Term], + singletonRcvrExp: Option[ast.Exp], + hints: Seq[Term] = Nil, + analysisInfo: AnalysisInfo): QuantifiedFieldChunk = { + val chunk = QuantifiedFieldChunk(id, fvf, condition, conditionExp, permValue, permValueExp, invs, singletonRcvr, singletonRcvrExp, hints, analysisInfo) + analysisInfo.assumptionAnalyzer.addPermissionNode(chunk, analysisInfo.sourceInfo, analysisInfo.assumptionType) + chunk + } +} + /* TODO: Instead of using the singletonRcvr to differentiate between QP chunks that * provide permissions to a single location and those providing permissions * to potentially multiple locations, consider using regular, non-quantified @@ -130,7 +147,7 @@ case class QuantifiedFieldChunk private(id: BasicChunkIdentifier, invs: Option[InverseFunctions], singletonRcvr: Option[Term], singletonRcvrExp: Option[ast.Exp], - hints: Seq[Term] = Nil) + hints: Seq[Term]) extends QuantifiedBasicChunk { require(fvf.sort.isInstanceOf[terms.sorts.FieldValueFunction], @@ -170,7 +187,28 @@ case class QuantifiedFieldChunk private(id: BasicChunkIdentifier, override lazy val toString = s"${terms.Forall} ${`?r`} :: ${`?r`}.$id -> $fvf # $perm" } -case class QuantifiedPredicateChunk(id: BasicChunkIdentifier, +object QuantifiedPredicateChunk { + def apply(id: BasicChunkIdentifier, + quantifiedVars: Seq[Var], + quantifiedVarExps: Option[Seq[ast.LocalVarDecl]], + psf: Term, + condition: Term, + conditionExp: Option[ast.Exp], + permValue: Term, + permValueExp: Option[ast.Exp], + invs: Option[InverseFunctions], + singletonArgs: Option[Seq[Term]], + singletonArgExps: Option[Seq[ast.Exp]], + hints: Seq[Term] = Nil, + analysisInfo: AnalysisInfo): QuantifiedPredicateChunk = { + val chunk = QuantifiedPredicateChunk(id, quantifiedVars, quantifiedVarExps, psf, condition, conditionExp, permValue, permValueExp, invs, singletonArgs, singletonArgExps, hints) + analysisInfo.assumptionAnalyzer.addPermissionNode(chunk, analysisInfo.sourceInfo, analysisInfo.assumptionType) + chunk + } +} + + +case class QuantifiedPredicateChunk private(id: BasicChunkIdentifier, quantifiedVars: Seq[Var], quantifiedVarExps: Option[Seq[ast.LocalVarDecl]], psf: Term, @@ -181,7 +219,7 @@ case class QuantifiedPredicateChunk(id: BasicChunkIdentifier, invs: Option[InverseFunctions], singletonArgs: Option[Seq[Term]], singletonArgExps: Option[Seq[ast.Exp]], - hints: Seq[Term] = Nil) + hints: Seq[Term]) extends QuantifiedBasicChunk { require(psf.sort.isInstanceOf[terms.sorts.PredicateSnapFunction], s"Quantified predicate chunk values must be of sort PredicateSnapFunction ($psf), but found ${psf.sort}") @@ -211,7 +249,25 @@ case class QuantifiedPredicateChunk(id: BasicChunkIdentifier, override lazy val toString = s"${terms.Forall} ${quantifiedVars.mkString(",")} :: $id(${quantifiedVars.mkString(",")}) -> $psf # $perm" } -case class QuantifiedMagicWandChunk(id: MagicWandIdentifier, +object QuantifiedMagicWandChunk { + def apply(id: MagicWandIdentifier, + quantifiedVars: Seq[Var], + quantifiedVarExps: Option[Seq[ast.LocalVarDecl]], + wsf: Term, + perm: Term, + permExp: Option[ast.Exp], + invs: Option[InverseFunctions], + singletonArgs: Option[Seq[Term]], + singletonArgExps: Option[Seq[ast.Exp]], + hints: Seq[Term] = Nil, + analysisInfo: AnalysisInfo): QuantifiedMagicWandChunk = { + val chunk = QuantifiedMagicWandChunk(id, quantifiedVars, quantifiedVarExps, wsf, perm, permExp, invs, singletonArgs, singletonArgExps, hints) + analysisInfo.assumptionAnalyzer.addPermissionNode(chunk, analysisInfo.sourceInfo, analysisInfo.assumptionType) + chunk + } +} + +case class QuantifiedMagicWandChunk private(id: MagicWandIdentifier, quantifiedVars: Seq[Var], quantifiedVarExps: Option[Seq[ast.LocalVarDecl]], wsf: Term, @@ -220,7 +276,7 @@ case class QuantifiedMagicWandChunk(id: MagicWandIdentifier, invs: Option[InverseFunctions], singletonArgs: Option[Seq[Term]], singletonArgExps: Option[Seq[ast.Exp]], - hints: Seq[Term] = Nil) + hints: Seq[Term]) extends QuantifiedBasicChunk { require(wsf.sort.isInstanceOf[terms.sorts.PredicateSnapFunction] && wsf.sort.asInstanceOf[terms.sorts.PredicateSnapFunction].codomainSort == sorts.Snap, s"Quantified magic wand chunk values must be of sort MagicWandSnapFunction ($wsf), but found ${wsf.sort}") @@ -280,7 +336,7 @@ object MagicWandChunk { } } -case class MagicWandChunk(id: MagicWandIdentifier, +case class MagicWandChunk private(id: MagicWandIdentifier, bindings: Map[ast.AbstractLocalVar, (Term, Option[ast.Exp])], args: Seq[Term], argsExp: Option[Seq[ast.Exp]], From e8c7a261b58a4c7d28f4825beb090032f8526a52 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 13 May 2025 09:08:43 +0200 Subject: [PATCH 033/474] collect source exp in Evaluator --- .../AssumptionAnalysisGraph.scala | 29 ++++++++++++++----- .../AssumptionAnalyzer.scala | 4 ++- src/main/scala/decider/Decider.scala | 7 ++++- src/main/scala/rules/Evaluator.scala | 2 ++ src/test/resources/andrea/method-sum.vpr | 2 +- 5 files changed, 33 insertions(+), 11 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index 23545d9ea..7b9b99ef2 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -1,7 +1,6 @@ package viper.silicon.assumptionAnalysis import viper.silicon.interfaces.state.Chunk -import viper.silicon.state.terms.Term import viper.silver.ast import java.util.concurrent.atomic.AtomicInteger @@ -32,6 +31,18 @@ trait AssumptionAnalysisGraph { def addEdges(sources: Iterable[Int], target: Int): Unit def addEdges(sources: Iterable[Int], targets: Iterable[Int]): Unit + def getExplicitAndAssertNodesOnly(): Seq[AssumptionAnalysisNode] = { + nodes.filter(n => n.assumptionType.equals(AssumptionType.Explicit) || n.isInstanceOf[GeneralAssertionNode]) + } + + def getImplicitNodesOnly(): Seq[AssumptionAnalysisNode] = { + getNodesByAssumptionType(AssumptionType.Implicit) + } + + def getNodesByAssumptionType(assumptionType: AssumptionType): Seq[AssumptionAnalysisNode] = { + nodes.filter(n => n.assumptionType.equals(assumptionType)) + } + // def findDependentAssumptions(assertion, enableTransitivity=false) // def findDependentAssertions(assumption, enableTransitivity=false) // def findUnnecessaryAssumptions(enableTransitivity=false) @@ -84,6 +95,8 @@ trait AssumptionAnalysisNode { } } +trait GeneralAssumptionNode extends AssumptionAnalysisNode {} +trait GeneralAssertionNode extends AssumptionAnalysisNode {} trait ChunkAnalysisInfo { val chunk: Chunk @@ -91,31 +104,31 @@ trait ChunkAnalysisInfo { def getChunk: Chunk = chunk } -case class SimpleAssumptionNode(assumption: ast.Exp, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Unknown) extends AssumptionAnalysisNode { +case class SimpleAssumptionNode(assumption: ast.Exp, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Unknown) extends GeneralAssumptionNode { override def getNodeString: String ="assume " + assumption.toString } -case class StringAssumptionNode(description: String, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Unknown) extends AssumptionAnalysisNode { +case class StringAssumptionNode(description: String, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Unknown) extends GeneralAssumptionNode { override def getNodeString: String = "assume " + description } -case class SimpleAssertionNode(assertion: ast.Exp, isAsserted: Boolean, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Explicit) extends AssumptionAnalysisNode { +case class SimpleAssertionNode(assertion: ast.Exp, isAsserted: Boolean, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Explicit) extends GeneralAssertionNode { override def getNodeString: String = "assert " + assertion.toString } -case class StringAssertionNode(description: String, isAsserted: Boolean, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Explicit) extends AssumptionAnalysisNode { +case class StringAssertionNode(description: String, isAsserted: Boolean, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Explicit) extends GeneralAssertionNode { override def getNodeString: String = "assert " + description } -case class PermissionInhaleNode(chunk: Chunk, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Unknown) extends AssumptionAnalysisNode with ChunkAnalysisInfo { +case class PermissionInhaleNode(chunk: Chunk, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Unknown) extends GeneralAssumptionNode with ChunkAnalysisInfo { override def getNodeString: String = "inhale " + chunk.toString } -case class PermissionExhaleNode(chunk: Chunk, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Explicit) extends AssumptionAnalysisNode with ChunkAnalysisInfo { +case class PermissionExhaleNode(chunk: Chunk, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Explicit) extends GeneralAssertionNode with ChunkAnalysisInfo { override def getNodeString: String = "exhale " + chunk.toString } -case class PermissionAssertNode(chunk: Chunk, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Explicit) extends AssumptionAnalysisNode with ChunkAnalysisInfo { +case class PermissionAssertNode(chunk: Chunk, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Explicit) extends GeneralAssertionNode with ChunkAnalysisInfo { override def getNodeString: String = "assert " + chunk.toString } diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 6190a863d..0cc09110a 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -1,9 +1,10 @@ package viper.silicon.assumptionAnalysis +import viper.silicon.Stack import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType +import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.state.Chunk -import viper.silicon.state.terms.Term import viper.silver.ast import viper.silver.ast.{Member, NoPosition} @@ -32,6 +33,7 @@ trait AssumptionAnalyzer { val assumptionGraph: AssumptionAnalysisGraph = new DefaultAssumptionAnalysisGraph() var currentAnalysisInfo: AnalysisInfo = new NoAnalysisInfo() + var currentExpStack: InsertionOrderedSet[ast.Exp] = InsertionOrderedSet.empty def setCurrentAnalysisInfo(analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): AnalysisInfo = { currentAnalysisInfo = AnalysisInfo(this, analysisSourceInfo, assumptionType) diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index dc6bea603..ba8653740 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -321,6 +321,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } val filteredAssumptionsWithLabels = filteredAssumptions map{case (t, de) => + val sourceExp = assumptionAnalyzer.currentExpStack.headOption val assumptionId: Option[Int] = if(de.isDefined) assumptionAnalyzer.addSingleAssumption(de.get, analysisInfo) else None (t, AssumptionAnalyzer.createAssumptionLabel(assumptionId)) } @@ -333,6 +334,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => def assume(assumptions: Seq[Term], debugExps: Option[Seq[DebugExp]]): Unit = { val assumptionIds = if(debugExps.isDefined) assumptionAnalyzer.addAssumptions(debugExps.get, assumptionAnalyzer.currentAnalysisInfo) else Seq.empty + val sourceExp = assumptionAnalyzer.currentExpStack.headOption val assumptionsWithLabels = if(assumptions.size == assumptionIds.size) assumptions.zip(assumptionIds).map{case (t, id) => (t, AssumptionAnalyzer.createAssumptionLabel(Some(id)))} else assumptions map (t => (t, "")) @@ -352,6 +354,8 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => if (debugMode) { addDebugExp(debugExp.get.withTerm(And(filteredTerms))) + + val sourceExp = assumptionAnalyzer.currentExpStack.headOption val assumptionId: Option[Int] = if(debugExp.isDefined) assumptionAnalyzer.addSingleAssumption(debugExp.get.withTerm(And(filteredTerms)), assumptionAnalyzer.currentAnalysisInfo) else None val termsWithLabel = filteredTerms.zipWithIndex.iterator.map {case (t, idx) => (t, AssumptionAnalyzer.createAssumptionLabel(assumptionId, idx))}.toSeq assumeWithoutSmokeChecks(InsertionOrderedSet(termsWithLabel)) @@ -427,7 +431,8 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => val sepIdentifier = symbExLog.openScope(assertRecord) val asserted = if(Verifier.config.enableAssumptionAnalysis()) t.equals(True) else isKnownToBeTrue(t) - val assertionId: Option[Int] = if(!asserted) assumptionAnalyzer.addAssertion(e, false, decider.assumptionAnalyzer.currentAnalysisInfo) else None + val sourceExp = assumptionAnalyzer.currentExpStack.headOption + val assertionId: Option[Int] = if(!asserted) assumptionAnalyzer.addAssertion(e, false, decider.assumptionAnalyzer.currentAnalysisInfo.withAssumptionType(AssumptionType.Assertion)) else None val result = asserted || proverAssert(t, timeout, AssumptionAnalyzer.createAssertionLabel(assertionId)) symbExLog.closeScope(sepIdentifier) diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 097ae1845..6ce3c75db 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -93,7 +93,9 @@ object evaluator extends EvaluationRules { : VerificationResult = { val sepIdentifier = v.symbExLog.openScope(new EvaluateRecord(e, s, v.decider.pcs)) + v.decider.assumptionAnalyzer.currentExpStack = InsertionOrderedSet(Set(e) ++ v.decider.assumptionAnalyzer.currentExpStack) eval3(s, e, pve, v)((s1, t, eNew, v1) => { + v.decider.assumptionAnalyzer.currentExpStack = v.decider.assumptionAnalyzer.currentExpStack.tail v1.symbExLog.closeScope(sepIdentifier) Q(s1, t, eNew, v1)}) } diff --git a/src/test/resources/andrea/method-sum.vpr b/src/test/resources/andrea/method-sum.vpr index 78f0e6d94..e5733af98 100644 --- a/src/test/resources/andrea/method-sum.vpr +++ b/src/test/resources/andrea/method-sum.vpr @@ -14,7 +14,7 @@ method foo(x: Int, y: Int) assume y >= 0 assume x < y var n: Int := sum(x, y) - var n2: Int := sum(x, y) + var n2: Int := sum(x/y, y) n := n + n2 assert n == 2*x + 2*y From b2634eca788fcbb43d38d79bbf83aa0d00d24f19 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 13 May 2025 09:12:35 +0200 Subject: [PATCH 034/474] collect analysis infos for methods and functions --- src/main/scala/rules/Consumer.scala | 3 ++- src/main/scala/rules/Executor.scala | 9 ++++++--- src/main/scala/rules/Producer.scala | 1 + src/main/scala/supporters/MethodSupporter.scala | 9 ++++++--- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index 4d877cd5a..45df3c726 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -8,7 +8,7 @@ package viper.silicon.rules import viper.silicon.debugger.DebugExp import viper.silicon.Config.JoinMode -import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType, StringAnalysisSourceInfo} +import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType, ExpAnalysisSourceInfo, StringAnalysisSourceInfo} import scala.collection.mutable import viper.silver.ast @@ -197,6 +197,7 @@ object consumer extends ConsumptionRules { */ v.logger.debug(s"\nCONSUME ${viper.silicon.utils.ast.sourceLineColumn(a)}: $a") +// v.decider.assumptionAnalyzer.updateCurrentAnalysisInfo(ExpAnalysisSourceInfo(a)) // TODO ake v.logger.debug(v.stateFormatter.format(s, v.decider.pcs)) v.logger.debug("h = " + v.stateFormatter.format(h)) if (s.reserveHeaps.nonEmpty) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 7f8ea6f6b..1aa7d4688 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -9,7 +9,7 @@ package viper.silicon.rules import viper.silicon.debugger.DebugExp import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.Config.JoinMode -import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType, ExpAnalysisSourceInfo, PermissionInhaleNode, StmtAnalysisSourceInfo} +import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType, ExpAnalysisSourceInfo, PermissionInhaleNode, StmtAnalysisSourceInfo, StringAnalysisSourceInfo} import scala.annotation.unused import viper.silver.cfg.silver.SilverCfg @@ -262,6 +262,7 @@ object executor extends ExecutionRules { (executionFlowController.locally(sBody, v)((s0, v0) => { v0.decider.prover.comment("Loop head block: Check well-definedness of invariant") val mark = v0.decider.setPathConditionMark() + v.decider.assumptionAnalyzer.setCurrentAnalysisInfo(if(invs.isEmpty) StringAnalysisSourceInfo("invariants", ast.NoPosition) else ExpAnalysisSourceInfo(invs.head), AssumptionType.Explicit) produces(s0, freshSnap, invs, ContractNotWellformed, v0)((s1, v1) => { phase1data = phase1data :+ (s1, v1.decider.pcs.after(mark), @@ -270,7 +271,8 @@ object executor extends ExecutionRules { })}) combine executionFlowController.locally(s, v)((s0, v0) => { v0.decider.prover.comment("Loop head block: Establish invariant") - consumes(s0, invs, false, LoopInvariantNotEstablished, v0, AnalysisInfo(v0.decider.assumptionAnalyzer, ExpAnalysisSourceInfo(invs.head), AssumptionType.Assertion))((sLeftover, _, v1) => { + v0.decider.assumptionAnalyzer.setCurrentAnalysisInfo(if(invs.isEmpty) StringAnalysisSourceInfo("no invariants", ast.NoPosition) else ExpAnalysisSourceInfo(invs.head), AssumptionType.Assertion) + consumes(s0, invs, false, LoopInvariantNotEstablished, v0, v0.decider.assumptionAnalyzer.currentAnalysisInfo)((sLeftover, _, v1) => { v1.decider.prover.comment("Loop head block: Execute statements of loop head block (in invariant state)") phase1data.foldLeft(Success(): VerificationResult) { case (result, _) if !result.continueVerification => result @@ -304,7 +306,8 @@ object executor extends ExecutionRules { * attempting to re-establish the invariant. */ v.decider.prover.comment("Loop head block: Re-establish invariant") - consumes(s, invs, false, e => LoopInvariantNotPreserved(e), v, AnalysisInfo(v.decider.assumptionAnalyzer, ExpAnalysisSourceInfo(invs.head), AssumptionType.Assertion))((_, _, _) => + v.decider.assumptionAnalyzer.setCurrentAnalysisInfo(if(invs.isEmpty) StringAnalysisSourceInfo("no invariants", ast.NoPosition) else ExpAnalysisSourceInfo(invs.head), AssumptionType.Assertion) + consumes(s, invs, false, e => LoopInvariantNotPreserved(e), v, v.decider.assumptionAnalyzer.currentAnalysisInfo)((_, _, _) => Success()) } } diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index c955fe3d9..49b7e0bf8 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -213,6 +213,7 @@ object producer extends ProductionRules { : VerificationResult = { v.logger.debug(s"\nPRODUCE ${viper.silicon.utils.ast.sourceLineColumn(a)}: $a") +// v.decider.assumptionAnalyzer.updateCurrentAnalysisInfo(ExpAnalysisSourceInfo(a)) TODO ake v.logger.debug(v.stateFormatter.format(s, v.decider.pcs)) val Q: (State, Verifier) => VerificationResult = (state, verifier) => diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index b27b5f7af..83cbb968b 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -101,6 +101,7 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif * rules in Smans' paper. */ executionFlowController.locally(s, v)((s1, v1) => { + v1.decider.assumptionAnalyzer.setCurrentAnalysisInfo(if(pres.isEmpty) StringAnalysisSourceInfo("no precondition", ast.NoPosition) else ExpAnalysisSourceInfo(pres.head), AssumptionType.Explicit) produces(s1, freshSnap, pres, ContractNotWellformed, v1)((s2, v2) => { v2.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) val s2a = s2.copy(oldHeaps = s2.oldHeaps + (Verifier.PRE_STATE_LABEL -> s2.h)) @@ -108,14 +109,16 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif val s4 = s3.copy(h = Heap()) val impLog = new WellformednessCheckRecord(posts, s, v.decider.pcs) val sepIdentifier = symbExLog.openScope(impLog) + v3.decider.assumptionAnalyzer.setCurrentAnalysisInfo(if(posts.isEmpty) StringAnalysisSourceInfo("no postcondition", ast.NoPosition) else ExpAnalysisSourceInfo(posts.head), AssumptionType.Implicit) produces(s4, freshSnap, posts, ContractNotWellformed, v3)((_, _) => { symbExLog.closeScope(sepIdentifier) Success()})}) && { executionFlowController.locally(s2a, v2)((s3, v3) => { - exec(s3, body, v3)((s4, v4) => - consumes(s4, posts, false, postViolated, v4, AnalysisInfo(v.decider.assumptionAnalyzer, if(posts.isEmpty) StringAnalysisSourceInfo("no postcondition", ast.NoPosition) else ExpAnalysisSourceInfo(posts.head), AssumptionType.Assertion))((_, _, _) => - Success()))}) } )})}) + exec(s3, body, v3)((s4, v4) =>{ + v4.decider.assumptionAnalyzer.setCurrentAnalysisInfo(if(posts.isEmpty) StringAnalysisSourceInfo("no postcondition", ast.NoPosition) else ExpAnalysisSourceInfo(posts.head), AssumptionType.Assertion) + consumes(s4, posts, false, postViolated, v4, v4.decider.assumptionAnalyzer.currentAnalysisInfo)((_, _, _) => + Success())})}) } )})}) result.assumptionAnalyzer = v.decider.assumptionAnalyzer v.decider.removeAssumptionAnalyzer() From a54aeddad442bc55ced154ec2588d23ae68f003e Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 13 May 2025 09:41:11 +0200 Subject: [PATCH 035/474] make chunk constructors private --- src/main/scala/rules/MagicWandSupporter.scala | 2 +- src/main/scala/state/Chunks.scala | 93 ++++++++++++++----- 2 files changed, 73 insertions(+), 22 deletions(-) diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 02743f3af..b36aa5caa 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -94,7 +94,7 @@ object magicWandSupporter extends SymbolicExecutionRules { (Q: (State, MagicWandChunk, Verifier) => VerificationResult) : VerificationResult = { evaluateWandArguments(s, wand, pve, v)((s1, ts, esNew, v1) => { - val newChunk = MagicWandChunk.apply(MagicWandIdentifier(wand, s.program), s1.g.values, ts, esNew, snap, FullPerm, + val newChunk = MagicWandChunk(MagicWandIdentifier(wand, s.program), s1.g.values, ts, esNew, snap, FullPerm, Option.when(withExp)(ast.FullPerm()(wand.pos, wand.info, wand.errT)), v.decider.assumptionAnalyzer.currentAnalysisInfo) Q(s1, newChunk, v1) }) diff --git a/src/main/scala/state/Chunks.scala b/src/main/scala/state/Chunks.scala index ea0d68ea3..334429e19 100644 --- a/src/main/scala/state/Chunks.scala +++ b/src/main/scala/state/Chunks.scala @@ -30,12 +30,11 @@ case class BasicChunkIdentifier(name: String) extends ChunkIdentifer { } object BasicChunk { - // TODO ake: remove once every apply was adapted to the new version - def apply(resourceID: BaseID, id: BasicChunkIdentifier, + private def apply(resourceID: BaseID, id: BasicChunkIdentifier, args: Seq[Term], argsExp: Option[Seq[ast.Exp]], snap: Term, snapExp: Option[ast.Exp], perm: Term, permExp: Option[ast.Exp]): BasicChunk = { - BasicChunk(resourceID, id, args, argsExp, snap, snapExp, perm, permExp) + new BasicChunk(resourceID, id, args, argsExp, snap, snapExp, perm, permExp) } def apply(resourceID: BaseID, id: BasicChunkIdentifier, @@ -43,7 +42,7 @@ object BasicChunk { snap: Term, snapExp: Option[ast.Exp], perm: Term, permExp: Option[ast.Exp], analysisInfo: AnalysisInfo): BasicChunk = { - val chunk = BasicChunk(resourceID, id, args, argsExp, snap, snapExp, perm, permExp) + val chunk = new BasicChunk(resourceID, id, args, argsExp, snap, snapExp, perm, permExp) analysisInfo.assumptionAnalyzer.addPermissionNode(chunk, analysisInfo.sourceInfo, analysisInfo.assumptionType) chunk } @@ -57,7 +56,7 @@ object BasicChunk { snapExp: Option[ast.Exp], perm: Term, permExp: Option[ast.Exp], analysisInfo: AnalysisInfo): BasicChunk = { - val newChunk = apply(resourceID, id, args, argsExp, snap, snapExp, perm, permExp) + val newChunk = BasicChunk(resourceID, id, args, argsExp, snap, snapExp, perm, permExp, analysisInfo) analysisInfo.assumptionAnalyzer.addPermissionDependencies(oldChunks, PermissionInhaleNode(newChunk, analysisInfo.sourceInfo, analysisInfo.assumptionType)) newChunk } @@ -85,8 +84,8 @@ case class BasicChunk private (resourceID: BaseID, withPerm(PermMinus(perm, newPerm), newPermExp.map(npe => ast.PermSub(permExp.get, npe)())) override protected def permPlus(newPerm: Term, newPermExp: Option[ast.Exp]): BasicChunk = withPerm(PermPlus(perm, newPerm), newPermExp.map(npe => ast.PermAdd(permExp.get, npe)())) - override protected def withPerm(newPerm: Term, newPermExp: Option[ast.Exp]): BasicChunk = BasicChunk(resourceID, id, args, argsExp, snap, snapExp, newPerm, newPermExp) - override protected def withSnap(newSnap: Term, newSnapExp: Option[ast.Exp]): BasicChunk = BasicChunk(resourceID, id, args, argsExp, newSnap, newSnapExp, perm, permExp) + override protected def withPerm(newPerm: Term, newPermExp: Option[ast.Exp]): BasicChunk = new BasicChunk(resourceID, id, args, argsExp, snap, snapExp, newPerm, newPermExp) + override protected def withSnap(newSnap: Term, newSnapExp: Option[ast.Exp]): BasicChunk = new BasicChunk(resourceID, id, args, argsExp, newSnap, newSnapExp, perm, permExp) override lazy val toString = resourceID match { case FieldID => s"${args.head}.$id -> $snap # $perm" @@ -116,6 +115,19 @@ sealed trait QuantifiedBasicChunk extends QuantifiedChunk { } object QuantifiedFieldChunk { + private def apply(id: BasicChunkIdentifier, + fvf: Term, + condition: Term, + conditionExp: Option[ast.Exp], + permValue: Term, + permValueExp: Option[ast.Exp], + invs: Option[InverseFunctions], + singletonRcvr: Option[Term], + singletonRcvrExp: Option[ast.Exp], + hints: Seq[Term]): QuantifiedFieldChunk = { + new QuantifiedFieldChunk(id, fvf, condition, conditionExp, permValue, permValueExp, invs, singletonRcvr, singletonRcvrExp, hints) + } + def apply(id: BasicChunkIdentifier, fvf: Term, condition: Term, @@ -127,7 +139,7 @@ object QuantifiedFieldChunk { singletonRcvrExp: Option[ast.Exp], hints: Seq[Term] = Nil, analysisInfo: AnalysisInfo): QuantifiedFieldChunk = { - val chunk = QuantifiedFieldChunk(id, fvf, condition, conditionExp, permValue, permValueExp, invs, singletonRcvr, singletonRcvrExp, hints, analysisInfo) + val chunk = new QuantifiedFieldChunk(id, fvf, condition, conditionExp, permValue, permValueExp, invs, singletonRcvr, singletonRcvrExp, hints) analysisInfo.assumptionAnalyzer.addPermissionNode(chunk, analysisInfo.sourceInfo, analysisInfo.assumptionType) chunk } @@ -174,20 +186,36 @@ case class QuantifiedFieldChunk private(id: BasicChunkIdentifier, } override protected def withPerm(newPerm: Term, newPermExp: Option[ast.Exp]) = - QuantifiedFieldChunk(id, fvf, condition, conditionExp, newPerm, newPermExp, invs, singletonRcvr, singletonRcvrExp, hints) + new QuantifiedFieldChunk(id, fvf, condition, conditionExp, newPerm, newPermExp, invs, singletonRcvr, singletonRcvrExp, hints) override protected def applyCondition(newCond: Term, newCondExp: Option[ast.Exp]) = - QuantifiedFieldChunk(id, fvf, terms.And(newCond, condition), newCondExp.map(nce => ast.And(nce, conditionExp.get)()), permValue, permValueExp, invs, singletonRcvr, singletonRcvrExp, hints) + new QuantifiedFieldChunk(id, fvf, terms.And(newCond, condition), newCondExp.map(nce => ast.And(nce, conditionExp.get)()), permValue, permValueExp, invs, singletonRcvr, singletonRcvrExp, hints) override protected def permMinus(newPerm: Term, newPermExp: Option[ast.Exp]) = withPerm(PermMinus(permValue, newPerm), newPermExp.map(npe => ast.PermSub(permValueExp.get, npe)())) override protected def permPlus(newPerm: Term, newPermExp: Option[ast.Exp]) = withPerm(PermPlus(permValue, newPerm), newPermExp.map(npe => ast.PermAdd(permValueExp.get, npe)())) override protected def withSnapshotMap(newFvf: Term) = - QuantifiedFieldChunk(id, newFvf, condition, conditionExp, permValue, permValueExp, invs, singletonRcvr, singletonRcvrExp, hints) + new QuantifiedFieldChunk(id, newFvf, condition, conditionExp, permValue, permValueExp, invs, singletonRcvr, singletonRcvrExp, hints) override lazy val toString = s"${terms.Forall} ${`?r`} :: ${`?r`}.$id -> $fvf # $perm" } object QuantifiedPredicateChunk { + private def apply(id: BasicChunkIdentifier, + quantifiedVars: Seq[Var], + quantifiedVarExps: Option[Seq[ast.LocalVarDecl]], + psf: Term, + condition: Term, + conditionExp: Option[ast.Exp], + permValue: Term, + permValueExp: Option[ast.Exp], + invs: Option[InverseFunctions], + singletonArgs: Option[Seq[Term]], + singletonArgExps: Option[Seq[ast.Exp]], + hints: Seq[Term]): QuantifiedPredicateChunk = { + new QuantifiedPredicateChunk(id, quantifiedVars, quantifiedVarExps, psf, condition, conditionExp, permValue, permValueExp, invs, singletonArgs, singletonArgExps, hints) + } + + def apply(id: BasicChunkIdentifier, quantifiedVars: Seq[Var], quantifiedVarExps: Option[Seq[ast.LocalVarDecl]], @@ -201,7 +229,7 @@ object QuantifiedPredicateChunk { singletonArgExps: Option[Seq[ast.Exp]], hints: Seq[Term] = Nil, analysisInfo: AnalysisInfo): QuantifiedPredicateChunk = { - val chunk = QuantifiedPredicateChunk(id, quantifiedVars, quantifiedVarExps, psf, condition, conditionExp, permValue, permValueExp, invs, singletonArgs, singletonArgExps, hints) + val chunk = new QuantifiedPredicateChunk(id, quantifiedVars, quantifiedVarExps, psf, condition, conditionExp, permValue, permValueExp, invs, singletonArgs, singletonArgExps, hints) analysisInfo.assumptionAnalyzer.addPermissionNode(chunk, analysisInfo.sourceInfo, analysisInfo.assumptionType) chunk } @@ -236,20 +264,33 @@ case class QuantifiedPredicateChunk private(id: BasicChunkIdentifier, override def valueAt(args: Seq[Term]) = PredicateLookup(id.name, psf, args) override protected def withPerm(newPerm: Term, newPermExp: Option[ast.Exp]) = - QuantifiedPredicateChunk(id, quantifiedVars, quantifiedVarExps, psf, condition, conditionExp, newPerm, newPermExp, invs, singletonArgs, singletonArgExps, hints) + new QuantifiedPredicateChunk(id, quantifiedVars, quantifiedVarExps, psf, condition, conditionExp, newPerm, newPermExp, invs, singletonArgs, singletonArgExps, hints) override protected def applyCondition(newCond: Term, newCondExp: Option[ast.Exp]) = - QuantifiedPredicateChunk(id, quantifiedVars, quantifiedVarExps, psf, terms.And(newCond, condition), newCondExp.map(nce => ast.And(nce, conditionExp.get)()), permValue, permValueExp, invs, singletonArgs, singletonArgExps, hints) + new QuantifiedPredicateChunk(id, quantifiedVars, quantifiedVarExps, psf, terms.And(newCond, condition), newCondExp.map(nce => ast.And(nce, conditionExp.get)()), permValue, permValueExp, invs, singletonArgs, singletonArgExps, hints) override protected def permMinus(newPerm: Term, newPermExp: Option[ast.Exp]) = withPerm(PermMinus(permValue, newPerm), newPermExp.map(npe => ast.PermSub(permValueExp.get, npe)())) override protected def permPlus(newPerm: Term, newPermExp: Option[ast.Exp]) = withPerm(PermPlus(permValue, newPerm), newPermExp.map(npe => ast.PermAdd(permValueExp.get, npe)())) override protected def withSnapshotMap(newPsf: Term) = - QuantifiedPredicateChunk(id, quantifiedVars, quantifiedVarExps, newPsf, condition, conditionExp, permValue, permValueExp, invs, singletonArgs, singletonArgExps, hints) + new QuantifiedPredicateChunk(id, quantifiedVars, quantifiedVarExps, newPsf, condition, conditionExp, permValue, permValueExp, invs, singletonArgs, singletonArgExps, hints) override lazy val toString = s"${terms.Forall} ${quantifiedVars.mkString(",")} :: $id(${quantifiedVars.mkString(",")}) -> $psf # $perm" } object QuantifiedMagicWandChunk { + private def apply(id: MagicWandIdentifier, + quantifiedVars: Seq[Var], + quantifiedVarExps: Option[Seq[ast.LocalVarDecl]], + wsf: Term, + perm: Term, + permExp: Option[ast.Exp], + invs: Option[InverseFunctions], + singletonArgs: Option[Seq[Term]], + singletonArgExps: Option[Seq[ast.Exp]], + hints: Seq[Term]): QuantifiedMagicWandChunk = { + new QuantifiedMagicWandChunk(id, quantifiedVars, quantifiedVarExps, wsf, perm, permExp, invs, singletonArgs, singletonArgExps, hints) + } + def apply(id: MagicWandIdentifier, quantifiedVars: Seq[Var], quantifiedVarExps: Option[Seq[ast.LocalVarDecl]], @@ -261,7 +302,7 @@ object QuantifiedMagicWandChunk { singletonArgExps: Option[Seq[ast.Exp]], hints: Seq[Term] = Nil, analysisInfo: AnalysisInfo): QuantifiedMagicWandChunk = { - val chunk = QuantifiedMagicWandChunk(id, quantifiedVars, quantifiedVarExps, wsf, perm, permExp, invs, singletonArgs, singletonArgExps, hints) + val chunk = new QuantifiedMagicWandChunk(id, quantifiedVars, quantifiedVarExps, wsf, perm, permExp, invs, singletonArgs, singletonArgExps, hints) analysisInfo.assumptionAnalyzer.addPermissionNode(chunk, analysisInfo.sourceInfo, analysisInfo.assumptionType) chunk } @@ -297,9 +338,9 @@ case class QuantifiedMagicWandChunk private(id: MagicWandIdentifier, override protected def permPlus(newPerm: Term, newPermExp: Option[ast.Exp]) = withPerm(PermPlus(perm, newPerm), newPermExp.map(npe => ast.PermAdd(permExp.get, npe)())) override protected def withPerm(newPerm: Term, newPermExp: Option[ast.Exp]) = - QuantifiedMagicWandChunk(id, quantifiedVars, quantifiedVarExps, wsf, newPerm, newPermExp, invs, singletonArgs, singletonArgExps, hints) + new QuantifiedMagicWandChunk(id, quantifiedVars, quantifiedVarExps, wsf, newPerm, newPermExp, invs, singletonArgs, singletonArgExps, hints) override protected def withSnapshotMap(newWsf: Term) = - QuantifiedMagicWandChunk(id, quantifiedVars, quantifiedVarExps, newWsf, perm, permExp, invs, singletonArgs, singletonArgExps, hints) + new QuantifiedMagicWandChunk(id, quantifiedVars, quantifiedVarExps, newWsf, perm, permExp, invs, singletonArgs, singletonArgExps, hints) override lazy val toString = s"${terms.Forall} ${quantifiedVars.mkString(",")} :: $id(${quantifiedVars.mkString(",")}) -> $wsf # $perm" } @@ -322,6 +363,16 @@ object MagicWandIdentifier { } object MagicWandChunk { + private def apply(id: MagicWandIdentifier, + bindings: Map[ast.AbstractLocalVar, (Term, Option[ast.Exp])], + args: Seq[Term], + argsExp: Option[Seq[ast.Exp]], + snap: MagicWandSnapshot, + perm: Term, + permExp: Option[ast.Exp]): MagicWandChunk = { + new MagicWandChunk(id, bindings, args, argsExp, snap, perm, permExp) + } + def apply(id: MagicWandIdentifier, bindings: Map[ast.AbstractLocalVar, (Term, Option[ast.Exp])], args: Seq[Term], @@ -330,7 +381,7 @@ object MagicWandChunk { perm: Term, permExp: Option[ast.Exp], analysisInfo: AnalysisInfo): MagicWandChunk = { - val chunk = MagicWandChunk(id, bindings, args, argsExp, snap, perm, permExp) + val chunk = new MagicWandChunk(id, bindings, args, argsExp, snap, perm, permExp) analysisInfo.assumptionAnalyzer.addPermissionNode(chunk, analysisInfo.sourceInfo, analysisInfo.assumptionType) chunk } @@ -355,12 +406,12 @@ case class MagicWandChunk private(id: MagicWandIdentifier, withPerm(PermMinus(perm, newPerm), newPermExp.map(npe => ast.PermSub(permExp.get, npe)())) override protected def permPlus(newPerm: Term, newPermExp: Option[ast.Exp]) = withPerm(PermPlus(perm, newPerm), newPermExp.map(npe => ast.PermAdd(permExp.get, npe)())) - override protected def withPerm(newPerm: Term, newPermExp: Option[ast.Exp]) = MagicWandChunk(id, bindings, args, argsExp, snap, newPerm, newPermExp) + override protected def withPerm(newPerm: Term, newPermExp: Option[ast.Exp]) = new MagicWandChunk(id, bindings, args, argsExp, snap, newPerm, newPermExp) override protected def withSnap(newSnap: Term, newSnapExp: Option[ast.Exp]) = { assert(newSnapExp.isEmpty) newSnap match { - case s: MagicWandSnapshot => MagicWandChunk(id, bindings, args, argsExp, s, perm, permExp) + case s: MagicWandSnapshot => new MagicWandChunk(id, bindings, args, argsExp, s, perm, permExp) case _ => sys.error(s"MagicWand snapshot has to be of type MagicWandSnapshot but found ${newSnap.getClass}") } } From 6165f15dba24bcea93036b0a4e41cf4a3e7f7e09 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 13 May 2025 11:28:43 +0200 Subject: [PATCH 036/474] add chunk nodes for asserts and exhales --- .../AssumptionAnalysisGraph.scala | 11 ++++++++++ .../AssumptionAnalyzer.scala | 22 ++++++++++++++++--- src/main/scala/rules/ChunkSupporter.scala | 2 ++ src/main/scala/state/Chunks.scala | 10 ++++----- 4 files changed, 37 insertions(+), 8 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index 7b9b99ef2..2891ff62b 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -43,6 +43,17 @@ trait AssumptionAnalysisGraph { nodes.filter(n => n.assumptionType.equals(assumptionType)) } + def getNodesPerChunk(): mutable.HashMap[Chunk, Seq[AssumptionAnalysisNode]] = { + val res = new mutable.HashMap[Chunk, Seq[AssumptionAnalysisNode]]() + nodes filter (_.isInstanceOf[ChunkAnalysisInfo]) foreach {n => + res.updateWith(n.asInstanceOf[ChunkAnalysisInfo].getChunk)({ + case Some(ns) => Some(ns ++ Seq(n)) + case None => Some(Seq(n)) + }) + } + res + } + // def findDependentAssumptions(assertion, enableTransitivity=false) // def findDependentAssertions(assumption, enableTransitivity=false) // def findUnnecessaryAssumptions(enableTransitivity=false) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 0cc09110a..74c074557 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -12,7 +12,9 @@ import viper.silver.ast.{Member, NoPosition} trait AssumptionAnalyzer { // def pushScope(stmt: ast.Stmt): Unit // def closeScope(): Unit - def addPermissionNode(chunk: Chunk, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] + def addPermissionInhaleNode(chunk: Chunk, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] + def addPermissionAssertNode(chunk: Chunk, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Assertion): Option[Int] + def addPermissionExhaleNode(chunk: Chunk, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Assertion): Option[Int] def addSingleAssumption(assumption: ast.Exp, analysisInfo: AnalysisInfo): Option[Int] @@ -155,12 +157,24 @@ class DefaultAssumptionAnalyzer(member: Member) extends AssumptionAnalyzer { assumptionGraph.addEdges(assumptionIds, assertionIds) } - override def addPermissionNode(chunk: Chunk, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { + override def addPermissionInhaleNode(chunk: Chunk, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { val node = PermissionInhaleNode(chunk, sourceInfo, assumptionType) addNode(node) Some(node.id) } + override def addPermissionAssertNode(chunk: Chunk, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Assertion): Option[Int] = { + val node = PermissionAssertNode(chunk, sourceInfo, assumptionType) + addNode(node) + Some(node.id) + } + + override def addPermissionExhaleNode(chunk: Chunk, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Assertion): Option[Int] = { + val node = PermissionExhaleNode(chunk, sourceInfo, assumptionType) + addNode(node) + Some(node.id) + } + override def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNode: AssumptionAnalysisNode): Unit = { val analysisChunks = assumptionGraph.nodes .filter(c => c.isInstanceOf[ChunkAnalysisInfo] && oldChunks.contains(c.asInstanceOf[ChunkAnalysisInfo].getChunk)) @@ -182,7 +196,9 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { override def processUnsatCoreAndAddDependencies(dep: String): Unit = { } - override def addPermissionNode(chunk: Chunk, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None + override def addPermissionInhaleNode(chunk: Chunk, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None + override def addPermissionExhaleNode(chunk: Chunk, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None + override def addPermissionAssertNode(chunk: Chunk, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None override def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNode: AssumptionAnalysisNode): Unit = { } diff --git a/src/main/scala/rules/ChunkSupporter.scala b/src/main/scala/rules/ChunkSupporter.scala index 2fdd077f3..2b9779d28 100644 --- a/src/main/scala/rules/ChunkSupporter.scala +++ b/src/main/scala/rules/ChunkSupporter.scala @@ -176,6 +176,7 @@ object chunkSupporter extends ChunkSupportRules { case Some(ch) => if (s.assertReadAccessOnly) { if (v.decider.check(Implies(IsPositive(perms), IsPositive(ch.perm)), Verifier.config.assertTimeout.getOrElse(0))) { + v.decider.assumptionAnalyzer.addPermissionAssertNode(ch, v.decider.assumptionAnalyzer.currentAnalysisInfo.sourceInfo) (Complete(), s, h, Some(ch)) } else { (Incomplete(perms, permsExp), s, h, None) @@ -259,6 +260,7 @@ object chunkSupporter extends ChunkSupportRules { val findRes = findChunk[NonQuantifiedChunk](h.values, id, args, v) findRes match { case Some(ch) if v.decider.check(IsPositive(ch.perm), Verifier.config.checkTimeout()) => + v.decider.assumptionAnalyzer.addPermissionAssertNode(ch, v.decider.assumptionAnalyzer.currentAnalysisInfo.sourceInfo) Q(s, ch.snap, v) case _ if v.decider.checkSmoke(true) => if (s.isInPackage) { diff --git a/src/main/scala/state/Chunks.scala b/src/main/scala/state/Chunks.scala index 334429e19..590ee1c85 100644 --- a/src/main/scala/state/Chunks.scala +++ b/src/main/scala/state/Chunks.scala @@ -43,7 +43,7 @@ object BasicChunk { perm: Term, permExp: Option[ast.Exp], analysisInfo: AnalysisInfo): BasicChunk = { val chunk = new BasicChunk(resourceID, id, args, argsExp, snap, snapExp, perm, permExp) - analysisInfo.assumptionAnalyzer.addPermissionNode(chunk, analysisInfo.sourceInfo, analysisInfo.assumptionType) + analysisInfo.assumptionAnalyzer.addPermissionInhaleNode(chunk, analysisInfo.sourceInfo, analysisInfo.assumptionType) chunk } @@ -140,7 +140,7 @@ object QuantifiedFieldChunk { hints: Seq[Term] = Nil, analysisInfo: AnalysisInfo): QuantifiedFieldChunk = { val chunk = new QuantifiedFieldChunk(id, fvf, condition, conditionExp, permValue, permValueExp, invs, singletonRcvr, singletonRcvrExp, hints) - analysisInfo.assumptionAnalyzer.addPermissionNode(chunk, analysisInfo.sourceInfo, analysisInfo.assumptionType) + analysisInfo.assumptionAnalyzer.addPermissionInhaleNode(chunk, analysisInfo.sourceInfo, analysisInfo.assumptionType) chunk } } @@ -230,7 +230,7 @@ object QuantifiedPredicateChunk { hints: Seq[Term] = Nil, analysisInfo: AnalysisInfo): QuantifiedPredicateChunk = { val chunk = new QuantifiedPredicateChunk(id, quantifiedVars, quantifiedVarExps, psf, condition, conditionExp, permValue, permValueExp, invs, singletonArgs, singletonArgExps, hints) - analysisInfo.assumptionAnalyzer.addPermissionNode(chunk, analysisInfo.sourceInfo, analysisInfo.assumptionType) + analysisInfo.assumptionAnalyzer.addPermissionInhaleNode(chunk, analysisInfo.sourceInfo, analysisInfo.assumptionType) chunk } } @@ -303,7 +303,7 @@ object QuantifiedMagicWandChunk { hints: Seq[Term] = Nil, analysisInfo: AnalysisInfo): QuantifiedMagicWandChunk = { val chunk = new QuantifiedMagicWandChunk(id, quantifiedVars, quantifiedVarExps, wsf, perm, permExp, invs, singletonArgs, singletonArgExps, hints) - analysisInfo.assumptionAnalyzer.addPermissionNode(chunk, analysisInfo.sourceInfo, analysisInfo.assumptionType) + analysisInfo.assumptionAnalyzer.addPermissionInhaleNode(chunk, analysisInfo.sourceInfo, analysisInfo.assumptionType) chunk } } @@ -382,7 +382,7 @@ object MagicWandChunk { permExp: Option[ast.Exp], analysisInfo: AnalysisInfo): MagicWandChunk = { val chunk = new MagicWandChunk(id, bindings, args, argsExp, snap, perm, permExp) - analysisInfo.assumptionAnalyzer.addPermissionNode(chunk, analysisInfo.sourceInfo, analysisInfo.assumptionType) + analysisInfo.assumptionAnalyzer.addPermissionInhaleNode(chunk, analysisInfo.sourceInfo, analysisInfo.assumptionType) chunk } } From cd96cc97d3f2716ed89bbc9a33aea5e2994a9911 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 13 May 2025 12:26:29 +0200 Subject: [PATCH 037/474] return consumed chunks from consume methods --- src/main/scala/rules/ChunkSupporter.scala | 26 ++--- src/main/scala/rules/Consumer.scala | 101 +++++++++--------- src/main/scala/rules/Evaluator.scala | 8 +- .../scala/rules/ExecutionFlowController.scala | 7 ++ src/main/scala/rules/Executor.scala | 22 ++-- src/main/scala/rules/MagicWandSupporter.scala | 8 +- .../rules/MoreCompleteExhaleSupporter.scala | 22 ++-- src/main/scala/rules/PredicateSupporter.scala | 8 +- .../scala/rules/QuantifiedChunkSupport.scala | 46 ++++---- .../scala/supporters/MethodSupporter.scala | 2 +- .../functions/FunctionVerificationUnit.scala | 2 +- 11 files changed, 130 insertions(+), 122 deletions(-) diff --git a/src/main/scala/rules/ChunkSupporter.scala b/src/main/scala/rules/ChunkSupporter.scala index 2b9779d28..4d87561e3 100644 --- a/src/main/scala/rules/ChunkSupporter.scala +++ b/src/main/scala/rules/ChunkSupporter.scala @@ -35,7 +35,7 @@ trait ChunkSupportRules extends SymbolicExecutionRules { v: Verifier, description: String, analysisInfo: AnalysisInfo) - (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) + (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult def produce(s: State, h: Heap, ch: NonQuantifiedChunk, v: Verifier) @@ -79,13 +79,13 @@ object chunkSupporter extends ChunkSupportRules { v: Verifier, description: String, analysisInfo: AnalysisInfo) - (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) + (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { - consume2(s, h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, analysisInfo)((s2, h2, optSnap, v2) => + consume2(s, h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, analysisInfo)((s2, h2, optSnap, chunk, v2) => optSnap match { case Some(snap) => - Q(s2, h2, Some(snap.convert(sorts.Snap)), v2) + Q(s2, h2, Some(snap.convert(sorts.Snap)), chunk, v2) case None if returnSnap => /* Not having consumed anything could mean that we are in an infeasible * branch, or that the permission amount to consume was zero. @@ -96,8 +96,8 @@ object chunkSupporter extends ChunkSupportRules { */ val fresh = v2.decider.fresh(sorts.Snap, Option.when(withExp)(PUnknown())) val s3 = s2.copy(functionRecorder = s2.functionRecorder.recordFreshSnapshot(fresh.applicable)) - Q(s3, h2, Some(fresh), v2) - case None => Q(s2, h2, None, v2) + Q(s3, h2, Some(fresh), chunk, v2) + case None => Q(s2, h2, None, chunk, v2) }) } @@ -112,7 +112,7 @@ object chunkSupporter extends ChunkSupportRules { ve: VerificationError, v: Verifier, analysisInfo: AnalysisInfo) - (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) + (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { val id = ChunkIdentifier(resource, s.program) @@ -120,15 +120,15 @@ object chunkSupporter extends ChunkSupportRules { val failure = createFailure(ve, v, s, "chunk consume in package") magicWandSupporter.transfer(s, perms, permsExp, failure, Seq(), v)(consumeGreedy(_, _, id, args, _, _, _, analysisInfo))((s1, optCh, v1) => if (returnSnap){ - Q(s1, h, optCh.flatMap(ch => Some(ch.snap)), v1) + Q(s1, h, optCh.flatMap(ch => Some(ch.snap)), optCh, v1) } else { - Q(s1, h, None, v1) + Q(s1, h, None, optCh, v1) }) } else { - executionFlowController.tryOrFail2[Heap, Option[Term]](s.copy(h = h), v)((s1, v1, QS) => + executionFlowController.tryOrFail3[Heap, Option[Term], Iterable[Chunk]](s.copy(h = h), v)((s1, v1, QS) => if (s1.moreCompleteExhale) { - moreCompleteExhaleSupporter.consumeComplete(s1, s1.h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v1, analysisInfo)((s2, h2, snap2, v2) => { - QS(s2.copy(h = s.h), h2, snap2, v2) + moreCompleteExhaleSupporter.consumeComplete(s1, s1.h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v1, analysisInfo)((s2, h2, snap2, chunks, v2) => { + QS(s2.copy(h = s.h), h2, snap2, chunks, v2) }) } else { consumeGreedy(s1, s1.h, id, args, perms, permsExp, v1, analysisInfo) match { @@ -142,7 +142,7 @@ object chunkSupporter extends ChunkSupportRules { } case _ => None } - QS(s2.copy(h = s.h), h2, snap, v1) + QS(s2.copy(h = s.h), h2, snap, optCh2.toList, v1) case _ if v1.decider.checkSmoke(true) => Success() // TODO: Mark branch as dead? case _ => diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index 45df3c726..20b59048a 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -16,6 +16,7 @@ import viper.silver.ast.utility.QuantifiedPermissions.QuantifiedPermissionAssert import viper.silver.verifier.PartialVerificationError import viper.silver.verifier.reasons._ import viper.silicon.interfaces.VerificationResult +import viper.silicon.interfaces.state.Chunk import viper.silicon.logger.records.data.{CondExpRecord, ConsumeRecord, ImpliesRecord} import viper.silicon.state._ import viper.silicon.state.terms._ @@ -33,13 +34,13 @@ trait ConsumptionRules extends SymbolicExecutionRules { * @param pve The error to report in case the consumption fails. * @param v The verifier to use. * @param Q The continuation to invoke if the consumption succeeded, with the following - * arguments: state (1st argument) and verifier (3rd argument) resulting from the - * consumption, and a heap snapshot (2bd argument )representing the values of the - * consumed partial heap. + * arguments: state (1st argument) and verifier (4th argument) resulting from the + * consumption, a heap snapshot (2bd argument )representing the values of the + * consumed partial heap, and the chunks (3rd argument) that were consumed * @return The result of the continuation. */ def consume(s: State, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, analysisInfo: AnalysisInfo) - (Q: (State, Option[Term], Verifier) => VerificationResult) + (Q: (State, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult /** Subsequently consumes the assertions `as` (from head to tail), starting in state `s`. @@ -62,7 +63,7 @@ trait ConsumptionRules extends SymbolicExecutionRules { returnSnap: Boolean, pvef: ast.Exp => PartialVerificationError, v: Verifier, analysisInfo: AnalysisInfo) - (Q: (State, Option[Term], Verifier) => VerificationResult) + (Q: (State, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult } @@ -76,13 +77,13 @@ object consumer extends ConsumptionRules { /** @inheritdoc */ def consume(s: State, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, analysisInfo: AnalysisInfo) - (Q: (State, Option[Term], Verifier) => VerificationResult) + (Q: (State, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { - consumeR(s, s.h, a.whenExhaling, returnSnap, pve, v, analysisInfo)((s1, h1, snap, v1) => { + consumeR(s, s.h, a.whenExhaling, returnSnap, pve, v, analysisInfo)((s1, h1, snap, chunks, v1) => { val s2 = s1.copy(h = h1, partiallyConsumedHeap = s.partiallyConsumedHeap) - Q(s2, snap, v1)}) + Q(s2, snap, chunks, v1)}) } /** @inheritdoc */ @@ -92,7 +93,7 @@ object consumer extends ConsumptionRules { pvef: ast.Exp => PartialVerificationError, v: Verifier, analysisInfo: AnalysisInfo) - (Q: (State, Option[Term], Verifier) => VerificationResult) + (Q: (State, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { val allTlcs = mutable.ListBuffer[ast.Exp]() @@ -106,10 +107,10 @@ object consumer extends ConsumptionRules { allPves ++= pves }) - consumeTlcs(s, s.h, allTlcs.result(), returnSnap, allPves.result(), v, analysisInfo)((s1, h1, snap1, v1) => { + consumeTlcs(s, s.h, allTlcs.result(), returnSnap, allPves.result(), v, analysisInfo)((s1, h1, snap1, chunks, v1) => { val s2 = s1.copy(h = h1, partiallyConsumedHeap = s.partiallyConsumedHeap) - Q(s2, snap1, v1) + Q(s2, snap1, chunks, v1) }) } @@ -120,11 +121,11 @@ object consumer extends ConsumptionRules { pves: Seq[PartialVerificationError], v: Verifier, analysisInfo: AnalysisInfo) - (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) + (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { if (tlcs.isEmpty) - Q(s, h, if (returnSnap) Some(Unit) else None, v) + Q(s, h, if (returnSnap) Some(Unit) else None, Seq.empty, v) else { val a = tlcs.head val pve = pves.head @@ -132,12 +133,12 @@ object consumer extends ConsumptionRules { if (tlcs.tail.isEmpty) wrappedConsumeTlc(s, h, a, returnSnap, pve, v, analysisInfo)(Q) else - wrappedConsumeTlc(s, h, a, returnSnap, pve, v, analysisInfo)((s1, h1, snap1, v1) => { - consumeTlcs(s1, h1, tlcs.tail, returnSnap, pves.tail, v1, analysisInfo)((s2, h2, snap2, v2) => + wrappedConsumeTlc(s, h, a, returnSnap, pve, v, analysisInfo)((s1, h1, snap1, chunksHead, v1) => { + consumeTlcs(s1, h1, tlcs.tail, returnSnap, pves.tail, v1, analysisInfo)((s2, h2, snap2, chunksTail, v2) => (snap1, snap2) match { - case (Some(sn1), Some(sn2)) if returnSnap => Q(s2, h2, Some(Combine(sn1, sn2)), v2) - case (None, None) if !returnSnap => Q(s2, h2, None, v2) + case (Some(sn1), Some(sn2)) if returnSnap => Q(s2, h2, Some(Combine(sn1, sn2)), chunksHead ++ chunksTail, v2) + case (None, None) if !returnSnap => Q(s2, h2, None, chunksHead ++ chunksTail, v2) case (_, _) => sys.error(s"Consume returned unexpected snapshot: ${(returnSnap, (snap1, snap2))}") }) }) @@ -145,7 +146,7 @@ object consumer extends ConsumptionRules { } private def consumeR(s: State, h: Heap, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, analysisInfo: AnalysisInfo) - (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) + (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { val tlcs = a.topLevelConjuncts @@ -165,7 +166,7 @@ object consumer extends ConsumptionRules { pve: PartialVerificationError, v: Verifier, analysisInfo: AnalysisInfo) - (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) + (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { /* tryOrFail effects the "main" heap s.h, so we temporarily set the consume-heap h to be the @@ -173,20 +174,20 @@ object consumer extends ConsumptionRules { * consume. */ val sInit = s.copy(h = h) - executionFlowController.tryOrFail2[Heap, Option[Term]](sInit, v)((s0, v1, QS) => { + executionFlowController.tryOrFail3[Heap, Option[Term], Iterable[Chunk]](sInit, v)((s0, v1, QS) => { val h0 = s0.h /* h0 is h, but potentially consolidated */ val s1 = s0.copy(h = s.h) /* s1 is s, but the retrying flag might be set */ val sepIdentifier = v1.symbExLog.openScope(new ConsumeRecord(a, s1, v.decider.pcs)) - consumeTlc(s1, h0, a, returnSnap, pve, v1, analysisInfo)((s2, h2, snap2, v2) => { + consumeTlc(s1, h0, a, returnSnap, pve, v1, analysisInfo)((s2, h2, snap2, chunks, v2) => { v2.symbExLog.closeScope(sepIdentifier) - QS(s2, h2, snap2, v2)}) + QS(s2, h2, snap2, chunks, v2)}) })(Q) } private def consumeTlc(s: State, h: Heap, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, analysisInfo: AnalysisInfo) - (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) + (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { /* ATTENTION: Expressions such as `perm(...)` must be evaluated in-place, @@ -215,13 +216,13 @@ object consumer extends ConsumptionRules { evaluator.eval(s, e0, pve, v)((s1, t0, e0New, v1) => branch(s1, t0, (e0, e0New), v1)( - (s2, v2) => consumeR(s2, h, a0, returnSnap, pve, v2, analysisInfo)((s3, h1, t1, v3) => { + (s2, v2) => consumeR(s2, h, a0, returnSnap, pve, v2, analysisInfo)((s3, h1, t1, chunks, v3) => { v3.symbExLog.closeScope(uidImplies) - Q(s3, h1, t1, v3) + Q(s3, h1, t1, chunks, v3) }), (s2, v2) => { v2.symbExLog.closeScope(uidImplies) - Q(s2, h, if (returnSnap) Some(Unit) else None, v2) + Q(s2, h, if (returnSnap) Some(Unit) else None, Seq.empty, v2) })) case ite @ ast.CondExp(e0, a1, a2) if !a.isPure && s.moreJoins.id >= JoinMode.Impure.id => @@ -235,13 +236,13 @@ object consumer extends ConsumptionRules { eval(s, e0, pve, v)((s1, t0, e0New, v1) => branch(s1, t0, (e0, e0New), v1)( - (s2, v2) => consumeR(s2, h, a1, returnSnap, pve, v2, analysisInfo)((s3, h1, t1, v3) => { + (s2, v2) => consumeR(s2, h, a1, returnSnap, pve, v2, analysisInfo)((s3, h1, t1, chunks, v3) => { v3.symbExLog.closeScope(uidCondExp) - Q(s3, h1, t1, v3) + Q(s3, h1, t1, chunks, v3) }), - (s2, v2) => consumeR(s2, h, a2, returnSnap, pve, v2, analysisInfo)((s3, h1, t1, v3) => { + (s2, v2) => consumeR(s2, h, a2, returnSnap, pve, v2, analysisInfo)((s3, h1, t1, chunks, v3) => { v3.symbExLog.closeScope(uidCondExp) - Q(s3, h1, t1, v3) + Q(s3, h1, t1, chunks, v3) }))) /* TODO: Initial handling of QPs is identical/very similar in consumer @@ -283,8 +284,8 @@ object consumer extends ConsumptionRules { notInjectiveReason = QPAssertionNotInjective(acc.loc), insufficientPermissionReason = InsufficientPermission(acc.loc), v1, - analysisInfo)((s2, h2, snap, v2) => Q(s2.copy(constrainableARPs = s.constrainableARPs), h2, snap, v2)) - case (s1, _, _, _, _, None, v1) => Q(s1, h, if (returnSnap) Some(Unit) else None, v1) + analysisInfo)((s2, h2, snap, chunks, v2) => Q(s2.copy(constrainableARPs = s.constrainableARPs), h2, snap, chunks, v2)) + case (s1, _, _, _, _, None, v1) => Q(s1, h, if (returnSnap) Some(Unit) else None, Seq.empty, v1) // TODO ake: verify this } case QuantifiedPermissionAssertion(forall, cond, acc: ast.PredicateAccessPredicate) => @@ -330,8 +331,8 @@ object consumer extends ConsumptionRules { notInjectiveReason = QPAssertionNotInjective(acc.loc), insufficientPermissionReason = InsufficientPermission(acc.loc), v1, - analysisInfo)((s2, h2, snap, v2) => Q(s2.copy(constrainableARPs = s.constrainableARPs), h2, snap, v2)) - case (s1, _, _, _, _, None, v1) => Q(s1, h, if (returnSnap) Some(Unit) else None, v1) + analysisInfo)((s2, h2, snap, chunks, v2) => Q(s2.copy(constrainableARPs = s.constrainableARPs), h2, snap, chunks, v2)) + case (s1, _, _, _, _, None, v1) => Q(s1, h, if (returnSnap) Some(Unit) else None, Seq.empty, v1) } case QuantifiedPermissionAssertion(forall, cond, wand: ast.MagicWand) => @@ -373,8 +374,8 @@ object consumer extends ConsumptionRules { notInjectiveReason = sys.error("Quantified wand not injective"), /*ReceiverNotInjective(...)*/ insufficientPermissionReason = MagicWandChunkNotFound(wand), /*InsufficientPermission(...)*/ v1, - analysisInfo)((s2, h2, snap, v2) => Q(s2.copy(constrainableARPs = s.constrainableARPs), h2, snap, v2)) - case (s1, _, _, _, _, None, v1) => Q(s1.copy(constrainableARPs = s.constrainableARPs), h, if (returnSnap) Some(Unit) else None, v1) + analysisInfo)((s2, h2, snap, chunks, v2) => Q(s2.copy(constrainableARPs = s.constrainableARPs), h2, snap, chunks, v2)) + case (s1, _, _, _, _, None, v1) => Q(s1.copy(constrainableARPs = s.constrainableARPs), h, if (returnSnap) Some(Unit) else None, Seq.empty, v1) } case accPred@ast.AccessPredicate(loc @ ast.FieldAccess(eRcvr, field), ePerm) @@ -415,10 +416,10 @@ object consumer extends ConsumptionRules { pve, v2, analysisInfo - )((s3, h3, snap, v3) => { + )((s3, h3, snap, chunks, v3) => { val s4 = s3.copy(constrainableARPs = s1.constrainableARPs, partiallyConsumedHeap = Some(h3)) - Q(s4, h3, snap, v3)})})) + Q(s4, h3, snap, chunks, v3)})})) case ast.AccessPredicate(loc @ ast.PredicateAccess(eArgs, predname), ePerm) if s.qpPredicates.contains(s.program.findPredicate(predname)) => @@ -461,10 +462,10 @@ object consumer extends ConsumptionRules { pve, v2, analysisInfo - )((s3, h3, snap, v3) => { + )((s3, h3, snap, chunks, v3) => { val s4 = s3.copy(constrainableARPs = s1.constrainableARPs, partiallyConsumedHeap = Some(h3)) - Q(s4, h3, snap, v3)})})) + Q(s4, h3, snap, chunks, v3)})})) case let: ast.Let if !let.isPure => letSupporter.handle[ast.Exp](s, let, pve, v)((s1, g1, body, v1) => { @@ -483,10 +484,10 @@ object consumer extends ConsumptionRules { val lossExp = permNew.map(p => ast.PermMul(p, s3.permissionScalingFactorExp.get)(p.pos, p.info, p.errT)) val ve = pve dueTo InsufficientPermission(locacc) val description = s"consume ${a.pos}: $a" - chunkSupporter.consume(s3, h, resource, tArgs, eArgs, loss, lossExp, returnSnap, ve, v3, description, analysisInfo)((s4, h1, snap1, v4) => { + chunkSupporter.consume(s3, h, resource, tArgs, eArgs, loss, lossExp, returnSnap, ve, v3, description, analysisInfo)((s4, h1, snap1, chunks, v4) => { val s5 = s4.copy(partiallyConsumedHeap = Some(h1), constrainableARPs = s.constrainableARPs) - Q(s5, h1, snap1, v4)})}))) + Q(s5, h1, snap1, chunks, v4)})}))) case _: ast.InhaleExhaleExp => createFailure(viper.silicon.utils.consistency.createUnexpectedInhaleExhaleExpressionError(a), v, s, "valid AST") @@ -528,10 +529,10 @@ object consumer extends ConsumptionRules { pve, v1, analysisInfo - )((s3, h3, snap, v3) => { + )((s3, h3, snap, chunks, v3) => { val s4 = s3.copy(constrainableARPs = s1.constrainableARPs, partiallyConsumedHeap = Some(h3)) - Q(s4, h3, snap, v3)})}) + Q(s4, h3, snap, chunks, v3)})}) case wand: ast.MagicWand => magicWandSupporter.evaluateWandArguments(s, wand, pve, v)((s1, tArgs, eArgs, v1) => { @@ -542,7 +543,7 @@ object consumer extends ConsumptionRules { case _ => evalAndAssert(s, a, returnSnap, pve, v)((s1, t, v1) => { - Q(s1, h, t, v1) + Q(s1, h, t, Seq.empty, v1) }) } @@ -553,19 +554,19 @@ object consumer extends ConsumptionRules { returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, analysisInfo: AnalysisInfo) - (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) + (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { eval(s, e0, pve, v)((s1, t0, e0New, v1) => - joiner.join[(Heap, Option[Term]), (Heap, Option[Term])](s1, v1, resetState = false)((s1, v1, QB) => { + joiner.join[(Heap, Option[Term]), (Heap, Option[Term])](s1, v1, resetState = false)((s1, v1, QB) => { // TODO ake: what to do with chunks? branch(s1.copy(parallelizeBranches = false), t0, (e0, e0New), v1)( (s2, v2) => - consumeR(s2.copy(parallelizeBranches = s1.parallelizeBranches), h, a1, returnSnap, pve, v2, analysisInfo)((s3, h1, t1, v3) => { + consumeR(s2.copy(parallelizeBranches = s1.parallelizeBranches), h, a1, returnSnap, pve, v2, analysisInfo)((s3, h1, t1, chunks, v3) => { v3.symbExLog.closeScope(scopeUid) QB(s3, (h1, t1), v3) }), (s2, v2) => a2 match { - case Some(a2) => consumeR(s2.copy(parallelizeBranches = s1.parallelizeBranches), h, a2, returnSnap, pve, v2, analysisInfo)((s3, h1, t1, v3) => { + case Some(a2) => consumeR(s2.copy(parallelizeBranches = s1.parallelizeBranches), h, a2, returnSnap, pve, v2, analysisInfo)((s3, h1, t1, chunks, v3) => { v3.symbExLog.closeScope(scopeUid) QB(s3, (h1, t1), v3) }) @@ -597,7 +598,7 @@ object consumer extends ConsumptionRules { } s2 })((s4, data, v4) => { - Q(s4, data._1, data._2, v4) + Q(s4, data._1, data._2, Seq.empty, v4) // TODO ake: get chunks }) ) } diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 6ce3c75db..8ce84774f 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -933,7 +933,7 @@ object evaluator extends EvaluationRules { moreJoins = JoinMode.Off, assertReadAccessOnly = if (Verifier.config.respectFunctionPrePermAmounts()) s2.assertReadAccessOnly /* should currently always be false */ else true) - consumes(s3, pres, true, _ => pvePre, v2, AnalysisInfo(v2.decider.assumptionAnalyzer, ExpAnalysisSourceInfo(fapp), AssumptionType.Assertion))((s4, snap, v3) => { + consumes(s3, pres, true, _ => pvePre, v2, AnalysisInfo(v2.decider.assumptionAnalyzer, ExpAnalysisSourceInfo(fapp), AssumptionType.Assertion))((s4, snap, consumedChunks, v3) => { // TODO ake: add edges from consumedChunks val snap1 = snap.get.convert(sorts.Snap) val preFApp = App(functionSupporter.preconditionVersion(v3.symbolConverter.toFunction(func)), snap1 :: tArgs) val preExp = Option.when(withExp)({ @@ -996,7 +996,7 @@ object evaluator extends EvaluationRules { // val c4 = c3.decCycleCounter(predicate) // eval(σ1, eIn, pve, c4)((tIn, c5) => // QB(tIn, c5))}) - consume(s4, acc, true, pve, v3, AnalysisInfo(v3.decider.assumptionAnalyzer, ExpAnalysisSourceInfo(acc), AssumptionType.Implicit))((s5, snap, v4) => { + consume(s4, acc, true, pve, v3, AnalysisInfo(v3.decider.assumptionAnalyzer, ExpAnalysisSourceInfo(acc), AssumptionType.Implicit))((s5, snap, consumedChunks, v4) => { val fr6 = s5.functionRecorder.recordSnapshot(pa, v4.decider.pcs.branchConditions, snap.get) .changeDepthBy(+1) @@ -1018,7 +1018,7 @@ object evaluator extends EvaluationRules { val argsPairs: List[(Term, Option[ast.Exp])] = if (withExp) tArgs zip eArgsNew.get.map(Some(_)) else tArgs zip Seq.fill(tArgs.size)(None) val insg = s7.g + Store(predicate.formalArgs map (_.localVar) zip argsPairs) val s7a = s7.copy(g = insg).setConstrainable(s7.constrainableARPs, false) - produce(s7a, toSf(snap.get), body, pve, v4)((s8, v5) => { + produce(s7a, toSf(snap.get), body, pve, v4)((s8, v5) => { // TODO ake: add edges from consumedChunks val s9 = s8.copy(g = s7.g, functionRecorder = s8.functionRecorder.changeDepthBy(-1), recordVisited = s3.recordVisited, @@ -1051,7 +1051,7 @@ object evaluator extends EvaluationRules { => Q(s4, r4._1, r4._2, v4)) case ast.Asserting(eAss, eIn) => - consume(s, eAss, false, pve, v, AnalysisInfo(v.decider.assumptionAnalyzer, ExpAnalysisSourceInfo(eAss), AssumptionType.Assertion))((s2, _, v2) => { + consume(s, eAss, false, pve, v, AnalysisInfo(v.decider.assumptionAnalyzer, ExpAnalysisSourceInfo(eAss), AssumptionType.Assertion))((s2, _, consumedChunks, v2) => { // TODO ake: what to do with chunks here? val s3 = s2.copy(g = s.g, h = s.h) eval(s3, eIn, pve, v2)(Q) }) diff --git a/src/main/scala/rules/ExecutionFlowController.scala b/src/main/scala/rules/ExecutionFlowController.scala index 8cc287d0f..58b8ca051 100644 --- a/src/main/scala/rules/ExecutionFlowController.scala +++ b/src/main/scala/rules/ExecutionFlowController.scala @@ -172,4 +172,11 @@ object executionFlowController extends ExecutionFlowRules { : VerificationResult = tryOrFailWithResult[(R1, R2)](s, v)((s1, v1, QS) => action(s1, v1, (s2, r21, r22, v2) => QS(s2, (r21, r22), v2)))((s2, r, v2) => Q(s2, r._1, r._2, v2)) + + def tryOrFail3[R1, R2, R3](s: State, v: Verifier) + (action: (State, Verifier, (State, R1, R2, R3, Verifier) => VerificationResult) => VerificationResult) + (Q: (State, R1, R2, R3, Verifier) => VerificationResult) + : VerificationResult = + + tryOrFailWithResult[(R1, R2, R3)](s, v)((s1, v1, QS) => action(s1, v1, (s2, r21, r22, r23, v2) => QS(s2, (r21, r22, r23), v2)))((s2, r, v2) => Q(s2, r._1, r._2, r._3, v2)) } diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 1aa7d4688..c79517fb5 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -272,7 +272,7 @@ object executor extends ExecutionRules { combine executionFlowController.locally(s, v)((s0, v0) => { v0.decider.prover.comment("Loop head block: Establish invariant") v0.decider.assumptionAnalyzer.setCurrentAnalysisInfo(if(invs.isEmpty) StringAnalysisSourceInfo("no invariants", ast.NoPosition) else ExpAnalysisSourceInfo(invs.head), AssumptionType.Assertion) - consumes(s0, invs, false, LoopInvariantNotEstablished, v0, v0.decider.assumptionAnalyzer.currentAnalysisInfo)((sLeftover, _, v1) => { + consumes(s0, invs, false, LoopInvariantNotEstablished, v0, v0.decider.assumptionAnalyzer.currentAnalysisInfo)((sLeftover, _, consumedChunks, v1) => { // TODO ake: add edges from consumedChunks v1.decider.prover.comment("Loop head block: Execute statements of loop head block (in invariant state)") phase1data.foldLeft(Success(): VerificationResult) { case (result, _) if !result.continueVerification => result @@ -307,7 +307,7 @@ object executor extends ExecutionRules { */ v.decider.prover.comment("Loop head block: Re-establish invariant") v.decider.assumptionAnalyzer.setCurrentAnalysisInfo(if(invs.isEmpty) StringAnalysisSourceInfo("no invariants", ast.NoPosition) else ExpAnalysisSourceInfo(invs.head), AssumptionType.Assertion) - consumes(s, invs, false, e => LoopInvariantNotPreserved(e), v, v.decider.assumptionAnalyzer.currentAnalysisInfo)((_, _, _) => + consumes(s, invs, false, e => LoopInvariantNotPreserved(e), v, v.decider.assumptionAnalyzer.currentAnalysisInfo)((_, _, _, _) => Success()) } } @@ -417,7 +417,7 @@ object executor extends ExecutionRules { AnalysisInfo(v2.decider.assumptionAnalyzer, ExpAnalysisSourceInfo(fa), AssumptionType.Implicit) ) result match { - case (Complete(), s3, remainingChunks) => + case (Complete(), s3, remainingChunks, consumedChunks) => // TODO ake: what to do with consumedChunks? val h3 = Heap(remainingChunks ++ otherChunks) val (sm, smValueDef) = quantifiedChunkSupporter.singletonSnapshotMap(s3, field, Seq(tRcvr), tRhs, v2) v1.decider.prover.comment("Definitional axioms for singleton-FVF's value") @@ -433,7 +433,7 @@ object executor extends ExecutionRules { val (debugHeapName, _) = v.getDebugOldLabel(s4, fa.pos) val s5 = if (withExp) s4.copy(oldHeaps = s4.oldHeaps + (debugHeapName -> magicWandSupporter.getEvalHeap(s4))) else s4 Q(s5, v2) - case (Incomplete(_, _), s3, _) => + case (Incomplete(_, _), s3, _, _) => createFailure(pve dueTo InsufficientPermission(fa), v2, s3, "sufficient permission")}})) case ass @ ast.FieldAssign(fa @ ast.FieldAccess(eRcvr, field), rhs) => @@ -445,10 +445,10 @@ object executor extends ExecutionRules { val ve = pve dueTo InsufficientPermission(fa) val description = s"consume ${ass.pos}: $ass" val analysisInfo = AnalysisInfo(v2.decider.assumptionAnalyzer, ExpAnalysisSourceInfo(fa), AssumptionType.Assertion) - chunkSupporter.consume(s2, s2.h, resource, Seq(tRcvr), eRcvrNew.map(Seq(_)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), false, ve, v2, description, analysisInfo)((s3, h3, _, v3) => { + chunkSupporter.consume(s2, s2.h, resource, Seq(tRcvr), eRcvrNew.map(Seq(_)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), false, ve, v2, description, analysisInfo)((s3, h3, _, consumedChunks, v3) => { val (tSnap, _) = ssaifyRhs(tRhs, rhs, rhsNew, field.name, field.typ, v3, s3) val id = BasicChunkIdentifier(field.name) - val newChunk = BasicChunk(FieldID, id, Seq(tRcvr), eRcvrNew.map(Seq(_)), tSnap, rhsNew, FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), + val newChunk = BasicChunk.createDerivedChunk(consumedChunks.toSet, FieldID, id, Seq(tRcvr), eRcvrNew.map(Seq(_)), tSnap, rhsNew, FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), AnalysisInfo(v3.decider.assumptionAnalyzer, ExpAnalysisSourceInfo(fa), AssumptionType.Implicit)) chunkSupporter.produce(s3, h3, newChunk, v3)((s4, h4, v4) => { val s5 = s4.copy(h = h4) @@ -506,7 +506,7 @@ object executor extends ExecutionRules { case exhale @ ast.Exhale(a) => val pve = ExhaleFailed(exhale) - consume(s, a, false, pve, v, v.decider.assumptionAnalyzer.currentAnalysisInfo)((s1, _, v1) => + consume(s, a, false, pve, v, v.decider.assumptionAnalyzer.currentAnalysisInfo)((s1, _, consumedChunks, v1) => Q(s1, v1)) case assert @ ast.Assert(a: ast.FalseLit) if !s.isInPackage => @@ -520,7 +520,7 @@ object executor extends ExecutionRules { case assert @ ast.Assert(a) if Verifier.config.disableSubsumption() => val r = - consume(s, a, false, AssertFailed(assert), v, v.decider.assumptionAnalyzer.currentAnalysisInfo)((_, _, _) => + consume(s, a, false, AssertFailed(assert), v, v.decider.assumptionAnalyzer.currentAnalysisInfo)((_, _, _, _) => Success()) r combine Q(s, v) @@ -536,11 +536,11 @@ object executor extends ExecutionRules { * hUsed (reserveHeaps.head) instead of consuming them. hUsed is later discarded and replaced * by s.h. By copying hUsed to s.h the contained permissions remain available inside the wand. */ - consume(s, a, false, pve, v, v.decider.assumptionAnalyzer.currentAnalysisInfo)((s2, _, v1) => { + consume(s, a, false, pve, v, v.decider.assumptionAnalyzer.currentAnalysisInfo)((s2, _, _, v1) => { Q(s2.copy(h = s2.reserveHeaps.head), v1) }) } else - consume(s, a, false, pve, v, v.decider.assumptionAnalyzer.currentAnalysisInfo)((s1, _, v1) => { + consume(s, a, false, pve, v, v.decider.assumptionAnalyzer.currentAnalysisInfo)((s1, _, _, v1) => { val s2 = s1.copy(h = s.h, reserveHeaps = s.reserveHeaps) Q(s2, v1)}) @@ -600,7 +600,7 @@ object executor extends ExecutionRules { tArgs zip Seq.fill(tArgs.size)(None) val s2 = s1.copy(g = Store(fargs.zip(argsWithExp)), recordVisited = true) - consumes(s2, meth.pres, false, _ => pvePre, v1, v1.decider.assumptionAnalyzer.currentAnalysisInfo)((s3, _, v2) => { + consumes(s2, meth.pres, false, _ => pvePre, v1, v1.decider.assumptionAnalyzer.currentAnalysisInfo)((s3, _, consumedChunks, v2) => { // TODO ake: add edges from consumedChunks v2.symbExLog.closeScope(preCondId) val postCondLog = new CommentRecord("Postcondition", s3, v2.decider.pcs) val postCondId = v2.symbExLog.openScope(postCondLog) diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index b36aa5caa..f1c0b996f 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -401,7 +401,7 @@ object magicWandSupporter extends SymbolicExecutionRules { consume( proofScriptState.copy(oldHeaps = s2.oldHeaps, reserveCfgs = proofScriptState.reserveCfgs.tail), wand.right, true, pve, proofScriptVerifier, proofScriptVerifier.decider.assumptionAnalyzer.currentAnalysisInfo - )((s3, snapRhs, v3) => { + )((s3, snapRhs, consumedChunks, v3) => { // TODO ake: what to do with consumedChunks? createWandChunkAndRecordResults(s3.copy(exhaleExt = false, oldHeaps = s.oldHeaps), freshSnapRoot, snapRhs.get, v3) }) @@ -459,9 +459,9 @@ object magicWandSupporter extends SymbolicExecutionRules { (Q: (State, Verifier) => VerificationResult) : VerificationResult = { // Consume the magic wand instance "A --* B". - consume(s, wand, true, pve, v, v.decider.assumptionAnalyzer.currentAnalysisInfo)((s1, snapWand, v1) => { + consume(s, wand, true, pve, v, v.decider.assumptionAnalyzer.currentAnalysisInfo)((s1, snapWand, consumedChunksWand, v1) => { // Consume the wand's LHS "A". - consume(s1, wand.left, true, pve, v1, v1.decider.assumptionAnalyzer.currentAnalysisInfo)((s2, snapLhs, v2) => { + consume(s1, wand.left, true, pve, v1, v1.decider.assumptionAnalyzer.currentAnalysisInfo)((s2, snapLhs, consumedChunksLeft, v2) => { /* It is assumed that snap and MagicWandSnapshot.abstractLhs are structurally the same. * Equating the two snapshots is sound iff a wand is applied only once. * The old solution in this case did use this assumption: @@ -484,7 +484,7 @@ object magicWandSupporter extends SymbolicExecutionRules { } // Produce the wand's RHS. - produce(s3.copy(conservingSnapshotGeneration = true), toSf(magicWandSnapshotLookup), wand.right, pve, v2)((s4, v3) => { + produce(s3.copy(conservingSnapshotGeneration = true), toSf(magicWandSnapshotLookup), wand.right, pve, v2)((s4, v3) => { // TODO ake: add edges from consumedChunks // Recreate old state without the magic wand, and the state with the oldHeap called lhs. val s5 = s4.copy(g = s1.g, conservingSnapshotGeneration = s3.conservingSnapshotGeneration) diff --git a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala index 107e8626c..42cde2e37 100644 --- a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala +++ b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala @@ -238,7 +238,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { ve: VerificationError, v: Verifier, analysisInfo: AnalysisInfo) - (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) + (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { if (!s.assertReadAccessOnly) @@ -257,7 +257,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { returnSnap: Boolean, ve: VerificationError, v: Verifier) - (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) + (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { val id = ChunkIdentifier(resource, s.program) @@ -267,7 +267,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { summarise(s, relevantChunks, resource, args, argsExp, None, v)((s1, snap, permSum, permSumExp, v1) => v.decider.assert(Implies(IsPositive(perm), IsPositive(permSum)), Option.when(withExp)(ast.Implies(IsPositive(permExp.get)(), IsPositive(permSumExp.get)())(permExp.get.pos, permExp.get.info, permExp.get.errT))) { case true => - Q(s1, h, Some(snap), v1) + Q(s1, h, Some(snap), relevantChunks, v1) case false => createFailure(ve, v, s1, IsPositive(permSum), permSumExp.map(IsPositive(_)())) }) @@ -275,7 +275,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { val (s1, permSum, permSumExp) = permSummariseOnly(s, relevantChunks, resource, args, argsExp) v.decider.assert(Implies(IsPositive(perm), IsPositive(permSum)), Option.when(withExp)(ast.Implies(IsPositive(permExp.get)(), IsPositive(permSumExp.get)())(permExp.get.pos, permExp.get.info, permExp.get.errT))) { case true => - Q(s1, h, None, v) + Q(s1, h, None, relevantChunks, v) case false => createFailure(ve, v, s1, IsPositive(permSum), permSumExp.map(IsPositive(_)())) } @@ -293,7 +293,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { ve: VerificationError, v: Verifier, analysisInfo: AnalysisInfo) - (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) + (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { val id = ChunkIdentifier(resource, s.program) @@ -308,13 +308,13 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { val assertExp = permsExp.map(pe => ast.EqCmp(pe, ast.NoPerm()())(pe.pos, pe.info, pe.errT)) // if no permission is exhaled, return none v.decider.assert(perms === NoPerm, assertExp) { - case true => Q(s, h, None, v) + case true => Q(s, h, None, Seq.empty, v) case false => createFailure(ve, v, s, perms === NoPerm, assertExp) } } else { if (!terms.utils.consumeExactRead(perms, s.constrainableARPs)) { actualConsumeCompleteConstrainable(s, relevantChunks, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, analysisInfo)((s1, updatedChunks, optSnap, v2) => { - Q(s1, Heap(updatedChunks ++ otherChunks), optSnap, v2) + Q(s1, Heap(updatedChunks ++ otherChunks), optSnap, updatedChunks, v2) // TODO ake: verify returned chunk list (updatedChunks) }) } else { var pNeeded = perms @@ -403,12 +403,12 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { Ite(IsPositive(perms), snap.convert(sorts.Snap), Unit) }) if (!moreNeeded) { - Q(s1, newHeap, condSnap, v1) + Q(s1, newHeap, condSnap, relevantChunks, v1) // FIXME ake: return chunks } else { val assertExp = pNeededExp.map(pn => ast.EqCmp(pn, ast.NoPerm()())(pn.pos, pn.info, pn.errT)) v1.decider.assert(pNeeded === NoPerm, assertExp) { case true => - Q(s1, newHeap, condSnap, v1) + Q(s1, newHeap, condSnap, relevantChunks, v1) // FIXME ake: return chunks case false => createFailure(ve, v1, s1, pNeeded === NoPerm, assertExp) } @@ -416,12 +416,12 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { }) } else { if (!moreNeeded) { - Q(s0, newHeap, None, v) + Q(s0, newHeap, None, Seq.empty, v) // FIXME ake: return chunks } else { val assertExp = pNeededExp.map(pn => ast.EqCmp(pn, ast.NoPerm()())(pn.pos, pn.info, pn.errT)) v.decider.assert(pNeeded === NoPerm, assertExp) { case true => - Q(s0, newHeap, None, v) + Q(s0, newHeap, None, Seq.empty, v) // FIXME ake: return chunks case false => createFailure(ve, v, s0, pNeeded === NoPerm, assertExp) } diff --git a/src/main/scala/rules/PredicateSupporter.scala b/src/main/scala/rules/PredicateSupporter.scala index 95b49ace3..fdaebc10a 100644 --- a/src/main/scala/rules/PredicateSupporter.scala +++ b/src/main/scala/rules/PredicateSupporter.scala @@ -74,7 +74,7 @@ object predicateSupporter extends PredicateSupportRules { val s1 = s.copy(g = gIns, smDomainNeeded = true) .scalePermissionFactor(tPerm, ePerm) - consume(s1, body, true, pve, v, analysisInfo)((s1a, snap, v1) => { + consume(s1, body, true, pve, v, analysisInfo)((s1a, snap, consumedChunks, v1) => { // TODO ake: add edges from consumedChunks if (!Verifier.config.disableFunctionUnfoldTrigger()) { val predTrigger = App(s1a.predicateData(predicate).triggerFunction, snap.get.convert(terms.sorts.Snap) +: tArgs) @@ -164,10 +164,10 @@ object predicateSupporter extends PredicateSupportRules { pve, v, v.decider.assumptionAnalyzer.currentAnalysisInfo - )((s2, h2, snap, v1) => { + )((s2, h2, snap, consumedChunks, v1) => { val s3 = s2.copy(g = gIns, h = h2) .setConstrainable(constrainableWildcards, false) - produce(s3, toSf(snap.get), body, pve, v1)((s4, v2) => { + produce(s3, toSf(snap.get), body, pve, v1)((s4, v2) => { // TODO ake: add edge from consumedChunks to new assumptions v2.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterUnfold) if (!Verifier.config.disableFunctionUnfoldTrigger()) { val predicateTrigger = @@ -184,7 +184,7 @@ object predicateSupporter extends PredicateSupportRules { } else { val ve = pve dueTo InsufficientPermission(pa) val description = s"consume ${pa.pos}: $pa" - chunkSupporter.consume(s1, s1.h, predicate, tArgs, eArgs, s1.permissionScalingFactor, s1.permissionScalingFactorExp, true, ve, v, description, v.decider.assumptionAnalyzer.currentAnalysisInfo)((s2, h1, snap, v1) => { + chunkSupporter.consume(s1, s1.h, predicate, tArgs, eArgs, s1.permissionScalingFactor, s1.permissionScalingFactorExp, true, ve, v, description, v.decider.assumptionAnalyzer.currentAnalysisInfo)((s2, h1, snap, consumedChunks, v1) => { // TODO ake: add edges val s3 = s2.copy(g = gIns, h = h1) .setConstrainable(constrainableWildcards, false) produce(s3, toSf(snap.get), body, pve, v1)((s4, v2) => { diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 372c2537e..2b39d4bf8 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -1170,7 +1170,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { insufficientPermissionReason: => ErrorReason, v: Verifier, analysisInfo: AnalysisInfo) - (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) + (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { val (inverseFunctions, imagesOfFormalQVars) = @@ -1305,7 +1305,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val (relevantChunks, otherChunks) = quantifiedChunkSupporter.splitHeap[QuantifiedBasicChunk]( heap, ChunkIdentifier(resource, s.program)) - val (result, s3, remainingChunks) = + val (result, s3, remainingChunks, consumedChunks) = quantifiedChunkSupporter.removePermissions( s2, relevantChunks, @@ -1360,13 +1360,13 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val h2 = Heap(remainingChunks ++ otherChunks) val s4 = s3.copy(smCache = smCache2, constrainableARPs = s.constrainableARPs) - (result, s4, h2, Some(consumedChunk)) + (result, s4, h2, Some(consumedChunk)) // TODO ake: or consumedChunks? })((s4, optCh, v3) => optCh match { - case Some(ch) if returnSnap => Q(s4, s4.h, Some(ch.snapshotMap.convert(sorts.Snap)), v3) + case Some(ch) if returnSnap => Q(s4, s4.h, Some(ch.snapshotMap.convert(sorts.Snap)), optCh, v3) case None if returnSnap => - Q(s4, s4.h, Some(freshSnap(sorts.Snap, v3)), v3) - case _ => Q(s4, s4.h, None, v3) + Q(s4, s4.h, Some(freshSnap(sorts.Snap, v3)), optCh, v3) + case _ => Q(s4, s4.h, None, None, v3) } ) } else { @@ -1388,7 +1388,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { analysisInfo ) permissionRemovalResult match { - case (Complete(), s2, remainingChunks) => + case (Complete(), s2, remainingChunks, consumedChunks) => val h3 = Heap(remainingChunks ++ otherChunks) if (returnSnap) { val optSmDomainDefinitionCondition2 = @@ -1402,11 +1402,11 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { partiallyConsumedHeap = Some(h3), constrainableARPs = s.constrainableARPs, smCache = smCache2) - Q(s3, h3, Some(smDef2.sm.convert(sorts.Snap)), v) + Q(s3, h3, Some(smDef2.sm.convert(sorts.Snap)), consumedChunks, v) } else { - Q(s2, h3, None, v) + Q(s2, h3, None, consumedChunks, v) } - case (Incomplete(_, _), s2, _) => + case (Incomplete(_, _), s2, _, _) => createFailure(pve dueTo insufficientPermissionReason, v, s2, "QP consume")} } case false => @@ -1429,7 +1429,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { pve: PartialVerificationError, v: Verifier, analysisInfo: AnalysisInfo) - (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) + (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { val resource = resourceAccess.res(s.program) @@ -1452,7 +1452,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { magicWandSupporter.transfer(s, permissions, permissionsExp, failure, Seq(), v)((s1, h1, rPerm, rPermExp, v1) => { val (relevantChunks, otherChunks) = quantifiedChunkSupporter.splitHeap[QuantifiedBasicChunk](h1, chunkIdentifier) - val (result, s2, remainingChunks) = quantifiedChunkSupporter.removePermissions( + val (result, s2, remainingChunks, consumedChunks) = quantifiedChunkSupporter.removePermissions( s1, relevantChunks, codomainQVars, @@ -1487,15 +1487,15 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { codomainQVars, codomainQVarsExp, resource, arguments, argumentsExp, permsTaken, permsTakenExp, smDef1.sm, s.program, v1) val s3 = s2.copy(functionRecorder = s2.functionRecorder.recordFvfAndDomain(smDef1), smCache = smCache1) - (result, s3, h2, Some(consumedChunk)) + (result, s3, h2, Some(consumedChunk)) // TODO ake: or consumedChunks? })((s4, optCh, v2) => optCh match { case Some(ch) if returnSnap => val snap = ResourceLookup(resource, ch.snapshotMap, arguments, s4.program).convert(sorts.Snap) - Q(s4, s4.h, Some(snap), v2) + Q(s4, s4.h, Some(snap), optCh.toList, v2) case None if returnSnap => - Q(s4, s4.h, Some(freshSnap(sorts.Snap, v2)), v2) - case _ => Q(s4, s4.h, None, v2) + Q(s4, s4.h, Some(freshSnap(sorts.Snap, v2)), optCh.toList, v2) + case _ => Q(s4, s4.h, None, optCh.toList, v2) } ) } else { @@ -1518,7 +1518,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { analysisInfo ) result match { - case (Complete(), s1, remainingChunks) => + case (Complete(), s1, remainingChunks, consumedChunks) => val h1 = Heap(remainingChunks ++ otherChunks) if (returnSnap) { val (smDef1, smCache1) = @@ -1533,11 +1533,11 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val s2 = s1.copy(functionRecorder = s1.functionRecorder.recordFvfAndDomain(smDef1), smCache = smCache1) val snap = ResourceLookup(resource, smDef1.sm, arguments, s2.program).convert(sorts.Snap) - Q(s2, h1, Some(snap), v) + Q(s2, h1, Some(snap), consumedChunks, v) } else { - Q(s1, h1, None, v) + Q(s1, h1, None, consumedChunks, v) } - case (Incomplete(_, _), _, _) => + case (Incomplete(_, _), _, _, _) => resourceAccess match { case locAcc: ast.LocationAccess => createFailure(pve dueTo InsufficientPermission(locAcc), v, s, "single QP consume") case wand: ast.MagicWand => createFailure(pve dueTo MagicWandChunkNotFound(wand), v, s, "single QP consume") @@ -1595,7 +1595,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { chunkOrderHeuristic: Seq[QuantifiedBasicChunk] => Seq[QuantifiedBasicChunk], v: Verifier, analysisInfo: AnalysisInfo) - : (ConsumptionResult, State, Seq[QuantifiedBasicChunk]) = { + : (ConsumptionResult, State, Seq[QuantifiedBasicChunk], Seq[QuantifiedBasicChunk]) = { val rmPermRecord = new CommentRecord("removePermissions", s, v.decider.pcs) val sepIdentifier = v.symbExLog.openScope(rmPermRecord) @@ -1612,7 +1612,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val constrainPermissions = !consumeExactRead(perms, s.constrainableARPs) if (s.assertReadAccessOnly) { val result = assertReadPermission(s, candidates, codomainQVars, condition, perms, permsExp, v) - return (result, s, relevantChunks) + return (result, s, relevantChunks, Seq.empty) } @@ -1720,7 +1720,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.prover.comment("Done removing quantified permissions") v.symbExLog.closeScope(sepIdentifier) - (success, s.copy(functionRecorder = currentFunctionRecorder), remainingChunks) + (success, s.copy(functionRecorder = currentFunctionRecorder), remainingChunks, relevantChunks filterNot (remainingChunks.contains(_))) } private def createPermissionConstraintAndDepletedCheck(codomainQVars: Seq[Var], /* rs := r_1, ..., r_m */ diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index 83cbb968b..f011f4f20 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -117,7 +117,7 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif executionFlowController.locally(s2a, v2)((s3, v3) => { exec(s3, body, v3)((s4, v4) =>{ v4.decider.assumptionAnalyzer.setCurrentAnalysisInfo(if(posts.isEmpty) StringAnalysisSourceInfo("no postcondition", ast.NoPosition) else ExpAnalysisSourceInfo(posts.head), AssumptionType.Assertion) - consumes(s4, posts, false, postViolated, v4, v4.decider.assumptionAnalyzer.currentAnalysisInfo)((_, _, _) => + consumes(s4, posts, false, postViolated, v4, v4.decider.assumptionAnalyzer.currentAnalysisInfo)((_, _, _, _) => Success())})}) } )})}) result.assumptionAnalyzer = v.decider.assumptionAnalyzer diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index 7c60e4c55..60c9bb5cb 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -270,7 +270,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver Some(DebugExp.createInstance(e, eNew)) } else { None } decider.assume(BuiltinEquals(data.formalResult, tBody), debugExp) - consumes(s2, posts, false, postconditionViolated, v, AnalysisInfo(v.decider.assumptionAnalyzer, if(posts.isEmpty) StringAnalysisSourceInfo("no postcondition", ast.NoPosition) else ExpAnalysisSourceInfo(posts.head), AssumptionType.Assertion))((s3, _, _) => { + consumes(s2, posts, false, postconditionViolated, v, AnalysisInfo(v.decider.assumptionAnalyzer, if(posts.isEmpty) StringAnalysisSourceInfo("no postcondition", ast.NoPosition) else ExpAnalysisSourceInfo(posts.head), AssumptionType.Assertion))((s3, _, _, _) => { recorders :+= s3.functionRecorder Success()})})})} From 388cf2092a6eb3d23a5ed04a40af605a3c1059e8 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 13 May 2025 15:35:04 +0200 Subject: [PATCH 038/474] minor fixes --- .../scala/assumptionAnalysis/AssumptionAnalyzer.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 74c074557..704d1b2b9 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -165,22 +165,22 @@ class DefaultAssumptionAnalyzer(member: Member) extends AssumptionAnalyzer { override def addPermissionAssertNode(chunk: Chunk, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Assertion): Option[Int] = { val node = PermissionAssertNode(chunk, sourceInfo, assumptionType) - addNode(node) + addPermissionDependencies(Set(chunk), node) Some(node.id) } override def addPermissionExhaleNode(chunk: Chunk, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Assertion): Option[Int] = { val node = PermissionExhaleNode(chunk, sourceInfo, assumptionType) - addNode(node) + addPermissionDependencies(Set(chunk), node) Some(node.id) } override def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNode: AssumptionAnalysisNode): Unit = { - val analysisChunks = assumptionGraph.nodes - .filter(c => c.isInstanceOf[ChunkAnalysisInfo] && oldChunks.contains(c.asInstanceOf[ChunkAnalysisInfo].getChunk)) + val oldChunkNodeIds = assumptionGraph.nodes + .filter(c => !c.isInstanceOf[PermissionAssertNode] && c.isInstanceOf[ChunkAnalysisInfo] && oldChunks.contains(c.asInstanceOf[ChunkAnalysisInfo].getChunk)) .map(_.id).toSet addNode(newChunkNode) - assumptionGraph.addEdges(analysisChunks, newChunkNode.id) + assumptionGraph.addEdges(oldChunkNodeIds, newChunkNode.id) } override def getMember: Option[Member] = Some(member) From 687187926f1a1a7a801e9848957875efd6b1fd59 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 13 May 2025 15:35:13 +0200 Subject: [PATCH 039/474] update examples --- src/test/resources/andrea/permissions.vpr | 72 ++++++++++++++++--- src/test/resources/andrea/predicates-fold.vpr | 29 ++++++-- 2 files changed, 87 insertions(+), 14 deletions(-) diff --git a/src/test/resources/andrea/permissions.vpr b/src/test/resources/andrea/permissions.vpr index 077487328..70d33e4c5 100644 --- a/src/test/resources/andrea/permissions.vpr +++ b/src/test/resources/andrea/permissions.vpr @@ -1,23 +1,79 @@ field f: Int +function id(n: Int): Int + ensures result == n method foo(a: Ref) requires acc(a.f, 1/2) && a.f >= 0 ensures acc(a.f, 1/2) +method simple(x: Ref, y: Ref, p: Perm) + requires acc(x.f) && acc(y.f) + ensures acc(x.f) && acc(y.f) +{ + x.f := y.f + 1 +} + +method call1(x: Ref) + requires acc(x.f) + ensures acc(x.f) +{ + assume x.f > 0 + foo(x) + assert x.f >= 0 +} + +method call2(x: Ref) + requires acc(x.f) + ensures acc(x.f) +{ + assume x.f > 0 + x.f := id(x.f) + assert x.f >= 0 +} + +method inhaleExhalePartial(x: Ref) +{ + inhale acc(x.f) && x.f > 0 + foo(x) + exhale acc(x.f, 1/2) +} -method copyAndInc(x: Ref, y: Ref, z: Ref, p: Perm) +method inhaleExhaleFull(x: Ref) +{ + inhale acc(x.f) && x.f > 0 + foo(x) + exhale acc(x.f) +} + +method permAmount(x: Ref, p: Perm) +{ + assume p > none + inhale acc(x.f, p) && x.f > 0 + assume p > 1/2 + foo(x) +} + +method quantifiedPerm(xs: Seq[Ref]) +{ + assume |xs| > 5 + inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) + + assert xs[2].f > 0 + + var a : Ref := xs[0] + a.f := 0 +} + +method bar(x: Ref, y: Ref, p: Perm) requires p > none - requires acc(x.f) && acc(y.f, p) && acc(z.f, 1/2) + requires acc(x.f) && acc(y.f, p) ensures acc(x.f) && acc(y.f, p) { - assume p < 1/2 + x.f := 5 + assume p > 1/2 assume y.f == 1 x.f := y.f + 1 - assume y == z foo(x) - foo(x) - assert x.f == z.f + 1 - x.f := 5 - assert x.f > 0 + assert x.f == y.f + 1 } \ No newline at end of file diff --git a/src/test/resources/andrea/predicates-fold.vpr b/src/test/resources/andrea/predicates-fold.vpr index 23c039d9c..87aea9790 100644 --- a/src/test/resources/andrea/predicates-fold.vpr +++ b/src/test/resources/andrea/predicates-fold.vpr @@ -5,15 +5,32 @@ predicate greater0(n: Int) n > 0 } -method foo(n: Int) +predicate greater5(n: Int) +{ + n > 5 +} + +method unfoldP(n: Int) requires greater0(n) { - var x: Int := 1 - - x := n + x unfold greater0(n) - fold greater0(x) - + x := n + x assert x > 1 +} + +method foldP(n: Int) + requires n > 10 +{ + var x: Int := 1 + fold greater0(x + n) +} + +method unfoldFoldP(a: Int, b: Int) + requires greater0(a) && greater5(b) + ensures greater5(a + b) +{ + unfold greater0(a) + unfold greater5(b) + fold greater5(a + b) } \ No newline at end of file From fa377f5483f97beb5bc51fb85bdc77628ff1f647 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 15 May 2025 09:55:41 +0200 Subject: [PATCH 040/474] chunk nodes: store permAmount and fix node types --- .../AssumptionAnalysisGraph.scala | 8 +-- .../AssumptionAnalyzer.scala | 53 +++++++++++-------- src/main/scala/interfaces/state/Chunks.scala | 33 +++++++----- src/main/scala/rules/ChunkSupporter.scala | 8 +-- src/main/scala/rules/StateConsolidator.scala | 4 +- src/main/scala/state/Chunks.scala | 33 +++++++----- 6 files changed, 81 insertions(+), 58 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index 2891ff62b..199517f60 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -111,7 +111,7 @@ trait GeneralAssertionNode extends AssumptionAnalysisNode {} trait ChunkAnalysisInfo { val chunk: Chunk - + val permAmount: Option[ast.Exp] def getChunk: Chunk = chunk } @@ -131,15 +131,15 @@ case class StringAssertionNode(description: String, isAsserted: Boolean, sourceI override def getNodeString: String = "assert " + description } -case class PermissionInhaleNode(chunk: Chunk, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Unknown) extends GeneralAssumptionNode with ChunkAnalysisInfo { +case class PermissionInhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Unknown) extends GeneralAssumptionNode with ChunkAnalysisInfo { override def getNodeString: String = "inhale " + chunk.toString } -case class PermissionExhaleNode(chunk: Chunk, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Explicit) extends GeneralAssertionNode with ChunkAnalysisInfo { +case class PermissionExhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Explicit) extends GeneralAssertionNode with ChunkAnalysisInfo { override def getNodeString: String = "exhale " + chunk.toString } -case class PermissionAssertNode(chunk: Chunk, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Explicit) extends GeneralAssertionNode with ChunkAnalysisInfo { +case class PermissionAssertNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Explicit) extends GeneralAssertionNode with ChunkAnalysisInfo { override def getNodeString: String = "assert " + chunk.toString } diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 704d1b2b9..411d9844d 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -1,20 +1,20 @@ package viper.silicon.assumptionAnalysis -import viper.silicon.Stack import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.state.Chunk import viper.silver.ast -import viper.silver.ast.{Member, NoPosition} +import viper.silver.ast.{Exp, Member, NoPosition} trait AssumptionAnalyzer { // def pushScope(stmt: ast.Stmt): Unit // def closeScope(): Unit - def addPermissionInhaleNode(chunk: Chunk, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] - def addPermissionAssertNode(chunk: Chunk, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Assertion): Option[Int] - def addPermissionExhaleNode(chunk: Chunk, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Assertion): Option[Int] + def addPermissionInhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] + def addPermissionAssertNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Assertion): Option[Int] + def addPermissionExhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Assertion): Option[Int] + def addPermissionNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Assertion, isExhale: Boolean=false): Option[Int] def addSingleAssumption(assumption: ast.Exp, analysisInfo: AnalysisInfo): Option[Int] @@ -28,7 +28,7 @@ trait AssumptionAnalyzer { def addAssertion(assertion: Either[String, ast.Exp], isAsserted: Boolean, analysisInfo: AnalysisInfo): Option[Int] - def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNode: AssumptionAnalysisNode): Unit + def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNodeId: Option[Int]): Unit def processUnsatCoreAndAddDependencies(dep: String): Unit @@ -157,30 +157,38 @@ class DefaultAssumptionAnalyzer(member: Member) extends AssumptionAnalyzer { assumptionGraph.addEdges(assumptionIds, assertionIds) } - override def addPermissionInhaleNode(chunk: Chunk, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { - val node = PermissionInhaleNode(chunk, sourceInfo, assumptionType) + override def addPermissionInhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { + val node = PermissionInhaleNode(chunk, permAmount, sourceInfo, assumptionType) addNode(node) Some(node.id) } - override def addPermissionAssertNode(chunk: Chunk, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Assertion): Option[Int] = { - val node = PermissionAssertNode(chunk, sourceInfo, assumptionType) - addPermissionDependencies(Set(chunk), node) + override def addPermissionAssertNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Assertion): Option[Int] = { + val node = PermissionAssertNode(chunk, permAmount, sourceInfo, assumptionType) + addNode(node) + addPermissionDependencies(Set(chunk), Some(node.id)) Some(node.id) } - override def addPermissionExhaleNode(chunk: Chunk, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Assertion): Option[Int] = { - val node = PermissionExhaleNode(chunk, sourceInfo, assumptionType) - addPermissionDependencies(Set(chunk), node) + override def addPermissionExhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Assertion): Option[Int] = { + val node = PermissionExhaleNode(chunk, permAmount, sourceInfo, assumptionType) + addNode(node) + addPermissionDependencies(Set(chunk), Some(node.id)) Some(node.id) } - override def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNode: AssumptionAnalysisNode): Unit = { + override def addPermissionNode(chunk: Chunk, permAmount: Option[Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isExhale: Boolean=false): Option[Int] = { + val nodeFunction = if(isExhale) addPermissionExhaleNode _ else addPermissionInhaleNode _ + nodeFunction(chunk, permAmount, sourceInfo, assumptionType) + } + + override def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNodeId: Option[Int]): Unit = { + if(newChunkNodeId.isEmpty) return + val oldChunkNodeIds = assumptionGraph.nodes - .filter(c => !c.isInstanceOf[PermissionAssertNode] && c.isInstanceOf[ChunkAnalysisInfo] && oldChunks.contains(c.asInstanceOf[ChunkAnalysisInfo].getChunk)) + .filter(c => c.id != newChunkNodeId.get && !c.isInstanceOf[PermissionAssertNode] && c.isInstanceOf[ChunkAnalysisInfo] && oldChunks.contains(c.asInstanceOf[ChunkAnalysisInfo].getChunk)) .map(_.id).toSet - addNode(newChunkNode) - assumptionGraph.addEdges(oldChunkNodeIds, newChunkNode.id) + assumptionGraph.addEdges(oldChunkNodeIds, newChunkNodeId.get) } override def getMember: Option[Member] = Some(member) @@ -196,11 +204,12 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { override def processUnsatCoreAndAddDependencies(dep: String): Unit = { } - override def addPermissionInhaleNode(chunk: Chunk, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None - override def addPermissionExhaleNode(chunk: Chunk, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None - override def addPermissionAssertNode(chunk: Chunk, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None + override def addPermissionInhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None + override def addPermissionExhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None + override def addPermissionAssertNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None + override def addPermissionNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isExhale: Boolean=false): Option[Int] = None - override def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNode: AssumptionAnalysisNode): Unit = { + override def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNode: Option[Int]): Unit = { } override def getMember: Option[Member] = None diff --git a/src/main/scala/interfaces/state/Chunks.scala b/src/main/scala/interfaces/state/Chunks.scala index c213a8a18..e87496d23 100644 --- a/src/main/scala/interfaces/state/Chunks.scala +++ b/src/main/scala/interfaces/state/Chunks.scala @@ -11,44 +11,49 @@ import viper.silicon.resources.ResourceID import viper.silicon.state.terms.{Term, Var} import viper.silver.ast -trait Chunk +trait Chunk { + val perm: Term + val permExp: Option[ast.Exp] +} trait ChunkIdentifer trait GeneralChunk extends Chunk { val resourceID: ResourceID val id: ChunkIdentifer - val perm: Term + protected def applyCondition(newCond: Term, newCondExp: Option[ast.Exp]): GeneralChunk protected def permMinus(perm: Term, permExp: Option[ast.Exp]): GeneralChunk protected def permPlus(perm: Term, permExp: Option[ast.Exp]): GeneralChunk protected def withPerm(newPerm: Term, newPermExp: Option[ast.Exp]): GeneralChunk - - val permExp: Option[ast.Exp] } object GeneralChunk { def applyCondition(chunk: GeneralChunk, newCond: Term, newCondExp: Option[ast.Exp], analysisInfo: AnalysisInfo): GeneralChunk = { val newChunk = chunk.applyCondition(newCond, newCondExp) - analysisInfo.assumptionAnalyzer.addPermissionDependencies(Set(chunk), PermissionInhaleNode(newChunk, analysisInfo.sourceInfo, analysisInfo.assumptionType)) + val newNodeId = analysisInfo.assumptionAnalyzer.addPermissionInhaleNode(newChunk, newChunk.permExp, analysisInfo.sourceInfo, analysisInfo.assumptionType) + analysisInfo.assumptionAnalyzer.addPermissionDependencies(Set(chunk), newNodeId) newChunk } - def permMinus(chunk: GeneralChunk, newPerm: Term, newPermExp: Option[ast.Exp], analysisInfo: AnalysisInfo): GeneralChunk = { + def permMinus(chunk: GeneralChunk, newPerm: Term, newPermExp: Option[ast.Exp], analysisInfo: AnalysisInfo, isExhale: Boolean=false): GeneralChunk = { val newChunk = chunk.permMinus(newPerm, newPermExp) - analysisInfo.assumptionAnalyzer.addPermissionDependencies(Set(chunk), PermissionInhaleNode(newChunk, analysisInfo.sourceInfo, analysisInfo.assumptionType)) + val newNodeId = analysisInfo.assumptionAnalyzer.addPermissionNode(newChunk, newPermExp, analysisInfo.sourceInfo, analysisInfo.assumptionType, isExhale) + analysisInfo.assumptionAnalyzer.addPermissionDependencies(Set(chunk), newNodeId) newChunk } - def permPlus(chunk: GeneralChunk, newPerm: Term, newPermExp: Option[ast.Exp], analysisInfo: AnalysisInfo): GeneralChunk = { + def permPlus(chunk: GeneralChunk, newPerm: Term, newPermExp: Option[ast.Exp], analysisInfo: AnalysisInfo, isExhale: Boolean=false): GeneralChunk = { val newChunk = chunk.permPlus(newPerm, newPermExp) - analysisInfo.assumptionAnalyzer.addPermissionDependencies(Set(chunk), PermissionInhaleNode(newChunk, analysisInfo.sourceInfo, analysisInfo.assumptionType)) + val newNodeId = analysisInfo.assumptionAnalyzer.addPermissionNode(newChunk, newPermExp, analysisInfo.sourceInfo, analysisInfo.assumptionType, isExhale) + analysisInfo.assumptionAnalyzer.addPermissionDependencies(Set(chunk), newNodeId) newChunk } - def withPerm(chunk: GeneralChunk, newPerm: Term, newPermExp: Option[ast.Exp], analysisInfo: AnalysisInfo): GeneralChunk = { + def withPerm(chunk: GeneralChunk, newPerm: Term, newPermExp: Option[ast.Exp], analysisInfo: AnalysisInfo, isExhale: Boolean=false): GeneralChunk = { val newChunk = chunk.withPerm(newPerm, newPermExp) - analysisInfo.assumptionAnalyzer.addPermissionDependencies(Set(chunk), PermissionInhaleNode(newChunk, analysisInfo.sourceInfo, analysisInfo.assumptionType)) + val newNodeId = analysisInfo.assumptionAnalyzer.addPermissionNode(newChunk, newPermExp, analysisInfo.sourceInfo, analysisInfo.assumptionType, isExhale) + analysisInfo.assumptionAnalyzer.addPermissionDependencies(Set(chunk), newNodeId) newChunk } } @@ -67,7 +72,8 @@ trait NonQuantifiedChunk extends GeneralChunk { object NonQuantifiedChunk { def withSnap(chunk: NonQuantifiedChunk, snap: Term, snapExp: Option[ast.Exp], analysisInfo: AnalysisInfo): NonQuantifiedChunk = { val newChunk = chunk.withSnap(snap, snapExp) - analysisInfo.assumptionAnalyzer.addPermissionDependencies(Set(chunk), PermissionInhaleNode(newChunk, analysisInfo.sourceInfo, analysisInfo.assumptionType)) + val newNodeId = analysisInfo.assumptionAnalyzer.addPermissionInhaleNode(newChunk, newChunk.permExp, analysisInfo.sourceInfo, analysisInfo.assumptionType) + analysisInfo.assumptionAnalyzer.addPermissionDependencies(Set(chunk), newNodeId) newChunk } } @@ -87,7 +93,8 @@ trait QuantifiedChunk extends GeneralChunk { object QuantifiedChunk { def withSnapshotMap(chunk: QuantifiedChunk, snap: Term, analysisInfo: AnalysisInfo): QuantifiedChunk = { val newChunk = chunk.withSnapshotMap(snap) - analysisInfo.assumptionAnalyzer.addPermissionDependencies(Set(chunk), PermissionInhaleNode(newChunk, analysisInfo.sourceInfo, analysisInfo.assumptionType)) + val newNodeId = analysisInfo.assumptionAnalyzer.addPermissionInhaleNode(newChunk, newChunk.permExp, analysisInfo.sourceInfo, analysisInfo.assumptionType) + analysisInfo.assumptionAnalyzer.addPermissionDependencies(Set(chunk), newNodeId) newChunk } } \ No newline at end of file diff --git a/src/main/scala/rules/ChunkSupporter.scala b/src/main/scala/rules/ChunkSupporter.scala index 4d87561e3..ecf72009d 100644 --- a/src/main/scala/rules/ChunkSupporter.scala +++ b/src/main/scala/rules/ChunkSupporter.scala @@ -176,7 +176,7 @@ object chunkSupporter extends ChunkSupportRules { case Some(ch) => if (s.assertReadAccessOnly) { if (v.decider.check(Implies(IsPositive(perms), IsPositive(ch.perm)), Verifier.config.assertTimeout.getOrElse(0))) { - v.decider.assumptionAnalyzer.addPermissionAssertNode(ch, v.decider.assumptionAnalyzer.currentAnalysisInfo.sourceInfo) + v.decider.assumptionAnalyzer.addPermissionAssertNode(ch, ch.permExp.map(pe => IsPositive(pe)(pe.pos, pe.info, pe.errT)), v.decider.assumptionAnalyzer.currentAnalysisInfo.sourceInfo) (Complete(), s, h, Some(ch)) } else { (Incomplete(perms, permsExp), s, h, None) @@ -186,7 +186,7 @@ object chunkSupporter extends ChunkSupportRules { val toTakeExp = permsExp.map(pe => buildMinExp(Seq(ch.permExp.get, pe), ast.Perm)) val newPermExp = permsExp.map(pe => ast.PermSub(ch.permExp.get, toTakeExp.get)(pe.pos, pe.info, pe.errT)) val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, toTake), newPermExp, analysisInfo).asInstanceOf[NonQuantifiedChunk] - val takenChunk = Some(GeneralChunk.withPerm(ch, toTake, toTakeExp, analysisInfo).asInstanceOf[NonQuantifiedChunk]) + val takenChunk = Some(GeneralChunk.withPerm(ch, toTake, toTakeExp, analysisInfo, isExhale = true).asInstanceOf[NonQuantifiedChunk]) var newHeap = h - ch if (!v.decider.check(newChunk.perm === NoPerm, Verifier.config.checkTimeout())) { newHeap = newHeap + newChunk @@ -200,7 +200,7 @@ object chunkSupporter extends ChunkSupportRules { v.decider.assume(PermLess(perms, ch.perm), Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp))) val newPermExp = permsExp.map(pe => ast.PermSub(ch.permExp.get, pe)(pe.pos, pe.info, pe.errT)) val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, perms), newPermExp, analysisInfo).asInstanceOf[NonQuantifiedChunk] - val takenChunk = GeneralChunk.withPerm(ch, perms, permsExp, analysisInfo).asInstanceOf[NonQuantifiedChunk] + val takenChunk = GeneralChunk.withPerm(ch, perms, permsExp, analysisInfo, isExhale=true).asInstanceOf[NonQuantifiedChunk] val newHeap = h - ch + newChunk assumeProperties(newChunk, newHeap) (Complete(), s, newHeap, Some(takenChunk)) @@ -260,7 +260,7 @@ object chunkSupporter extends ChunkSupportRules { val findRes = findChunk[NonQuantifiedChunk](h.values, id, args, v) findRes match { case Some(ch) if v.decider.check(IsPositive(ch.perm), Verifier.config.checkTimeout()) => - v.decider.assumptionAnalyzer.addPermissionAssertNode(ch, v.decider.assumptionAnalyzer.currentAnalysisInfo.sourceInfo) + v.decider.assumptionAnalyzer.addPermissionAssertNode(ch, ch.permExp.map(pe => IsPositive(pe)(pe.pos, pe.info, pe.errT)), v.decider.assumptionAnalyzer.currentAnalysisInfo.sourceInfo) Q(s, ch.snap, v) case _ if v.decider.checkSmoke(true) => if (s.isInPackage) { diff --git a/src/main/scala/rules/StateConsolidator.scala b/src/main/scala/rules/StateConsolidator.scala index 87b82516a..5464b59e0 100644 --- a/src/main/scala/rules/StateConsolidator.scala +++ b/src/main/scala/rules/StateConsolidator.scala @@ -202,8 +202,8 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol val result = mergeChunks1(fr1, chunk1, chunk2, qvars, v) if(result.isDefined){ val (_, newChunk, _) = result.get - val newChunkNode = PermissionInhaleNode(newChunk, StringAnalysisSourceInfo("state consolidation", NoPosition), AssumptionType.Internal) - v.decider.assumptionAnalyzer.addPermissionDependencies(Set(chunk1, chunk2), newChunkNode) + val newChunkNode = PermissionInhaleNode(newChunk, newChunk.permExp, StringAnalysisSourceInfo("state consolidation", NoPosition), AssumptionType.Internal) + v.decider.assumptionAnalyzer.addPermissionDependencies(Set(chunk1, chunk2), Some(newChunkNode.id)) } result } diff --git a/src/main/scala/state/Chunks.scala b/src/main/scala/state/Chunks.scala index 590ee1c85..ca09b6b55 100644 --- a/src/main/scala/state/Chunks.scala +++ b/src/main/scala/state/Chunks.scala @@ -41,9 +41,9 @@ object BasicChunk { args: Seq[Term], argsExp: Option[Seq[ast.Exp]], snap: Term, snapExp: Option[ast.Exp], perm: Term, permExp: Option[ast.Exp], - analysisInfo: AnalysisInfo): BasicChunk = { + analysisInfo: AnalysisInfo, isExhale: Boolean=false): BasicChunk = { val chunk = new BasicChunk(resourceID, id, args, argsExp, snap, snapExp, perm, permExp) - analysisInfo.assumptionAnalyzer.addPermissionInhaleNode(chunk, analysisInfo.sourceInfo, analysisInfo.assumptionType) + analysisInfo.assumptionAnalyzer.addPermissionNode(chunk, permExp, analysisInfo.sourceInfo, analysisInfo.assumptionType, isExhale) chunk } @@ -55,9 +55,12 @@ object BasicChunk { snap: Term, snapExp: Option[ast.Exp], perm: Term, - permExp: Option[ast.Exp], analysisInfo: AnalysisInfo): BasicChunk = { - val newChunk = BasicChunk(resourceID, id, args, argsExp, snap, snapExp, perm, permExp, analysisInfo) - analysisInfo.assumptionAnalyzer.addPermissionDependencies(oldChunks, PermissionInhaleNode(newChunk, analysisInfo.sourceInfo, analysisInfo.assumptionType)) + permExp: Option[ast.Exp], + analysisInfo: AnalysisInfo, + isExhale: Boolean=false): BasicChunk = { + val newChunk = new BasicChunk(resourceID, id, args, argsExp, snap, snapExp, perm, permExp) + val newNode = analysisInfo.assumptionAnalyzer.addPermissionNode(newChunk, permExp, analysisInfo.sourceInfo, analysisInfo.assumptionType, isExhale) + analysisInfo.assumptionAnalyzer.addPermissionDependencies(oldChunks, newNode) newChunk } } @@ -138,9 +141,10 @@ object QuantifiedFieldChunk { singletonRcvr: Option[Term], singletonRcvrExp: Option[ast.Exp], hints: Seq[Term] = Nil, - analysisInfo: AnalysisInfo): QuantifiedFieldChunk = { + analysisInfo: AnalysisInfo, + isExhale: Boolean=false): QuantifiedFieldChunk = { val chunk = new QuantifiedFieldChunk(id, fvf, condition, conditionExp, permValue, permValueExp, invs, singletonRcvr, singletonRcvrExp, hints) - analysisInfo.assumptionAnalyzer.addPermissionInhaleNode(chunk, analysisInfo.sourceInfo, analysisInfo.assumptionType) + analysisInfo.assumptionAnalyzer.addPermissionNode(chunk, permValueExp, analysisInfo.sourceInfo, analysisInfo.assumptionType, isExhale) chunk } } @@ -228,9 +232,10 @@ object QuantifiedPredicateChunk { singletonArgs: Option[Seq[Term]], singletonArgExps: Option[Seq[ast.Exp]], hints: Seq[Term] = Nil, - analysisInfo: AnalysisInfo): QuantifiedPredicateChunk = { + analysisInfo: AnalysisInfo, + isExhale: Boolean=false): QuantifiedPredicateChunk = { val chunk = new QuantifiedPredicateChunk(id, quantifiedVars, quantifiedVarExps, psf, condition, conditionExp, permValue, permValueExp, invs, singletonArgs, singletonArgExps, hints) - analysisInfo.assumptionAnalyzer.addPermissionInhaleNode(chunk, analysisInfo.sourceInfo, analysisInfo.assumptionType) + analysisInfo.assumptionAnalyzer.addPermissionNode(chunk, permValueExp, analysisInfo.sourceInfo, analysisInfo.assumptionType, isExhale) chunk } } @@ -301,9 +306,10 @@ object QuantifiedMagicWandChunk { singletonArgs: Option[Seq[Term]], singletonArgExps: Option[Seq[ast.Exp]], hints: Seq[Term] = Nil, - analysisInfo: AnalysisInfo): QuantifiedMagicWandChunk = { + analysisInfo: AnalysisInfo, + isExhale: Boolean=false): QuantifiedMagicWandChunk = { val chunk = new QuantifiedMagicWandChunk(id, quantifiedVars, quantifiedVarExps, wsf, perm, permExp, invs, singletonArgs, singletonArgExps, hints) - analysisInfo.assumptionAnalyzer.addPermissionInhaleNode(chunk, analysisInfo.sourceInfo, analysisInfo.assumptionType) + analysisInfo.assumptionAnalyzer.addPermissionNode(chunk, permExp, analysisInfo.sourceInfo, analysisInfo.assumptionType, isExhale) chunk } } @@ -380,9 +386,10 @@ object MagicWandChunk { snap: MagicWandSnapshot, perm: Term, permExp: Option[ast.Exp], - analysisInfo: AnalysisInfo): MagicWandChunk = { + analysisInfo: AnalysisInfo, + isExhale: Boolean=false): MagicWandChunk = { val chunk = new MagicWandChunk(id, bindings, args, argsExp, snap, perm, permExp) - analysisInfo.assumptionAnalyzer.addPermissionInhaleNode(chunk, analysisInfo.sourceInfo, analysisInfo.assumptionType) + analysisInfo.assumptionAnalyzer.addPermissionNode(chunk, permExp, analysisInfo.sourceInfo, analysisInfo.assumptionType, isExhale) chunk } } From ed71b461e956954276add4f860d82e731e00d794 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 15 May 2025 09:56:22 +0200 Subject: [PATCH 041/474] minor fixes - source info --- src/main/scala/rules/Brancher.scala | 6 +++++ src/main/scala/rules/Executor.scala | 2 +- src/test/resources/andrea/branches.vpr | 29 +++++++++++++++++++++-- src/test/resources/andrea/permissions.vpr | 9 +++++++ 4 files changed, 43 insertions(+), 3 deletions(-) diff --git a/src/main/scala/rules/Brancher.scala b/src/main/scala/rules/Brancher.scala index fc2422b49..3898cbff9 100644 --- a/src/main/scala/rules/Brancher.scala +++ b/src/main/scala/rules/Brancher.scala @@ -6,6 +6,8 @@ package viper.silicon.rules +import viper.silicon.assumptionAnalysis.{AssumptionType, ExpAnalysisSourceInfo} + import java.util.concurrent._ import viper.silicon.common.concurrency._ import viper.silicon.decider.PathConditionStack @@ -143,7 +145,9 @@ object brancher extends BranchingRules { executionFlowController.locally(s, v0)((s1, v1) => { v1.decider.prover.comment(s"[else-branch: $cnt | $negatedCondition]") + v1.decider.assumptionAnalyzer.setCurrentAnalysisInfo(ExpAnalysisSourceInfo(negatedConditionExp), AssumptionType.PathCondition) v1.decider.setCurrentBranchCondition(negatedCondition, (negatedConditionExp, negatedConditionExpNew)) + v0.decider.assumptionAnalyzer.clearCurrentAnalysisInfo() var functionsOfElseBranchdDeciderBefore: Set[FunctionDecl] = null var nMacrosOfElseBranchDeciderBefore: Int = 0 @@ -193,7 +197,9 @@ object brancher extends BranchingRules { v.symbExLog.markReachable(uidBranchPoint) executionFlowController.locally(s, v)((s1, v1) => { v1.decider.prover.comment(s"[then-branch: $cnt | $condition]") + v1.decider.assumptionAnalyzer.setCurrentAnalysisInfo(ExpAnalysisSourceInfo(conditionExp._1), AssumptionType.PathCondition) v1.decider.setCurrentBranchCondition(condition, conditionExp) + v1.decider.assumptionAnalyzer.clearCurrentAnalysisInfo() fThen(v1.stateConsolidator(s1).consolidateOptionally(s1, v1), v1) }) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index c79517fb5..f3f3c5054 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -329,8 +329,8 @@ object executor extends ExecutionRules { val sepIdentifier = v.symbExLog.openScope(new ExecuteRecord(stmt, s, v.decider.pcs)) v.decider.assumptionAnalyzer.setCurrentAnalysisInfo(StmtAnalysisSourceInfo(stmt), AssumptionType.Implicit) exec2(s, stmt, v)((s1, v1) => { - v1.symbExLog.closeScope(sepIdentifier) v.decider.assumptionAnalyzer.clearCurrentAnalysisInfo() + v1.symbExLog.closeScope(sepIdentifier) Q(s1, v1)}) } diff --git a/src/test/resources/andrea/branches.vpr b/src/test/resources/andrea/branches.vpr index 44740ac3f..d14b2cfa4 100644 --- a/src/test/resources/andrea/branches.vpr +++ b/src/test/resources/andrea/branches.vpr @@ -1,7 +1,32 @@ field f: Int -method foo(a: Ref, b: Ref) + +method foo(a: Int, b: Int) +{ + var n:Int, c: Bool + + assume a > 0 + assume b > 0 + + if(c){ + n := a + 1 + }else{ + n := b + 1 + } + + var x: Int + if(a >= n){ + x := a + b + }else{ + x := n + 1 + assert x >= 1 + } + assert x > n +} + + +method fooPerm(a: Ref, b: Ref) requires acc(a.f) && acc(b.f) { var n:Int, c: Bool @@ -22,5 +47,5 @@ method foo(a: Ref, b: Ref) x := n assert x >= 1 } - assert x > 0 + assert x >= n } \ No newline at end of file diff --git a/src/test/resources/andrea/permissions.vpr b/src/test/resources/andrea/permissions.vpr index 70d33e4c5..c5c6b5381 100644 --- a/src/test/resources/andrea/permissions.vpr +++ b/src/test/resources/andrea/permissions.vpr @@ -76,4 +76,13 @@ method bar(x: Ref, y: Ref, p: Perm) x.f := y.f + 1 foo(x) assert x.f == y.f + 1 +} + +method transitivity(a: Ref, n: Int) + requires n > 0 + requires acc(a.f) && a.f > 0 +{ + var res: Int + res := a.f / n + assert res >= 0 } \ No newline at end of file From 67f9e1fd9b1e9dcfe245a7056d455d1daeed1c9d Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 15 May 2025 10:41:40 +0200 Subject: [PATCH 042/474] add check nodes --- .../assumptionAnalysis/AnalysisInfo.scala | 8 +++++-- .../AssumptionAnalysisGraph.scala | 22 +++++++++++-------- .../AssumptionAnalyzer.scala | 19 ++++++++-------- src/main/scala/decider/Decider.scala | 16 ++++++++++---- src/test/resources/andrea/quickTest.vpr | 13 +++++------ 5 files changed, 46 insertions(+), 32 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AnalysisInfo.scala b/src/main/scala/assumptionAnalysis/AnalysisInfo.scala index 2f4f22a2d..6a0722947 100644 --- a/src/main/scala/assumptionAnalysis/AnalysisInfo.scala +++ b/src/main/scala/assumptionAnalysis/AnalysisInfo.scala @@ -1,7 +1,11 @@ package viper.silicon.assumptionAnalysis -import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType -import viper.silicon.verifier.Verifier +object AssumptionType extends Enumeration { + type AssumptionType = Value + val Explicit, PathCondition, Internal, Implicit, Assertion, Unknown = Value +} +import viper.silicon.assumptionAnalysis.AssumptionType._ + case class AnalysisInfo(assumptionAnalyzer: AssumptionAnalyzer, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType) { diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index 199517f60..459116061 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -1,15 +1,11 @@ package viper.silicon.assumptionAnalysis import viper.silicon.interfaces.state.Chunk +import viper.silicon.state.terms.Term import viper.silver.ast import java.util.concurrent.atomic.AtomicInteger import scala.collection.mutable - -object AssumptionType extends Enumeration { - type AssumptionType = Value - val Explicit, PathCondition, Internal, Implicit, Assertion, Unknown = Value -} import viper.silicon.assumptionAnalysis.AssumptionType._ @@ -66,7 +62,6 @@ class DefaultAssumptionAnalysisGraph extends AssumptionAnalysisGraph { override var edges: mutable.Map[Int, Set[Int]] = mutable.Map.empty override def addNode(node: AssumptionAnalysisNode): Unit = { - val identicalNodes = nodes.filter(node.equals) // TODO ake: when to merge identical nodes? nodes = nodes :+ node } @@ -107,7 +102,9 @@ trait AssumptionAnalysisNode { } trait GeneralAssumptionNode extends AssumptionAnalysisNode {} -trait GeneralAssertionNode extends AssumptionAnalysisNode {} +trait GeneralAssertionNode extends AssumptionAnalysisNode { + var isAsserted = false +} trait ChunkAnalysisInfo { val chunk: Chunk @@ -123,25 +120,32 @@ case class StringAssumptionNode(description: String, sourceInfo: AnalysisSourceI override def getNodeString: String = "assume " + description } -case class SimpleAssertionNode(assertion: ast.Exp, isAsserted: Boolean, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Explicit) extends GeneralAssertionNode { +case class SimpleAssertionNode(assertion: ast.Exp, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Explicit) extends GeneralAssertionNode { override def getNodeString: String = "assert " + assertion.toString } -case class StringAssertionNode(description: String, isAsserted: Boolean, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Explicit) extends GeneralAssertionNode { +case class StringAssertionNode(description: String, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Explicit) extends GeneralAssertionNode { override def getNodeString: String = "assert " + description } +case class SimpleCheckNode(t: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Unknown) extends GeneralAssertionNode { + override def getNodeString: String = "check " + t +} + case class PermissionInhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Unknown) extends GeneralAssumptionNode with ChunkAnalysisInfo { override def getNodeString: String = "inhale " + chunk.toString } case class PermissionExhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Explicit) extends GeneralAssertionNode with ChunkAnalysisInfo { + isAsserted = true override def getNodeString: String = "exhale " + chunk.toString } case class PermissionAssertNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Explicit) extends GeneralAssertionNode with ChunkAnalysisInfo { + isAsserted = true override def getNodeString: String = "assert " + chunk.toString } + diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 411d9844d..e61e5bed1 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -4,6 +4,7 @@ import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.state.Chunk +import viper.silicon.state.terms.Term import viper.silver.ast import viper.silver.ast.{Exp, Member, NoPosition} @@ -26,7 +27,7 @@ trait AssumptionAnalyzer { def addAssumptions(assumptions: Iterable[DebugExp], analysisInfo: AnalysisInfo): Seq[Int] - def addAssertion(assertion: Either[String, ast.Exp], isAsserted: Boolean, analysisInfo: AnalysisInfo): Option[Int] + def createAssertOrCheckNode(term: Term, assertion: Either[String, ast.Exp], analysisInfo: AnalysisInfo, isCheck: Boolean): Option[GeneralAssertionNode] def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNodeId: Option[Int]): Unit @@ -140,13 +141,13 @@ class DefaultAssumptionAnalyzer(member: Member) extends AssumptionAnalyzer { newNodes.map(_.id).toSeq } - override def addAssertion(assertion: Either[String, ast.Exp], isAsserted: Boolean, analysisInfo: AnalysisInfo): Option[Int] = { - val newNode = assertion match { - case Left(description) => StringAssertionNode(description, isAsserted, analysisInfo.sourceInfo, analysisInfo.assumptionType) - case Right(exp) => SimpleAssertionNode(exp, isAsserted, analysisInfo.sourceInfo, analysisInfo.assumptionType) - } - addNode(newNode) - Some(newNode.id) + override def createAssertOrCheckNode(term: Term, assertion: Either[String, ast.Exp], analysisInfo: AnalysisInfo, isCheck: Boolean): Option[GeneralAssertionNode] = { + if(isCheck) return Some(SimpleCheckNode(term, analysisInfo.sourceInfo, analysisInfo.assumptionType)) + + Some(assertion match { + case Left(description) => StringAssertionNode(description, analysisInfo.sourceInfo, analysisInfo.assumptionType) + case Right(exp) => SimpleAssertionNode(exp, analysisInfo.sourceInfo, analysisInfo.assumptionType) + }) } override def processUnsatCoreAndAddDependencies(dep: String): Unit = { @@ -199,7 +200,7 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { override def addAssumptions(assumptions: Iterable[DebugExp], analysisInfo: AnalysisInfo): Seq[Int] = Seq.empty - override def addAssertion(assertion: Either[String, ast.Exp], isAsserted: Boolean, analysisInfo: AnalysisInfo): Option[Int] = None + override def createAssertOrCheckNode(term: Term, assertion: Either[String, ast.Exp], analysisInfo: AnalysisInfo, isCheck: Boolean): Option[GeneralAssertionNode] = None override def processUnsatCoreAndAddDependencies(dep: String): Unit = { } diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index ba8653740..48136af7f 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -400,7 +400,9 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => prover.check(timeout) == Unsat } - def check(t: Term, timeout: Int): Boolean = deciderAssert(t, Left("check"), Some(timeout)) // TODO ake + def check(t: Term, timeout: Int): Boolean = { + deciderAssert(t, Left("check " + t.toString), Some(timeout), isCheck=true) + } def assert(t: Term, description: String, timeout: Option[Int])(Q: Boolean => VerificationResult): VerificationResult = { @@ -426,14 +428,20 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => Q(success) } - private def deciderAssert(t: Term, e: Either[String, ast.Exp], timeout: Option[Int]) = { + private def deciderAssert(t: Term, e: Either[String, ast.Exp], timeout: Option[Int], isCheck: Boolean=false) = { val assertRecord = new DeciderAssertRecord(t, timeout) val sepIdentifier = symbExLog.openScope(assertRecord) val asserted = if(Verifier.config.enableAssumptionAnalysis()) t.equals(True) else isKnownToBeTrue(t) val sourceExp = assumptionAnalyzer.currentExpStack.headOption - val assertionId: Option[Int] = if(!asserted) assumptionAnalyzer.addAssertion(e, false, decider.assumptionAnalyzer.currentAnalysisInfo.withAssumptionType(AssumptionType.Assertion)) else None - val result = asserted || proverAssert(t, timeout, AssumptionAnalyzer.createAssertionLabel(assertionId)) + val assertNode = if(!asserted) assumptionAnalyzer.createAssertOrCheckNode(t, e, decider.assumptionAnalyzer.currentAnalysisInfo, isCheck) else None + + val result = asserted || proverAssert(t, timeout, AssumptionAnalyzer.createAssertionLabel(assertNode map (_.id))) + + assertNode foreach (_.isAsserted = result) + if(result || !isCheck) assertNode foreach assumptionAnalyzer.assumptionGraph.addNode + + symbExLog.closeScope(sepIdentifier) result diff --git a/src/test/resources/andrea/quickTest.vpr b/src/test/resources/andrea/quickTest.vpr index 0af6666e6..6071f6fd0 100644 --- a/src/test/resources/andrea/quickTest.vpr +++ b/src/test/resources/andrea/quickTest.vpr @@ -1,11 +1,8 @@ field f: Int -function id(n: Int): Int - ensures n == result - -method foo(x: Ref, n: Int) +method simple(x: Ref, y: Ref, p: Perm) + requires acc(x.f) && acc(y.f) + ensures acc(x.f) && acc(y.f) { - var a: Int - a := id(5) - assert a > 0 -} \ No newline at end of file + x.f := y.f + 1 +} From cd34ab25494b4cfbcd4aa90635fba3d18c612bc0 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 17 May 2025 08:35:17 +0200 Subject: [PATCH 043/474] add transitive edges --- .../AssumptionAnalysisGraph.scala | 39 +++++++++++++++++-- .../scala/verifier/DefaultMainVerifier.scala | 1 + 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index 459116061..b5ae56230 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -20,6 +20,7 @@ object AssumptionAnalysisGraphHelper { trait AssumptionAnalysisGraph { var nodes: Seq[AssumptionAnalysisNode] var edges: mutable.Map[Int, Set[Int]] + var transitiveEdges: mutable.Map[Int, Set[Int]] = mutable.Map.empty def addNode(node: AssumptionAnalysisNode): Unit def addNodes(nodes: Set[AssumptionAnalysisNode]): Unit @@ -50,6 +51,35 @@ trait AssumptionAnalysisGraph { res } + def getNodesPerSourceInfo(): mutable.HashMap[AnalysisSourceInfo, Seq[AssumptionAnalysisNode]] = { + val res = new mutable.HashMap[AnalysisSourceInfo, Seq[AssumptionAnalysisNode]]() + nodes foreach {n => + res.updateWith(n.sourceInfo)({ + case Some(ns) => Some(ns ++ Seq(n)) + case None => Some(Seq(n)) + }) + } + res + } + + def addTransitiveEdges(source: Int, targets: Iterable[Int]): Unit = { + val oldTargets = transitiveEdges.getOrElse(source, Set.empty) + val newTargets = targets filter(t => t > source) // we only want forward edges + if(newTargets.nonEmpty) transitiveEdges.update(source, oldTargets ++ newTargets) + } + + def addTransitiveEdges(source: Iterable[Int], targets: Iterable[Int]): Unit = { + source foreach (s => addTransitiveEdges(s, targets)) + } + + def addTransitiveEdges(): Unit = { + val nodesPerSourceInfo = getNodesPerSourceInfo() + nodesPerSourceInfo foreach {nodes => + val asserts = nodes._2.filter(_.isInstanceOf[GeneralAssertionNode]).map(_.id) + val assumes = nodes._2.filter(_.isInstanceOf[GeneralAssumptionNode]).map(_.id) + addTransitiveEdges(asserts, assumes) + } + } // def findDependentAssumptions(assertion, enableTransitivity=false) // def findDependentAssertions(assumption, enableTransitivity=false) // def findUnnecessaryAssumptions(enableTransitivity=false) @@ -71,7 +101,8 @@ class DefaultAssumptionAnalysisGraph extends AssumptionAnalysisGraph { override def addEdges(source: Int, targets: Iterable[Int]): Unit = { val oldTargets = edges.getOrElse(source, Set.empty) - edges.update(source, oldTargets ++ targets) + val newTargets = targets filter(t => t > source) // TODO ake: only forward edges? + edges.update(source, oldTargets ++ newTargets) } override def addEdges(sources: Iterable[Int], target: Int): Unit = { @@ -133,17 +164,17 @@ case class SimpleCheckNode(t: Term, sourceInfo: AnalysisSourceInfo, assumptionTy } case class PermissionInhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Unknown) extends GeneralAssumptionNode with ChunkAnalysisInfo { - override def getNodeString: String = "inhale " + chunk.toString + override def getNodeString: String = "inhale " + permAmount.getOrElse("") + " at chunk " + chunk.toString } case class PermissionExhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Explicit) extends GeneralAssertionNode with ChunkAnalysisInfo { isAsserted = true - override def getNodeString: String = "exhale " + chunk.toString + override def getNodeString: String = "exhale " + permAmount.getOrElse("") + " at chunk " + chunk.toString } case class PermissionAssertNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Explicit) extends GeneralAssertionNode with ChunkAnalysisInfo { isAsserted = true - override def getNodeString: String = "assert " + chunk.toString + override def getNodeString: String = "assert " + permAmount.getOrElse("") + " at chunk " + chunk.toString } diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index 679a39cc4..c5e4419d8 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -309,6 +309,7 @@ class DefaultMainVerifier(config: Config, if(Verifier.config.enableAssumptionAnalysis()){ val assumptionAnalyzers = verificationResults.filter(_.assumptionAnalyzer.isInstanceOf[DefaultAssumptionAnalyzer]).map(_.assumptionAnalyzer) + assumptionAnalyzers.foreach(_.assumptionGraph.addTransitiveEdges()) logger debug s"assumption analyzers ${assumptionAnalyzers.mkString(", ")}" } From fec80741de57c91cfd19eeb15574a50eee704a5a Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 17 May 2025 12:09:01 +0200 Subject: [PATCH 044/474] add graph export functionality --- .../AnalysisSourceInfo.scala | 19 +++++++++---- .../AssumptionAnalysisGraph.scala | 28 +++++++++++++++---- .../AssumptionAnalyzer.scala | 18 +++++++++++- src/main/scala/interfaces/state/Chunks.scala | 5 +++- src/main/scala/state/Chunks.scala | 6 ++++ .../scala/verifier/DefaultMainVerifier.scala | 1 + 6 files changed, 65 insertions(+), 12 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala index cfe55bf1c..b1833c237 100644 --- a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala +++ b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala @@ -1,11 +1,20 @@ package viper.silicon.assumptionAnalysis import viper.silver.ast -import viper.silver.ast.{NoPosition, Position} +import viper.silver.ast.{HasLineColumn, NoPosition, Position, VirtualPosition} abstract class AnalysisSourceInfo { - override def toString: String = getPosition.toString + override def toString: String = getStringForExport + + def getStringForExport: String = { + getPosition match { + case NoPosition => "unknown position" + case column: HasLineColumn => "line " + column.line.toString + case VirtualPosition(identifier) => "label " + identifier + } + } + def getPosition: Position } @@ -14,7 +23,7 @@ case class NoAnalysisSourceInfo() extends AnalysisSourceInfo { } case class ExpAnalysisSourceInfo(source: ast.Exp) extends AnalysisSourceInfo { - override def toString: String = source.toString + " (" + source.pos + ")" + override def toString: String = source.toString + " (" + super.toString + ")" override def getPosition: Position = source.pos @@ -28,7 +37,7 @@ case class ExpAnalysisSourceInfo(source: ast.Exp) extends AnalysisSourceInfo { } case class StmtAnalysisSourceInfo(source: ast.Stmt) extends AnalysisSourceInfo { - override def toString: String = source.toString + " (" + source.pos + ")" + override def toString: String = source.toString + " (" + super.toString + ")" override def getPosition: Position = source.pos override def equals(obj: Any): Boolean = { @@ -41,7 +50,7 @@ case class StmtAnalysisSourceInfo(source: ast.Stmt) extends AnalysisSourceInfo { } case class StringAnalysisSourceInfo(description: String, position: Position) extends AnalysisSourceInfo { - override def toString: String = description + " (" + position + ")" + override def toString: String = description + " (" + super.toString + ")" override def getPosition: Position = position } diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index b5ae56230..6b66873d1 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -1,12 +1,13 @@ package viper.silicon.assumptionAnalysis +import viper.silicon.assumptionAnalysis.AssumptionType._ import viper.silicon.interfaces.state.Chunk import viper.silicon.state.terms.Term import viper.silver.ast +import java.io.PrintWriter import java.util.concurrent.atomic.AtomicInteger import scala.collection.mutable -import viper.silicon.assumptionAnalysis.AssumptionType._ object AssumptionAnalysisGraphHelper { @@ -84,6 +85,23 @@ trait AssumptionAnalysisGraph { // def findDependentAssertions(assumption, enableTransitivity=false) // def findUnnecessaryAssumptions(enableTransitivity=false) // def mergeIdenticalNodes() + + def getNodeExportString(node: AssumptionAnalysisNode): String = { + node.id + " | " + node.getClass.getSimpleName + " | " + node.assumptionType + " | " + node.getNodeString + " | " + node.sourceInfo.getStringForExport + } + + def exportGraph(fileName: String): Unit = { + val writer = new PrintWriter(fileName) + writer.println("====== Nodes ======") + writer.println("id | node type | assumption type | node info | source info") + nodes foreach (n => writer.println(getNodeExportString(n))) + writer.println("====== Edges ======") + edges foreach (e => writer.println(e._1 + " -> " + e._2.mkString(","))) + writer.println("====== Transitive Edges ======") + transitiveEdges foreach (e => writer.println(e._1 + " -> " + e._2.mkString(","))) + writer.println("====== End ======") + writer.close() + } } @@ -119,7 +137,7 @@ trait AssumptionAnalysisNode { val sourceInfo: AnalysisSourceInfo val assumptionType: AssumptionType - override def toString: String = id.toString + ": " + getNodeString + " at " + sourceInfo.toString + override def toString: String = id.toString + " | " + getNodeString + " | " + sourceInfo.toString def getNodeString: String @@ -164,17 +182,17 @@ case class SimpleCheckNode(t: Term, sourceInfo: AnalysisSourceInfo, assumptionTy } case class PermissionInhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Unknown) extends GeneralAssumptionNode with ChunkAnalysisInfo { - override def getNodeString: String = "inhale " + permAmount.getOrElse("") + " at chunk " + chunk.toString + override def getNodeString: String = "inhale " + chunk.getAnalysisInfo } case class PermissionExhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Explicit) extends GeneralAssertionNode with ChunkAnalysisInfo { isAsserted = true - override def getNodeString: String = "exhale " + permAmount.getOrElse("") + " at chunk " + chunk.toString + override def getNodeString: String = "exhale " + chunk.getAnalysisInfo } case class PermissionAssertNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Explicit) extends GeneralAssertionNode with ChunkAnalysisInfo { isAsserted = true - override def getNodeString: String = "assert " + permAmount.getOrElse("") + " at chunk " + chunk.toString + override def getNodeString: String = "assert " + permAmount.getOrElse("") + " for " + chunk.getAnalysisInfo } diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index e61e5bed1..9a5cba340 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -6,7 +6,7 @@ import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.state.Chunk import viper.silicon.state.terms.Term import viper.silver.ast -import viper.silver.ast.{Exp, Member, NoPosition} +import viper.silver.ast._ trait AssumptionAnalyzer { @@ -61,6 +61,8 @@ trait AssumptionAnalyzer { } def getMember: Option[Member] + + def exportGraph(): Unit } object AssumptionAnalyzer { @@ -194,6 +196,18 @@ class DefaultAssumptionAnalyzer(member: Member) extends AssumptionAnalyzer { override def getMember: Option[Member] = Some(member) + override def exportGraph(): Unit = { + val filename: Option[String] = getMember map { + case Method(name, _, _, _, _, _) => name + case ast.Function(name, _, _, _, _, _) => name + case Domain(name, _, _, _, _) => name + case contracted: Contracted => contracted.toString() + case location: Location => location.pos.toString + case member: ExtensionMember => member.pos.toString + } + assumptionGraph.exportGraph("graphExports/" + filename.getOrElse("latestExport") + ".txt") + } + } class NoAssumptionAnalyzer extends AssumptionAnalyzer { @@ -219,4 +233,6 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { override def addSingleAssumption(assumption: DebugExp, analysisInfo: AnalysisInfo): Option[Int] = None override def addSingleAssumption(assumption: ast.Exp, analysisInfo: AnalysisInfo): Option[Int] = None override def addSingleAssumption(description: String, analysisInfo: AnalysisInfo): Option[Int] = None + + override def exportGraph(): Unit = {} } \ No newline at end of file diff --git a/src/main/scala/interfaces/state/Chunks.scala b/src/main/scala/interfaces/state/Chunks.scala index e87496d23..2378e80a1 100644 --- a/src/main/scala/interfaces/state/Chunks.scala +++ b/src/main/scala/interfaces/state/Chunks.scala @@ -14,6 +14,8 @@ import viper.silver.ast trait Chunk { val perm: Term val permExp: Option[ast.Exp] + + def getAnalysisInfo: String } trait ChunkIdentifer @@ -62,6 +64,7 @@ trait NonQuantifiedChunk extends GeneralChunk { val args: Seq[Term] val argsExp: Option[Seq[ast.Exp]] val snap: Term + override def getAnalysisInfo: String = argsExp.getOrElse("") + " " + permExp.getOrElse("") override protected def applyCondition(newCond: Term, newCondExp: Option[ast.Exp]): NonQuantifiedChunk override protected def permMinus(perm: Term, permExp: Option[ast.Exp]): NonQuantifiedChunk override protected def permPlus(perm: Term, permExp: Option[ast.Exp]): NonQuantifiedChunk @@ -81,7 +84,7 @@ object NonQuantifiedChunk { trait QuantifiedChunk extends GeneralChunk { val quantifiedVars: Seq[Var] val quantifiedVarExps: Option[Seq[ast.LocalVarDecl]] - + override def getAnalysisInfo: String = quantifiedVarExps.getOrElse("") + " " + permExp.getOrElse("") def snapshotMap: Term def valueAt(arguments: Seq[Term]): Term override protected def applyCondition(newCond: Term, newCondExp: Option[ast.Exp]): QuantifiedChunk diff --git a/src/main/scala/state/Chunks.scala b/src/main/scala/state/Chunks.scala index ca09b6b55..b84949ad3 100644 --- a/src/main/scala/state/Chunks.scala +++ b/src/main/scala/state/Chunks.scala @@ -81,6 +81,12 @@ case class BasicChunk private (resourceID: BaseID, case PredicateID => require(snap.sort == sorts.Snap, s"A predicate chunk's snapshot ($snap) is expected to be of sort Snap, but found ${snap.sort}") } + override def getAnalysisInfo: String = perm + " for " + (resourceID match { + case PredicateID => id.name + "(" + (argsExp map (_.mkString(", "))).getOrElse("") + ")" + case FieldID => (argsExp map (_.head)).getOrElse("") + "." + id.name + }) + + override protected def applyCondition(newCond: Term, newCondExp: Option[ast.Exp]): BasicChunk = withPerm(Ite(newCond, perm, NoPerm), newCondExp.map(nce => ast.CondExp(nce, permExp.get, ast.NoPerm()())())) override protected def permMinus(newPerm: Term, newPermExp: Option[ast.Exp]): BasicChunk = diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index c5e4419d8..c9526b469 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -310,6 +310,7 @@ class DefaultMainVerifier(config: Config, if(Verifier.config.enableAssumptionAnalysis()){ val assumptionAnalyzers = verificationResults.filter(_.assumptionAnalyzer.isInstanceOf[DefaultAssumptionAnalyzer]).map(_.assumptionAnalyzer) assumptionAnalyzers.foreach(_.assumptionGraph.addTransitiveEdges()) + assumptionAnalyzers foreach (_.exportGraph()) logger debug s"assumption analyzers ${assumptionAnalyzers.mkString(", ")}" } From 3b24e37b02789438cf8e7d0505a30697cfe506e4 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 17 May 2025 15:40:49 +0200 Subject: [PATCH 045/474] minor fixes and cleanup --- .../AnalysisSourceInfo.scala | 2 +- .../AssumptionAnalysisGraph.scala | 2 +- .../AssumptionAnalyzer.scala | 35 ++++++------------- src/main/scala/rules/Brancher.scala | 4 --- src/main/scala/rules/ChunkSupporter.scala | 10 +++--- src/main/scala/rules/Consumer.scala | 16 ++++----- src/main/scala/rules/Executor.scala | 9 +++-- .../scala/rules/QuantifiedChunkSupport.scala | 4 +-- src/main/scala/rules/StateConsolidator.scala | 12 +++---- .../scala/supporters/MethodSupporter.scala | 7 ++-- src/test/resources/andrea/permissions.vpr | 11 ++++++ src/test/resources/andrea/quickTest.vpr | 14 +++++--- 12 files changed, 65 insertions(+), 61 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala index b1833c237..e431d80dc 100644 --- a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala +++ b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala @@ -9,7 +9,7 @@ abstract class AnalysisSourceInfo { def getStringForExport: String = { getPosition match { - case NoPosition => "unknown position" + case NoPosition => "???" case column: HasLineColumn => "line " + column.line.toString case VirtualPosition(identifier) => "label " + identifier } diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index 6b66873d1..43fdc041a 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -87,7 +87,7 @@ trait AssumptionAnalysisGraph { // def mergeIdenticalNodes() def getNodeExportString(node: AssumptionAnalysisNode): String = { - node.id + " | " + node.getClass.getSimpleName + " | " + node.assumptionType + " | " + node.getNodeString + " | " + node.sourceInfo.getStringForExport + node.id + " | " + node.getClass.getSimpleName + " | " + node.assumptionType + " | " + node.getNodeString + " | " + node.sourceInfo.toString } def exportGraph(fileName: String): Unit = { diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 9a5cba340..1ba42ecb5 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -17,11 +17,6 @@ trait AssumptionAnalyzer { def addPermissionExhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Assertion): Option[Int] def addPermissionNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Assertion, isExhale: Boolean=false): Option[Int] - def addSingleAssumption(assumption: ast.Exp, analysisInfo: AnalysisInfo): Option[Int] - - def addSingleAssumption(description: String, analysisInfo: AnalysisInfo): Option[Int] - - def addSingleAssumption(assumption: DebugExp, assumptionType: AssumptionType = AssumptionType.Unknown): Option[Int] def addSingleAssumption(assumption: DebugExp, analysisInfo: AnalysisInfo): Option[Int] @@ -30,6 +25,7 @@ trait AssumptionAnalyzer { def createAssertOrCheckNode(term: Term, assertion: Either[String, ast.Exp], analysisInfo: AnalysisInfo, isCheck: Boolean): Option[GeneralAssertionNode] def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNodeId: Option[Int]): Unit + def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNodeId: Chunk): Unit def processUnsatCoreAndAddDependencies(dep: String): Unit @@ -112,23 +108,6 @@ class DefaultAssumptionAnalyzer(member: Member) extends AssumptionAnalyzer { assumptionGraph.addNode(node) } - override def addSingleAssumption(assumption: ast.Exp, analysisInfo: AnalysisInfo): Option[Int] = { - val node = SimpleAssumptionNode(assumption, analysisInfo.sourceInfo, analysisInfo.assumptionType) - addNode(node) - Some(node.id) - } - - override def addSingleAssumption(description: String, analysisInfo: AnalysisInfo): Option[Int] = { - val node = StringAssumptionNode(description, analysisInfo.sourceInfo, analysisInfo.assumptionType) - addNode(node) - Some(node.id) - } - - override def addSingleAssumption(assumption: DebugExp, assumptionType: AssumptionType = AssumptionType.Unknown): Option[Int] = { - if(assumption.originalExp.isDefined) addSingleAssumption(assumption, AnalysisInfo(this, ExpAnalysisSourceInfo(assumption.originalExp.get), assumptionType)) - else addSingleAssumption(assumption, AnalysisInfo(this, StringAnalysisSourceInfo(assumption.description.getOrElse("unknown"), NoPosition), assumptionType)) - } - override def addSingleAssumption(assumption: DebugExp, analysisInfo: AnalysisInfo): Option[Int] = { val node = if(assumption.originalExp.isDefined) SimpleAssumptionNode(assumption.originalExp.get, analysisInfo.sourceInfo, analysisInfo.assumptionType) else StringAssumptionNode(assumption.description.getOrElse("unknown"), analysisInfo.sourceInfo, analysisInfo.assumptionType) @@ -194,6 +173,13 @@ class DefaultAssumptionAnalyzer(member: Member) extends AssumptionAnalyzer { assumptionGraph.addEdges(oldChunkNodeIds, newChunkNodeId.get) } + override def addPermissionDependencies(oldChunks: Set[Chunk], newChunk: Chunk): Unit = { + val newChunkId = assumptionGraph.nodes + .filter(c => c.isInstanceOf[PermissionInhaleNode] && c.isInstanceOf[ChunkAnalysisInfo] && newChunk.equals(c.asInstanceOf[ChunkAnalysisInfo].getChunk)) + .map(_.id).toSet + addPermissionDependencies(oldChunks, newChunkId.headOption) + } + override def getMember: Option[Member] = Some(member) override def exportGraph(): Unit = { @@ -227,12 +213,11 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { override def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNode: Option[Int]): Unit = { } + override def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNodeId: Chunk): Unit = {} + override def getMember: Option[Member] = None - override def addSingleAssumption(assumption: DebugExp, assumptionType: AssumptionType = AssumptionType.Unknown): Option[Int] = None override def addSingleAssumption(assumption: DebugExp, analysisInfo: AnalysisInfo): Option[Int] = None - override def addSingleAssumption(assumption: ast.Exp, analysisInfo: AnalysisInfo): Option[Int] = None - override def addSingleAssumption(description: String, analysisInfo: AnalysisInfo): Option[Int] = None override def exportGraph(): Unit = {} } \ No newline at end of file diff --git a/src/main/scala/rules/Brancher.scala b/src/main/scala/rules/Brancher.scala index 3898cbff9..6c276b179 100644 --- a/src/main/scala/rules/Brancher.scala +++ b/src/main/scala/rules/Brancher.scala @@ -145,9 +145,7 @@ object brancher extends BranchingRules { executionFlowController.locally(s, v0)((s1, v1) => { v1.decider.prover.comment(s"[else-branch: $cnt | $negatedCondition]") - v1.decider.assumptionAnalyzer.setCurrentAnalysisInfo(ExpAnalysisSourceInfo(negatedConditionExp), AssumptionType.PathCondition) v1.decider.setCurrentBranchCondition(negatedCondition, (negatedConditionExp, negatedConditionExpNew)) - v0.decider.assumptionAnalyzer.clearCurrentAnalysisInfo() var functionsOfElseBranchdDeciderBefore: Set[FunctionDecl] = null var nMacrosOfElseBranchDeciderBefore: Int = 0 @@ -197,9 +195,7 @@ object brancher extends BranchingRules { v.symbExLog.markReachable(uidBranchPoint) executionFlowController.locally(s, v)((s1, v1) => { v1.decider.prover.comment(s"[then-branch: $cnt | $condition]") - v1.decider.assumptionAnalyzer.setCurrentAnalysisInfo(ExpAnalysisSourceInfo(conditionExp._1), AssumptionType.PathCondition) v1.decider.setCurrentBranchCondition(condition, conditionExp) - v1.decider.assumptionAnalyzer.clearCurrentAnalysisInfo() fThen(v1.stateConsolidator(s1).consolidateOptionally(s1, v1), v1) }) diff --git a/src/main/scala/rules/ChunkSupporter.scala b/src/main/scala/rules/ChunkSupporter.scala index ecf72009d..6dd0acc98 100644 --- a/src/main/scala/rules/ChunkSupporter.scala +++ b/src/main/scala/rules/ChunkSupporter.scala @@ -78,10 +78,11 @@ object chunkSupporter extends ChunkSupportRules { ve: VerificationError, v: Verifier, description: String, - analysisInfo: AnalysisInfo) + analysisInfo0: AnalysisInfo) (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { + val analysisInfo = v.decider.assumptionAnalyzer.currentAnalysisInfo consume2(s, h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, analysisInfo)((s2, h2, optSnap, chunk, v2) => optSnap match { case Some(snap) => @@ -111,10 +112,11 @@ object chunkSupporter extends ChunkSupportRules { returnSnap: Boolean, ve: VerificationError, v: Verifier, - analysisInfo: AnalysisInfo) + analysisInfo0: AnalysisInfo) (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { + val analysisInfo = v.decider.assumptionAnalyzer.currentAnalysisInfo val id = ChunkIdentifier(resource, s.program) if (s.exhaleExt) { val failure = createFailure(ve, v, s, "chunk consume in package") @@ -160,9 +162,9 @@ object chunkSupporter extends ChunkSupportRules { perms: Term, permsExp: Option[ast.Exp], v: Verifier, - analysisInfo: AnalysisInfo) + analysisInfo0: AnalysisInfo) : (ConsumptionResult, State, Heap, Option[NonQuantifiedChunk]) = { - + val analysisInfo = v.decider.assumptionAnalyzer.currentAnalysisInfo val consumeExact = terms.utils.consumeExactRead(perms, s.constrainableARPs) def assumeProperties(chunk: NonQuantifiedChunk, heap: Heap): Unit = { diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index 20b59048a..3892a847c 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -6,15 +6,9 @@ package viper.silicon.rules -import viper.silicon.debugger.DebugExp import viper.silicon.Config.JoinMode -import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType, ExpAnalysisSourceInfo, StringAnalysisSourceInfo} - -import scala.collection.mutable -import viper.silver.ast -import viper.silver.ast.utility.QuantifiedPermissions.QuantifiedPermissionAssertion -import viper.silver.verifier.PartialVerificationError -import viper.silver.verifier.reasons._ +import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType, StringAnalysisSourceInfo} +import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.VerificationResult import viper.silicon.interfaces.state.Chunk import viper.silicon.logger.records.data.{CondExpRecord, ConsumeRecord, ImpliesRecord} @@ -23,6 +17,12 @@ import viper.silicon.state.terms._ import viper.silicon.state.terms.predef.`?r` import viper.silicon.utils.ast.BigAnd import viper.silicon.verifier.Verifier +import viper.silver.ast +import viper.silver.ast.utility.QuantifiedPermissions.QuantifiedPermissionAssertion +import viper.silver.verifier.PartialVerificationError +import viper.silver.verifier.reasons._ + +import scala.collection.mutable trait ConsumptionRules extends SymbolicExecutionRules { diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index f3f3c5054..1607af84b 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -67,21 +67,24 @@ object executor extends ExecutionRules { case ce: cfg.ConditionalEdge[ast.Stmt, ast.Exp] => val condEdgeRecord = new ConditionalEdgeRecord(ce.condition, s, v.decider.pcs) val sepIdentifier = v.symbExLog.openScope(condEdgeRecord) + v.decider.assumptionAnalyzer.updateCurrentAnalysisInfo(ExpAnalysisSourceInfo(ce.condition), AssumptionType.PathCondition) val s1 = handleOutEdge(s, edge, v) - eval(s1, ce.condition, IfFailed(ce.condition), v)((s2, tCond, condNew, v1) => + eval(s1, ce.condition, IfFailed(ce.condition), v)((s2, tCond, condNew, v1) => { /* Using branch(...) here ensures that the edge condition is recorded * as a branch condition on the pathcondition stack. */ + v.decider.assumptionAnalyzer.updateCurrentAnalysisInfo(AssumptionType.Implicit) brancher.branch(s2.copy(parallelizeBranches = false), tCond, (ce.condition, condNew), v1)( (s3, v3) => exec(s3.copy(parallelizeBranches = s2.parallelizeBranches), ce.target, ce.kind, v3, joinPoint)((s4, v4) => { + v.decider.assumptionAnalyzer.clearCurrentAnalysisInfo() v4.symbExLog.closeScope(sepIdentifier) Q(s4, v4) }), (_, v3) => { v3.symbExLog.closeScope(sepIdentifier) Success() - })) + })}) case ue: cfg.UnconditionalEdge[ast.Stmt, ast.Exp] => val s1 = handleOutEdge(s, edge, v) @@ -497,7 +500,7 @@ object executor extends ExecutionRules { /* We're done */ Success() case _ => - v.decider.assumptionAnalyzer.setCurrentAnalysisInfo(ExpAnalysisSourceInfo(a), AssumptionType.Explicit) + v.decider.assumptionAnalyzer.updateCurrentAnalysisInfo(AssumptionType.Explicit) produce(s, freshSnap, a, InhaleFailed(inhale), v)((s1, v1) => { v1.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterInhale) Q(s1, v1)}) diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 2b39d4bf8..8b35da4a6 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -1677,7 +1677,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.assume(permissionConstraint, permissionConstraintExp, permissionConstraintExp) remainingChunks = - remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, analysisInfo) + remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.assumptionAnalyzer.currentAnalysisInfo) } else { v.decider.prover.comment(s"Chunk depleted?") val chunkDepleted = v.decider.check(depletedCheck, Verifier.config.splitTimeout()) @@ -1688,7 +1688,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { remainingChunks = remainingChunks :+ ithChunk } else { remainingChunks = - remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, analysisInfo) + remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.assumptionAnalyzer.currentAnalysisInfo) } } } diff --git a/src/main/scala/rules/StateConsolidator.scala b/src/main/scala/rules/StateConsolidator.scala index 5464b59e0..88f84b8e9 100644 --- a/src/main/scala/rules/StateConsolidator.scala +++ b/src/main/scala/rules/StateConsolidator.scala @@ -7,7 +7,7 @@ package viper.silicon.rules import viper.silicon.Config -import viper.silicon.assumptionAnalysis.{AssumptionType, PermissionInhaleNode, StringAnalysisSourceInfo} +import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType, PermissionInhaleNode, StringAnalysisSourceInfo} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.state._ @@ -202,18 +202,18 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol val result = mergeChunks1(fr1, chunk1, chunk2, qvars, v) if(result.isDefined){ val (_, newChunk, _) = result.get - val newChunkNode = PermissionInhaleNode(newChunk, newChunk.permExp, StringAnalysisSourceInfo("state consolidation", NoPosition), AssumptionType.Internal) - v.decider.assumptionAnalyzer.addPermissionDependencies(Set(chunk1, chunk2), Some(newChunkNode.id)) + v.decider.assumptionAnalyzer.addPermissionDependencies(Set(chunk1, chunk2), newChunk) } result } private def mergeChunks1(fr1: FunctionRecorder, chunk1: Chunk, chunk2: Chunk, qvars: Seq[Var], v: Verifier): Option[(FunctionRecorder, Chunk, Term)] = { + val analysisInfo = AnalysisInfo(v.decider.assumptionAnalyzer, StringAnalysisSourceInfo("state consolidation", NoPosition), AssumptionType.Internal) (chunk1, chunk2) match { case (BasicChunk(rid1, id1, args1, args1Exp, snap1, snap1Exp, perm1, perm1Exp), BasicChunk(_, _, _, _, snap2, _, perm2, perm2Exp)) => val (fr2, combinedSnap, snapEq) = combineSnapshots(fr1, snap1, snap2, perm1, perm2, qvars, v) val newExp = perm1Exp.map(p1 => ast.PermAdd(p1, perm2Exp.get)()) - Some(fr2, BasicChunk(rid1, id1, args1, args1Exp, combinedSnap, snap1Exp, PermPlus(perm1, perm2), newExp, v.decider.assumptionAnalyzer.currentAnalysisInfo), snapEq) + Some(fr2, BasicChunk(rid1, id1, args1, args1Exp, combinedSnap, snap1Exp, PermPlus(perm1, perm2), newExp, analysisInfo), snapEq) case (l@QuantifiedFieldChunk(id1, fvf1, condition1, condition1Exp, perm1, perm1Exp, invs1, singletonRcvr1, singletonRcvr1Exp, hints1), r@QuantifiedFieldChunk(_, fvf2, _, _, perm2, perm2Exp, _, _, _, hints2)) => assert(l.quantifiedVars == Seq(`?r`)) @@ -223,14 +223,14 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol val permSum = PermPlus(perm1, perm2) val permSumExp = perm1Exp.map(p1 => ast.PermAdd(p1, perm2Exp.get)()) val bestHints = if (hints1.nonEmpty) hints1 else hints2 - Some(fr2, QuantifiedFieldChunk(id1, combinedSnap, condition1, condition1Exp, permSum, permSumExp, invs1, singletonRcvr1, singletonRcvr1Exp, bestHints, v.decider.assumptionAnalyzer.currentAnalysisInfo), snapEq) + Some(fr2, QuantifiedFieldChunk(id1, combinedSnap, condition1, condition1Exp, permSum, permSumExp, invs1, singletonRcvr1, singletonRcvr1Exp, bestHints, analysisInfo), snapEq) case (l@QuantifiedPredicateChunk(id1, qVars1, qVars1Exp, psf1, _, _, perm1, perm1Exp, _, _, _, _), r@QuantifiedPredicateChunk(_, qVars2, qVars2Exp, psf2, condition2, condition2Exp, perm2, perm2Exp, invs2, singletonArgs2, singletonArgs2Exp, hints2)) => val (fr2, combinedSnap, snapEq) = quantifiedChunkSupporter.combinePredicateSnapshotMaps(fr1, id1.name, qVars2, psf1, psf2, l.perm.replace(qVars1, qVars2), r.perm, v) val permSum = PermPlus(perm1.replace(qVars1, qVars2), perm2) val permSumExp = perm1Exp.map(p1 => ast.PermAdd(p1.replace(qVars1Exp.get.zip(qVars2Exp.get).toMap), perm2Exp.get)()) - Some(fr2, QuantifiedPredicateChunk(id1, qVars2, qVars2Exp, combinedSnap, condition2, condition2Exp, permSum, permSumExp, invs2, singletonArgs2, singletonArgs2Exp, hints2, v.decider.assumptionAnalyzer.currentAnalysisInfo), snapEq) + Some(fr2, QuantifiedPredicateChunk(id1, qVars2, qVars2Exp, combinedSnap, condition2, condition2Exp, permSum, permSumExp, invs2, singletonArgs2, singletonArgs2Exp, hints2, analysisInfo), snapEq) case _ => None } diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index f011f4f20..03b84d676 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -111,14 +111,17 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif val sepIdentifier = symbExLog.openScope(impLog) v3.decider.assumptionAnalyzer.setCurrentAnalysisInfo(if(posts.isEmpty) StringAnalysisSourceInfo("no postcondition", ast.NoPosition) else ExpAnalysisSourceInfo(posts.head), AssumptionType.Implicit) produces(s4, freshSnap, posts, ContractNotWellformed, v3)((_, _) => { + v3.decider.assumptionAnalyzer.clearCurrentAnalysisInfo() symbExLog.closeScope(sepIdentifier) Success()})}) && { executionFlowController.locally(s2a, v2)((s3, v3) => { exec(s3, body, v3)((s4, v4) =>{ v4.decider.assumptionAnalyzer.setCurrentAnalysisInfo(if(posts.isEmpty) StringAnalysisSourceInfo("no postcondition", ast.NoPosition) else ExpAnalysisSourceInfo(posts.head), AssumptionType.Assertion) - consumes(s4, posts, false, postViolated, v4, v4.decider.assumptionAnalyzer.currentAnalysisInfo)((_, _, _, _) => - Success())})}) } )})}) + consumes(s4, posts, false, postViolated, v4, v4.decider.assumptionAnalyzer.currentAnalysisInfo)((_, _, _, _) => { + v3.decider.assumptionAnalyzer.clearCurrentAnalysisInfo() + Success() + })})}) } )})}) result.assumptionAnalyzer = v.decider.assumptionAnalyzer v.decider.removeAssumptionAnalyzer() diff --git a/src/test/resources/andrea/permissions.vpr b/src/test/resources/andrea/permissions.vpr index c5c6b5381..53150fcfe 100644 --- a/src/test/resources/andrea/permissions.vpr +++ b/src/test/resources/andrea/permissions.vpr @@ -85,4 +85,15 @@ method transitivity(a: Ref, n: Int) var res: Int res := a.f / n assert res >= 0 +} + +method maybeAlias(a: Ref, b: Ref, c: Bool, n: Int) + requires acc(a.f, 1/2) && acc(b.f, 1/2) + requires c ==> a == b + requires a.f > 0 && n > 0 +{ + if(c){ + a.f := n + 1 + } + assert a.f >= 0 } \ No newline at end of file diff --git a/src/test/resources/andrea/quickTest.vpr b/src/test/resources/andrea/quickTest.vpr index 6071f6fd0..f30577de4 100644 --- a/src/test/resources/andrea/quickTest.vpr +++ b/src/test/resources/andrea/quickTest.vpr @@ -1,8 +1,12 @@ field f: Int -method simple(x: Ref, y: Ref, p: Perm) - requires acc(x.f) && acc(y.f) - ensures acc(x.f) && acc(y.f) +method maybeAlias(a: Ref, b: Ref, c: Bool, n: Int) + requires acc(a.f, 1/2) && acc(b.f, 1/2) + requires c ==> a == b + requires a.f > 0 && n > 0 { - x.f := y.f + 1 -} + if(c){ + a.f := n + 1 + } + assert a.f >= 0 +} \ No newline at end of file From 72aecae0e00b61e384d01f772949dec49e0f97b8 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 19 May 2025 08:12:32 +0200 Subject: [PATCH 046/474] minor fixes --- .../scala/assumptionAnalysis/AssumptionAnalysisGraph.scala | 2 +- src/test/resources/andrea/method-sum.vpr | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index 43fdc041a..4c6f02066 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -94,7 +94,7 @@ trait AssumptionAnalysisGraph { val writer = new PrintWriter(fileName) writer.println("====== Nodes ======") writer.println("id | node type | assumption type | node info | source info") - nodes foreach (n => writer.println(getNodeExportString(n))) + nodes foreach (n => writer.println(getNodeExportString(n).replace("\n", " "))) writer.println("====== Edges ======") edges foreach (e => writer.println(e._1 + " -> " + e._2.mkString(","))) writer.println("====== Transitive Edges ======") diff --git a/src/test/resources/andrea/method-sum.vpr b/src/test/resources/andrea/method-sum.vpr index e5733af98..79ce75bf3 100644 --- a/src/test/resources/andrea/method-sum.vpr +++ b/src/test/resources/andrea/method-sum.vpr @@ -8,7 +8,7 @@ method sum(x: Int, y: Int) returns(res: Int) //} -method foo(x: Int, y: Int) +method methodSum(x: Int, y: Int) { assume x >= 0 assume y >= 0 @@ -17,7 +17,7 @@ method foo(x: Int, y: Int) var n2: Int := sum(x/y, y) n := n + n2 - assert n == 2*x + 2*y + assert n >= x + 2*y assert n < 0 } \ No newline at end of file From 219510f2428d1603a37e223bd10d96ecb54ea024 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 19 May 2025 09:49:37 +0200 Subject: [PATCH 047/474] label assumes with assumption type --- .../assumptionAnalysis/AnalysisInfo.scala | 2 +- .../AssumptionAnalyzer.scala | 18 ++--- src/main/scala/decider/Decider.scala | 34 +++++----- src/main/scala/rules/ChunkSupporter.scala | 4 +- src/main/scala/rules/Consumer.scala | 12 ++-- src/main/scala/rules/Evaluator.scala | 50 +++++++------- src/main/scala/rules/Executor.scala | 14 ++-- src/main/scala/rules/HavocSupporter.scala | 8 +-- src/main/scala/rules/Joiner.scala | 4 +- src/main/scala/rules/MagicWandSupporter.scala | 6 +- .../rules/MoreCompleteExhaleSupporter.scala | 12 ++-- src/main/scala/rules/PredicateSupporter.scala | 8 +-- src/main/scala/rules/Producer.scala | 12 ++-- .../scala/rules/QuantifiedChunkSupport.scala | 66 +++++++++---------- src/main/scala/rules/StateConsolidator.scala | 16 ++--- .../scala/supporters/SnapshotSupporter.scala | 3 +- .../functions/FunctionVerificationUnit.scala | 4 +- 17 files changed, 137 insertions(+), 136 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AnalysisInfo.scala b/src/main/scala/assumptionAnalysis/AnalysisInfo.scala index 6a0722947..79048c3f8 100644 --- a/src/main/scala/assumptionAnalysis/AnalysisInfo.scala +++ b/src/main/scala/assumptionAnalysis/AnalysisInfo.scala @@ -2,7 +2,7 @@ package viper.silicon.assumptionAnalysis object AssumptionType extends Enumeration { type AssumptionType = Value - val Explicit, PathCondition, Internal, Implicit, Assertion, Unknown = Value + val Assertion, Explicit, LoopInvariant, PathCondition, Implicit, Internal, Unknown = Value } import viper.silicon.assumptionAnalysis.AssumptionType._ diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 1ba42ecb5..c4c6de261 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -18,9 +18,9 @@ trait AssumptionAnalyzer { def addPermissionNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Assertion, isExhale: Boolean=false): Option[Int] - def addSingleAssumption(assumption: DebugExp, analysisInfo: AnalysisInfo): Option[Int] + def addSingleAssumption(assumption: DebugExp, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] - def addAssumptions(assumptions: Iterable[DebugExp], analysisInfo: AnalysisInfo): Seq[Int] + def addAssumptions(assumptions: Iterable[DebugExp], analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Seq[Int] def createAssertOrCheckNode(term: Term, assertion: Either[String, ast.Exp], analysisInfo: AnalysisInfo, isCheck: Boolean): Option[GeneralAssertionNode] @@ -108,16 +108,16 @@ class DefaultAssumptionAnalyzer(member: Member) extends AssumptionAnalyzer { assumptionGraph.addNode(node) } - override def addSingleAssumption(assumption: DebugExp, analysisInfo: AnalysisInfo): Option[Int] = { - val node = if(assumption.originalExp.isDefined) SimpleAssumptionNode(assumption.originalExp.get, analysisInfo.sourceInfo, analysisInfo.assumptionType) - else StringAssumptionNode(assumption.description.getOrElse("unknown"), analysisInfo.sourceInfo, analysisInfo.assumptionType) + override def addSingleAssumption(assumption: DebugExp, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { + val node = if(assumption.originalExp.isDefined) SimpleAssumptionNode(assumption.originalExp.get, analysisSourceInfo, assumptionType) + else StringAssumptionNode(assumption.description.getOrElse("unknown"), analysisSourceInfo, assumptionType) addNode(node) Some(node.id) } - override def addAssumptions(assumptions: Iterable[DebugExp], analysisInfo: AnalysisInfo): Seq[Int] = { + override def addAssumptions(assumptions: Iterable[DebugExp], analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Seq[Int] = { val newNodes = assumptions.filter(_.originalExp.isDefined) - .map(a => SimpleAssumptionNode(a.originalExp.get, analysisInfo.sourceInfo, analysisInfo.assumptionType)) + .map(a => SimpleAssumptionNode(a.originalExp.get, analysisSourceInfo, assumptionType)) newNodes foreach addNode newNodes.map(_.id).toSeq } @@ -198,7 +198,7 @@ class DefaultAssumptionAnalyzer(member: Member) extends AssumptionAnalyzer { class NoAssumptionAnalyzer extends AssumptionAnalyzer { - override def addAssumptions(assumptions: Iterable[DebugExp], analysisInfo: AnalysisInfo): Seq[Int] = Seq.empty + override def addAssumptions(assumptions: Iterable[DebugExp], analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Seq[Int] = Seq.empty override def createAssertOrCheckNode(term: Term, assertion: Either[String, ast.Exp], analysisInfo: AnalysisInfo, isCheck: Boolean): Option[GeneralAssertionNode] = None @@ -217,7 +217,7 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { override def getMember: Option[Member] = None - override def addSingleAssumption(assumption: DebugExp, analysisInfo: AnalysisInfo): Option[Int] = None + override def addSingleAssumption(assumption: DebugExp, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None override def exportGraph(): Unit = {} } \ No newline at end of file diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 48136af7f..33fce57c7 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -60,11 +60,11 @@ trait Decider { def startDebugSubExp(): Unit - def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp]): Unit - def assume(t: Term, debugExp: Option[DebugExp]): Unit - def assume(terms: Seq[Term], debugExps: Option[Seq[DebugExp]]): Unit + def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp], assumptionType: AssumptionType): Unit + def assume(t: Term, debugExp: Option[DebugExp], assumptionType: AssumptionType): Unit + def assume(terms: Seq[Term], debugExps: Option[Seq[DebugExp]], assumptionType: AssumptionType): Unit def assumeDefinition(t: Term, debugExp: Option[DebugExp]): Unit - def assume(terms: Iterable[Term], debugExp: Option[DebugExp], enforceAssumption: Boolean): Unit + def assume(terms: Iterable[Term], debugExp: Option[DebugExp], enforceAssumption: Boolean, assumptionType: AssumptionType): Unit def check(t: Term, timeout: Int): Boolean @@ -269,7 +269,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => def setCurrentBranchCondition(t: Term, te: (ast.Exp, Option[ast.Exp])): Unit = { pathConditions.setCurrentBranchCondition(t, te) - assume(t, Option.when(te._2.isDefined)(te._1), te._2) + assume(t, Option.when(te._2.isDefined)(te._1), te._2, AssumptionType.PathCondition) } def setPathConditionMark(): Mark = pathConditions.mark() @@ -294,23 +294,23 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } } - def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp]): Unit = { + def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp], assumptionType: AssumptionType): Unit = { if (finalExp.isDefined) { - assume(assumptions=InsertionOrderedSet((t, Some(DebugExp.createInstance(e.get, finalExp.get)))), assumptionAnalyzer.currentAnalysisInfo, false, false) + assume(assumptions=InsertionOrderedSet((t, Some(DebugExp.createInstance(e.get, finalExp.get)))), assumptionAnalyzer.currentAnalysisInfo.sourceInfo, enforceAssumption = false, isDefinition = false, assumptionType) } else { - assume(assumptions=InsertionOrderedSet((t, None)), assumptionAnalyzer.currentAnalysisInfo, false, false) + assume(assumptions=InsertionOrderedSet((t, None)), assumptionAnalyzer.currentAnalysisInfo.sourceInfo, enforceAssumption = false, isDefinition = false, assumptionType) } } - def assume(t: Term, debugExp: Option[DebugExp]): Unit = { - assume(InsertionOrderedSet(Seq((t, debugExp))), assumptionAnalyzer.currentAnalysisInfo, false, false) + def assume(t: Term, debugExp: Option[DebugExp], assumptionType: AssumptionType): Unit = { + assume(InsertionOrderedSet(Seq((t, debugExp))), assumptionAnalyzer.currentAnalysisInfo.sourceInfo, enforceAssumption = false, isDefinition = false, assumptionType) } def assumeDefinition(t: Term, debugExp: Option[DebugExp]): Unit = { - assume(InsertionOrderedSet(Seq((t, debugExp))), assumptionAnalyzer.currentAnalysisInfo, enforceAssumption=false, isDefinition=true) + assume(InsertionOrderedSet(Seq((t, debugExp))), assumptionAnalyzer.currentAnalysisInfo.sourceInfo, enforceAssumption=false, isDefinition=true, assumptionType=AssumptionType.Implicit) } - def assume(assumptions: InsertionOrderedSet[(Term, Option[DebugExp])], analysisInfo: AnalysisInfo, enforceAssumption: Boolean, isDefinition: Boolean): Unit = { + def assume(assumptions: InsertionOrderedSet[(Term, Option[DebugExp])], analysisSourceInfo: AnalysisSourceInfo, enforceAssumption: Boolean, isDefinition: Boolean, assumptionType: AssumptionType): Unit = { val filteredAssumptions = if (enforceAssumption) assumptions else assumptions filterNot (a => isKnownToBeTrue(a._1)) @@ -322,7 +322,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => val filteredAssumptionsWithLabels = filteredAssumptions map{case (t, de) => val sourceExp = assumptionAnalyzer.currentExpStack.headOption - val assumptionId: Option[Int] = if(de.isDefined) assumptionAnalyzer.addSingleAssumption(de.get, analysisInfo) else None + val assumptionId: Option[Int] = if(de.isDefined) assumptionAnalyzer.addSingleAssumption(de.get, analysisSourceInfo, assumptionType) else None (t, AssumptionAnalyzer.createAssumptionLabel(assumptionId)) } @@ -331,8 +331,8 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } } - def assume(assumptions: Seq[Term], debugExps: Option[Seq[DebugExp]]): Unit = { - val assumptionIds = if(debugExps.isDefined) assumptionAnalyzer.addAssumptions(debugExps.get, assumptionAnalyzer.currentAnalysisInfo) else Seq.empty + def assume(assumptions: Seq[Term], debugExps: Option[Seq[DebugExp]], assumptionType: AssumptionType): Unit = { + val assumptionIds = if(debugExps.isDefined) assumptionAnalyzer.addAssumptions(debugExps.get, assumptionAnalyzer.currentAnalysisInfo.sourceInfo, assumptionType) else Seq.empty val sourceExp = assumptionAnalyzer.currentExpStack.headOption val assumptionsWithLabels = @@ -344,7 +344,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } } - def assume(terms: Iterable[Term], debugExp: Option[DebugExp], enforceAssumption: Boolean): Unit = { + def assume(terms: Iterable[Term], debugExp: Option[DebugExp], enforceAssumption: Boolean, assumptionType: AssumptionType): Unit = { val filteredTerms = if (enforceAssumption) terms @@ -356,7 +356,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => addDebugExp(debugExp.get.withTerm(And(filteredTerms))) val sourceExp = assumptionAnalyzer.currentExpStack.headOption - val assumptionId: Option[Int] = if(debugExp.isDefined) assumptionAnalyzer.addSingleAssumption(debugExp.get.withTerm(And(filteredTerms)), assumptionAnalyzer.currentAnalysisInfo) else None + val assumptionId: Option[Int] = if(debugExp.isDefined) assumptionAnalyzer.addSingleAssumption(debugExp.get.withTerm(And(filteredTerms)), assumptionAnalyzer.currentAnalysisInfo.sourceInfo, assumptionType) else None val termsWithLabel = filteredTerms.zipWithIndex.iterator.map {case (t, idx) => (t, AssumptionAnalyzer.createAssumptionLabel(assumptionId, idx))}.toSeq assumeWithoutSmokeChecks(InsertionOrderedSet(termsWithLabel)) }else{ diff --git a/src/main/scala/rules/ChunkSupporter.scala b/src/main/scala/rules/ChunkSupporter.scala index 6dd0acc98..a4a27f9f5 100644 --- a/src/main/scala/rules/ChunkSupporter.scala +++ b/src/main/scala/rules/ChunkSupporter.scala @@ -171,7 +171,7 @@ object chunkSupporter extends ChunkSupportRules { val interpreter = new NonQuantifiedPropertyInterpreter(heap.values, v) val resource = Resources.resourceDescriptions(chunk.resourceID) val pathCond = interpreter.buildPathConditionsForChunk(chunk, resource.instanceProperties(s.mayAssumeUpperBounds)) - pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)))) + pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), AssumptionType.PathCondition)) } findChunk[NonQuantifiedChunk](h.values, id, args, v) match { @@ -199,7 +199,7 @@ object chunkSupporter extends ChunkSupportRules { } else { if (v.decider.check(ch.perm !== NoPerm, Verifier.config.checkTimeout())) { val constraintExp = permsExp.map(pe => ast.PermLtCmp(pe, ch.permExp.get)(pe.pos, pe.info, pe.errT)) - v.decider.assume(PermLess(perms, ch.perm), Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp))) + v.decider.assume(PermLess(perms, ch.perm), Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp)), AssumptionType.Implicit) val newPermExp = permsExp.map(pe => ast.PermSub(ch.permExp.get, pe)(pe.pos, pe.info, pe.errT)) val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, perms), newPermExp, analysisInfo).asInstanceOf[NonQuantifiedChunk] val takenChunk = GeneralChunk.withPerm(ch, perms, permsExp, analysisInfo, isExhale=true).asInstanceOf[NonQuantifiedChunk] diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index 3892a847c..be4d09cb4 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -390,7 +390,7 @@ object consumer extends ConsumptionRules { quantifiedChunkSupporter.summarisingSnapshotMap( s2, field, Seq(`?r`), relevantChunks, v2) val debugExp = Option.when(withExp)(DebugExp.createInstance(s"Field Trigger: ${eRcvrNew.get.toString}.${field.name}")) - v2.decider.assume(FieldTrigger(field.name, smDef1.sm, tRcvr), debugExp) + v2.decider.assume(FieldTrigger(field.name, smDef1.sm, tRcvr), debugExp, AssumptionType.Internal) // v2.decider.assume(PermAtMost(tPerm, FullPerm())) s2.copy(smCache = smCache1) } else { @@ -436,7 +436,7 @@ object consumer extends ConsumptionRules { quantifiedChunkSupporter.summarisingSnapshotMap( s2, predicate, s2.predicateFormalVarMap(predicate), relevantChunks, v2) val debugExp = Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}(${eArgsNew.mkString(", ")}))", isInternal_ = true)) - v2.decider.assume(PredicateTrigger(predicate.name, smDef1.sm, tArgs), debugExp) + v2.decider.assume(PredicateTrigger(predicate.name, smDef1.sm, tArgs), debugExp, AssumptionType.Internal) s2.copy(smCache = smCache1) } else { s2 @@ -507,7 +507,7 @@ object consumer extends ConsumptionRules { val argsString = bodyVarsNew.mkString(", ") val predName = MagicWandIdentifier(wand, s.program).toString val debugExp = Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger($predName($argsString))", isInternal_ = true)) - v1.decider.assume(PredicateTrigger(predName, smDef1.sm, tArgs), debugExp) + v1.decider.assume(PredicateTrigger(predName, smDef1.sm, tArgs), debugExp, AssumptionType.Internal) s1.copy(smCache = smCache1) } else { s1 @@ -627,18 +627,18 @@ object consumer extends ConsumptionRules { val termToAssert = t match { case Quantification(q, vars, body, trgs, name, isGlob, weight) => val transformed = FunctionPreconditionTransformer.transform(body, s3.program) - v2.decider.assume(Quantification(q, vars, transformed, trgs, name+"_precondition", isGlob, weight), Option.when(withExp)(e), eNew) + v2.decider.assume(Quantification(q, vars, transformed, trgs, name+"_precondition", isGlob, weight), Option.when(withExp)(e), eNew, AssumptionType.Internal) Quantification(q, vars, Implies(transformed, body), trgs, name, isGlob, weight) case _ => t } v2.decider.assert(termToAssert, Some(e)) { case true => - v2.decider.assume(t, Option.when(withExp)(e), eNew) + v2.decider.assume(t, Option.when(withExp)(e), eNew, AssumptionType.Unknown) QS(s3, v2) case false => val failure = createFailure(pve dueTo AssertionFalse(e), v2, s3, termToAssert, eNew) if (s3.retryLevel == 0 && v2.reportFurtherErrors()){ - v2.decider.assume(t, Option.when(withExp)(e), eNew) + v2.decider.assume(t, Option.when(withExp)(e), eNew, AssumptionType.Unknown) failure combine QS(s3, v2) } else failure}}) })((s4, v4) => { diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 8ce84774f..8d4b7aab9 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -223,11 +223,11 @@ object evaluator extends EvaluationRules { * quantifier in whose body field 'fa.field' was accessed) * which is protected by a trigger term that we currently don't have. */ - v1.decider.assume(And(fvfDef.valueDefinitions), Option.when(withExp)(DebugExp.createInstance("Value definitions", isInternal_ = true))) + v1.decider.assume(And(fvfDef.valueDefinitions), Option.when(withExp)(DebugExp.createInstance("Value definitions", isInternal_ = true)), AssumptionType.Internal) if (s1.heapDependentTriggers.contains(fa.field)){ val trigger = FieldTrigger(fa.field.name, fvfDef.sm, tRcvr) val triggerExp = Option.when(withExp)(DebugExp.createInstance(s"FieldTrigger(${eRcvr.toString()}.${fa.field.name})")) - v1.decider.assume(trigger, triggerExp) + v1.decider.assume(trigger, triggerExp, AssumptionType.Internal) } if (s1.triggerExp) { val fvfLookup = Lookup(fa.field.name, fvfDef.sm, tRcvr) @@ -258,7 +258,7 @@ object evaluator extends EvaluationRules { if (s1.heapDependentTriggers.contains(fa.field)) { val trigger = FieldTrigger(fa.field.name, relevantChunks.head.fvf, tRcvr) val triggerExp = Option.when(withExp)(DebugExp.createInstance(s"FieldTrigger(${eRcvr.toString()}.${fa.field.name})")) - v1.decider.assume(trigger, triggerExp) + v1.decider.assume(trigger, triggerExp, AssumptionType.Internal) } val (permCheck, permCheckExp) = if (s1.triggerExp) { @@ -291,7 +291,7 @@ object evaluator extends EvaluationRules { if (s2.heapDependentTriggers.contains(fa.field)) { val trigger = FieldTrigger(fa.field.name, smDef1.sm, tRcvr) val triggerExp = Option.when(withExp)(DebugExp.createInstance(s"FieldTrigger(${eRcvr.toString()}.${fa.field.name})")) - v1.decider.assume(trigger, triggerExp) + v1.decider.assume(trigger, triggerExp, AssumptionType.Internal) } val (permCheck, permCheckExp) = if (s2.triggerExp) { @@ -561,7 +561,7 @@ object evaluator extends EvaluationRules { val (s2, pmDef) = if (s1.heapDependentTriggers.contains(MagicWandIdentifier(wand, s1.program))) { val (s2, smDef, pmDef) = quantifiedChunkSupporter.heapSummarisingMaps(s1, wand, formalVars, relevantChunks, v1) val debugExp = Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${identifier.toString}($eArgsString))", isInternal_ = true)) - v1.decider.assume(PredicateTrigger(identifier.toString, smDef.sm, args), debugExp) + v1.decider.assume(PredicateTrigger(identifier.toString, smDef.sm, args), debugExp, AssumptionType.Internal) (s2, pmDef) } else { val (pmDef, pmCache) = @@ -578,7 +578,7 @@ object evaluator extends EvaluationRules { val (s2, pmDef) = if (s1.heapDependentTriggers.contains(field)) { val (s2, smDef, pmDef) = quantifiedChunkSupporter.heapSummarisingMaps(s1, field, Seq(`?r`), relevantChunks, v1) val debugExp = Option.when(withExp)(DebugExp.createInstance(s"Field Trigger: ${eArgsNew.head}.${field.name}")) - v1.decider.assume(FieldTrigger(field.name, smDef.sm, args.head), debugExp) + v1.decider.assume(FieldTrigger(field.name, smDef.sm, args.head), debugExp, AssumptionType.Internal) (s2, pmDef) } else { val (pmDef, pmCache) = @@ -591,7 +591,7 @@ object evaluator extends EvaluationRules { v1.decider.prover.comment(s"perm($resacc) ~~> assume upper permission bound") val (debugHeapName, debugLabel) = v1.getDebugOldLabel(s2, resacc.pos, Some(h)) val exp = Option.when(withExp)(ast.PermLeCmp(ast.DebugLabelledOld(ast.CurrentPerm(resacc)(), debugLabel)(), ast.FullPerm()())()) - v1.decider.assume(PermAtMost(currentPermAmount, FullPerm), exp, exp.map(s2.substituteVarsInExp(_))) + v1.decider.assume(PermAtMost(currentPermAmount, FullPerm), exp, exp.map(s2.substituteVarsInExp(_)), AssumptionType.Internal) val s3 = if (Verifier.config.enableDebugging()) s2.copy(oldHeaps = s2.oldHeaps + (debugHeapName -> h)) else s2 (s3, currentPermAmount) @@ -604,7 +604,7 @@ object evaluator extends EvaluationRules { if (s2.heapDependentTriggers.contains(predicate)){ val trigger = PredicateTrigger(predicate.name, smDef.sm, args) val argsString = eArgsNew.mkString(", ") - v1.decider.assume(trigger, Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}($argsString))", isInternal_ = true))) + v1.decider.assume(trigger, Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}($argsString))", isInternal_ = true)), AssumptionType.Internal) } (s2, PredicatePermLookup(identifier.toString, pmDef.pm, args)) } @@ -826,10 +826,10 @@ object evaluator extends EvaluationRules { val auxNonGlobalsExp = auxExps.map(_._2) val commentGlobal = "Nested auxiliary terms: globals (aux)" v1.decider.prover.comment(commentGlobal) - v1.decider.assume(tAuxGlobal, Option.when(withExp)(DebugExp.createInstance(description=commentGlobal, children=auxGlobalsExp.get)), enforceAssumption = false) + v1.decider.assume(tAuxGlobal, Option.when(withExp)(DebugExp.createInstance(description=commentGlobal, children=auxGlobalsExp.get)), enforceAssumption = false, assumptionType=AssumptionType.Internal) val commentNonGlobals = "Nested auxiliary terms: non-globals (aux)" v1.decider.prover.comment(commentNonGlobals) - v1.decider.assume(tAuxHeapIndep/*tAux*/, Option.when(withExp)(DebugExp.createInstance(description=commentNonGlobals, children=auxNonGlobalsExp.get)), enforceAssumption = false) + v1.decider.assume(tAuxHeapIndep/*tAux*/, Option.when(withExp)(DebugExp.createInstance(description=commentNonGlobals, children=auxNonGlobalsExp.get)), enforceAssumption = false, assumptionType=AssumptionType.Internal) if (qantOp == Exists) { // For universal quantification, the non-global auxiliary assumptions will contain the information that @@ -843,7 +843,7 @@ object evaluator extends EvaluationRules { val exp = ast.Forall(eQuant.variables, eTriggers, body)(sourceQuant.pos, sourceQuant.info, sourceQuant.errT) DebugExp.createInstance(exp, expNew) }) - v1.decider.assume(Quantification(Forall, tVars, FunctionPreconditionTransformer.transform(tBody, s1.program), tTriggers, name, quantWeight), debugExp) + v1.decider.assume(Quantification(Forall, tVars, FunctionPreconditionTransformer.transform(tBody, s1.program), tTriggers, name, quantWeight), debugExp, AssumptionType.Unknown) } val tQuant = Quantification(qantOp, tVars, tBody, tTriggers, name, quantWeight) @@ -939,7 +939,7 @@ object evaluator extends EvaluationRules { val preExp = Option.when(withExp)({ DebugExp.createInstance(Some(s"precondition of ${func.name}(${eArgsNew.get.mkString(", ")}) holds"), None, None, InsertionOrderedSet.empty) }) - v3.decider.assume(preFApp, preExp) + v3.decider.assume(preFApp, preExp, AssumptionType.Implicit) val funcAnn = func.info.getUniqueInfo[AnnotationInfo] val tFApp = funcAnn match { case Some(a) if a.values.contains("opaque") => @@ -1011,7 +1011,7 @@ object evaluator extends EvaluationRules { if (!Verifier.config.disableFunctionUnfoldTrigger()) { val eArgsString = eArgsNew.mkString(", ") val debugExp = Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}($eArgsString))", isInternal_ = true)) - v4.decider.assume(App(s.predicateData(predicate).triggerFunction, snap.get.convert(terms.sorts.Snap) +: tArgs), debugExp) + v4.decider.assume(App(s.predicateData(predicate).triggerFunction, snap.get.convert(terms.sorts.Snap) +: tArgs), debugExp, AssumptionType.Internal) } val body = predicate.body.get /* Only non-abstract predicates can be unfolded */ val s7 = s6.scalePermissionFactor(tPerm, ePermNew) @@ -1082,21 +1082,21 @@ object evaluator extends EvaluationRules { case false => val failure = createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, idxLtLength, idxLtLengthExp) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { - v1.decider.assume(idxLtLength, idxLtLengthExp, idxLtLengthExpNew) + v1.decider.assume(idxLtLength, idxLtLengthExp, idxLtLengthExpNew, AssumptionType.Unknown) failure combine Q(s1, SeqAt(t0, t1), eNew, v1) } else failure} case false => val failure1 = createFailure(pve dueTo SeqIndexNegative(e0, e1), v1, s1, idxGe0, idxGe0ExpNew) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { - v1.decider.assume(idxGe0, idxGe0Exp, idxGe0ExpNew) + v1.decider.assume(idxGe0, idxGe0Exp, idxGe0ExpNew, AssumptionType.Unknown) v1.decider.assert(idxLtLength, idxLtLengthExp) { case true => failure1 combine Q(s1, SeqAt(t0, t1), eNew, v1) case false => val failure2 = failure1 combine createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, idxLtLength, idxLtLengthExpNew) if (v1.reportFurtherErrors()) { - v1.decider.assume(idxLtLength, idxLtLengthExp, idxLtLengthExpNew) + v1.decider.assume(idxLtLength, idxLtLengthExp, idxLtLengthExpNew, AssumptionType.Unknown) failure2 combine Q(s1, SeqAt(t0, t1), eNew, v1) } else failure2} } else failure1}}}) @@ -1133,20 +1133,20 @@ object evaluator extends EvaluationRules { case false => val failure = createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, idxLtLength, idxLtLengthExpNew) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { - v1.decider.assume(idxLtLength, idxLtLengthExp, idxLtLengthExpNew) + v1.decider.assume(idxLtLength, idxLtLengthExp, idxLtLengthExpNew, AssumptionType.Unknown) failure combine Q(s1, SeqUpdate(t0, t1, t2), eNew, v1)} else failure} case false => val failure1 = createFailure(pve dueTo SeqIndexNegative(e0, e1), v1, s1, idxGe0, idxGe0ExpNew) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { - v1.decider.assume(idxGe0, idxGe0Exp, idxGe0ExpNew) + v1.decider.assume(idxGe0, idxGe0Exp, idxGe0ExpNew, AssumptionType.Unknown) v1.decider.assert(idxLtLength, idxLtLengthExp) { case true => failure1 combine Q(s1, SeqUpdate(t0, t1, t2), eNew, v1) case false => val failure2 = failure1 combine createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, idxLtLength, idxLtLengthExpNew) if (v1.reportFurtherErrors()) { - v1.decider.assume(idxLtLength, idxLtLengthExp, idxLtLengthExpNew) + v1.decider.assume(idxLtLength, idxLtLengthExp, idxLtLengthExpNew, AssumptionType.Unknown) failure2 combine Q(s1, SeqUpdate(t0, t1, t2), eNew, v1) } else failure2} } else failure1}}}) @@ -1161,7 +1161,7 @@ object evaluator extends EvaluationRules { val exp = ast.EqCmp(ast.SeqLength(seq)(), ast.IntLit(es.size)())(seq.pos, seq.info, seq.errT) DebugExp.createInstance(exp, expNew) }) - v1.decider.assume(SeqLength(tSeq) === IntLiteral(es.size), debugExp) + v1.decider.assume(SeqLength(tSeq) === IntLiteral(es.size), debugExp, AssumptionType.Implicit) Q(s1, tSeq, esNew.map(en => ast.ExplicitSeq(en)(e.pos, e.info, e.errT)), v1)}) /* Sets and multisets */ @@ -1265,7 +1265,7 @@ object evaluator extends EvaluationRules { case false => val failure1 = createFailure(pve dueTo MapKeyNotContained(base, key), v1, s1, SetIn(keyT, MapDomain(baseT)), assertExpNew) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { - v1.decider.assume(SetIn(keyT, MapDomain(baseT)), assertExp, assertExpNew) + v1.decider.assume(SetIn(keyT, MapDomain(baseT)), assertExp, assertExpNew, AssumptionType.Unknown) failure1 combine Q(s1, MapLookup(baseT, keyT), eNew, v1) } else { failure1 @@ -1519,7 +1519,7 @@ object evaluator extends EvaluationRules { case false => val failure = createFailure(pve dueTo DivisionByZero(eDivisor), v, s, tDivisor !== tZero, notZeroExpNew) if (s.retryLevel == 0 && v.reportFurtherErrors()) { - v.decider.assume(tDivisor !== tZero, notZeroExp, notZeroExpNew) + v.decider.assume(tDivisor !== tZero, notZeroExp, notZeroExpNew, AssumptionType.Unknown) failure combine Q(s, t, v) } else failure } @@ -1663,7 +1663,7 @@ object evaluator extends EvaluationRules { (r, optRemainingTriggerTerms) match { case (Success(), Some(remainingTriggerTerms)) => - v.decider.assume(pcDelta, Option.when(withExp)(DebugExp.createInstance("pcDeltaExp", children = pcDeltaExp)), enforceAssumption = false) + v.decider.assume(pcDelta, Option.when(withExp)(DebugExp.createInstance("pcDeltaExp", children = pcDeltaExp)), enforceAssumption = false, assumptionType=AssumptionType.Internal) Q(s, cachedTriggerTerms ++ remainingTriggerTerms, v) case _ => for (e <- remainingTriggerExpressions) @@ -1705,7 +1705,7 @@ object evaluator extends EvaluationRules { var sJoined = entries.tail.foldLeft(entries.head.s)((sAcc, entry) => sAcc.merge(entry.s)) sJoined = sJoined.copy(functionRecorder = sJoined.functionRecorder.recordPathSymbol(joinSymbol)) - joinDefEqs foreach { case (t, exp, expNew) => v.decider.assume(t, exp, expNew)} + joinDefEqs foreach { case (t, exp, expNew) => v.decider.assume(t, exp, expNew, AssumptionType.Unknown)} (sJoined, (joinTerm, joinExp)) } @@ -1740,7 +1740,7 @@ object evaluator extends EvaluationRules { } val triggerString = exps.mkString(", ") - v.decider.assume(triggerAxioms, Option.when(withExp)(DebugExp.createInstance(s"Heap Triggers ($triggerString)")), enforceAssumption = false) + v.decider.assume(triggerAxioms, Option.when(withExp)(DebugExp.createInstance(s"Heap Triggers ($triggerString)")), enforceAssumption = false, assumptionType=AssumptionType.Internal) var fr = s.functionRecorder for (smDef <- smDefs){ fr = fr.recordFvfAndDomain(smDef) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 1607af84b..f6728c1c2 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -283,7 +283,7 @@ object executor extends ExecutionRules { val s2 = s1.copy(invariantContexts = sLeftover.h +: s1.invariantContexts) intermediateResult combine executionFlowController.locally(s2, v1)((s3, v2) => { v2.decider.declareAndRecordAsFreshFunctions(ff1 -- v2.decider.freshFunctions) /* [BRANCH-PARALLELISATION] */ - v2.decider.assume(pcs.assumptions, Option.when(withExp)(DebugExp.createInstance("Loop invariant", pcs.assumptionExps)), false) + v2.decider.assume(pcs.assumptions, Option.when(withExp)(DebugExp.createInstance("Loop invariant", pcs.assumptionExps)), false, assumptionType=AssumptionType.LoopInvariant) v2.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) if (v2.decider.checkSmoke()) Success() @@ -398,7 +398,7 @@ object executor extends ExecutionRules { quantifiedChunkSupporter.summarisingSnapshotMap( s2, field, Seq(`?r`), relevantChunks, v1) val debugExp = Option.when(withExp)(DebugExp.createInstance(s"Field Trigger: (${eRcvrNew.toString()}).${field.name}")) - v2.decider.assume(FieldTrigger(field.name, smDef1.sm, tRcvr), debugExp) + v2.decider.assume(FieldTrigger(field.name, smDef1.sm, tRcvr), debugExp, AssumptionType.Internal) s2.copy(smCache = smCache1) } else { s2 @@ -430,7 +430,7 @@ object executor extends ExecutionRules { field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), sm, s.program, v1) if (s3.heapDependentTriggers.contains(field)) { val debugExp2 = Option.when(withExp)(DebugExp.createInstance(s"FieldTrigger(${eRcvrNew.toString()}.${field.name})")) - v1.decider.assume(FieldTrigger(field.name, sm, tRcvr), debugExp2) + v1.decider.assume(FieldTrigger(field.name, sm, tRcvr), debugExp2, AssumptionType.Internal) } val s4 = s3.copy(h = h3 + ch) val (debugHeapName, _) = v.getDebugOldLabel(s4, fa.pos) @@ -468,7 +468,7 @@ object executor extends ExecutionRules { val debugExp = Option.when(withExp)(ast.NeCmp(x, ast.NullLit()())()) val debugExpSubst = Option.when(withExp)(ast.NeCmp(eRcvrNew.get, ast.NullLit()())()) val (debugHeapName, debugLabel) = v.getDebugOldLabel(s, stmt.pos) - v.decider.assume(tRcvr !== Null, debugExp, debugExpSubst) + v.decider.assume(tRcvr !== Null, debugExp, debugExpSubst, AssumptionType.Implicit) val newChunks = fields map (field => { val p = FullPerm val pExp = Option.when(withExp)(ast.FullPerm()(stmt.pos, stmt.info, stmt.errT)) @@ -491,7 +491,7 @@ object executor extends ExecutionRules { val esNew = eRcvrNew.map(rcvr => BigAnd(viper.silicon.state.utils.computeReferenceDisjointnessesExp(s, rcvr))) val s1 = s.copy(g = s.g + (x, (tRcvr, eRcvrNew)), h = s.h + Heap(newChunks)) val s2 = if (withExp) s1.copy(oldHeaps = s1.oldHeaps + (debugHeapName -> magicWandSupporter.getEvalHeap(s1))) else s1 - v.decider.assume(ts, Option.when(withExp)(DebugExp.createInstance(Some("Reference Disjointness"), esNew, esNew, InsertionOrderedSet.empty)), enforceAssumption = false) + v.decider.assume(ts, Option.when(withExp)(DebugExp.createInstance(Some("Reference Disjointness"), esNew, esNew, InsertionOrderedSet.empty)), enforceAssumption = false, assumptionType=AssumptionType.Implicit) Q(s2, v) case inhale @ ast.Inhale(a) => @@ -656,7 +656,7 @@ object executor extends ExecutionRules { val eArgsStr = eArgsNew.mkString(", ") val debugExp = Option.when(withExp)(DebugExp.createInstance(Some(s"PredicateTrigger(${predicate.name}($eArgsStr))"), Some(pa), Some(ast.PredicateAccess(eArgsNew.get, predicateName)(pa.pos, pa.info, pa.errT)), None, isInternal_ = true, InsertionOrderedSet.empty)) - v2.decider.assume(PredicateTrigger(predicate.name, smDef1.sm, tArgs), debugExp) + v2.decider.assume(PredicateTrigger(predicate.name, smDef1.sm, tArgs), debugExp, AssumptionType.Internal) smCache1 } else { s2.smCache @@ -708,7 +708,7 @@ object executor extends ExecutionRules { quantifiedChunkSupporter.summarisingSnapshotMap( s2, wand, formalVars, relevantChunks, v1) v1.decider.assume(PredicateTrigger(ch.id.toString, smDef.sm, ch.singletonArgs.get), - Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${ch.id.toString}(${ch.singletonArgExps.get}))", isInternal_ = true))) + Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${ch.id.toString}(${ch.singletonArgExps.get}))", isInternal_ = true)), AssumptionType.Internal) smCache case _ => s2.smCache } diff --git a/src/main/scala/rules/HavocSupporter.scala b/src/main/scala/rules/HavocSupporter.scala index d9e4612e3..357e6f3f1 100644 --- a/src/main/scala/rules/HavocSupporter.scala +++ b/src/main/scala/rules/HavocSupporter.scala @@ -126,8 +126,8 @@ object havocSupporter extends SymbolicExecutionRules { v.decider.prover.comment("Check havocall receiver injectivity") val notInjectiveReason = QuasihavocallNotInjective(havocall) val comment = "QP receiver injectivity check is well-defined" - val injectivityDebugExp = Option.when(withExp)(DebugExp.createInstance(comment, true)) - v.decider.assume(FunctionPreconditionTransformer.transform(receiverInjectivityCheck, s.program), injectivityDebugExp) + val injectivityDebugExp = Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)) + v.decider.assume(FunctionPreconditionTransformer.transform(receiverInjectivityCheck, s.program), injectivityDebugExp, AssumptionType.Internal) v.decider.assert(receiverInjectivityCheck, comment, Verifier.config.checkTimeout.toOption) { case false => createFailure(pve dueTo notInjectiveReason, v, s1, receiverInjectivityCheck, "QP receiver injective") case true => @@ -148,7 +148,7 @@ object havocSupporter extends SymbolicExecutionRules { ) val comment = "Definitional axioms for havocall inverse functions" v.decider.prover.comment(comment) - v.decider.assume(inverseFunctions.definitionalAxioms, Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false) + v.decider.assume(inverseFunctions.definitionalAxioms, Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, assumptionType=AssumptionType.Internal) // Call the havoc helper function, which returns a new set of chunks, some of // which may be havocked. Since we are executing a Havocall statement, we wrap @@ -279,7 +279,7 @@ object havocSupporter extends SymbolicExecutionRules { v.decider.prover.comment("axiomatized snapshot map after havoc") val debugExp = Option.when(withExp)(DebugExp.createInstance("havoc new axiom", isInternal_ = true)) - v.decider.assume(newAxiom, debugExp) + v.decider.assume(newAxiom, debugExp, AssumptionType.Unknown) QuantifiedChunk.withSnapshotMap(ch, newSm, v.decider.assumptionAnalyzer.currentAnalysisInfo) } diff --git a/src/main/scala/rules/Joiner.scala b/src/main/scala/rules/Joiner.scala index c5012c018..6e8ac0641 100644 --- a/src/main/scala/rules/Joiner.scala +++ b/src/main/scala/rules/Joiner.scala @@ -105,13 +105,13 @@ object joiner extends JoiningRules { val pcsExp = Option.when(withExp)(entry.pathConditions.conditionalizedExp) val comment = "Joined path conditions" v.decider.prover.comment(comment) - v.decider.assume(pcs, Option.when(withExp)(DebugExp.createInstance(comment, InsertionOrderedSet(pcsExp.get))), enforceAssumption = false) + v.decider.assume(pcs, Option.when(withExp)(DebugExp.createInstance(comment, InsertionOrderedSet(pcsExp.get))), enforceAssumption = false, assumptionType=AssumptionType.Implicit) feasibleBranches = And(entry.pathConditions.branchConditions) :: feasibleBranches feasibleBranchesExp = feasibleBranchesExp.map(fbe => BigAnd(entry.pathConditions.branchConditionExps.map(_._1)) :: fbe) feasibleBranchesExpNew = feasibleBranchesExpNew.map(fbe => BigAnd(entry.pathConditions.branchConditionExps.map(_._2.get)) :: fbe) }) // Assume we are in a feasible branch - v.decider.assume(Or(feasibleBranches), Option.when(withExp)(DebugExp.createInstance(Some("Feasible Branches"), feasibleBranchesExp.map(BigOr(_)), feasibleBranchesExpNew.map(BigOr(_)), InsertionOrderedSet.empty))) + v.decider.assume(Or(feasibleBranches), Option.when(withExp)(DebugExp.createInstance(Some("Feasible Branches"), feasibleBranchesExp.map(BigOr(_)), feasibleBranchesExpNew.map(BigOr(_)), InsertionOrderedSet.empty)), AssumptionType.Unknown) Q(sJoined, dataJoined, v) } } diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index f1c0b996f..9ecd4d21c 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -183,7 +183,7 @@ object magicWandSupporter extends SymbolicExecutionRules { case (Some(ch1: QuantifiedBasicChunk), Some(ch2: QuantifiedBasicChunk)) => ch1.snapshotMap === ch2.snapshotMap case _ => True } - v.decider.assume(tEq, Option.when(withExp)(DebugExp.createInstance("Snapshots", isInternal_ = true))) + v.decider.assume(tEq, Option.when(withExp)(DebugExp.createInstance("Snapshots", isInternal_ = true)), AssumptionType.Internal) /* In the future it might be worth to recheck whether the permissions needed, in the case of * success being an instance of Incomplete, are zero. @@ -433,7 +433,7 @@ object magicWandSupporter extends SymbolicExecutionRules { v1.decider.setCurrentBranchCondition(And(branchConditions), (exp, expNew)) // Recreate all path conditions in the Z3 proof script that we recorded for that branch - v1.decider.assume(conservedPcs._1, conservedPcs._2) + v1.decider.assume(conservedPcs._1, conservedPcs._2, AssumptionType.Unknown) // Execute the continuation Q Q(s2, magicWandChunk, v1) @@ -478,7 +478,7 @@ object magicWandSupporter extends SymbolicExecutionRules { case SortWrapper(snapshot: MagicWandSnapshot, _) => snapshot.applyToMWSF(snapLhs.get) // Fallback solution for quantified magic wands case predicateLookup: PredicateLookup => - v2.decider.assume(snapLhs.get === First(snapWand.get), Option.when(withExp)(DebugExp.createInstance("Magic wand snapshot", true))) + v2.decider.assume(snapLhs.get === First(snapWand.get), Option.when(withExp)(DebugExp.createInstance("Magic wand snapshot", isInternal_ = true)), AssumptionType.Internal) Second(predicateLookup) case _ => snapWand.get } diff --git a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala index 42cde2e37..9168ce409 100644 --- a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala +++ b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala @@ -6,7 +6,7 @@ package viper.silicon.rules -import viper.silicon.assumptionAnalysis.AnalysisInfo +import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType} import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.state._ import viper.silicon.interfaces.{Success, VerificationResult} @@ -387,7 +387,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { newChunks foreach { ch => val resource = Resources.resourceDescriptions(ch.resourceID) val pathCond = interpreter.buildPathConditionsForChunk(ch, resource.instanceProperties(s.mayAssumeUpperBounds)) - pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)))) + pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), AssumptionType.PathCondition)) } val newHeap = Heap(allChunks) @@ -478,7 +478,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { ast.Implies(ast.Not(eqExp.get)(), ast.EqCmp(permTakenExp.get, ast.NoPerm()())())(pe.pos, pe.info, pe.errT)))) - v.decider.assume(constraint, Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp))) + v.decider.assume(constraint, Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp)), AssumptionType.Unknown) newFr = newFr.recordPathSymbol(permTaken.applicable.asInstanceOf[Function]).recordConstraint(constraint) @@ -494,7 +494,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { val constraintExp = permsExp.map(pe => ast.Implies(ast.NeCmp(totalPermSumExp.get, ast.NoPerm()())(), ast.And(ast.PermLeCmp(ast.NoPerm()(), totalPermTakenExp.get)(), ast.PermLeCmp(totalPermTakenExp.get, totalPermSumExp.get)())(pe.pos, pe.info, pe.errT))()) - v.decider.assume(totalTakenBounds, constraintExp, constraintExp) + v.decider.assume(totalTakenBounds, constraintExp, constraintExp, AssumptionType.Unknown) newFr = newFr.recordConstraint(totalTakenBounds) @@ -504,7 +504,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { v.decider.assert(Implies(PermLess(NoPerm, perms), totalPermTaken !== NoPerm), totalPermTakenImplExp) { case true => val constraintExp = permsExp.map(pe => ast.EqCmp(pe, totalPermTakenExp.get)()) - v.decider.assume(perms === totalPermTaken, Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp))) + v.decider.assume(perms === totalPermTaken, Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp)), AssumptionType.Unknown) if (returnSnap) { summarise(s1, relevantChunks.toSeq, resource, args, argsExp, None, v)((s2, snap, _, _, v1) => Q(s2, updatedChunks, Some(snap), v1)) @@ -548,7 +548,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { relevantChunks foreach (chunk => { val instantiatedPermSum = permissionSum.replace(freeReceiver, chunk.args.head) val exp = permissionSumExp.map(pse => ast.PermLeCmp(replaceVarsInExp(pse, Seq(freeReceiverExp.name), Seq(chunk.argsExp.get.head)), ast.FullPerm()())()) - v.decider.assume(PermAtMost(instantiatedPermSum, FullPerm), exp, exp) + v.decider.assume(PermAtMost(instantiatedPermSum, FullPerm), exp, exp, AssumptionType.Unknown) }) } } diff --git a/src/main/scala/rules/PredicateSupporter.scala b/src/main/scala/rules/PredicateSupporter.scala index fdaebc10a..12aacc5c4 100644 --- a/src/main/scala/rules/PredicateSupporter.scala +++ b/src/main/scala/rules/PredicateSupporter.scala @@ -79,7 +79,7 @@ object predicateSupporter extends PredicateSupportRules { val predTrigger = App(s1a.predicateData(predicate).triggerFunction, snap.get.convert(terms.sorts.Snap) +: tArgs) val eArgsString = eArgs.mkString(", ") - v1.decider.assume(predTrigger, Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}($eArgsString))"))) + v1.decider.assume(predTrigger, Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}($eArgsString))")), AssumptionType.Internal) } val s2 = s1a.setConstrainable(constrainableWildcards, false) if (s2.qpPredicates.contains(predicate)) { @@ -103,7 +103,7 @@ object predicateSupporter extends PredicateSupportRules { s2, predicate, s2.predicateFormalVarMap(predicate), relevantChunks, v1) val eArgsString = eArgs.mkString(", ") v1.decider.assume(PredicateTrigger(predicate.name, smDef1.sm, tArgs), - Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}($eArgsString))"))) + Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}($eArgsString))")), AssumptionType.Internal) smCache1 } else { s2.smCache @@ -174,7 +174,7 @@ object predicateSupporter extends PredicateSupportRules { App(s4.predicateData(predicate).triggerFunction, snap.get.convert(terms.sorts.Snap) +: tArgs) val eargs = eArgs.mkString(", ") - v2.decider.assume(predicateTrigger, Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}($eargs))"))) + v2.decider.assume(predicateTrigger, Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}($eargs))")), AssumptionType.Internal) } Q(s4.copy(g = s.g, permissionScalingFactor = s.permissionScalingFactor, @@ -193,7 +193,7 @@ object predicateSupporter extends PredicateSupportRules { val predicateTrigger = App(s4.predicateData(predicate).triggerFunction, snap.get +: tArgs) val eargs = eArgs.mkString(", ") - v2.decider.assume(predicateTrigger, Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${pa.predicateName}($eargs))"))) + v2.decider.assume(predicateTrigger, Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${pa.predicateName}($eargs))")), AssumptionType.Internal) } val s5 = s4.copy(g = s.g, permissionScalingFactor = s.permissionScalingFactor, diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index 49b7e0bf8..a977c4ff0 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -233,7 +233,7 @@ object producer extends ProductionRules { QB(s3, null, v3) }), (s2, v2) => { - v2.decider.assume(sf(sorts.Snap, v2) === Unit, Option.when(withExp)(DebugExp.createInstance("Empty snapshot", true))) + v2.decider.assume(sf(sorts.Snap, v2) === Unit, Option.when(withExp)(DebugExp.createInstance("Empty snapshot", isInternal_ = true)), AssumptionType.Internal) /* TODO: Avoid creating a fresh var (by invoking) `sf` that is not used * otherwise. In order words, only make this assumption if `sf` has * already been used, e.g. in a snapshot equality such as `s0 == (s1, s2)`. @@ -264,7 +264,7 @@ object producer extends ProductionRules { Q(s3, v3) }), (s2, v2) => { - v2.decider.assume(sf(sorts.Snap, v2) === Unit, Option.when(withExp)(DebugExp.createInstance("Empty snapshot", true))) + v2.decider.assume(sf(sorts.Snap, v2) === Unit, Option.when(withExp)(DebugExp.createInstance("Empty snapshot", isInternal_ = true)), AssumptionType.Internal) /* TODO: Avoid creating a fresh var (by invoking) `sf` that is not used * otherwise. In order words, only make this assumption if `sf` has * already been used, e.g. in a snapshot equality such as `s0 == (s1, s2)`. @@ -374,7 +374,7 @@ object producer extends ProductionRules { && !Verifier.config.disableFunctionUnfoldTrigger()) { val argsString = eArgsNew.mkString(", ") val debugExp = Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}($argsString))", isInternal_ = true)) - v3.decider.assume(App(s3.predicateData(predicate).triggerFunction, snap1 +: tArgs), debugExp) + v3.decider.assume(App(s3.predicateData(predicate).triggerFunction, snap1 +: tArgs), debugExp, AssumptionType.Internal) } Q(s3.copy(h = h3), v3)}) }}))) @@ -405,7 +405,7 @@ object producer extends ProductionRules { s1, wand, formalVars, relevantChunks, v1) val argsStr = bodyVarsNew.mkString(", ") val debugExp = Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${ch.id.toString}($argsStr))", isInternal_ = true)) - v1.decider.assume(PredicateTrigger(ch.id.toString, smDef1.sm, args), debugExp) + v1.decider.assume(PredicateTrigger(ch.id.toString, smDef1.sm, args), debugExp, AssumptionType.Internal) smCache1 } else { s1.smCache @@ -552,9 +552,9 @@ object producer extends ProductionRules { /* Any regular expressions, i.e. boolean and arithmetic. */ case _ => v.decider.assume(sf(sorts.Snap, v) === Unit, - Option.when(withExp)(DebugExp.createInstance("Empty snapshot", true))) /* TODO: See comment for case ast.Implies above */ + Option.when(withExp)(DebugExp.createInstance("Empty snapshot", isInternal_ = true)), AssumptionType.Internal) /* TODO: See comment for case ast.Implies above */ eval(s, a, pve, v)((s1, t, aNew, v1) => { - v1.decider.assume(t, Option.when(withExp)(a), aNew) + v1.decider.assume(t, Option.when(withExp)(a), aNew, AssumptionType.Explicit) // TODO ake: always explicit? Q(s1, v1)}) } diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 8b35da4a6..8570aba90 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -7,7 +7,7 @@ package viper.silicon.rules import viper.silicon.Map -import viper.silicon.assumptionAnalysis.AnalysisInfo +import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.VerificationResult @@ -717,13 +717,13 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { Verifier.config.mapCache(s.pmCache.get(resource, relevantChunks)) match { case Some(pmDef) => - v.decider.assume(pmDef.valueDefinitions, Option.when(withExp)(DebugExp.createInstance("value definitions", isInternal_ = true)), enforceAssumption = false) + v.decider.assume(pmDef.valueDefinitions, Option.when(withExp)(DebugExp.createInstance("value definitions", isInternal_ = true)), enforceAssumption = false, assumptionType=AssumptionType.Internal) (pmDef, s.pmCache) case _ => val (pm, valueDef) = quantifiedChunkSupporter.summarisePerm(s, relevantChunks, formalQVars, resource, smDef, v) val pmDef = PermMapDefinition(resource, pm, valueDef) - v.decider.assume(valueDef, Option.when(withExp)(DebugExp.createInstance("value definitions", isInternal_ = true)), enforceAssumption = false) + v.decider.assume(valueDef, Option.when(withExp)(DebugExp.createInstance("value definitions", isInternal_ = true)), enforceAssumption = false, assumptionType=AssumptionType.Internal) (pmDef, s.pmCache + ((resource, relevantChunks) -> pmDef)) } } @@ -764,7 +764,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { case None => val comment = "Definitional axioms for snapshot map domain" v.decider.prover.comment(comment) - v.decider.assume(smDef.domainDefinitions, Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false) + v.decider.assume(smDef.domainDefinitions, Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, assumptionType=AssumptionType.Internal) case Some(_instantiations) => // TODO: Avoid pattern matching on resource val instantiations = resource match { @@ -776,7 +776,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.prover.comment(comment) // TODO: Avoid cast to Quantification v.decider.assume(smDef.domainDefinitions.map(_.asInstanceOf[Quantification].instantiate(instantiations)), - Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false) + Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, assumptionType=AssumptionType.Internal) } } @@ -784,7 +784,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { case None => val comment = "Definitional axioms for snapshot map values" v.decider.prover.comment(comment) - v.decider.assume(smDef.valueDefinitions, Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false) + v.decider.assume(smDef.valueDefinitions, Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, assumptionType=AssumptionType.Internal) case Some(_instantiations) => // TODO: Avoid pattern matching on resource val instantiations = resource match { @@ -796,7 +796,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.prover.comment(comment) // TODO: Avoid cast to Quantification v.decider.assume(smDef.valueDefinitions.map(_.asInstanceOf[Quantification].instantiate(instantiations)), - Option.when(withExp)(DebugExp.createInstance(comment, true)), enforceAssumption = false) + Option.when(withExp)(DebugExp.createInstance(comment, true)), enforceAssumption = false, assumptionType=AssumptionType.Internal) } } @@ -973,7 +973,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val commentGlobals = "Nested auxiliary terms: globals" v.decider.prover.comment(commentGlobals) v.decider.assume(auxGlobals, Option.when(withExp)(DebugExp.createInstance(description=commentGlobals, children=auxGlobalsExp.get)), - enforceAssumption = false) + enforceAssumption = false, assumptionType=AssumptionType.Internal) val commentNonGlobals = "Nested auxiliary terms: non-globals" v.decider.prover.comment(commentNonGlobals) @@ -981,7 +981,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { auxNonGlobals.map(_.copy( vars = effectiveTriggersQVars, triggers = effectiveTriggers)), - Option.when(withExp)(DebugExp.createInstance(description=commentNonGlobals, children=auxNonGlobalsExp.get)), enforceAssumption = false) + Option.when(withExp)(DebugExp.createInstance(description=commentNonGlobals, children=auxNonGlobalsExp.get)), enforceAssumption = false, assumptionType=AssumptionType.Internal) val nonNegImplication = Implies(tCond, perms.IsNonNegative(tPerm)) val nonNegImplicationExp = eCond.map(c => ast.Implies(c, ast.PermGeCmp(ePerm.get, ast.NoPerm()())())(c.pos, c.info, c.errT)) @@ -1009,7 +1009,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val comment = "Check receiver injectivity" v.decider.prover.comment(comment) val debugExp = Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)) - v.decider.assume(FunctionPreconditionTransformer.transform(receiverInjectivityCheck, s.program), debugExp) + v.decider.assume(FunctionPreconditionTransformer.transform(receiverInjectivityCheck, s.program), debugExp, AssumptionType.Internal) v.decider.assert(receiverInjectivityCheck, comment, Verifier.config.checkTimeout.toOption) { case true => val ax = inverseFunctions.axiomInversesOfInvertibles @@ -1019,8 +1019,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.prover.comment(comment) val definitionalAxiomMark = v.decider.setPathConditionMark() v.decider.assume(inv.definitionalAxioms.map(a => FunctionPreconditionTransformer.transform(a, s.program)), - Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false) - v.decider.assume(inv.definitionalAxioms, Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false) + Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, assumptionType=AssumptionType.Internal) + v.decider.assume(inv.definitionalAxioms, Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, assumptionType=AssumptionType.Internal) val conservedPcs = if (s.recordPcs) (s.conservedPcs.head :+ v.decider.pcs.after(definitionalAxiomMark)) +: s.conservedPcs.tail else s.conservedPcs @@ -1043,7 +1043,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { triggers = effectiveTriggers, qidPrefix = qid ) - v.decider.assume(pcsForChunk, pcsForChunkExp, pcsForChunkExp) + v.decider.assume(pcsForChunk, pcsForChunkExp, pcsForChunkExp, AssumptionType.Unknown) }) val (fr1, h1) = v.stateConsolidator(s).merge(s.functionRecorder, s, s.h, Heap(Seq(ch)), v) @@ -1071,7 +1071,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val qvarsToInv = inv.qvarsToInversesOf(codomainVars) val condOfInv = tCond.replace(qvarsToInv) v.decider.assume(Forall(codomainVars, Implies(condOfInv, trigger), Trigger(inv.inversesOf(codomainVars))), - Option.when(withExp)(DebugExp.createInstance("Inverse Trigger", true))) + Option.when(withExp)(DebugExp.createInstance("Inverse Trigger", isInternal_ = true)), AssumptionType.Internal) smCache1 } else { s.smCache @@ -1116,7 +1116,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val interpreter = new NonQuantifiedPropertyInterpreter(h1.values, v) val resourceDescription = Resources.resourceDescriptions(ch.resourceID) val pcs = interpreter.buildPathConditionsForChunk(ch, resourceDescription.instanceProperties(s.mayAssumeUpperBounds)) - pcs.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)))) + pcs.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), AssumptionType.PathCondition)) val resourceIdentifier = resource match { case wand: ast.MagicWand => MagicWandIdentifier(wand, s.program) @@ -1128,7 +1128,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val (smDef1, smCache1) = quantifiedChunkSupporter.summarisingSnapshotMap( s, resource, formalQVars, relevantChunks, v) - v.decider.assume(resourceTriggerFactory(smDef1.sm), Option.when(withExp)(DebugExp.createInstance("Resource Trigger", true))) + v.decider.assume(resourceTriggerFactory(smDef1.sm), Option.when(withExp)(DebugExp.createInstance("Resource Trigger", isInternal_ = true)), AssumptionType.Internal) smCache1 } else { s.smCache @@ -1204,7 +1204,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val comment = "Nested auxiliary terms: globals" v.decider.prover.comment(comment) - v.decider.assume(auxGlobals, Option.when(withExp)(DebugExp.createInstance(description=comment, children=auxGlobalsExp.get)), enforceAssumption = false) + v.decider.assume(auxGlobals, Option.when(withExp)(DebugExp.createInstance(description=comment, children=auxGlobalsExp.get)), enforceAssumption = false, assumptionType=AssumptionType.Internal) val comment2 = "Nested auxiliary terms: non-globals" v.decider.prover.comment(comment2) @@ -1214,10 +1214,10 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.assume( auxNonGlobals.map(_.copy( vars = effectiveTriggersQVars, - triggers = effectiveTriggers)), Option.when(withExp)(DebugExp.createInstance(description=comment2, children=auxNonGlobalsExp.get)), enforceAssumption = false) + triggers = effectiveTriggers)), Option.when(withExp)(DebugExp.createInstance(description=comment2, children=auxNonGlobalsExp.get)), enforceAssumption = false, assumptionType=AssumptionType.Internal) case Some(_) => /* Explicit triggers were provided. */ - v.decider.assume(auxNonGlobals, Option.when(withExp)(DebugExp.createInstance(description=comment2, children=auxNonGlobalsExp.get)), enforceAssumption = false) + v.decider.assume(auxNonGlobals, Option.when(withExp)(DebugExp.createInstance(description=comment2, children=auxNonGlobalsExp.get)), enforceAssumption = false, assumptionType=AssumptionType.Internal) } val nonNegImplication = Implies(tCond, perms.IsNonNegative(tPerm)) @@ -1264,7 +1264,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { program = s.program) v.decider.prover.comment("Check receiver injectivity") val debugExp = Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)) - v.decider.assume(FunctionPreconditionTransformer.transform(receiverInjectivityCheck, s.program), debugExp) + v.decider.assume(FunctionPreconditionTransformer.transform(receiverInjectivityCheck, s.program), debugExp, AssumptionType.Internal) v.decider.assert(receiverInjectivityCheck, comment, Verifier.config.checkTimeout.toOption) { case true => val qvarsToInvOfLoc = inverseFunctions.qvarsToInversesOf(formalQVars) @@ -1279,8 +1279,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.prover.comment("Definitional axioms for inverse functions") v.decider.assume(inverseFunctions.definitionalAxioms.map(a => FunctionPreconditionTransformer.transform(a, s.program)), - Option.when(withExp)(DebugExp.createInstance("Inverse Function Axioms", isInternal_ = true)), enforceAssumption = false) - v.decider.assume(inverseFunctions.definitionalAxioms, Option.when(withExp)(DebugExp.createInstance("Inverse function axiom", isInternal_ = true)), enforceAssumption = false) + Option.when(withExp)(DebugExp.createInstance("Inverse Function Axioms", isInternal_ = true)), enforceAssumption = false, assumptionType=AssumptionType.Internal) + v.decider.assume(inverseFunctions.definitionalAxioms, Option.when(withExp)(DebugExp.createInstance("Inverse function axiom", isInternal_ = true)), enforceAssumption = false, assumptionType=AssumptionType.Internal) if (s.heapDependentTriggers.contains(resourceIdentifier)){ v.decider.assume( @@ -1288,7 +1288,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { formalQVars, Implies(condOfInvOfLoc, ResourceTriggerFunction(resource, smDef1.get.sm, formalQVars, s.program)), Trigger(inverseFunctions.inversesOf(formalQVars)))), - Option.when(withExp)(DebugExp.createInstance("Inverse Function", isInternal_ = true)), enforceAssumption = false) + Option.when(withExp)(DebugExp.createInstance("Inverse Function", isInternal_ = true)), enforceAssumption = false, assumptionType=AssumptionType.Internal) } @@ -1351,12 +1351,12 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v2, s.program ) - val debugExp = Option.when(withExp)(DebugExp.createInstance("Inverse functions for quantified permission", true)) - v.decider.assume(FunctionPreconditionTransformer.transform(inverseFunctions.axiomInvertiblesOfInverses, s3.program), debugExp) - v.decider.assume(inverseFunctions.axiomInvertiblesOfInverses, debugExp) + val debugExp = Option.when(withExp)(DebugExp.createInstance("Inverse functions for quantified permission", isInternal_ = true)) + v.decider.assume(FunctionPreconditionTransformer.transform(inverseFunctions.axiomInvertiblesOfInverses, s3.program), debugExp, AssumptionType.Internal) + v.decider.assume(inverseFunctions.axiomInvertiblesOfInverses, debugExp, AssumptionType.Internal) val substitutedAxiomInversesOfInvertibles = inverseFunctions.axiomInversesOfInvertibles.replace(formalQVars, tArgs) - v.decider.assume(FunctionPreconditionTransformer.transform(substitutedAxiomInversesOfInvertibles, s3.program), debugExp) - v.decider.assume(substitutedAxiomInversesOfInvertibles, debugExp) + v.decider.assume(FunctionPreconditionTransformer.transform(substitutedAxiomInversesOfInvertibles, s3.program), debugExp, AssumptionType.Internal) + v.decider.assume(substitutedAxiomInversesOfInvertibles, debugExp, AssumptionType.Internal) val h2 = Heap(remainingChunks ++ otherChunks) val s4 = s3.copy(smCache = smCache2, constrainableARPs = s.constrainableARPs) @@ -1675,7 +1675,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { if (constrainPermissions) { v.decider.prover.comment(s"Constrain original permissions $perms") - v.decider.assume(permissionConstraint, permissionConstraintExp, permissionConstraintExp) + v.decider.assume(permissionConstraint, permissionConstraintExp, permissionConstraintExp, AssumptionType.Unknown) remainingChunks = remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.assumptionAnalyzer.currentAnalysisInfo) } else { @@ -2070,8 +2070,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val result = v.decider.check(And(equalityCond, equalityTerm), Verifier.config.checkTimeout()) if (result) { // Learn the equality - val debugExp = Option.when(withExp)(DebugExp.createInstance("Chunks alias", true)) - v.decider.assume(equalityTerm, debugExp) + val debugExp = Option.when(withExp)(DebugExp.createInstance("Chunks alias", isInternal_ = true)) + v.decider.assume(equalityTerm, debugExp, AssumptionType.Unknown) } result case _ => false @@ -2096,8 +2096,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val result = v.decider.check(equalityTerm, Verifier.config.checkTimeout()) if (result) { // Learn the equality - val debugExp = Option.when(withExp)(DebugExp.createInstance("Chunks alias", true)) - v.decider.assume(equalityTerm, debugExp) + val debugExp = Option.when(withExp)(DebugExp.createInstance("Chunks alias", isInternal_ = true)) + v.decider.assume(equalityTerm, debugExp, AssumptionType.Unknown) } result } else { diff --git a/src/main/scala/rules/StateConsolidator.scala b/src/main/scala/rules/StateConsolidator.scala index 88f84b8e9..0350bbbfa 100644 --- a/src/main/scala/rules/StateConsolidator.scala +++ b/src/main/scala/rules/StateConsolidator.scala @@ -82,7 +82,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol val (_functionRecorder, _mergedChunks, _, snapEqs) = singleMerge(functionRecorder, destChunks, newChunks, s.functionRecorderQuantifiedVariables().map(_._1), v) - snapEqs foreach (t => v.decider.assume(t, Option.when(withExp)(DebugExp.createInstance("Snapshot Equations", true)))) + snapEqs foreach (t => v.decider.assume(t, Option.when(withExp)(DebugExp.createInstance("Snapshot Equations", isInternal_ = true)), AssumptionType.PathCondition)) functionRecorder = _functionRecorder mergedChunks = _mergedChunks @@ -100,12 +100,12 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol mergedChunks.filter(_.isInstanceOf[BasicChunk]) foreach { case ch: BasicChunk => val resource = Resources.resourceDescriptions(ch.resourceID) val pathCond = interpreter.buildPathConditionsForChunk(ch, resource.instanceProperties(s.mayAssumeUpperBounds)) - pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)))) + pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), AssumptionType.Internal)) } Resources.resourceDescriptions foreach { case (id, desc) => val pathCond = interpreter.buildPathConditionsForResource(id, desc.delayedProperties(s.mayAssumeUpperBounds)) - pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)))) + pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), AssumptionType.PathCondition)) } v.symbExLog.closeScope(sepIdentifier) @@ -134,13 +134,13 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol val sepIdentifier = v.symbExLog.openScope(mergeLog) val (fr2, mergedChunks, newlyAddedChunks, snapEqs) = singleMerge(fr1, h.values.toSeq, newH.values.toSeq, s.functionRecorderQuantifiedVariables().map(_._1), v) - v.decider.assume(snapEqs, Option.when(withExp)(DebugExp.createInstance("Snapshot", isInternal_ = true)), enforceAssumption = false) + v.decider.assume(snapEqs, Option.when(withExp)(DebugExp.createInstance("Snapshot", isInternal_ = true)), enforceAssumption = false, assumptionType=AssumptionType.Internal) val interpreter = new NonQuantifiedPropertyInterpreter(mergedChunks, v) newlyAddedChunks.filter(_.isInstanceOf[BasicChunk]) foreach { case ch: BasicChunk => val resource = Resources.resourceDescriptions(ch.resourceID) val pathCond = interpreter.buildPathConditionsForChunk(ch, resource.instanceProperties(s.mayAssumeUpperBounds)) - pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)))) + pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), AssumptionType.PathCondition)) } v.symbExLog.closeScope(sepIdentifier) @@ -299,7 +299,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol Some(DebugExp.createInstance(exp, exp)) } else { None } v.decider.assume( - Forall(receiver, PermAtMost(currentPermAmount, FullPerm), Trigger(trigger), "qp-fld-prm-bnd"), debugExp) + Forall(receiver, PermAtMost(currentPermAmount, FullPerm), Trigger(trigger), "qp-fld-prm-bnd"), debugExp, AssumptionType.Unknown) } else { /* If we don't use heap-dependent triggers, the trigger x.f does not work. Instead, we assume the permission @@ -316,7 +316,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol val exp = ast.PermLeCmp(permExp, ast.FullPerm()())() Some(DebugExp.createInstance(exp, exp)) } else { None } - v.decider.assume(PermAtMost(PermLookup(field.name, pmDef.pm, chunk.singletonRcvr.get), FullPerm), debugExp) + v.decider.assume(PermAtMost(PermLookup(field.name, pmDef.pm, chunk.singletonRcvr.get), FullPerm), debugExp, AssumptionType.Unknown) } else { val chunkReceivers = chunk.invs.get.inverses.map(i => App(i, chunk.invs.get.additionalArguments ++ chunk.quantifiedVars)) val triggers = chunkReceivers.map(r => Trigger(r)).toSeq @@ -332,7 +332,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol Some(DebugExp.createInstance(exp, exp)) } else { None } v.decider.assume( - Forall(chunk.quantifiedVars, PermAtMost(currentPermAmount, FullPerm), triggers, "qp-fld-prm-bnd"), debugExp) + Forall(chunk.quantifiedVars, PermAtMost(currentPermAmount, FullPerm), triggers, "qp-fld-prm-bnd"), debugExp, AssumptionType.Unknown) } } diff --git a/src/main/scala/supporters/SnapshotSupporter.scala b/src/main/scala/supporters/SnapshotSupporter.scala index 05212e370..eecb1c69c 100644 --- a/src/main/scala/supporters/SnapshotSupporter.scala +++ b/src/main/scala/supporters/SnapshotSupporter.scala @@ -6,6 +6,7 @@ package viper.silicon.supporters +import viper.silicon.assumptionAnalysis.AssumptionType import viper.silicon.debugger.DebugExp import viper.silicon.state.terms.{Combine, First, Second, Sort, Term, Unit, sorts} import viper.silicon.state.{MagicWandIdentifier, State, SymbolConverter} @@ -160,7 +161,7 @@ class DefaultSnapshotSupporter(symbolConverter: SymbolConverter) extends Snapsho (snap0, snap1, snap === Combine(snap0, snap1)) } - v.decider.assume(snapshotEq, Option.when(Verifier.config.enableDebugging())(DebugExp.createInstance("Snapshot", true))) + v.decider.assume(snapshotEq, Option.when(Verifier.config.enableDebugging())(DebugExp.createInstance("Snapshot", isInternal_ = true)), AssumptionType.Internal) (snap0, snap1) } diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index 60c9bb5cb..54f8ec4cf 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -261,7 +261,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver case (intermediateResult, Phase1Data(sPre, bcsPre, bcsPreExp, pcsPre, pcsPreExp)) => intermediateResult && executionFlowController.locally(sPre, v)((s1, _) => { decider.setCurrentBranchCondition(And(bcsPre), (BigAnd(bcsPreExp.map(_._1)), Option.when(wExp)(BigAnd(bcsPreExp.map(_._2.get))))) - decider.assume(pcsPre, Option.when(wExp)(DebugExp.createInstance(s"precondition of ${function.name}", pcsPreExp.get)), enforceAssumption = false) + decider.assume(pcsPre, Option.when(wExp)(DebugExp.createInstance(s"precondition of ${function.name}", pcsPreExp.get)), enforceAssumption = false, assumptionType=AssumptionType.Explicit) v.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) eval(s1, body, FunctionNotWellformed(function), v)((s2, tBody, bodyNew, _) => { val debugExp = if (wExp) { @@ -269,7 +269,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver val eNew = ast.EqCmp(ast.Result(function.typ)(), bodyNew.get)(function.pos, function.info, function.errT) Some(DebugExp.createInstance(e, eNew)) } else { None } - decider.assume(BuiltinEquals(data.formalResult, tBody), debugExp) + decider.assume(BuiltinEquals(data.formalResult, tBody), debugExp, AssumptionType.Implicit) consumes(s2, posts, false, postconditionViolated, v, AnalysisInfo(v.decider.assumptionAnalyzer, if(posts.isEmpty) StringAnalysisSourceInfo("no postcondition", ast.NoPosition) else ExpAnalysisSourceInfo(posts.head), AssumptionType.Assertion))((s3, _, _, _) => { recorders :+= s3.functionRecorder Success()})})})} From 2f43a1920d502cc44e4c76d923fba6a5da1fb0b3 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 19 May 2025 10:03:55 +0200 Subject: [PATCH 048/474] label assumes with assumption type --- src/main/scala/decider/Decider.scala | 6 +++--- src/main/scala/rules/Evaluator.scala | 4 ++-- src/main/scala/rules/Executor.scala | 6 +++--- src/main/scala/rules/MagicWandSupporter.scala | 6 +++--- src/main/scala/rules/MoreCompleteExhaleSupporter.scala | 2 +- src/main/scala/rules/PredicateSupporter.scala | 4 ++-- src/main/scala/rules/Producer.scala | 4 ++-- src/main/scala/rules/QuantifiedChunkSupport.scala | 2 +- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 33fce57c7..970890cce 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -63,7 +63,7 @@ trait Decider { def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp], assumptionType: AssumptionType): Unit def assume(t: Term, debugExp: Option[DebugExp], assumptionType: AssumptionType): Unit def assume(terms: Seq[Term], debugExps: Option[Seq[DebugExp]], assumptionType: AssumptionType): Unit - def assumeDefinition(t: Term, debugExp: Option[DebugExp]): Unit + def assumeDefinition(t: Term, debugExp: Option[DebugExp], assumptionType: AssumptionType): Unit def assume(terms: Iterable[Term], debugExp: Option[DebugExp], enforceAssumption: Boolean, assumptionType: AssumptionType): Unit def check(t: Term, timeout: Int): Boolean @@ -306,8 +306,8 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => assume(InsertionOrderedSet(Seq((t, debugExp))), assumptionAnalyzer.currentAnalysisInfo.sourceInfo, enforceAssumption = false, isDefinition = false, assumptionType) } - def assumeDefinition(t: Term, debugExp: Option[DebugExp]): Unit = { - assume(InsertionOrderedSet(Seq((t, debugExp))), assumptionAnalyzer.currentAnalysisInfo.sourceInfo, enforceAssumption=false, isDefinition=true, assumptionType=AssumptionType.Implicit) + def assumeDefinition(t: Term, debugExp: Option[DebugExp], assumptionType: AssumptionType): Unit = { + assume(InsertionOrderedSet(Seq((t, debugExp))), assumptionAnalyzer.currentAnalysisInfo.sourceInfo, enforceAssumption=false, isDefinition=true, assumptionType) } def assume(assumptions: InsertionOrderedSet[(Term, Option[DebugExp])], analysisSourceInfo: AnalysisSourceInfo, enforceAssumption: Boolean, isDefinition: Boolean, assumptionType: AssumptionType): Unit = { diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 8d4b7aab9..45d80e375 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -187,7 +187,7 @@ object evaluator extends EvaluationRules { case _: ast.WildcardPerm => val (tVar, tConstraints, eVar) = v.decider.freshARP() val constraintExp = Option.when(withExp)(DebugExp.createInstance(s"${eVar.get.toString} > none", true)) - v.decider.assumeDefinition(tConstraints, constraintExp) + v.decider.assumeDefinition(tConstraints, constraintExp, AssumptionType.Implicit) /* TODO: Only record wildcards in State.constrainableARPs that are used in exhale * position. Currently, wildcards used in inhale position (only) may not be removed * from State.constrainableARPs (potentially inefficient, but should be sound). @@ -368,7 +368,7 @@ object evaluator extends EvaluationRules { eval(s, e0, pve, v)((s1, t0, e0New, v1) => { val t = v1.decider.appliedFresh("letvar", v1.symbolConverter.toSort(x.typ), s1.relevantQuantifiedVariables.map(_._1)) val debugExp = Option.when(withExp)(DebugExp.createInstance("letvar assignment", InsertionOrderedSet(DebugExp.createInstance(ast.EqCmp(x.localVar, e0)(), ast.EqCmp(x.localVar, e0New.get)())))) - v1.decider.assumeDefinition(BuiltinEquals(t, t0), debugExp) + v1.decider.assumeDefinition(BuiltinEquals(t, t0), debugExp, AssumptionType.Implicit) val newFuncRec = s1.functionRecorder.recordFreshSnapshot(t.applicable.asInstanceOf[Function]) val possibleTriggersBefore = if (s1.recordPossibleTriggers) s1.possibleTriggers else Map.empty eval(s1.copy(g = s1.g + (x.localVar, (t0, e0New)), functionRecorder = newFuncRec), e1, pve, v1)((s2, t2, e1New, v2) => { diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index f6728c1c2..bcaaeab17 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -425,7 +425,7 @@ object executor extends ExecutionRules { val (sm, smValueDef) = quantifiedChunkSupporter.singletonSnapshotMap(s3, field, Seq(tRcvr), tRhs, v2) v1.decider.prover.comment("Definitional axioms for singleton-FVF's value") val debugExp = Option.when(withExp)(DebugExp.createInstance("Definitional axioms for singleton-FVF's value", isInternal_ = true)) - v1.decider.assumeDefinition(smValueDef, debugExp) + v1.decider.assumeDefinition(smValueDef, debugExp, AssumptionType.Internal) val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(Seq(`?r`), Option.when(withExp)(Seq(ast.LocalVarDecl("r", ast.Ref)(ass.pos, ass.info, ass.errT))), field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), sm, s.program, v1) if (s3.heapDependentTriggers.contains(field)) { @@ -478,7 +478,7 @@ object executor extends ExecutionRules { val (sm, smValueDef) = quantifiedChunkSupporter.singletonSnapshotMap(s, field, Seq(tRcvr), snap, v) v.decider.prover.comment("Definitional axioms for singleton-FVF's value") val debugExp = Option.when(withExp)(DebugExp.createInstance("Definitional axioms for singleton-FVF's value", isInternal_ = true)) - v.decider.assumeDefinition(smValueDef, debugExp) + v.decider.assumeDefinition(smValueDef, debugExp, AssumptionType.Internal) quantifiedChunkSupporter.createSingletonQuantifiedChunk(Seq(`?r`), Option.when(withExp)(Seq(ast.LocalVarDecl("r", ast.Ref)(stmt.pos, stmt.info, stmt.errT))), field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), p, pExp, sm, s.program, v) } else { @@ -775,7 +775,7 @@ object executor extends ExecutionRules { } else { (None, None) } - v.decider.assumeDefinition(BuiltinEquals(t, rhs), debugExp) + v.decider.assumeDefinition(BuiltinEquals(t, rhs), debugExp, AssumptionType.Internal) (t, eNew) } } diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 9ecd4d21c..4ef052a68 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -120,7 +120,7 @@ object magicWandSupporter extends SymbolicExecutionRules { abstractLhs, MWSFLookup(mwsf, abstractLhs) === rhsSnapshot, Trigger(MWSFLookup(mwsf, abstractLhs)) - ), Option.when(withExp)(DebugExp.createInstance("Magic wand snapshot definition", true))) + ), Option.when(withExp)(DebugExp.createInstance("Magic wand snapshot definition", isInternal_ = true)), AssumptionType.Internal) magicWandSnapshot } @@ -323,8 +323,8 @@ object magicWandSupporter extends SymbolicExecutionRules { val snapshotTerm = Combine(freshSnapRoot, snapRhs) val (sm, smValueDef) = quantifiedChunkSupporter.singletonSnapshotMap(s5, wand, args, snapshotTerm, v4) v4.decider.prover.comment("Definitional axioms for singleton-SM's value") - val debugExp = Option.when(withExp)(DebugExp.createInstance("Definitional axioms for singleton-SM's value", true)) - v4.decider.assumeDefinition(smValueDef, debugExp) + val debugExp = Option.when(withExp)(DebugExp.createInstance("Definitional axioms for singleton-SM's value", isInternal_ = true)) + v4.decider.assumeDefinition(smValueDef, debugExp, AssumptionType.Internal) val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(formalVars, formalVarExps, wand, args, Option.when(withExp)(bodyVars), FullPerm, Option.when(withExp)(ast.FullPerm()()), sm, s.program, v4) val conservedPcs = s5.conservedPcs.head :+ v4.decider.pcs.after(preMark).definitionsOnly diff --git a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala index 9168ce409..b5a972a15 100644 --- a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala +++ b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala @@ -173,7 +173,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { } val (s1, taggedSnap, snapDefs, permSum, permSumExp) = summariseOnly(s, relevantChunks, resource, args, argsExp, knownValue, v) - v.decider.assumeDefinition(And(snapDefs), Option.when(withExp)(DebugExp.createInstance("Snapshot", true))) + v.decider.assumeDefinition(And(snapDefs), Option.when(withExp)(DebugExp.createInstance("Snapshot", true)), AssumptionType.Internal) // v.decider.assume(PermAtMost(permSum, FullPerm())) /* Done in StateConsolidator instead */ val s2 = diff --git a/src/main/scala/rules/PredicateSupporter.scala b/src/main/scala/rules/PredicateSupporter.scala index 12aacc5c4..ec9ebead5 100644 --- a/src/main/scala/rules/PredicateSupporter.scala +++ b/src/main/scala/rules/PredicateSupporter.scala @@ -88,8 +88,8 @@ object predicateSupporter extends PredicateSupportRules { val (sm, smValueDef) = quantifiedChunkSupporter.singletonSnapshotMap(s2, predicate, tArgs, predSnap, v1) v1.decider.prover.comment("Definitional axioms for singleton-SM's value") - val debugExp = Option.when(withExp)(DebugExp.createInstance("Definitional axioms for singleton-SM's value", true)) - v1.decider.assumeDefinition(smValueDef, debugExp) + val debugExp = Option.when(withExp)(DebugExp.createInstance("Definitional axioms for singleton-SM's value", isInternal_ = true)) + v1.decider.assumeDefinition(smValueDef, debugExp, AssumptionType.Internal) val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk( formalArgs, Option.when(withExp)(predicate.formalArgs), predicate, tArgs, eArgs, tPerm, ePerm, sm, s.program, v1) diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index a977c4ff0..b2413a1a4 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -388,8 +388,8 @@ object producer extends ProductionRules { quantifiedChunkSupporter.singletonSnapshotMap(s1, wand, args, sf(v1.snapshotSupporter.optimalSnapshotSort(wand, s1, v1), v1), v1) v1.decider.prover.comment("Definitional axioms for singleton-SM's value") val definitionalAxiomMark = v1.decider.setPathConditionMark() - val debugExp = Option.when(withExp)(DebugExp.createInstance("Definitional axioms for singleton-SM's value", true)) - v1.decider.assumeDefinition(smValueDef, debugExp) + val debugExp = Option.when(withExp)(DebugExp.createInstance("Definitional axioms for singleton-SM's value", isInternal_ = true)) + v1.decider.assumeDefinition(smValueDef, debugExp, AssumptionType.Internal) val conservedPcs = if (s1.recordPcs) (s1.conservedPcs.head :+ v1.decider.pcs.after(definitionalAxiomMark)) +: s1.conservedPcs.tail else s1.conservedPcs diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 8570aba90..6d4eb3405 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -1106,7 +1106,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val comment = "Definitional axioms for singleton-SM's value" v.decider.prover.comment(comment) val definitionalAxiomMark = v.decider.setPathConditionMark() - v.decider.assumeDefinition(smValueDef, Option.when(withExp)(DebugExp.createInstance(comment, true))) + v.decider.assumeDefinition(smValueDef, Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), AssumptionType.Internal) val conservedPcs = if (s.recordPcs) (s.conservedPcs.head :+ v.decider.pcs.after(definitionalAxiomMark)) +: s.conservedPcs.tail else s.conservedPcs From 8096348d3baddf93b59bdd01fb61b8e1db222bfb Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 19 May 2025 11:05:47 +0200 Subject: [PATCH 049/474] minor fixes --- .../AnalysisSourceInfo.scala | 5 ++- .../AssumptionAnalyzer.scala | 39 +++++++------------ src/main/scala/decider/Decider.scala | 22 +++++------ src/main/scala/rules/ChunkSupporter.scala | 4 +- src/main/scala/rules/Executor.scala | 4 +- .../scala/supporters/MethodSupporter.scala | 2 +- 6 files changed, 30 insertions(+), 46 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala index e431d80dc..fc37c4506 100644 --- a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala +++ b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala @@ -54,6 +54,7 @@ case class StringAnalysisSourceInfo(description: String, position: Position) ext override def getPosition: Position = position } -case class CombinedAnalysisSourceInfo(mainSource: AnalysisSourceInfo, sndSource: AnalysisSourceInfo) extends AnalysisSourceInfo { - override def getPosition: Position = mainSource.getPosition +case class CompositeAnalysisSourceInfo(coarseGrainedSource: AnalysisSourceInfo, fineGrainedSource: AnalysisSourceInfo) extends AnalysisSourceInfo { + override def toString: String = coarseGrainedSource.toString + " (" + super.toString + ")" + override def getPosition: Position = coarseGrainedSource.getPosition } diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index c4c6de261..7d534f8ca 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -10,8 +10,6 @@ import viper.silver.ast._ trait AssumptionAnalyzer { - // def pushScope(stmt: ast.Stmt): Unit - // def closeScope(): Unit def addPermissionInhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] def addPermissionAssertNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Assertion): Option[Int] def addPermissionExhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Assertion): Option[Int] @@ -22,7 +20,7 @@ trait AssumptionAnalyzer { def addAssumptions(assumptions: Iterable[DebugExp], analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Seq[Int] - def createAssertOrCheckNode(term: Term, assertion: Either[String, ast.Exp], analysisInfo: AnalysisInfo, isCheck: Boolean): Option[GeneralAssertionNode] + def createAssertOrCheckNode(term: Term, assertion: Either[String, ast.Exp], analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNodeId: Option[Int]): Unit def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNodeId: Chunk): Unit @@ -34,6 +32,14 @@ trait AssumptionAnalyzer { var currentAnalysisInfo: AnalysisInfo = new NoAnalysisInfo() var currentExpStack: InsertionOrderedSet[ast.Exp] = InsertionOrderedSet.empty + def getFullSourceInfo: AnalysisSourceInfo = { + if(currentExpStack.nonEmpty){ + CompositeAnalysisSourceInfo(currentAnalysisInfo.sourceInfo, ExpAnalysisSourceInfo(currentExpStack.head)) + }else{ + currentAnalysisInfo.sourceInfo + } + } + def setCurrentAnalysisInfo(analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): AnalysisInfo = { currentAnalysisInfo = AnalysisInfo(this, analysisSourceInfo, assumptionType) currentAnalysisInfo @@ -87,23 +93,6 @@ object AssumptionAnalyzer { } class DefaultAssumptionAnalyzer(member: Member) extends AssumptionAnalyzer { - // private var scope : mutable.Set[AssumptionAnalysisNode] = mutable.Set.empty - // private var isScopeOpen: Boolean = false - // private var scopeStmt: ast.Stmt = ??? - - - // override def pushScope(stmt: ast.Stmt): Unit = { - //// scopeStmt = stmt - // scope = mutable.Set.empty - // isScopeOpen = true - // } - // - // override def closeScope(): Unit = { - //// assumptionGraph.addNode(new StatementGroupNode(scopeStmt, scope.toSet)) - // scope = mutable.Set.empty - // isScopeOpen = false - // } - def addNode(node: AssumptionAnalysisNode): Unit = { assumptionGraph.addNode(node) } @@ -122,12 +111,12 @@ class DefaultAssumptionAnalyzer(member: Member) extends AssumptionAnalyzer { newNodes.map(_.id).toSeq } - override def createAssertOrCheckNode(term: Term, assertion: Either[String, ast.Exp], analysisInfo: AnalysisInfo, isCheck: Boolean): Option[GeneralAssertionNode] = { - if(isCheck) return Some(SimpleCheckNode(term, analysisInfo.sourceInfo, analysisInfo.assumptionType)) + override def createAssertOrCheckNode(term: Term, assertion: Either[String, ast.Exp], analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] = { + if(isCheck) return Some(SimpleCheckNode(term, analysisSourceInfo, AssumptionType.Implicit)) Some(assertion match { - case Left(description) => StringAssertionNode(description, analysisInfo.sourceInfo, analysisInfo.assumptionType) - case Right(exp) => SimpleAssertionNode(exp, analysisInfo.sourceInfo, analysisInfo.assumptionType) + case Left(description) => StringAssertionNode(description, analysisSourceInfo, AssumptionType.Explicit) + case Right(exp) => SimpleAssertionNode(exp, analysisSourceInfo, AssumptionType.Explicit) }) } @@ -200,7 +189,7 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { override def addAssumptions(assumptions: Iterable[DebugExp], analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Seq[Int] = Seq.empty - override def createAssertOrCheckNode(term: Term, assertion: Either[String, ast.Exp], analysisInfo: AnalysisInfo, isCheck: Boolean): Option[GeneralAssertionNode] = None + override def createAssertOrCheckNode(term: Term, assertion: Either[String, ast.Exp], analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] = None override def processUnsatCoreAndAddDependencies(dep: String): Unit = { } diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 970890cce..09fefd206 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -102,7 +102,7 @@ trait Decider { def statistics(): Map[String, String] - var assumptionAnalyzer: AssumptionAnalyzer // TODO ake + var assumptionAnalyzer: AssumptionAnalyzer def initAssumptionAnalyzer(member: Member): Unit def removeAssumptionAnalyzer(): Unit } @@ -296,18 +296,18 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp], assumptionType: AssumptionType): Unit = { if (finalExp.isDefined) { - assume(assumptions=InsertionOrderedSet((t, Some(DebugExp.createInstance(e.get, finalExp.get)))), assumptionAnalyzer.currentAnalysisInfo.sourceInfo, enforceAssumption = false, isDefinition = false, assumptionType) + assume(assumptions=InsertionOrderedSet((t, Some(DebugExp.createInstance(e.get, finalExp.get)))), assumptionAnalyzer.getFullSourceInfo, enforceAssumption = false, isDefinition = false, assumptionType) } else { - assume(assumptions=InsertionOrderedSet((t, None)), assumptionAnalyzer.currentAnalysisInfo.sourceInfo, enforceAssumption = false, isDefinition = false, assumptionType) + assume(assumptions=InsertionOrderedSet((t, None)), assumptionAnalyzer.getFullSourceInfo, enforceAssumption = false, isDefinition = false, assumptionType) } } def assume(t: Term, debugExp: Option[DebugExp], assumptionType: AssumptionType): Unit = { - assume(InsertionOrderedSet(Seq((t, debugExp))), assumptionAnalyzer.currentAnalysisInfo.sourceInfo, enforceAssumption = false, isDefinition = false, assumptionType) + assume(InsertionOrderedSet(Seq((t, debugExp))), assumptionAnalyzer.getFullSourceInfo, enforceAssumption = false, isDefinition = false, assumptionType) } def assumeDefinition(t: Term, debugExp: Option[DebugExp], assumptionType: AssumptionType): Unit = { - assume(InsertionOrderedSet(Seq((t, debugExp))), assumptionAnalyzer.currentAnalysisInfo.sourceInfo, enforceAssumption=false, isDefinition=true, assumptionType) + assume(InsertionOrderedSet(Seq((t, debugExp))), assumptionAnalyzer.getFullSourceInfo, enforceAssumption=false, isDefinition=true, assumptionType) } def assume(assumptions: InsertionOrderedSet[(Term, Option[DebugExp])], analysisSourceInfo: AnalysisSourceInfo, enforceAssumption: Boolean, isDefinition: Boolean, assumptionType: AssumptionType): Unit = { @@ -321,7 +321,6 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } val filteredAssumptionsWithLabels = filteredAssumptions map{case (t, de) => - val sourceExp = assumptionAnalyzer.currentExpStack.headOption val assumptionId: Option[Int] = if(de.isDefined) assumptionAnalyzer.addSingleAssumption(de.get, analysisSourceInfo, assumptionType) else None (t, AssumptionAnalyzer.createAssumptionLabel(assumptionId)) } @@ -332,9 +331,8 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } def assume(assumptions: Seq[Term], debugExps: Option[Seq[DebugExp]], assumptionType: AssumptionType): Unit = { - val assumptionIds = if(debugExps.isDefined) assumptionAnalyzer.addAssumptions(debugExps.get, assumptionAnalyzer.currentAnalysisInfo.sourceInfo, assumptionType) else Seq.empty + val assumptionIds = if(debugExps.isDefined) assumptionAnalyzer.addAssumptions(debugExps.get, assumptionAnalyzer.getFullSourceInfo, assumptionType) else Seq.empty - val sourceExp = assumptionAnalyzer.currentExpStack.headOption val assumptionsWithLabels = if(assumptions.size == assumptionIds.size) assumptions.zip(assumptionIds).map{case (t, id) => (t, AssumptionAnalyzer.createAssumptionLabel(Some(id)))} else assumptions map (t => (t, "")) @@ -355,8 +353,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => if (debugMode) { addDebugExp(debugExp.get.withTerm(And(filteredTerms))) - val sourceExp = assumptionAnalyzer.currentExpStack.headOption - val assumptionId: Option[Int] = if(debugExp.isDefined) assumptionAnalyzer.addSingleAssumption(debugExp.get.withTerm(And(filteredTerms)), assumptionAnalyzer.currentAnalysisInfo.sourceInfo, assumptionType) else None + val assumptionId: Option[Int] = if(debugExp.isDefined) assumptionAnalyzer.addSingleAssumption(debugExp.get.withTerm(And(filteredTerms)), assumptionAnalyzer.getFullSourceInfo, assumptionType) else None val termsWithLabel = filteredTerms.zipWithIndex.iterator.map {case (t, idx) => (t, AssumptionAnalyzer.createAssumptionLabel(assumptionId, idx))}.toSeq assumeWithoutSmokeChecks(InsertionOrderedSet(termsWithLabel)) }else{ @@ -401,7 +398,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } def check(t: Term, timeout: Int): Boolean = { - deciderAssert(t, Left("check " + t.toString), Some(timeout), isCheck=true) + deciderAssert(t, Left(""), Some(timeout), isCheck=true) } @@ -433,8 +430,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => val sepIdentifier = symbExLog.openScope(assertRecord) val asserted = if(Verifier.config.enableAssumptionAnalysis()) t.equals(True) else isKnownToBeTrue(t) - val sourceExp = assumptionAnalyzer.currentExpStack.headOption - val assertNode = if(!asserted) assumptionAnalyzer.createAssertOrCheckNode(t, e, decider.assumptionAnalyzer.currentAnalysisInfo, isCheck) else None + val assertNode = if(!asserted) assumptionAnalyzer.createAssertOrCheckNode(t, e, decider.assumptionAnalyzer.getFullSourceInfo, isCheck) else None val result = asserted || proverAssert(t, timeout, AssumptionAnalyzer.createAssertionLabel(assertNode map (_.id))) diff --git a/src/main/scala/rules/ChunkSupporter.scala b/src/main/scala/rules/ChunkSupporter.scala index a4a27f9f5..072d403a7 100644 --- a/src/main/scala/rules/ChunkSupporter.scala +++ b/src/main/scala/rules/ChunkSupporter.scala @@ -178,7 +178,7 @@ object chunkSupporter extends ChunkSupportRules { case Some(ch) => if (s.assertReadAccessOnly) { if (v.decider.check(Implies(IsPositive(perms), IsPositive(ch.perm)), Verifier.config.assertTimeout.getOrElse(0))) { - v.decider.assumptionAnalyzer.addPermissionAssertNode(ch, ch.permExp.map(pe => IsPositive(pe)(pe.pos, pe.info, pe.errT)), v.decider.assumptionAnalyzer.currentAnalysisInfo.sourceInfo) + v.decider.assumptionAnalyzer.addPermissionAssertNode(ch, ch.permExp.map(pe => IsPositive(pe)(pe.pos, pe.info, pe.errT)), v.decider.assumptionAnalyzer.getFullSourceInfo) (Complete(), s, h, Some(ch)) } else { (Incomplete(perms, permsExp), s, h, None) @@ -262,7 +262,7 @@ object chunkSupporter extends ChunkSupportRules { val findRes = findChunk[NonQuantifiedChunk](h.values, id, args, v) findRes match { case Some(ch) if v.decider.check(IsPositive(ch.perm), Verifier.config.checkTimeout()) => - v.decider.assumptionAnalyzer.addPermissionAssertNode(ch, ch.permExp.map(pe => IsPositive(pe)(pe.pos, pe.info, pe.errT)), v.decider.assumptionAnalyzer.currentAnalysisInfo.sourceInfo) + v.decider.assumptionAnalyzer.addPermissionAssertNode(ch, ch.permExp.map(pe => IsPositive(pe)(pe.pos, pe.info, pe.errT)), v.decider.assumptionAnalyzer.getFullSourceInfo) Q(s, ch.snap, v) case _ if v.decider.checkSmoke(true) => if (s.isInPackage) { diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index bcaaeab17..b76cded6b 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -67,13 +67,11 @@ object executor extends ExecutionRules { case ce: cfg.ConditionalEdge[ast.Stmt, ast.Exp] => val condEdgeRecord = new ConditionalEdgeRecord(ce.condition, s, v.decider.pcs) val sepIdentifier = v.symbExLog.openScope(condEdgeRecord) - v.decider.assumptionAnalyzer.updateCurrentAnalysisInfo(ExpAnalysisSourceInfo(ce.condition), AssumptionType.PathCondition) val s1 = handleOutEdge(s, edge, v) eval(s1, ce.condition, IfFailed(ce.condition), v)((s2, tCond, condNew, v1) => { /* Using branch(...) here ensures that the edge condition is recorded * as a branch condition on the pathcondition stack. */ - v.decider.assumptionAnalyzer.updateCurrentAnalysisInfo(AssumptionType.Implicit) brancher.branch(s2.copy(parallelizeBranches = false), tCond, (ce.condition, condNew), v1)( (s3, v3) => exec(s3.copy(parallelizeBranches = s2.parallelizeBranches), ce.target, ce.kind, v3, joinPoint)((s4, v4) => { @@ -265,7 +263,7 @@ object executor extends ExecutionRules { (executionFlowController.locally(sBody, v)((s0, v0) => { v0.decider.prover.comment("Loop head block: Check well-definedness of invariant") val mark = v0.decider.setPathConditionMark() - v.decider.assumptionAnalyzer.setCurrentAnalysisInfo(if(invs.isEmpty) StringAnalysisSourceInfo("invariants", ast.NoPosition) else ExpAnalysisSourceInfo(invs.head), AssumptionType.Explicit) + v.decider.assumptionAnalyzer.setCurrentAnalysisInfo(if(invs.isEmpty) StringAnalysisSourceInfo("invariants", ast.NoPosition) else ExpAnalysisSourceInfo(invs.head), AssumptionType.LoopInvariant) produces(s0, freshSnap, invs, ContractNotWellformed, v0)((s1, v1) => { phase1data = phase1data :+ (s1, v1.decider.pcs.after(mark), diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index 03b84d676..ae91b5eaa 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -109,7 +109,7 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif val s4 = s3.copy(h = Heap()) val impLog = new WellformednessCheckRecord(posts, s, v.decider.pcs) val sepIdentifier = symbExLog.openScope(impLog) - v3.decider.assumptionAnalyzer.setCurrentAnalysisInfo(if(posts.isEmpty) StringAnalysisSourceInfo("no postcondition", ast.NoPosition) else ExpAnalysisSourceInfo(posts.head), AssumptionType.Implicit) + v3.decider.assumptionAnalyzer.setCurrentAnalysisInfo(if(posts.isEmpty) StringAnalysisSourceInfo("no postcondition", ast.NoPosition) else ExpAnalysisSourceInfo(posts.head), AssumptionType.Internal) produces(s4, freshSnap, posts, ContractNotWellformed, v3)((_, _) => { v3.decider.assumptionAnalyzer.clearCurrentAnalysisInfo() symbExLog.closeScope(sepIdentifier) From 1156d2b7fff54fe0a17d8576dcdd34b00173de21 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 19 May 2025 11:48:37 +0200 Subject: [PATCH 050/474] add assumption type argument to produce functions --- .../AssumptionAnalyzer.scala | 21 ++--- src/main/scala/rules/ChunkSupporter.scala | 6 +- src/main/scala/rules/Executor.scala | 39 ++++---- src/main/scala/rules/HavocSupporter.scala | 6 +- src/main/scala/rules/MagicWandSupporter.scala | 27 +++--- src/main/scala/rules/PredicateSupporter.scala | 12 +-- src/main/scala/rules/Producer.scala | 93 +++++++++++-------- .../scala/rules/QuantifiedChunkSupport.scala | 48 ++++++---- .../scala/supporters/MethodSupporter.scala | 14 ++- .../PredicateVerificationUnit.scala | 3 +- .../functions/FunctionVerificationUnit.scala | 4 +- 11 files changed, 143 insertions(+), 130 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 7d534f8ca..34c94fc08 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -29,9 +29,13 @@ trait AssumptionAnalyzer { val assumptionGraph: AssumptionAnalysisGraph = new DefaultAssumptionAnalysisGraph() - var currentAnalysisInfo: AnalysisInfo = new NoAnalysisInfo() + private var currentAnalysisInfo: AnalysisInfo = new NoAnalysisInfo() var currentExpStack: InsertionOrderedSet[ast.Exp] = InsertionOrderedSet.empty + def getAnalysisInfo: AnalysisInfo = currentAnalysisInfo + + def getAnalysisInfo(assumptionType: AssumptionType): AnalysisInfo = AnalysisInfo(this, currentAnalysisInfo.sourceInfo, assumptionType) + def getFullSourceInfo: AnalysisSourceInfo = { if(currentExpStack.nonEmpty){ CompositeAnalysisSourceInfo(currentAnalysisInfo.sourceInfo, ExpAnalysisSourceInfo(currentExpStack.head)) @@ -45,21 +49,8 @@ trait AssumptionAnalyzer { currentAnalysisInfo } - def clearCurrentAnalysisInfo(): AnalysisInfo = { + def clearCurrentAnalysisInfo(): Unit = { currentAnalysisInfo = new NoAnalysisInfo() - currentAnalysisInfo - } - - def updateCurrentAnalysisInfo(at: AssumptionType): AnalysisInfo = { - setCurrentAnalysisInfo(currentAnalysisInfo.sourceInfo, at) - } - - def updateCurrentAnalysisInfo(si: AnalysisSourceInfo): AnalysisInfo = { - setCurrentAnalysisInfo(si, currentAnalysisInfo.assumptionType) - } - - def updateCurrentAnalysisInfo(si: AnalysisSourceInfo, at: AssumptionType): AnalysisInfo = { - setCurrentAnalysisInfo(si, at) } def getMember: Option[Member] diff --git a/src/main/scala/rules/ChunkSupporter.scala b/src/main/scala/rules/ChunkSupporter.scala index 072d403a7..dee2b98ce 100644 --- a/src/main/scala/rules/ChunkSupporter.scala +++ b/src/main/scala/rules/ChunkSupporter.scala @@ -82,7 +82,7 @@ object chunkSupporter extends ChunkSupportRules { (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { - val analysisInfo = v.decider.assumptionAnalyzer.currentAnalysisInfo + val analysisInfo = v.decider.assumptionAnalyzer.getAnalysisInfo consume2(s, h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, analysisInfo)((s2, h2, optSnap, chunk, v2) => optSnap match { case Some(snap) => @@ -116,7 +116,7 @@ object chunkSupporter extends ChunkSupportRules { (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { - val analysisInfo = v.decider.assumptionAnalyzer.currentAnalysisInfo + val analysisInfo = v.decider.assumptionAnalyzer.getAnalysisInfo val id = ChunkIdentifier(resource, s.program) if (s.exhaleExt) { val failure = createFailure(ve, v, s, "chunk consume in package") @@ -164,7 +164,7 @@ object chunkSupporter extends ChunkSupportRules { v: Verifier, analysisInfo0: AnalysisInfo) : (ConsumptionResult, State, Heap, Option[NonQuantifiedChunk]) = { - val analysisInfo = v.decider.assumptionAnalyzer.currentAnalysisInfo + val analysisInfo = v.decider.assumptionAnalyzer.getAnalysisInfo val consumeExact = terms.utils.consumeExactRead(perms, s.constrainableARPs) def assumeProperties(chunk: NonQuantifiedChunk, heap: Heap): Unit = { diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index b76cded6b..216d213db 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -75,7 +75,6 @@ object executor extends ExecutionRules { brancher.branch(s2.copy(parallelizeBranches = false), tCond, (ce.condition, condNew), v1)( (s3, v3) => exec(s3.copy(parallelizeBranches = s2.parallelizeBranches), ce.target, ce.kind, v3, joinPoint)((s4, v4) => { - v.decider.assumptionAnalyzer.clearCurrentAnalysisInfo() v4.symbExLog.closeScope(sepIdentifier) Q(s4, v4) }), @@ -263,8 +262,7 @@ object executor extends ExecutionRules { (executionFlowController.locally(sBody, v)((s0, v0) => { v0.decider.prover.comment("Loop head block: Check well-definedness of invariant") val mark = v0.decider.setPathConditionMark() - v.decider.assumptionAnalyzer.setCurrentAnalysisInfo(if(invs.isEmpty) StringAnalysisSourceInfo("invariants", ast.NoPosition) else ExpAnalysisSourceInfo(invs.head), AssumptionType.LoopInvariant) - produces(s0, freshSnap, invs, ContractNotWellformed, v0)((s1, v1) => { + produces(s0, freshSnap, invs, ContractNotWellformed, v0, AssumptionType.LoopInvariant)((s1, v1) => { // TODO ake: set source phase1data = phase1data :+ (s1, v1.decider.pcs.after(mark), v1.decider.freshFunctions /* [BRANCH-PARALLELISATION] */) @@ -272,8 +270,8 @@ object executor extends ExecutionRules { })}) combine executionFlowController.locally(s, v)((s0, v0) => { v0.decider.prover.comment("Loop head block: Establish invariant") - v0.decider.assumptionAnalyzer.setCurrentAnalysisInfo(if(invs.isEmpty) StringAnalysisSourceInfo("no invariants", ast.NoPosition) else ExpAnalysisSourceInfo(invs.head), AssumptionType.Assertion) - consumes(s0, invs, false, LoopInvariantNotEstablished, v0, v0.decider.assumptionAnalyzer.currentAnalysisInfo)((sLeftover, _, consumedChunks, v1) => { // TODO ake: add edges from consumedChunks + // TODO ake: set source + consumes(s0, invs, false, LoopInvariantNotEstablished, v0, v0.decider.assumptionAnalyzer.getAnalysisInfo)((sLeftover, _, consumedChunks, v1) => { // TODO ake: add edges from consumedChunks v1.decider.prover.comment("Loop head block: Execute statements of loop head block (in invariant state)") phase1data.foldLeft(Success(): VerificationResult) { case (result, _) if !result.continueVerification => result @@ -307,8 +305,8 @@ object executor extends ExecutionRules { * attempting to re-establish the invariant. */ v.decider.prover.comment("Loop head block: Re-establish invariant") - v.decider.assumptionAnalyzer.setCurrentAnalysisInfo(if(invs.isEmpty) StringAnalysisSourceInfo("no invariants", ast.NoPosition) else ExpAnalysisSourceInfo(invs.head), AssumptionType.Assertion) - consumes(s, invs, false, e => LoopInvariantNotPreserved(e), v, v.decider.assumptionAnalyzer.currentAnalysisInfo)((_, _, _, _) => + // TODO ake: set source + consumes(s, invs, false, e => LoopInvariantNotPreserved(e), v, v.decider.assumptionAnalyzer.getAnalysisInfo)((_, _, _, _) => Success()) } } @@ -425,7 +423,7 @@ object executor extends ExecutionRules { val debugExp = Option.when(withExp)(DebugExp.createInstance("Definitional axioms for singleton-FVF's value", isInternal_ = true)) v1.decider.assumeDefinition(smValueDef, debugExp, AssumptionType.Internal) val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(Seq(`?r`), Option.when(withExp)(Seq(ast.LocalVarDecl("r", ast.Ref)(ass.pos, ass.info, ass.errT))), - field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), sm, s.program, v1) + field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), sm, s.program, v1, AssumptionType.Implicit) // TODO ake: or internal? if (s3.heapDependentTriggers.contains(field)) { val debugExp2 = Option.when(withExp)(DebugExp.createInstance(s"FieldTrigger(${eRcvrNew.toString()}.${field.name})")) v1.decider.assume(FieldTrigger(field.name, sm, tRcvr), debugExp2, AssumptionType.Internal) @@ -478,7 +476,7 @@ object executor extends ExecutionRules { val debugExp = Option.when(withExp)(DebugExp.createInstance("Definitional axioms for singleton-FVF's value", isInternal_ = true)) v.decider.assumeDefinition(smValueDef, debugExp, AssumptionType.Internal) quantifiedChunkSupporter.createSingletonQuantifiedChunk(Seq(`?r`), Option.when(withExp)(Seq(ast.LocalVarDecl("r", ast.Ref)(stmt.pos, stmt.info, stmt.errT))), - field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), p, pExp, sm, s.program, v) + field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), p, pExp, sm, s.program, v, AssumptionType.Implicit) } else { val newChunk = BasicChunk(FieldID, BasicChunkIdentifier(field.name), Seq(tRcvr), Option.when(withExp)(Seq(x)), snap, snapExp, p, pExp, AnalysisInfo(v.decider.assumptionAnalyzer, ExpAnalysisSourceInfo(ast.FieldAccess(x, field)(field.pos, field.info, field.errT)), AssumptionType.Explicit)) @@ -498,8 +496,7 @@ object executor extends ExecutionRules { /* We're done */ Success() case _ => - v.decider.assumptionAnalyzer.updateCurrentAnalysisInfo(AssumptionType.Explicit) - produce(s, freshSnap, a, InhaleFailed(inhale), v)((s1, v1) => { + produce(s, freshSnap, a, InhaleFailed(inhale), v, AssumptionType.Explicit)((s1, v1) => { v1.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterInhale) Q(s1, v1)}) } @@ -507,7 +504,7 @@ object executor extends ExecutionRules { case exhale @ ast.Exhale(a) => val pve = ExhaleFailed(exhale) - consume(s, a, false, pve, v, v.decider.assumptionAnalyzer.currentAnalysisInfo)((s1, _, consumedChunks, v1) => + consume(s, a, false, pve, v, v.decider.assumptionAnalyzer.getAnalysisInfo)((s1, _, consumedChunks, v1) => Q(s1, v1)) case assert @ ast.Assert(a: ast.FalseLit) if !s.isInPackage => @@ -521,7 +518,7 @@ object executor extends ExecutionRules { case assert @ ast.Assert(a) if Verifier.config.disableSubsumption() => val r = - consume(s, a, false, AssertFailed(assert), v, v.decider.assumptionAnalyzer.currentAnalysisInfo)((_, _, _, _) => + consume(s, a, false, AssertFailed(assert), v, v.decider.assumptionAnalyzer.getAnalysisInfo)((_, _, _, _) => Success()) r combine Q(s, v) @@ -537,11 +534,11 @@ object executor extends ExecutionRules { * hUsed (reserveHeaps.head) instead of consuming them. hUsed is later discarded and replaced * by s.h. By copying hUsed to s.h the contained permissions remain available inside the wand. */ - consume(s, a, false, pve, v, v.decider.assumptionAnalyzer.currentAnalysisInfo)((s2, _, _, v1) => { + consume(s, a, false, pve, v, v.decider.assumptionAnalyzer.getAnalysisInfo)((s2, _, _, v1) => { Q(s2.copy(h = s2.reserveHeaps.head), v1) }) } else - consume(s, a, false, pve, v, v.decider.assumptionAnalyzer.currentAnalysisInfo)((s1, _, _, v1) => { + consume(s, a, false, pve, v, v.decider.assumptionAnalyzer.getAnalysisInfo)((s1, _, _, v1) => { val s2 = s1.copy(h = s.h, reserveHeaps = s.reserveHeaps) Q(s2, v1)}) @@ -550,7 +547,7 @@ object executor extends ExecutionRules { case ast.MethodCall(methodName, _, _) if !Verifier.config.disableHavocHack407() && methodName.startsWith(hack407_method_name_prefix) => - val analysisInfo = v.decider.assumptionAnalyzer.updateCurrentAnalysisInfo(AssumptionType.Explicit) + val analysisInfo = v.decider.assumptionAnalyzer.getAnalysisInfo(AssumptionType.Explicit) val resourceName = methodName.stripPrefix(hack407_method_name_prefix) val member = s.program.collectFirst { case m: ast.Field if m.name == resourceName => m @@ -570,8 +567,7 @@ object executor extends ExecutionRules { // Calling hack510() triggers a state consolidation. // See also Silicon issue #510. case ast.MethodCall(`hack510_method_name`, _, _) => - v.decider.assumptionAnalyzer.updateCurrentAnalysisInfo(AssumptionType.Explicit) - val s1 = v.stateConsolidator(s).consolidate(s, v) + val s1 = v.stateConsolidator(s).consolidate(s, v) // TODO ake: AssumptionType.Explicit Q(s1, v) case call @ ast.MethodCall(methodName, eArgs, lhs) => @@ -601,15 +597,14 @@ object executor extends ExecutionRules { tArgs zip Seq.fill(tArgs.size)(None) val s2 = s1.copy(g = Store(fargs.zip(argsWithExp)), recordVisited = true) - consumes(s2, meth.pres, false, _ => pvePre, v1, v1.decider.assumptionAnalyzer.currentAnalysisInfo)((s3, _, consumedChunks, v2) => { // TODO ake: add edges from consumedChunks + consumes(s2, meth.pres, false, _ => pvePre, v1, v1.decider.assumptionAnalyzer.getAnalysisInfo)((s3, _, consumedChunks, v2) => { // TODO ake: add edges from consumedChunks v2.symbExLog.closeScope(preCondId) val postCondLog = new CommentRecord("Postcondition", s3, v2.decider.pcs) val postCondId = v2.symbExLog.openScope(postCondLog) val outs = meth.formalReturns.map(_.localVar) val gOuts = Store(outs.map(x => (x, v2.decider.fresh(x))).toMap) val s4 = s3.copy(g = s3.g + gOuts, oldHeaps = s3.oldHeaps + (Verifier.PRE_STATE_LABEL -> magicWandSupporter.getEvalHeap(s1))) - v2.decider.assumptionAnalyzer.updateCurrentAnalysisInfo(AssumptionType.Explicit) - produces(s4, freshSnap, meth.posts, _ => pveCallTransformed, v2)((s5, v3) => { + produces(s4, freshSnap, meth.posts, _ => pveCallTransformed, v2, AssumptionType.Explicit)((s5, v3) => { v3.symbExLog.closeScope(postCondId) v3.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) val gLhs = Store(lhs.zip(outs) @@ -630,7 +625,7 @@ object executor extends ExecutionRules { eval(s1, ePerm, pve, v1)((s2, tPerm, ePermNew, v2) => permissionSupporter.assertPositive(s2, tPerm, if (withExp) ePermNew.get else ePerm, pve, v2)((s3, v3) => { val wildcards = s3.constrainableARPs -- s1.constrainableARPs - predicateSupporter.fold(s3, predicate, tArgs, eArgsNew, tPerm, ePermNew, wildcards, pve, v3, v3.decider.assumptionAnalyzer.currentAnalysisInfo)((s4, v4) => { + predicateSupporter.fold(s3, predicate, tArgs, eArgsNew, tPerm, ePermNew, wildcards, pve, v3, v3.decider.assumptionAnalyzer.getAnalysisInfo)((s4, v4) => { v3.decider.finishDebugSubExp(s"folded ${predAcc.toString}") Q(s4, v4) } diff --git a/src/main/scala/rules/HavocSupporter.scala b/src/main/scala/rules/HavocSupporter.scala index 357e6f3f1..ef29feb17 100644 --- a/src/main/scala/rules/HavocSupporter.scala +++ b/src/main/scala/rules/HavocSupporter.scala @@ -193,12 +193,12 @@ object havocSupporter extends SymbolicExecutionRules { val havockedSnap = v.decider.fresh("mwsf", sorts.MagicWandSnapFunction, Option.when(withExp)(PUnknown())) val cond = replacementCond(lhs, ch.args, condInfo) val magicWandSnapshot = MagicWandSnapshot(Ite(cond, havockedSnap, ch.snap.mwsf)) - NonQuantifiedChunk.withSnap(ch, magicWandSnapshot, None, v.decider.assumptionAnalyzer.currentAnalysisInfo) + NonQuantifiedChunk.withSnap(ch, magicWandSnapshot, None, v.decider.assumptionAnalyzer.getAnalysisInfo) case ch => val havockedSnap = freshSnap(ch.snap.sort, v) val cond = replacementCond(lhs, ch.args, condInfo) - NonQuantifiedChunk.withSnap(ch, Ite(cond, havockedSnap, ch.snap), None, v.decider.assumptionAnalyzer.currentAnalysisInfo) + NonQuantifiedChunk.withSnap(ch, Ite(cond, havockedSnap, ch.snap), None, v.decider.assumptionAnalyzer.getAnalysisInfo) } otherChunks ++ newChunks } @@ -281,7 +281,7 @@ object havocSupporter extends SymbolicExecutionRules { val debugExp = Option.when(withExp)(DebugExp.createInstance("havoc new axiom", isInternal_ = true)) v.decider.assume(newAxiom, debugExp, AssumptionType.Unknown) - QuantifiedChunk.withSnapshotMap(ch, newSm, v.decider.assumptionAnalyzer.currentAnalysisInfo) + QuantifiedChunk.withSnapshotMap(ch, newSm, v.decider.assumptionAnalyzer.getAnalysisInfo) } newChunks ++ otherChunks } diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 4ef052a68..3a33ba936 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -8,6 +8,7 @@ package viper.silicon.rules import viper.silicon.debugger.DebugExp import viper.silicon._ +import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType, ExpAnalysisSourceInfo, PermissionInhaleNode} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.decider.RecordedPathConditions @@ -90,12 +91,13 @@ object magicWandSupporter extends SymbolicExecutionRules { wand: ast.MagicWand, snap: MagicWandSnapshot, pve: PartialVerificationError, - v: Verifier) + v: Verifier, + assumptionType: AssumptionType) (Q: (State, MagicWandChunk, Verifier) => VerificationResult) : VerificationResult = { evaluateWandArguments(s, wand, pve, v)((s1, ts, esNew, v1) => { val newChunk = MagicWandChunk(MagicWandIdentifier(wand, s.program), s1.g.values, ts, esNew, snap, FullPerm, - Option.when(withExp)(ast.FullPerm()(wand.pos, wand.info, wand.errT)), v.decider.assumptionAnalyzer.currentAnalysisInfo) + Option.when(withExp)(ast.FullPerm()(wand.pos, wand.info, wand.errT)), v.decider.assumptionAnalyzer.getAnalysisInfo(assumptionType)) Q(s1, newChunk, v1) }) } @@ -306,7 +308,8 @@ object magicWandSupporter extends SymbolicExecutionRules { def createWandChunkAndRecordResults(s4: State, freshSnapRoot: Var, snapRhs: Term, - v3: Verifier) + v3: Verifier, + assumptionType: AssumptionType) : VerificationResult = { val preMark = v3.decider.setPathConditionMark() @@ -326,13 +329,13 @@ object magicWandSupporter extends SymbolicExecutionRules { val debugExp = Option.when(withExp)(DebugExp.createInstance("Definitional axioms for singleton-SM's value", isInternal_ = true)) v4.decider.assumeDefinition(smValueDef, debugExp, AssumptionType.Internal) val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(formalVars, formalVarExps, wand, args, - Option.when(withExp)(bodyVars), FullPerm, Option.when(withExp)(ast.FullPerm()()), sm, s.program, v4) + Option.when(withExp)(bodyVars), FullPerm, Option.when(withExp)(ast.FullPerm()()), sm, s.program, v4, assumptionType) val conservedPcs = s5.conservedPcs.head :+ v4.decider.pcs.after(preMark).definitionsOnly appendToResults(s5, ch, v4.decider.pcs.after(preMark), (conservedPcs.flatMap(_.conditionalized), Option.when(withExp)(conservedPcs.flatMap(_.conditionalizedExp))), v4) Success() }) } else { - this.createChunk(s4, wand, wandSnapshot, pve, v3)((s5, ch, v4) => { + this.createChunk(s4, wand, wandSnapshot, pve, v3, assumptionType)((s5, ch, v4) => { val conservedPcs = s5.conservedPcs.head :+ v4.decider.pcs.after(preMark).definitionsOnly // Partition path conditions into a set which include the freshSnapRoot and those which do not val (pcsWithFreshSnapRoot, pcsWithoutFreshSnapRoot) = conservedPcs.flatMap(pcs => pcs.conditionalized).partition(_.contains(freshSnapRoot)) @@ -365,7 +368,7 @@ object magicWandSupporter extends SymbolicExecutionRules { val freshSnapRoot = freshSnap(sorts.Snap, v1) // Produce the wand's LHS. - produce(s1.copy(conservingSnapshotGeneration = true), toSf(freshSnapRoot), wand.left, pve, v1)((sLhs, v2) => { + produce(s1.copy(conservingSnapshotGeneration = true), toSf(freshSnapRoot), wand.left, pve, v1, AssumptionType.Explicit)((sLhs, v2) => { val proofScriptCfg = proofScript.toCfg() /* Expected shape of reserveHeaps is either @@ -400,10 +403,10 @@ object magicWandSupporter extends SymbolicExecutionRules { // This part indirectly calls the methods `this.transfer` and `this.consumeFromMultipleHeaps`. consume( proofScriptState.copy(oldHeaps = s2.oldHeaps, reserveCfgs = proofScriptState.reserveCfgs.tail), - wand.right, true, pve, proofScriptVerifier, proofScriptVerifier.decider.assumptionAnalyzer.currentAnalysisInfo + wand.right, true, pve, proofScriptVerifier, proofScriptVerifier.decider.assumptionAnalyzer.getAnalysisInfo )((s3, snapRhs, consumedChunks, v3) => { // TODO ake: what to do with consumedChunks? - createWandChunkAndRecordResults(s3.copy(exhaleExt = false, oldHeaps = s.oldHeaps), freshSnapRoot, snapRhs.get, v3) + createWandChunkAndRecordResults(s3.copy(exhaleExt = false, oldHeaps = s.oldHeaps), freshSnapRoot, snapRhs.get, v3, AssumptionType.Explicit) }) }) }) @@ -414,7 +417,7 @@ object magicWandSupporter extends SymbolicExecutionRules { // and thus, that no wand chunk was created. In order to continue, we create one now. // Moreover, we need to set reserveHeaps to structurally match [State RHS] below. val s1 = sEmp.copy(reserveHeaps = Heap() +: Heap() +: Heap() +: s.reserveHeaps.tail) - createWandChunkAndRecordResults(s1, freshSnap(sorts.Snap, v), freshSnap(sorts.Snap, v), v) + createWandChunkAndRecordResults(s1, freshSnap(sorts.Snap, v), freshSnap(sorts.Snap, v), v, AssumptionType.Explicit) } recordedBranches.foldLeft(tempResult)((prevRes, recordedState) => { @@ -459,9 +462,9 @@ object magicWandSupporter extends SymbolicExecutionRules { (Q: (State, Verifier) => VerificationResult) : VerificationResult = { // Consume the magic wand instance "A --* B". - consume(s, wand, true, pve, v, v.decider.assumptionAnalyzer.currentAnalysisInfo)((s1, snapWand, consumedChunksWand, v1) => { + consume(s, wand, true, pve, v, v.decider.assumptionAnalyzer.getAnalysisInfo)((s1, snapWand, consumedChunksWand, v1) => { // Consume the wand's LHS "A". - consume(s1, wand.left, true, pve, v1, v1.decider.assumptionAnalyzer.currentAnalysisInfo)((s2, snapLhs, consumedChunksLeft, v2) => { + consume(s1, wand.left, true, pve, v1, v1.decider.assumptionAnalyzer.getAnalysisInfo)((s2, snapLhs, consumedChunksLeft, v2) => { /* It is assumed that snap and MagicWandSnapshot.abstractLhs are structurally the same. * Equating the two snapshots is sound iff a wand is applied only once. * The old solution in this case did use this assumption: @@ -484,7 +487,7 @@ object magicWandSupporter extends SymbolicExecutionRules { } // Produce the wand's RHS. - produce(s3.copy(conservingSnapshotGeneration = true), toSf(magicWandSnapshotLookup), wand.right, pve, v2)((s4, v3) => { // TODO ake: add edges from consumedChunks + produce(s3.copy(conservingSnapshotGeneration = true), toSf(magicWandSnapshotLookup), wand.right, pve, v2, AssumptionType.Explicit)((s4, v3) => { // TODO ake: add edges from consumedChunks, explicit assumption? // Recreate old state without the magic wand, and the state with the oldHeap called lhs. val s5 = s4.copy(g = s1.g, conservingSnapshotGeneration = s3.conservingSnapshotGeneration) diff --git a/src/main/scala/rules/PredicateSupporter.scala b/src/main/scala/rules/PredicateSupporter.scala index ec9ebead5..f891625b8 100644 --- a/src/main/scala/rules/PredicateSupporter.scala +++ b/src/main/scala/rules/PredicateSupporter.scala @@ -92,7 +92,7 @@ object predicateSupporter extends PredicateSupportRules { v1.decider.assumeDefinition(smValueDef, debugExp, AssumptionType.Internal) val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk( - formalArgs, Option.when(withExp)(predicate.formalArgs), predicate, tArgs, eArgs, tPerm, ePerm, sm, s.program, v1) + formalArgs, Option.when(withExp)(predicate.formalArgs), predicate, tArgs, eArgs, tPerm, ePerm, sm, s.program, v1, AssumptionType.Unknown) val h3 = s2.h + ch val smDef = SnapshotMapDefinition(predicate, sm, Seq(smValueDef), Seq()) val smCache = if (s2.heapDependentTriggers.contains(predicate)) { @@ -117,7 +117,7 @@ object predicateSupporter extends PredicateSupportRules { functionRecorder = s2.functionRecorder.recordFvfAndDomain(smDef)) Q(s3, v1) } else { - val ch = BasicChunk(PredicateID, BasicChunkIdentifier(predicate.name), tArgs, eArgs, snap.get.convert(sorts.Snap), None, tPerm, ePerm, v1.decider.assumptionAnalyzer.currentAnalysisInfo) + val ch = BasicChunk(PredicateID, BasicChunkIdentifier(predicate.name), tArgs, eArgs, snap.get.convert(sorts.Snap), None, tPerm, ePerm, v1.decider.assumptionAnalyzer.getAnalysisInfo) val s3 = s2.copy(g = s.g, smDomainNeeded = s.smDomainNeeded, permissionScalingFactor = s.permissionScalingFactor, @@ -163,11 +163,11 @@ object predicateSupporter extends PredicateSupportRules { None, pve, v, - v.decider.assumptionAnalyzer.currentAnalysisInfo + v.decider.assumptionAnalyzer.getAnalysisInfo )((s2, h2, snap, consumedChunks, v1) => { val s3 = s2.copy(g = gIns, h = h2) .setConstrainable(constrainableWildcards, false) - produce(s3, toSf(snap.get), body, pve, v1)((s4, v2) => { // TODO ake: add edge from consumedChunks to new assumptions + produce(s3, toSf(snap.get), body, pve, v1, AssumptionType.Explicit)((s4, v2) => { // TODO ake: add edge from consumedChunks to new assumptions, explicit assumption? v2.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterUnfold) if (!Verifier.config.disableFunctionUnfoldTrigger()) { val predicateTrigger = @@ -184,10 +184,10 @@ object predicateSupporter extends PredicateSupportRules { } else { val ve = pve dueTo InsufficientPermission(pa) val description = s"consume ${pa.pos}: $pa" - chunkSupporter.consume(s1, s1.h, predicate, tArgs, eArgs, s1.permissionScalingFactor, s1.permissionScalingFactorExp, true, ve, v, description, v.decider.assumptionAnalyzer.currentAnalysisInfo)((s2, h1, snap, consumedChunks, v1) => { // TODO ake: add edges + chunkSupporter.consume(s1, s1.h, predicate, tArgs, eArgs, s1.permissionScalingFactor, s1.permissionScalingFactorExp, true, ve, v, description, v.decider.assumptionAnalyzer.getAnalysisInfo)((s2, h1, snap, consumedChunks, v1) => { // TODO ake: add edges val s3 = s2.copy(g = gIns, h = h1) .setConstrainable(constrainableWildcards, false) - produce(s3, toSf(snap.get), body, pve, v1)((s4, v2) => { + produce(s3, toSf(snap.get), body, pve, v1, AssumptionType.Explicit)((s4, v2) => { // TODO ake: explicit assumption v2.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterUnfold) if (!Verifier.config.disableFunctionUnfoldTrigger()) { val predicateTrigger = diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index b2413a1a4..351d322b6 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -6,25 +6,25 @@ package viper.silicon.rules -import viper.silicon.debugger.DebugExp import viper.silicon.Config.JoinMode -import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType, ExpAnalysisSourceInfo, PermissionInhaleNode} - -import scala.collection.mutable -import viper.silver.ast -import viper.silver.ast.utility.QuantifiedPermissions.QuantifiedPermissionAssertion -import viper.silver.verifier.PartialVerificationError +import viper.silicon.assumptionAnalysis.AssumptionType +import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType +import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.{Unreachable, VerificationResult} import viper.silicon.logger.records.data.{CondExpRecord, ImpliesRecord, ProduceRecord} import viper.silicon.resources.{FieldID, PredicateID} -import viper.silicon.rules.predicateSupporter.withExp import viper.silicon.state._ import viper.silicon.state.terms._ import viper.silicon.state.terms.predef.`?r` import viper.silicon.supporters.functions.NoopFunctionRecorder import viper.silicon.verifier.Verifier +import viper.silver.ast +import viper.silver.ast.utility.QuantifiedPermissions.QuantifiedPermissionAssertion +import viper.silver.verifier.PartialVerificationError import viper.silver.verifier.reasons.{NegativePermission, QPAssertionNotInjective} +import scala.collection.mutable + trait ProductionRules extends SymbolicExecutionRules { /** Produce assertion `a` into state `s`. @@ -34,6 +34,7 @@ trait ProductionRules extends SymbolicExecutionRules { * @param a The assertion to produce. * @param pve The error to report in case the production fails. * @param v The verifier to use. + * @param assumptionType The assumption type used for analysis purposes * @param Q The continuation to invoke if the production succeeded, with the state and * the verifier resulting from the production as arguments. * @return The result of the continuation. @@ -42,7 +43,8 @@ trait ProductionRules extends SymbolicExecutionRules { sf: (Sort, Verifier) => Term, a: ast.Exp, pve: PartialVerificationError, - v: Verifier) + v: Verifier, + assumptionType: AssumptionType = AssumptionType.Implicit) (Q: (State, Verifier) => VerificationResult) : VerificationResult @@ -58,6 +60,7 @@ trait ProductionRules extends SymbolicExecutionRules { * @param pvef The error to report in case the production fails. Given assertions `as`, an error * `pvef(as_i)` will be reported if producing assertion `as_i` fails. * @param v @see [[produce]] + * @param assumptionType @see [[produce]] * @param Q @see [[produce]] * @return @see [[produce]] */ @@ -65,7 +68,8 @@ trait ProductionRules extends SymbolicExecutionRules { sf: (Sort, Verifier) => Term, as: Seq[ast.Exp], pvef: ast.Exp => PartialVerificationError, - v: Verifier) + v: Verifier, + assumptionType: AssumptionType = AssumptionType.Implicit) (Q: (State, Verifier) => VerificationResult) : VerificationResult } @@ -104,18 +108,20 @@ object producer extends ProductionRules { sf: (Sort, Verifier) => Term, a: ast.Exp, pve: PartialVerificationError, - v: Verifier) + v: Verifier, + assumptionType: AssumptionType = AssumptionType.Implicit) (Q: (State, Verifier) => VerificationResult) : VerificationResult = - produceR(s, sf, a.whenInhaling, pve, v)(Q) + produceR(s, sf, a.whenInhaling, pve, v, assumptionType)(Q) /** @inheritdoc */ def produces(s: State, sf: (Sort, Verifier) => Term, as: Seq[ast.Exp], pvef: ast.Exp => PartialVerificationError, - v: Verifier) + v: Verifier, + assumptionType: AssumptionType = AssumptionType.Implicit) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -130,14 +136,15 @@ object producer extends ProductionRules { allPves ++= pves }) - produceTlcs(s, sf, allTlcs.result(), allPves.result(), v)(Q) + produceTlcs(s, sf, allTlcs.result(), allPves.result(), v, assumptionType)(Q) } private def produceTlcs(s: State, sf: (Sort, Verifier) => Term, as: Seq[ast.Exp], pves: Seq[PartialVerificationError], - v: Verifier) + v: Verifier, + assumptionType: AssumptionType) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -148,7 +155,7 @@ object producer extends ProductionRules { val pve = pves.head if (as.tail.isEmpty) - wrappedProduceTlc(s, sf, a, pve, v)(Q) + wrappedProduceTlc(s, sf, a, pve, v, assumptionType)(Q) else { try { val (sf0, sf1) = @@ -159,8 +166,8 @@ object producer extends ProductionRules { * over and over again. */ - wrappedProduceTlc(s, sf0, a, pve, v)((s1, v1) => - produceTlcs(s1, sf1, as.tail, pves.tail, v1)(Q)) + wrappedProduceTlc(s, sf0, a, pve, v, assumptionType)((s1, v1) => + produceTlcs(s1, sf1, as.tail, pves.tail, v1, assumptionType)(Q)) } catch { // We will get an IllegalArgumentException from createSnapshotPair if sf(...) returns Unit. // This should never happen if we're in a reachable state, so here we check for that @@ -177,14 +184,15 @@ object producer extends ProductionRules { sf: (Sort, Verifier) => Term, a: ast.Exp, pve: PartialVerificationError, - v: Verifier) + v: Verifier, + assumptionType: AssumptionType) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { val tlcs = a.topLevelConjuncts val pves = Seq.fill(tlcs.length)(pve) - produceTlcs(s, sf, tlcs, pves, v)(Q) + produceTlcs(s, sf, tlcs, pves, v, assumptionType)(Q) } /** Wrapper/decorator for consume that injects the following operations: @@ -194,12 +202,13 @@ object producer extends ProductionRules { sf: (Sort, Verifier) => Term, a: ast.Exp, pve: PartialVerificationError, - v: Verifier) + v: Verifier, + assumptionType: AssumptionType) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { val sepIdentifier = v.symbExLog.openScope(new ProduceRecord(a, s, v.decider.pcs)) - produceTlc(s, sf, a, pve, v)((s1, v1) => { + produceTlc(s, sf, a, pve, v, assumptionType)((s1, v1) => { v1.symbExLog.closeScope(sepIdentifier) Q(s1, v1)}) } @@ -208,7 +217,8 @@ object producer extends ProductionRules { sf: (Sort, Verifier) => Term, a: ast.Exp, pve: PartialVerificationError, - v: Verifier) + v: Verifier, + assumptionType: AssumptionType) (continuation: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -228,7 +238,7 @@ object producer extends ProductionRules { // The type arguments here are Null because there is no need to pass any join data. joiner.join[scala.Null, scala.Null](s1, v1, resetState = false)((s1, v1, QB) => branch(s1.copy(parallelizeBranches = false), t0, (e0, e0New), v1)( - (s2, v2) => produceR(s2.copy(parallelizeBranches = s1.parallelizeBranches), sf, a0, pve, v2)((s3, v3) => { + (s2, v2) => produceR(s2.copy(parallelizeBranches = s1.parallelizeBranches), sf, a0, pve, v2, assumptionType)((s3, v3) => { v3.symbExLog.closeScope(uidImplies) QB(s3, null, v3) }), @@ -259,7 +269,7 @@ object producer extends ProductionRules { eval(s, e0, pve, v)((s1, t0, e0New, v1) => branch(s1, t0, (e0, e0New), v1)( - (s2, v2) => produceR(s2, sf, a0, pve, v2)((s3, v3) => { + (s2, v2) => produceR(s2, sf, a0, pve, v2, assumptionType)((s3, v3) => { v3.symbExLog.closeScope(uidImplies) Q(s3, v3) }), @@ -281,11 +291,11 @@ object producer extends ProductionRules { // The type arguments here are Null because there is no need to pass any join data. joiner.join[scala.Null, scala.Null](s1, v1, resetState = false)((s1, v1, QB) => branch(s1.copy(parallelizeBranches = false), t0, (e0, e0New), v1)( - (s2, v2) => produceR(s2.copy(parallelizeBranches = s1.parallelizeBranches), sf, a1, pve, v2)((s3, v3) => { + (s2, v2) => produceR(s2.copy(parallelizeBranches = s1.parallelizeBranches), sf, a1, pve, v2, assumptionType)((s3, v3) => { v3.symbExLog.closeScope(uidCondExp) QB(s3, null, v3) }), - (s2, v2) => produceR(s2.copy(parallelizeBranches = s1.parallelizeBranches), sf, a2, pve, v2)((s3, v3) => { + (s2, v2) => produceR(s2.copy(parallelizeBranches = s1.parallelizeBranches), sf, a2, pve, v2, assumptionType)((s3, v3) => { v3.symbExLog.closeScope(uidCondExp) QB(s3, null, v3) })) @@ -307,18 +317,18 @@ object producer extends ProductionRules { eval(s, e0, pve, v)((s1, t0, e0New, v1) => branch(s1, t0, (e0, e0New), v1)( - (s2, v2) => produceR(s2, sf, a1, pve, v2)((s3, v3) => { + (s2, v2) => produceR(s2, sf, a1, pve, v2, assumptionType)((s3, v3) => { v3.symbExLog.closeScope(uidCondExp) Q(s3, v3) }), - (s2, v2) => produceR(s2, sf, a2, pve, v2)((s3, v3) => { + (s2, v2) => produceR(s2, sf, a2, pve, v2, assumptionType)((s3, v3) => { v3.symbExLog.closeScope(uidCondExp) Q(s3, v3) }))) case let: ast.Let if !let.isPure => letSupporter.handle[ast.Exp](s, let, pve, v)((s1, g1, body, v1) => - produceR(s1.copy(g = s1.g + g1), sf, body, pve, v1)(Q)) + produceR(s1.copy(g = s1.g + g1), sf, body, pve, v1, assumptionType)(Q)) case accPred@ast.FieldAccessPredicate(ast.FieldAccess(eRcvr, field), _) => val perm = accPred.perm @@ -335,12 +345,12 @@ object producer extends ProductionRules { if (s3.qpFields.contains(field)) { val trigger = (sm: Term) => FieldTrigger(field.name, sm, tRcvr) quantifiedChunkSupporter.produceSingleLocation(s3, field, Seq(`?r`), Option.when(withExp)(Seq(ast.LocalVarDecl("r", ast.Ref)(accPred.pos, accPred.info, accPred.errT))), - Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), snap, gain, gainExp, trigger, v3)(Q) + Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), snap, gain, gainExp, trigger, v3, assumptionType)(Q) } else { val (debugHeapName, debugLabel) = v3.getDebugOldLabel(s3, accPred.pos) val snapExp = Option.when(withExp)(ast.DebugLabelledOld(ast.FieldAccess(eRcvrNew.get, field)(), debugLabel)(accPred.pos, accPred.info, accPred.errT)) val ch = BasicChunk(FieldID, BasicChunkIdentifier(field.name), Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), snap, snapExp, gain, gainExp, - v1.decider.assumptionAnalyzer.currentAnalysisInfo) + v1.decider.assumptionAnalyzer.getAnalysisInfo(assumptionType)) chunkSupporter.produce(s3, s3.h, ch, v3)((s4, h4, v4) => { val s5 = s4.copy(h = h4) val s6 = if (withExp) s5.copy(oldHeaps = s5.oldHeaps + (debugHeapName -> magicWandSupporter.getEvalHeap(s4))) else s5 @@ -364,11 +374,11 @@ object producer extends ProductionRules { val formalArgs = s2.predicateFormalVarMap(predicate) val trigger = (sm: Term) => PredicateTrigger(predicate.name, sm, tArgs) quantifiedChunkSupporter.produceSingleLocation( - s2, predicate, formalArgs, Option.when(withExp)(predicate.formalArgs), tArgs, eArgsNew, snap, gain, gainExp, trigger, v2)(Q) + s2, predicate, formalArgs, Option.when(withExp)(predicate.formalArgs), tArgs, eArgsNew, snap, gain, gainExp, trigger, v2, assumptionType)(Q) } else { val snap1 = snap.convert(sorts.Snap) val ch = BasicChunk(PredicateID, BasicChunkIdentifier(predicate.name), tArgs, eArgsNew, snap1, None, gain, gainExp, - v1.decider.assumptionAnalyzer.currentAnalysisInfo) + v1.decider.assumptionAnalyzer.getAnalysisInfo(assumptionType)) chunkSupporter.produce(s2, s2.h, ch, v2)((s3, h3, v3) => { if (Verifier.config.enablePredicateTriggersOnInhale() && s3.functionRecorder == NoopFunctionRecorder && !Verifier.config.disableFunctionUnfoldTrigger()) { @@ -395,7 +405,7 @@ object producer extends ProductionRules { else s1.conservedPcs val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(formalVars, formalVarExps, wand, args, bodyVarsNew, - FullPerm, Option.when(withExp)(ast.FullPerm()(wand.pos, wand.info, wand.errT)), sm, s.program, v1) + FullPerm, Option.when(withExp)(ast.FullPerm()(wand.pos, wand.info, wand.errT)), sm, s.program, v1, assumptionType) val h2 = s1.h + ch val smCache1 = if (s1.heapDependentTriggers.contains(MagicWandIdentifier(wand, s1.program))){ val (relevantChunks, _) = @@ -420,7 +430,7 @@ object producer extends ProductionRules { case wand: ast.MagicWand => val snap = sf(v.snapshotSupporter.optimalSnapshotSort(wand, s, v), v) - magicWandSupporter.createChunk(s, wand, MagicWandSnapshot(snap), pve, v)((s1, chWand, v1) => + magicWandSupporter.createChunk(s, wand, MagicWandSnapshot(snap), pve, v, assumptionType)((s1, chWand, v1) => chunkSupporter.produce(s1, s1.h, chWand, v1)((s2, h2, v2) => Q(s2.copy(h = h2), v2))) @@ -458,7 +468,8 @@ object producer extends ProductionRules { pve, NegativePermission(acc.perm), QPAssertionNotInjective(acc.loc), - v1 + v1, + assumptionType )(Q) case (s1, _, _, _, _, None, v1) => Q(s1.copy(constrainableARPs = s.constrainableARPs), v1) } @@ -500,7 +511,8 @@ object producer extends ProductionRules { pve, NegativePermission(acc.perm), QPAssertionNotInjective(acc.loc), - v1 + v1, + assumptionType )(Q) case (s1, _, _, _, _, None, v1) => Q(s1.copy(constrainableARPs = s.constrainableARPs), v1) } @@ -541,7 +553,8 @@ object producer extends ProductionRules { pve, NegativePermission(ast.FullPerm()()), QPAssertionNotInjective(wand), - v1 + v1, + assumptionType )(Q) case (s1, _, _, _, _, None, v1) => Q(s1, v1) } @@ -554,7 +567,7 @@ object producer extends ProductionRules { v.decider.assume(sf(sorts.Snap, v) === Unit, Option.when(withExp)(DebugExp.createInstance("Empty snapshot", isInternal_ = true)), AssumptionType.Internal) /* TODO: See comment for case ast.Implies above */ eval(s, a, pve, v)((s1, t, aNew, v1) => { - v1.decider.assume(t, Option.when(withExp)(a), aNew, AssumptionType.Explicit) // TODO ake: always explicit? + v1.decider.assume(t, Option.when(withExp)(a), aNew, assumptionType) Q(s1, v1)}) } diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 6d4eb3405..4066d4fe3 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -7,6 +7,7 @@ package viper.silicon.rules import viper.silicon.Map +import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp @@ -178,7 +179,8 @@ trait QuantifiedChunkSupport extends SymbolicExecutionRules { permissionsExp: Option[ast.Exp], sm: Term, program: ast.Program, - v: Verifier) + v: Verifier, + assumptionType: AssumptionType) : QuantifiedBasicChunk /** Creates a quantified chunk corresponding to the assertion @@ -216,7 +218,8 @@ trait QuantifiedChunkSupport extends SymbolicExecutionRules { userProvidedTriggers: Option[Seq[Trigger]], qidPrefix: String, v: Verifier, - program: ast.Program) + program: ast.Program, + assumptionType: AssumptionType) : (QuantifiedBasicChunk, InverseFunctions) def splitHeap[CH <: QuantifiedBasicChunk : NotNothing : ClassTag] @@ -271,7 +274,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { permissionsExp: Option[ast.Exp], sm: Term, program: ast.Program, - v: Verifier) + v: Verifier, + assumptionType: AssumptionType) : QuantifiedBasicChunk = { val condition = @@ -300,7 +304,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { argumentsExp, hints, program, - v) + v, + assumptionType) } /** @inheritdoc [[QuantifiedChunkSupport.createQuantifiedChunk]] */ @@ -321,7 +326,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { userProvidedTriggers: Option[Seq[Trigger]], qidPrefix: String, v: Verifier, - program: ast.Program) + program: ast.Program, + assumptionType: AssumptionType) : (QuantifiedBasicChunk, InverseFunctions) = { val (inverseFunctions, imagesOfCodomain) = @@ -362,7 +368,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { None, hints, program, - v) + v, + assumptionType) (ch, inverseFunctions) } @@ -405,7 +412,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optSingletonArgumentsExp: Option[Seq[ast.Exp]], hints: Seq[Term], program: ast.Program, - v: Verifier) + v: Verifier, + assumptionType: AssumptionType) : QuantifiedBasicChunk = { resource match { @@ -425,7 +433,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optSingletonArguments.map(_.head), optSingletonArgumentsExp.map(_.head), hints, - v.decider.assumptionAnalyzer.currentAnalysisInfo) + v.decider.assumptionAnalyzer.getAnalysisInfo(assumptionType)) case predicate: ast.Predicate => QuantifiedPredicateChunk( @@ -441,7 +449,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optSingletonArguments, optSingletonArgumentsExp, hints, - v.decider.assumptionAnalyzer.currentAnalysisInfo) + v.decider.assumptionAnalyzer.getAnalysisInfo(assumptionType)) case wand: ast.MagicWand => val conditionalizedPermissions = Ite(condition, permissions, NoPerm) @@ -457,7 +465,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optSingletonArguments, optSingletonArgumentsExp, hints, - v.decider.assumptionAnalyzer.currentAnalysisInfo) + v.decider.assumptionAnalyzer.getAnalysisInfo(assumptionType)) case other => sys.error(s"Found yet unsupported resource $other (${other.getClass.getSimpleName})") @@ -898,7 +906,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { pve: PartialVerificationError, negativePermissionReason: => ErrorReason, notInjectiveReason: => ErrorReason, - v: Verifier) + v: Verifier, + assumptionType: AssumptionType) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -927,7 +936,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { userProvidedTriggers = optTrigger.map(_ => tTriggers), qidPrefix = qid, v = v, - program = s.program) + program = s.program, + assumptionType = assumptionType) val (effectiveTriggers, effectiveTriggersQVars, effectiveTriggersQVarExps) = optTrigger match { case Some(_) => @@ -1098,7 +1108,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { tPerm: Term, ePerm: Option[ast.Exp], resourceTriggerFactory: Term => Term, /* Trigger with some snapshot */ - v: Verifier) + v: Verifier, + assumptionType: AssumptionType) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -1110,7 +1121,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val conservedPcs = if (s.recordPcs) (s.conservedPcs.head :+ v.decider.pcs.after(definitionalAxiomMark)) +: s.conservedPcs.tail else s.conservedPcs - val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(formalQVars, formalQVarsExp, resource, tArgs, eArgs, tPerm, ePerm, sm, s.program, v) + val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(formalQVars, formalQVarsExp, resource, tArgs, eArgs, tPerm, ePerm, sm, s.program, v, assumptionType) val (fr1, h1) = v.stateConsolidator(s).merge(s.functionRecorder, s, s.h, Heap(Seq(ch)), v) val interpreter = new NonQuantifiedPropertyInterpreter(h1.values, v) @@ -1349,7 +1360,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optTrigger.map(_ => tTriggers), qid, v2, - s.program + s.program, + AssumptionType.Unknown ) val debugExp = Option.when(withExp)(DebugExp.createInstance("Inverse functions for quantified permission", isInternal_ = true)) v.decider.assume(FunctionPreconditionTransformer.transform(inverseFunctions.axiomInvertiblesOfInverses, s3.program), debugExp, AssumptionType.Internal) @@ -1484,7 +1496,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { } val consumedChunk = quantifiedChunkSupporter.createSingletonQuantifiedChunk( - codomainQVars, codomainQVarsExp, resource, arguments, argumentsExp, permsTaken, permsTakenExp, smDef1.sm, s.program, v1) + codomainQVars, codomainQVarsExp, resource, arguments, argumentsExp, permsTaken, permsTakenExp, smDef1.sm, s.program, v1, AssumptionType.Unknown) val s3 = s2.copy(functionRecorder = s2.functionRecorder.recordFvfAndDomain(smDef1), smCache = smCache1) (result, s3, h2, Some(consumedChunk)) // TODO ake: or consumedChunks? @@ -1677,7 +1689,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.assume(permissionConstraint, permissionConstraintExp, permissionConstraintExp, AssumptionType.Unknown) remainingChunks = - remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.assumptionAnalyzer.currentAnalysisInfo) + remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.assumptionAnalyzer.getAnalysisInfo) } else { v.decider.prover.comment(s"Chunk depleted?") val chunkDepleted = v.decider.check(depletedCheck, Verifier.config.splitTimeout()) @@ -1688,7 +1700,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { remainingChunks = remainingChunks :+ ithChunk } else { remainingChunks = - remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.assumptionAnalyzer.currentAnalysisInfo) + remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.assumptionAnalyzer.getAnalysisInfo) } } } diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index ae91b5eaa..3670c9c10 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -101,25 +101,23 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif * rules in Smans' paper. */ executionFlowController.locally(s, v)((s1, v1) => { - v1.decider.assumptionAnalyzer.setCurrentAnalysisInfo(if(pres.isEmpty) StringAnalysisSourceInfo("no precondition", ast.NoPosition) else ExpAnalysisSourceInfo(pres.head), AssumptionType.Explicit) - produces(s1, freshSnap, pres, ContractNotWellformed, v1)((s2, v2) => { + // TODO ake: set source + produces(s1, freshSnap, pres, ContractNotWellformed, v1, AssumptionType.Explicit)((s2, v2) => { v2.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) val s2a = s2.copy(oldHeaps = s2.oldHeaps + (Verifier.PRE_STATE_LABEL -> s2.h)) ( executionFlowController.locally(s2a, v2)((s3, v3) => { val s4 = s3.copy(h = Heap()) val impLog = new WellformednessCheckRecord(posts, s, v.decider.pcs) val sepIdentifier = symbExLog.openScope(impLog) - v3.decider.assumptionAnalyzer.setCurrentAnalysisInfo(if(posts.isEmpty) StringAnalysisSourceInfo("no postcondition", ast.NoPosition) else ExpAnalysisSourceInfo(posts.head), AssumptionType.Internal) - produces(s4, freshSnap, posts, ContractNotWellformed, v3)((_, _) => { - v3.decider.assumptionAnalyzer.clearCurrentAnalysisInfo() + // TODO ake: set source + produces(s4, freshSnap, posts, ContractNotWellformed, v3, AssumptionType.Internal)((_, _) => { symbExLog.closeScope(sepIdentifier) Success()})}) && { executionFlowController.locally(s2a, v2)((s3, v3) => { exec(s3, body, v3)((s4, v4) =>{ - v4.decider.assumptionAnalyzer.setCurrentAnalysisInfo(if(posts.isEmpty) StringAnalysisSourceInfo("no postcondition", ast.NoPosition) else ExpAnalysisSourceInfo(posts.head), AssumptionType.Assertion) - consumes(s4, posts, false, postViolated, v4, v4.decider.assumptionAnalyzer.currentAnalysisInfo)((_, _, _, _) => { - v3.decider.assumptionAnalyzer.clearCurrentAnalysisInfo() + // TODO ake: set source + consumes(s4, posts, false, postViolated, v4, v4.decider.assumptionAnalyzer.getAnalysisInfo)((_, _, _, _) => { Success() })})}) } )})}) diff --git a/src/main/scala/supporters/PredicateVerificationUnit.scala b/src/main/scala/supporters/PredicateVerificationUnit.scala index e5ddb9dc9..548048da7 100644 --- a/src/main/scala/supporters/PredicateVerificationUnit.scala +++ b/src/main/scala/supporters/PredicateVerificationUnit.scala @@ -7,6 +7,7 @@ package viper.silicon.supporters import com.typesafe.scalalogging.Logger +import viper.silicon.assumptionAnalysis.AssumptionType import viper.silver.ast import viper.silver.ast.Program import viper.silver.components.StatefulComponent @@ -100,7 +101,7 @@ trait DefaultPredicateVerificationUnitProvider extends VerifierComponent { v: Ve /* locallyXXX { magicWandSupporter.checkWandsAreSelfFraming(σ.γ, σ.h, predicate, c)} &&*/ executionFlowController.locally(s, v)((s1, _) => { - produce(s1, freshSnap, body, err, v)((_, _) => + produce(s1, freshSnap, body, err, v, AssumptionType.Internal)((_, _) => Success())}) } diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index 54f8ec4cf..8cba3aea8 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -225,14 +225,14 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver val result = executionFlowController.locally(s, v)((s0, _) => { val preMark = decider.setPathConditionMark() - produces(s0, toSf(`?s`), pres, ContractNotWellformed, v)((s1, _) => { + produces(s0, toSf(`?s`), pres, ContractNotWellformed, v, AssumptionType.Internal)((s1, _) => { val relevantPathConditionStack = decider.pcs.after(preMark) phase1Data :+= Phase1Data(s1, relevantPathConditionStack.branchConditions, relevantPathConditionStack.branchConditionExps, relevantPathConditionStack.assumptions, Option.when(evaluator.withExp)(relevantPathConditionStack.assumptionExps)) // The postcondition must be produced with a fresh snapshot (different from `?s`) because // the postcondition's snapshot structure is most likely different than that of the // precondition - produces(s1, freshSnap, posts, ContractNotWellformed, v)((s2, _) => { + produces(s1, freshSnap, posts, ContractNotWellformed, v, AssumptionType.Internal)((s2, _) => { recorders :+= s2.functionRecorder Success()})})}) From 4e7115e61900b04cbd055cc00297160b6d1396e4 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 19 May 2025 12:10:16 +0200 Subject: [PATCH 051/474] refactor analysis source info --- .../AnalysisSourceInfo.scala | 4 +++- .../AssumptionAnalyzer.scala | 18 +++++++++--------- src/main/scala/rules/Executor.scala | 2 +- src/main/scala/rules/HavocSupporter.scala | 6 +++--- src/main/scala/rules/PredicateSupporter.scala | 2 +- .../scala/rules/QuantifiedChunkSupport.scala | 6 +++--- 6 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala index fc37c4506..e820387bd 100644 --- a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala +++ b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala @@ -55,6 +55,8 @@ case class StringAnalysisSourceInfo(description: String, position: Position) ext } case class CompositeAnalysisSourceInfo(coarseGrainedSource: AnalysisSourceInfo, fineGrainedSource: AnalysisSourceInfo) extends AnalysisSourceInfo { - override def toString: String = coarseGrainedSource.toString + " (" + super.toString + ")" + override def toString: String = coarseGrainedSource.toString + " (" + super.toString + ") -> " + fineGrainedSource.toString override def getPosition: Position = coarseGrainedSource.getPosition + + override def equals(obj: Any): Boolean = coarseGrainedSource.equals(obj) // TODO ake } diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 34c94fc08..cd8281daa 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -29,28 +29,28 @@ trait AssumptionAnalyzer { val assumptionGraph: AssumptionAnalysisGraph = new DefaultAssumptionAnalysisGraph() - private var currentAnalysisInfo: AnalysisInfo = new NoAnalysisInfo() + private var currentSourceInfo: AnalysisSourceInfo = NoAnalysisSourceInfo() var currentExpStack: InsertionOrderedSet[ast.Exp] = InsertionOrderedSet.empty - def getAnalysisInfo: AnalysisInfo = currentAnalysisInfo + def getAnalysisInfo: AnalysisInfo = getAnalysisInfo(AssumptionType.Implicit) - def getAnalysisInfo(assumptionType: AssumptionType): AnalysisInfo = AnalysisInfo(this, currentAnalysisInfo.sourceInfo, assumptionType) + def getAnalysisInfo(assumptionType: AssumptionType): AnalysisInfo = AnalysisInfo(this, currentSourceInfo, assumptionType) def getFullSourceInfo: AnalysisSourceInfo = { if(currentExpStack.nonEmpty){ - CompositeAnalysisSourceInfo(currentAnalysisInfo.sourceInfo, ExpAnalysisSourceInfo(currentExpStack.head)) + CompositeAnalysisSourceInfo(currentSourceInfo, ExpAnalysisSourceInfo(currentExpStack.head)) }else{ - currentAnalysisInfo.sourceInfo + currentSourceInfo } } - def setCurrentAnalysisInfo(analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): AnalysisInfo = { - currentAnalysisInfo = AnalysisInfo(this, analysisSourceInfo, assumptionType) - currentAnalysisInfo + def setCurrentSourceInfo(analysisSourceInfo: AnalysisSourceInfo): AnalysisSourceInfo = { + currentSourceInfo = analysisSourceInfo + currentSourceInfo } def clearCurrentAnalysisInfo(): Unit = { - currentAnalysisInfo = new NoAnalysisInfo() + currentSourceInfo = new NoAnalysisSourceInfo() } def getMember: Option[Member] diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 216d213db..afcf68b9b 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -326,7 +326,7 @@ object executor extends ExecutionRules { (Q: (State, Verifier) => VerificationResult) : VerificationResult = { val sepIdentifier = v.symbExLog.openScope(new ExecuteRecord(stmt, s, v.decider.pcs)) - v.decider.assumptionAnalyzer.setCurrentAnalysisInfo(StmtAnalysisSourceInfo(stmt), AssumptionType.Implicit) + v.decider.assumptionAnalyzer.setCurrentSourceInfo(StmtAnalysisSourceInfo(stmt)) exec2(s, stmt, v)((s1, v1) => { v.decider.assumptionAnalyzer.clearCurrentAnalysisInfo() v1.symbExLog.closeScope(sepIdentifier) diff --git a/src/main/scala/rules/HavocSupporter.scala b/src/main/scala/rules/HavocSupporter.scala index ef29feb17..5d05b7cb7 100644 --- a/src/main/scala/rules/HavocSupporter.scala +++ b/src/main/scala/rules/HavocSupporter.scala @@ -193,7 +193,7 @@ object havocSupporter extends SymbolicExecutionRules { val havockedSnap = v.decider.fresh("mwsf", sorts.MagicWandSnapFunction, Option.when(withExp)(PUnknown())) val cond = replacementCond(lhs, ch.args, condInfo) val magicWandSnapshot = MagicWandSnapshot(Ite(cond, havockedSnap, ch.snap.mwsf)) - NonQuantifiedChunk.withSnap(ch, magicWandSnapshot, None, v.decider.assumptionAnalyzer.getAnalysisInfo) + NonQuantifiedChunk.withSnap(ch, magicWandSnapshot, None, v.decider.assumptionAnalyzer.getAnalysisInfo) // TODO ake: assumptionType? case ch => val havockedSnap = freshSnap(ch.snap.sort, v) @@ -279,9 +279,9 @@ object havocSupporter extends SymbolicExecutionRules { v.decider.prover.comment("axiomatized snapshot map after havoc") val debugExp = Option.when(withExp)(DebugExp.createInstance("havoc new axiom", isInternal_ = true)) - v.decider.assume(newAxiom, debugExp, AssumptionType.Unknown) + v.decider.assume(newAxiom, debugExp, AssumptionType.Internal) - QuantifiedChunk.withSnapshotMap(ch, newSm, v.decider.assumptionAnalyzer.getAnalysisInfo) + QuantifiedChunk.withSnapshotMap(ch, newSm, v.decider.assumptionAnalyzer.getAnalysisInfo) // TODO ake: assumption type? } newChunks ++ otherChunks } diff --git a/src/main/scala/rules/PredicateSupporter.scala b/src/main/scala/rules/PredicateSupporter.scala index f891625b8..03cf4052a 100644 --- a/src/main/scala/rules/PredicateSupporter.scala +++ b/src/main/scala/rules/PredicateSupporter.scala @@ -117,7 +117,7 @@ object predicateSupporter extends PredicateSupportRules { functionRecorder = s2.functionRecorder.recordFvfAndDomain(smDef)) Q(s3, v1) } else { - val ch = BasicChunk(PredicateID, BasicChunkIdentifier(predicate.name), tArgs, eArgs, snap.get.convert(sorts.Snap), None, tPerm, ePerm, v1.decider.assumptionAnalyzer.getAnalysisInfo) + val ch = BasicChunk(PredicateID, BasicChunkIdentifier(predicate.name), tArgs, eArgs, snap.get.convert(sorts.Snap), None, tPerm, ePerm, v1.decider.assumptionAnalyzer.getAnalysisInfo(AssumptionType.Explicit)) val s3 = s2.copy(g = s.g, smDomainNeeded = s.smDomainNeeded, permissionScalingFactor = s.permissionScalingFactor, diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 4066d4fe3..a3b4ca746 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -1361,7 +1361,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { qid, v2, s.program, - AssumptionType.Unknown + AssumptionType.Unknown // TODO ake: should be exhale ) val debugExp = Option.when(withExp)(DebugExp.createInstance("Inverse functions for quantified permission", isInternal_ = true)) v.decider.assume(FunctionPreconditionTransformer.transform(inverseFunctions.axiomInvertiblesOfInverses, s3.program), debugExp, AssumptionType.Internal) @@ -1689,7 +1689,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.assume(permissionConstraint, permissionConstraintExp, permissionConstraintExp, AssumptionType.Unknown) remainingChunks = - remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.assumptionAnalyzer.getAnalysisInfo) + remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.assumptionAnalyzer.getAnalysisInfo) // TODO ake: assumptionType? } else { v.decider.prover.comment(s"Chunk depleted?") val chunkDepleted = v.decider.check(depletedCheck, Verifier.config.splitTimeout()) @@ -1700,7 +1700,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { remainingChunks = remainingChunks :+ ithChunk } else { remainingChunks = - remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.assumptionAnalyzer.getAnalysisInfo) + remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.assumptionAnalyzer.getAnalysisInfo)// TODO ake: assumptionType? } } } From 07ba5b1e50961e9a4daea044493b48bd6290a465 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 19 May 2025 15:18:29 +0200 Subject: [PATCH 052/474] fixes --- .../AnalysisSourceInfo.scala | 2 +- .../AssumptionAnalyzer.scala | 22 ++++++++++++++----- src/main/scala/rules/Brancher.scala | 4 ++++ src/main/scala/rules/Consumer.scala | 3 ++- src/main/scala/rules/Evaluator.scala | 4 ++-- src/main/scala/rules/Executor.scala | 9 ++++---- src/main/scala/rules/Joiner.scala | 2 +- src/main/scala/rules/Producer.scala | 13 ++++++++--- src/main/scala/rules/StateConsolidator.scala | 2 +- .../scala/supporters/MethodSupporter.scala | 2 +- src/test/resources/andrea/quickTest.vpr | 2 +- 11 files changed, 45 insertions(+), 20 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala index e820387bd..2fe3b7f5f 100644 --- a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala +++ b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala @@ -55,7 +55,7 @@ case class StringAnalysisSourceInfo(description: String, position: Position) ext } case class CompositeAnalysisSourceInfo(coarseGrainedSource: AnalysisSourceInfo, fineGrainedSource: AnalysisSourceInfo) extends AnalysisSourceInfo { - override def toString: String = coarseGrainedSource.toString + " (" + super.toString + ") -> " + fineGrainedSource.toString + override def toString: String = coarseGrainedSource.toString + " -> " + fineGrainedSource.toString override def getPosition: Position = coarseGrainedSource.getPosition override def equals(obj: Any): Boolean = coarseGrainedSource.equals(obj) // TODO ake diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index cd8281daa..c39ba3293 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -34,13 +34,15 @@ trait AssumptionAnalyzer { def getAnalysisInfo: AnalysisInfo = getAnalysisInfo(AssumptionType.Implicit) - def getAnalysisInfo(assumptionType: AssumptionType): AnalysisInfo = AnalysisInfo(this, currentSourceInfo, assumptionType) + def getAnalysisInfo(assumptionType: AssumptionType): AnalysisInfo = AnalysisInfo(this, getFullSourceInfo, assumptionType) def getFullSourceInfo: AnalysisSourceInfo = { - if(currentExpStack.nonEmpty){ - CompositeAnalysisSourceInfo(currentSourceInfo, ExpAnalysisSourceInfo(currentExpStack.head)) - }else{ + if(currentExpStack.isEmpty){ currentSourceInfo + }else if(currentSourceInfo.isInstanceOf[NoAnalysisSourceInfo]){ + ExpAnalysisSourceInfo(currentExpStack.head) + }else{ + CompositeAnalysisSourceInfo(currentSourceInfo, ExpAnalysisSourceInfo(currentExpStack.head)) } } @@ -50,12 +52,22 @@ trait AssumptionAnalyzer { } def clearCurrentAnalysisInfo(): Unit = { - currentSourceInfo = new NoAnalysisSourceInfo() + currentSourceInfo = NoAnalysisSourceInfo() + } + + def addExpToStack(e: ast.Exp): Unit = { + currentExpStack = InsertionOrderedSet(Set(e) ++ currentExpStack) + } + + def popExpFromStack(): Unit = { + if(currentExpStack.nonEmpty) + currentExpStack = currentExpStack.tail } def getMember: Option[Member] def exportGraph(): Unit + } object AssumptionAnalyzer { diff --git a/src/main/scala/rules/Brancher.scala b/src/main/scala/rules/Brancher.scala index 6c276b179..dd1a08b43 100644 --- a/src/main/scala/rules/Brancher.scala +++ b/src/main/scala/rules/Brancher.scala @@ -145,7 +145,9 @@ object brancher extends BranchingRules { executionFlowController.locally(s, v0)((s1, v1) => { v1.decider.prover.comment(s"[else-branch: $cnt | $negatedCondition]") + v1.decider.assumptionAnalyzer.addExpToStack(conditionExp._1) v1.decider.setCurrentBranchCondition(negatedCondition, (negatedConditionExp, negatedConditionExpNew)) + v1.decider.assumptionAnalyzer.popExpFromStack() var functionsOfElseBranchdDeciderBefore: Set[FunctionDecl] = null var nMacrosOfElseBranchDeciderBefore: Int = 0 @@ -195,7 +197,9 @@ object brancher extends BranchingRules { v.symbExLog.markReachable(uidBranchPoint) executionFlowController.locally(s, v)((s1, v1) => { v1.decider.prover.comment(s"[then-branch: $cnt | $condition]") + v1.decider.assumptionAnalyzer.addExpToStack(conditionExp._1) v1.decider.setCurrentBranchCondition(condition, conditionExp) + v1.decider.assumptionAnalyzer.popExpFromStack() fThen(v1.stateConsolidator(s1).consolidateOptionally(s1, v1), v1) }) diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index be4d09cb4..680dc7cf5 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -179,8 +179,10 @@ object consumer extends ConsumptionRules { val s1 = s0.copy(h = s.h) /* s1 is s, but the retrying flag might be set */ val sepIdentifier = v1.symbExLog.openScope(new ConsumeRecord(a, s1, v.decider.pcs)) + v.decider.assumptionAnalyzer.addExpToStack(a) consumeTlc(s1, h0, a, returnSnap, pve, v1, analysisInfo)((s2, h2, snap2, chunks, v2) => { + v.decider.assumptionAnalyzer.popExpFromStack() v2.symbExLog.closeScope(sepIdentifier) QS(s2, h2, snap2, chunks, v2)}) })(Q) @@ -198,7 +200,6 @@ object consumer extends ConsumptionRules { */ v.logger.debug(s"\nCONSUME ${viper.silicon.utils.ast.sourceLineColumn(a)}: $a") -// v.decider.assumptionAnalyzer.updateCurrentAnalysisInfo(ExpAnalysisSourceInfo(a)) // TODO ake v.logger.debug(v.stateFormatter.format(s, v.decider.pcs)) v.logger.debug("h = " + v.stateFormatter.format(h)) if (s.reserveHeaps.nonEmpty) diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 45d80e375..aa88c586e 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -93,9 +93,9 @@ object evaluator extends EvaluationRules { : VerificationResult = { val sepIdentifier = v.symbExLog.openScope(new EvaluateRecord(e, s, v.decider.pcs)) - v.decider.assumptionAnalyzer.currentExpStack = InsertionOrderedSet(Set(e) ++ v.decider.assumptionAnalyzer.currentExpStack) + v.decider.assumptionAnalyzer.addExpToStack(e) eval3(s, e, pve, v)((s1, t, eNew, v1) => { - v.decider.assumptionAnalyzer.currentExpStack = v.decider.assumptionAnalyzer.currentExpStack.tail + v.decider.assumptionAnalyzer.popExpFromStack() v1.symbExLog.closeScope(sepIdentifier) Q(s1, t, eNew, v1)}) } diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index afcf68b9b..ac228abb8 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -440,19 +440,20 @@ object executor extends ExecutionRules { val pve = AssignmentFailed(ass) eval(s, eRcvr, pve, v)((s1, tRcvr, eRcvrNew, v1) => eval(s1, rhs, pve, v1)((s2, tRhs, rhsNew, v2) => { + v2.decider.assumptionAnalyzer.addExpToStack(fa) val resource = fa.res(s.program) val ve = pve dueTo InsufficientPermission(fa) val description = s"consume ${ass.pos}: $ass" - val analysisInfo = AnalysisInfo(v2.decider.assumptionAnalyzer, ExpAnalysisSourceInfo(fa), AssumptionType.Assertion) - chunkSupporter.consume(s2, s2.h, resource, Seq(tRcvr), eRcvrNew.map(Seq(_)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), false, ve, v2, description, analysisInfo)((s3, h3, _, consumedChunks, v3) => { + chunkSupporter.consume(s2, s2.h, resource, Seq(tRcvr), eRcvrNew.map(Seq(_)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), false, ve, v2, description, v2.decider.assumptionAnalyzer.getAnalysisInfo)((s3, h3, _, consumedChunks, v3) => { val (tSnap, _) = ssaifyRhs(tRhs, rhs, rhsNew, field.name, field.typ, v3, s3) val id = BasicChunkIdentifier(field.name) val newChunk = BasicChunk.createDerivedChunk(consumedChunks.toSet, FieldID, id, Seq(tRcvr), eRcvrNew.map(Seq(_)), tSnap, rhsNew, FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), - AnalysisInfo(v3.decider.assumptionAnalyzer, ExpAnalysisSourceInfo(fa), AssumptionType.Implicit)) + v3.decider.assumptionAnalyzer.getAnalysisInfo(AssumptionType.Implicit)) chunkSupporter.produce(s3, h3, newChunk, v3)((s4, h4, v4) => { val s5 = s4.copy(h = h4) val (debugHeapName, _) = v4.getDebugOldLabel(s5, fa.pos) val s6 = if (withExp) s5.copy(oldHeaps = s5.oldHeaps + (debugHeapName -> magicWandSupporter.getEvalHeap(s5))) else s5 + v4.decider.assumptionAnalyzer.popExpFromStack() Q(s6, v4) }) }) @@ -768,7 +769,7 @@ object executor extends ExecutionRules { } else { (None, None) } - v.decider.assumeDefinition(BuiltinEquals(t, rhs), debugExp, AssumptionType.Internal) + v.decider.assumeDefinition(BuiltinEquals(t, rhs), debugExp, AssumptionType.Implicit) (t, eNew) } } diff --git a/src/main/scala/rules/Joiner.scala b/src/main/scala/rules/Joiner.scala index 6e8ac0641..a408a99bb 100644 --- a/src/main/scala/rules/Joiner.scala +++ b/src/main/scala/rules/Joiner.scala @@ -111,7 +111,7 @@ object joiner extends JoiningRules { feasibleBranchesExpNew = feasibleBranchesExpNew.map(fbe => BigAnd(entry.pathConditions.branchConditionExps.map(_._2.get)) :: fbe) }) // Assume we are in a feasible branch - v.decider.assume(Or(feasibleBranches), Option.when(withExp)(DebugExp.createInstance(Some("Feasible Branches"), feasibleBranchesExp.map(BigOr(_)), feasibleBranchesExpNew.map(BigOr(_)), InsertionOrderedSet.empty)), AssumptionType.Unknown) + v.decider.assume(Or(feasibleBranches), Option.when(withExp)(DebugExp.createInstance(Some("Feasible Branches"), feasibleBranchesExp.map(BigOr(_)), feasibleBranchesExpNew.map(BigOr(_)), InsertionOrderedSet.empty)), AssumptionType.Internal) Q(sJoined, dataJoined, v) } } diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index 351d322b6..2f420f7b8 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -154,8 +154,13 @@ object producer extends ProductionRules { val a = as.head.whenInhaling val pve = pves.head + + v.decider.assumptionAnalyzer.addExpToStack(a) if (as.tail.isEmpty) - wrappedProduceTlc(s, sf, a, pve, v, assumptionType)(Q) + wrappedProduceTlc(s, sf, a, pve, v, assumptionType)((s1, v1) => { + v.decider.assumptionAnalyzer.popExpFromStack() + Q(s1, v1) + }) else { try { val (sf0, sf1) = @@ -166,8 +171,10 @@ object producer extends ProductionRules { * over and over again. */ - wrappedProduceTlc(s, sf0, a, pve, v, assumptionType)((s1, v1) => - produceTlcs(s1, sf1, as.tail, pves.tail, v1, assumptionType)(Q)) + wrappedProduceTlc(s, sf0, a, pve, v, assumptionType)((s1, v1) => { + v1.decider.assumptionAnalyzer.popExpFromStack() + produceTlcs(s1, sf1, as.tail, pves.tail, v1, assumptionType)(Q) + }) } catch { // We will get an IllegalArgumentException from createSnapshotPair if sf(...) returns Unit. // This should never happen if we're in a reachable state, so here we check for that diff --git a/src/main/scala/rules/StateConsolidator.scala b/src/main/scala/rules/StateConsolidator.scala index 0350bbbfa..b064388a0 100644 --- a/src/main/scala/rules/StateConsolidator.scala +++ b/src/main/scala/rules/StateConsolidator.scala @@ -140,7 +140,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol newlyAddedChunks.filter(_.isInstanceOf[BasicChunk]) foreach { case ch: BasicChunk => val resource = Resources.resourceDescriptions(ch.resourceID) val pathCond = interpreter.buildPathConditionsForChunk(ch, resource.instanceProperties(s.mayAssumeUpperBounds)) - pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), AssumptionType.PathCondition)) + pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), AssumptionType.Implicit)) } v.symbExLog.closeScope(sepIdentifier) diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index 3670c9c10..60843cca9 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -117,7 +117,7 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif executionFlowController.locally(s2a, v2)((s3, v3) => { exec(s3, body, v3)((s4, v4) =>{ // TODO ake: set source - consumes(s4, posts, false, postViolated, v4, v4.decider.assumptionAnalyzer.getAnalysisInfo)((_, _, _, _) => { + consumes(s4, posts, false, postViolated, v4, v4.decider.assumptionAnalyzer.getAnalysisInfo(AssumptionType.Explicit))((_, _, _, _) => { Success() })})}) } )})}) diff --git a/src/test/resources/andrea/quickTest.vpr b/src/test/resources/andrea/quickTest.vpr index f30577de4..fc2148be2 100644 --- a/src/test/resources/andrea/quickTest.vpr +++ b/src/test/resources/andrea/quickTest.vpr @@ -1,7 +1,7 @@ field f: Int method maybeAlias(a: Ref, b: Ref, c: Bool, n: Int) - requires acc(a.f, 1/2) && acc(b.f, 1/2) + requires acc(a.f) && acc(b.f, 1/2) requires c ==> a == b requires a.f > 0 && n > 0 { From 932593d515a5bafbe5df022373aa55f0392da868 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 19 May 2025 16:07:22 +0200 Subject: [PATCH 053/474] fixes --- src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala | 4 ++++ .../scala/assumptionAnalysis/AssumptionAnalysisGraph.scala | 4 ++-- src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala | 4 ++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala index 2fe3b7f5f..cba835b94 100644 --- a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala +++ b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala @@ -16,6 +16,8 @@ abstract class AnalysisSourceInfo { } def getPosition: Position + + def getTopLevelSource: AnalysisSourceInfo = this } case class NoAnalysisSourceInfo() extends AnalysisSourceInfo { @@ -59,4 +61,6 @@ case class CompositeAnalysisSourceInfo(coarseGrainedSource: AnalysisSourceInfo, override def getPosition: Position = coarseGrainedSource.getPosition override def equals(obj: Any): Boolean = coarseGrainedSource.equals(obj) // TODO ake + + override def getTopLevelSource: AnalysisSourceInfo = coarseGrainedSource } diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index 4c6f02066..2376dab14 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -55,7 +55,7 @@ trait AssumptionAnalysisGraph { def getNodesPerSourceInfo(): mutable.HashMap[AnalysisSourceInfo, Seq[AssumptionAnalysisNode]] = { val res = new mutable.HashMap[AnalysisSourceInfo, Seq[AssumptionAnalysisNode]]() nodes foreach {n => - res.updateWith(n.sourceInfo)({ + res.updateWith(n.sourceInfo.getTopLevelSource)({ case Some(ns) => Some(ns ++ Seq(n)) case None => Some(Seq(n)) }) @@ -119,7 +119,7 @@ class DefaultAssumptionAnalysisGraph extends AssumptionAnalysisGraph { override def addEdges(source: Int, targets: Iterable[Int]): Unit = { val oldTargets = edges.getOrElse(source, Set.empty) - val newTargets = targets filter(t => t > source) // TODO ake: only forward edges? + val newTargets = targets filter(t => t > source) // only forward edges edges.update(source, oldTargets ++ newTargets) } diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index c39ba3293..be13f5d1e 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -30,7 +30,7 @@ trait AssumptionAnalyzer { val assumptionGraph: AssumptionAnalysisGraph = new DefaultAssumptionAnalysisGraph() private var currentSourceInfo: AnalysisSourceInfo = NoAnalysisSourceInfo() - var currentExpStack: InsertionOrderedSet[ast.Exp] = InsertionOrderedSet.empty + private var currentExpStack: List[ast.Exp] = List.empty def getAnalysisInfo: AnalysisInfo = getAnalysisInfo(AssumptionType.Implicit) @@ -56,7 +56,7 @@ trait AssumptionAnalyzer { } def addExpToStack(e: ast.Exp): Unit = { - currentExpStack = InsertionOrderedSet(Set(e) ++ currentExpStack) + currentExpStack = e +: currentExpStack } def popExpFromStack(): Unit = { From 9c06f98ada10f4c16392777f9ba6bbc1d1d0e558 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 20 May 2025 10:21:28 +0200 Subject: [PATCH 054/474] review consume functions --- .../AssumptionAnalyzer.scala | 14 ++-- src/main/scala/decider/Decider.scala | 2 +- src/main/scala/interfaces/Verification.scala | 2 +- src/main/scala/rules/ChunkSupporter.scala | 17 ++--- src/main/scala/rules/Consumer.scala | 70 +++++++++---------- .../rules/MoreCompleteExhaleSupporter.scala | 18 ++--- .../scala/rules/QuantifiedChunkSupport.scala | 1 + 7 files changed, 64 insertions(+), 60 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index be13f5d1e..bc4360d35 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -11,9 +11,9 @@ import viper.silver.ast._ trait AssumptionAnalyzer { def addPermissionInhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] - def addPermissionAssertNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Assertion): Option[Int] - def addPermissionExhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Assertion): Option[Int] - def addPermissionNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Assertion, isExhale: Boolean=false): Option[Int] + def addPermissionAssertNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Explicit): Option[Int] + def addPermissionExhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Explicit): Option[Int] + def addPermissionNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Explicit, isExhale: Boolean=false): Option[Int] def addSingleAssumption(assumption: DebugExp, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] @@ -137,21 +137,21 @@ class DefaultAssumptionAnalyzer(member: Member) extends AssumptionAnalyzer { Some(node.id) } - override def addPermissionAssertNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Assertion): Option[Int] = { - val node = PermissionAssertNode(chunk, permAmount, sourceInfo, assumptionType) + override def addPermissionAssertNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Explicit): Option[Int] = { + val node = PermissionAssertNode(chunk, permAmount, sourceInfo, AssumptionType.Explicit) addNode(node) addPermissionDependencies(Set(chunk), Some(node.id)) Some(node.id) } - override def addPermissionExhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Assertion): Option[Int] = { + override def addPermissionExhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Explicit): Option[Int] = { val node = PermissionExhaleNode(chunk, permAmount, sourceInfo, assumptionType) addNode(node) addPermissionDependencies(Set(chunk), Some(node.id)) Some(node.id) } - override def addPermissionNode(chunk: Chunk, permAmount: Option[Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isExhale: Boolean=false): Option[Int] = { + override def addPermissionNode(chunk: Chunk, permAmount: Option[Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Explicit, isExhale: Boolean=false): Option[Int] = { val nodeFunction = if(isExhale) addPermissionExhaleNode _ else addPermissionInhaleNode _ nodeFunction(chunk, permAmount, sourceInfo, assumptionType) } diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 09fefd206..610379b1f 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -9,7 +9,7 @@ package viper.silicon.decider import com.typesafe.scalalogging.Logger import viper.silicon._ import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType -import viper.silicon.assumptionAnalysis.{AnalysisInfo, AnalysisSourceInfo, AssumptionAnalyzer, AssumptionType, DefaultAssumptionAnalyzer, NoAssumptionAnalyzer, StringAnalysisSourceInfo} +import viper.silicon.assumptionAnalysis._ import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.interfaces._ diff --git a/src/main/scala/interfaces/Verification.scala b/src/main/scala/interfaces/Verification.scala index e78bc26f5..4b7ce3f2f 100644 --- a/src/main/scala/interfaces/Verification.scala +++ b/src/main/scala/interfaces/Verification.scala @@ -31,7 +31,7 @@ sealed abstract class VerificationResult { var previous: Vector[VerificationResult] = Vector() //Sets had problems with equality val continueVerification: Boolean = true var isReported: Boolean = false - var assumptionAnalyzer: AssumptionAnalyzer = new NoAssumptionAnalyzer() // TODO ake + var assumptionAnalyzer: AssumptionAnalyzer = new NoAssumptionAnalyzer() def isFatal: Boolean def &&(other: => VerificationResult): VerificationResult diff --git a/src/main/scala/rules/ChunkSupporter.scala b/src/main/scala/rules/ChunkSupporter.scala index dee2b98ce..9f4f068fe 100644 --- a/src/main/scala/rules/ChunkSupporter.scala +++ b/src/main/scala/rules/ChunkSupporter.scala @@ -83,10 +83,10 @@ object chunkSupporter extends ChunkSupportRules { : VerificationResult = { val analysisInfo = v.decider.assumptionAnalyzer.getAnalysisInfo - consume2(s, h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, analysisInfo)((s2, h2, optSnap, chunk, v2) => + consume2(s, h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, analysisInfo)((s2, h2, optSnap, consumedChunks, v2) => optSnap match { case Some(snap) => - Q(s2, h2, Some(snap.convert(sorts.Snap)), chunk, v2) + Q(s2, h2, Some(snap.convert(sorts.Snap)), consumedChunks, v2) case None if returnSnap => /* Not having consumed anything could mean that we are in an infeasible * branch, or that the permission amount to consume was zero. @@ -97,8 +97,8 @@ object chunkSupporter extends ChunkSupportRules { */ val fresh = v2.decider.fresh(sorts.Snap, Option.when(withExp)(PUnknown())) val s3 = s2.copy(functionRecorder = s2.functionRecorder.recordFreshSnapshot(fresh.applicable)) - Q(s3, h2, Some(fresh), chunk, v2) - case None => Q(s2, h2, None, chunk, v2) + Q(s3, h2, Some(fresh), consumedChunks, v2) + case None => Q(s2, h2, None, consumedChunks, v2) }) } @@ -121,7 +121,7 @@ object chunkSupporter extends ChunkSupportRules { if (s.exhaleExt) { val failure = createFailure(ve, v, s, "chunk consume in package") magicWandSupporter.transfer(s, perms, permsExp, failure, Seq(), v)(consumeGreedy(_, _, id, args, _, _, _, analysisInfo))((s1, optCh, v1) => - if (returnSnap){ + if (returnSnap){ // TODO ake: check that optCh is indeed the consumed chunks Q(s1, h, optCh.flatMap(ch => Some(ch.snap)), optCh, v1) } else { Q(s1, h, None, optCh, v1) @@ -129,8 +129,8 @@ object chunkSupporter extends ChunkSupportRules { } else { executionFlowController.tryOrFail3[Heap, Option[Term], Iterable[Chunk]](s.copy(h = h), v)((s1, v1, QS) => if (s1.moreCompleteExhale) { - moreCompleteExhaleSupporter.consumeComplete(s1, s1.h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v1, analysisInfo)((s2, h2, snap2, chunks, v2) => { - QS(s2.copy(h = s.h), h2, snap2, chunks, v2) + moreCompleteExhaleSupporter.consumeComplete(s1, s1.h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v1, analysisInfo)((s2, h2, snap2, consumedChunks, v2) => { + QS(s2.copy(h = s.h), h2, snap2, consumedChunks, v2) }) } else { consumeGreedy(s1, s1.h, id, args, perms, permsExp, v1, analysisInfo) match { @@ -178,6 +178,7 @@ object chunkSupporter extends ChunkSupportRules { case Some(ch) => if (s.assertReadAccessOnly) { if (v.decider.check(Implies(IsPositive(perms), IsPositive(ch.perm)), Verifier.config.assertTimeout.getOrElse(0))) { + // TODO ake: add edge from check to permission assert node v.decider.assumptionAnalyzer.addPermissionAssertNode(ch, ch.permExp.map(pe => IsPositive(pe)(pe.pos, pe.info, pe.errT)), v.decider.assumptionAnalyzer.getFullSourceInfo) (Complete(), s, h, Some(ch)) } else { @@ -188,7 +189,7 @@ object chunkSupporter extends ChunkSupportRules { val toTakeExp = permsExp.map(pe => buildMinExp(Seq(ch.permExp.get, pe), ast.Perm)) val newPermExp = permsExp.map(pe => ast.PermSub(ch.permExp.get, toTakeExp.get)(pe.pos, pe.info, pe.errT)) val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, toTake), newPermExp, analysisInfo).asInstanceOf[NonQuantifiedChunk] - val takenChunk = Some(GeneralChunk.withPerm(ch, toTake, toTakeExp, analysisInfo, isExhale = true).asInstanceOf[NonQuantifiedChunk]) + val takenChunk = Some(GeneralChunk.withPerm(ch, toTake, toTakeExp, analysisInfo, isExhale=true).asInstanceOf[NonQuantifiedChunk]) var newHeap = h - ch if (!v.decider.check(newChunk.perm === NoPerm, Verifier.config.checkTimeout())) { newHeap = newHeap + newChunk diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index 680dc7cf5..03b5f0c47 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -80,10 +80,10 @@ object consumer extends ConsumptionRules { (Q: (State, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { - consumeR(s, s.h, a.whenExhaling, returnSnap, pve, v, analysisInfo)((s1, h1, snap, chunks, v1) => { + consumeR(s, s.h, a.whenExhaling, returnSnap, pve, v, analysisInfo)((s1, h1, snap, consumedChunks, v1) => { val s2 = s1.copy(h = h1, partiallyConsumedHeap = s.partiallyConsumedHeap) - Q(s2, snap, chunks, v1)}) + Q(s2, snap, consumedChunks, v1)}) } /** @inheritdoc */ @@ -133,12 +133,12 @@ object consumer extends ConsumptionRules { if (tlcs.tail.isEmpty) wrappedConsumeTlc(s, h, a, returnSnap, pve, v, analysisInfo)(Q) else - wrappedConsumeTlc(s, h, a, returnSnap, pve, v, analysisInfo)((s1, h1, snap1, chunksHead, v1) => { - consumeTlcs(s1, h1, tlcs.tail, returnSnap, pves.tail, v1, analysisInfo)((s2, h2, snap2, chunksTail, v2) => + wrappedConsumeTlc(s, h, a, returnSnap, pve, v, analysisInfo)((s1, h1, snap1, consumedChunksHead, v1) => { + consumeTlcs(s1, h1, tlcs.tail, returnSnap, pves.tail, v1, analysisInfo)((s2, h2, snap2, consumedChunksTail, v2) => (snap1, snap2) match { - case (Some(sn1), Some(sn2)) if returnSnap => Q(s2, h2, Some(Combine(sn1, sn2)), chunksHead ++ chunksTail, v2) - case (None, None) if !returnSnap => Q(s2, h2, None, chunksHead ++ chunksTail, v2) + case (Some(sn1), Some(sn2)) if returnSnap => Q(s2, h2, Some(Combine(sn1, sn2)), consumedChunksHead ++ consumedChunksTail, v2) + case (None, None) if !returnSnap => Q(s2, h2, None, consumedChunksHead ++ consumedChunksTail, v2) case (_, _) => sys.error(s"Consume returned unexpected snapshot: ${(returnSnap, (snap1, snap2))}") }) }) @@ -181,10 +181,10 @@ object consumer extends ConsumptionRules { val sepIdentifier = v1.symbExLog.openScope(new ConsumeRecord(a, s1, v.decider.pcs)) v.decider.assumptionAnalyzer.addExpToStack(a) - consumeTlc(s1, h0, a, returnSnap, pve, v1, analysisInfo)((s2, h2, snap2, chunks, v2) => { + consumeTlc(s1, h0, a, returnSnap, pve, v1, analysisInfo)((s2, h2, snap2, consumedChunks, v2) => { v.decider.assumptionAnalyzer.popExpFromStack() v2.symbExLog.closeScope(sepIdentifier) - QS(s2, h2, snap2, chunks, v2)}) + QS(s2, h2, snap2, consumedChunks, v2)}) })(Q) } @@ -217,9 +217,9 @@ object consumer extends ConsumptionRules { evaluator.eval(s, e0, pve, v)((s1, t0, e0New, v1) => branch(s1, t0, (e0, e0New), v1)( - (s2, v2) => consumeR(s2, h, a0, returnSnap, pve, v2, analysisInfo)((s3, h1, t1, chunks, v3) => { + (s2, v2) => consumeR(s2, h, a0, returnSnap, pve, v2, analysisInfo)((s3, h1, t1, consumedChunks, v3) => { v3.symbExLog.closeScope(uidImplies) - Q(s3, h1, t1, chunks, v3) + Q(s3, h1, t1, consumedChunks, v3) }), (s2, v2) => { v2.symbExLog.closeScope(uidImplies) @@ -237,13 +237,13 @@ object consumer extends ConsumptionRules { eval(s, e0, pve, v)((s1, t0, e0New, v1) => branch(s1, t0, (e0, e0New), v1)( - (s2, v2) => consumeR(s2, h, a1, returnSnap, pve, v2, analysisInfo)((s3, h1, t1, chunks, v3) => { + (s2, v2) => consumeR(s2, h, a1, returnSnap, pve, v2, analysisInfo)((s3, h1, t1, consumedChunks, v3) => { v3.symbExLog.closeScope(uidCondExp) - Q(s3, h1, t1, chunks, v3) + Q(s3, h1, t1, consumedChunks, v3) }), - (s2, v2) => consumeR(s2, h, a2, returnSnap, pve, v2, analysisInfo)((s3, h1, t1, chunks, v3) => { + (s2, v2) => consumeR(s2, h, a2, returnSnap, pve, v2, analysisInfo)((s3, h1, t1, consumedChunks, v3) => { v3.symbExLog.closeScope(uidCondExp) - Q(s3, h1, t1, chunks, v3) + Q(s3, h1, t1, consumedChunks, v3) }))) /* TODO: Initial handling of QPs is identical/very similar in consumer @@ -285,8 +285,8 @@ object consumer extends ConsumptionRules { notInjectiveReason = QPAssertionNotInjective(acc.loc), insufficientPermissionReason = InsufficientPermission(acc.loc), v1, - analysisInfo)((s2, h2, snap, chunks, v2) => Q(s2.copy(constrainableARPs = s.constrainableARPs), h2, snap, chunks, v2)) - case (s1, _, _, _, _, None, v1) => Q(s1, h, if (returnSnap) Some(Unit) else None, Seq.empty, v1) // TODO ake: verify this + analysisInfo)((s2, h2, snap, consumedChunks, v2) => Q(s2.copy(constrainableARPs = s.constrainableARPs), h2, snap, consumedChunks, v2)) + case (s1, _, _, _, _, None, v1) => Q(s1, h, if (returnSnap) Some(Unit) else None, Seq.empty, v1) } case QuantifiedPermissionAssertion(forall, cond, acc: ast.PredicateAccessPredicate) => @@ -332,7 +332,7 @@ object consumer extends ConsumptionRules { notInjectiveReason = QPAssertionNotInjective(acc.loc), insufficientPermissionReason = InsufficientPermission(acc.loc), v1, - analysisInfo)((s2, h2, snap, chunks, v2) => Q(s2.copy(constrainableARPs = s.constrainableARPs), h2, snap, chunks, v2)) + analysisInfo)((s2, h2, snap, consumedChunks, v2) => Q(s2.copy(constrainableARPs = s.constrainableARPs), h2, snap, consumedChunks, v2)) case (s1, _, _, _, _, None, v1) => Q(s1, h, if (returnSnap) Some(Unit) else None, Seq.empty, v1) } @@ -375,7 +375,7 @@ object consumer extends ConsumptionRules { notInjectiveReason = sys.error("Quantified wand not injective"), /*ReceiverNotInjective(...)*/ insufficientPermissionReason = MagicWandChunkNotFound(wand), /*InsufficientPermission(...)*/ v1, - analysisInfo)((s2, h2, snap, chunks, v2) => Q(s2.copy(constrainableARPs = s.constrainableARPs), h2, snap, chunks, v2)) + analysisInfo)((s2, h2, snap, consumedChunks, v2) => Q(s2.copy(constrainableARPs = s.constrainableARPs), h2, snap, consumedChunks, v2)) case (s1, _, _, _, _, None, v1) => Q(s1.copy(constrainableARPs = s.constrainableARPs), h, if (returnSnap) Some(Unit) else None, Seq.empty, v1) } @@ -417,10 +417,10 @@ object consumer extends ConsumptionRules { pve, v2, analysisInfo - )((s3, h3, snap, chunks, v3) => { + )((s3, h3, snap, consumedChunks, v3) => { val s4 = s3.copy(constrainableARPs = s1.constrainableARPs, partiallyConsumedHeap = Some(h3)) - Q(s4, h3, snap, chunks, v3)})})) + Q(s4, h3, snap, consumedChunks, v3)})})) case ast.AccessPredicate(loc @ ast.PredicateAccess(eArgs, predname), ePerm) if s.qpPredicates.contains(s.program.findPredicate(predname)) => @@ -463,10 +463,10 @@ object consumer extends ConsumptionRules { pve, v2, analysisInfo - )((s3, h3, snap, chunks, v3) => { + )((s3, h3, snap, consumedChunks, v3) => { val s4 = s3.copy(constrainableARPs = s1.constrainableARPs, partiallyConsumedHeap = Some(h3)) - Q(s4, h3, snap, chunks, v3)})})) + Q(s4, h3, snap, consumedChunks, v3)})})) case let: ast.Let if !let.isPure => letSupporter.handle[ast.Exp](s, let, pve, v)((s1, g1, body, v1) => { @@ -485,10 +485,10 @@ object consumer extends ConsumptionRules { val lossExp = permNew.map(p => ast.PermMul(p, s3.permissionScalingFactorExp.get)(p.pos, p.info, p.errT)) val ve = pve dueTo InsufficientPermission(locacc) val description = s"consume ${a.pos}: $a" - chunkSupporter.consume(s3, h, resource, tArgs, eArgs, loss, lossExp, returnSnap, ve, v3, description, analysisInfo)((s4, h1, snap1, chunks, v4) => { + chunkSupporter.consume(s3, h, resource, tArgs, eArgs, loss, lossExp, returnSnap, ve, v3, description, analysisInfo)((s4, h1, snap1, consumedChunks, v4) => { val s5 = s4.copy(partiallyConsumedHeap = Some(h1), constrainableARPs = s.constrainableARPs) - Q(s5, h1, snap1, chunks, v4)})}))) + Q(s5, h1, snap1, consumedChunks, v4)})}))) case _: ast.InhaleExhaleExp => createFailure(viper.silicon.utils.consistency.createUnexpectedInhaleExhaleExpressionError(a), v, s, "valid AST") @@ -530,10 +530,10 @@ object consumer extends ConsumptionRules { pve, v1, analysisInfo - )((s3, h3, snap, chunks, v3) => { + )((s3, h3, snap, consumedChunks, v3) => { val s4 = s3.copy(constrainableARPs = s1.constrainableARPs, partiallyConsumedHeap = Some(h3)) - Q(s4, h3, snap, chunks, v3)})}) + Q(s4, h3, snap, consumedChunks, v3)})}) case wand: ast.MagicWand => magicWandSupporter.evaluateWandArguments(s, wand, pve, v)((s1, tArgs, eArgs, v1) => { @@ -547,7 +547,6 @@ object consumer extends ConsumptionRules { Q(s1, h, t, Seq.empty, v1) }) } - consumed } @@ -558,22 +557,22 @@ object consumer extends ConsumptionRules { (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { eval(s, e0, pve, v)((s1, t0, e0New, v1) => - joiner.join[(Heap, Option[Term]), (Heap, Option[Term])](s1, v1, resetState = false)((s1, v1, QB) => { // TODO ake: what to do with chunks? + joiner.join[(Heap, Option[Term], Iterable[Chunk]), (Heap, Option[Term], Iterable[Chunk])](s1, v1, resetState = false)((s1, v1, QB) => { branch(s1.copy(parallelizeBranches = false), t0, (e0, e0New), v1)( (s2, v2) => - consumeR(s2.copy(parallelizeBranches = s1.parallelizeBranches), h, a1, returnSnap, pve, v2, analysisInfo)((s3, h1, t1, chunks, v3) => { + consumeR(s2.copy(parallelizeBranches = s1.parallelizeBranches), h, a1, returnSnap, pve, v2, analysisInfo)((s3, h1, t1, consumedChunks, v3) => { v3.symbExLog.closeScope(scopeUid) - QB(s3, (h1, t1), v3) + QB(s3, (h1, t1, consumedChunks), v3) }), (s2, v2) => a2 match { - case Some(a2) => consumeR(s2.copy(parallelizeBranches = s1.parallelizeBranches), h, a2, returnSnap, pve, v2, analysisInfo)((s3, h1, t1, chunks, v3) => { + case Some(a2) => consumeR(s2.copy(parallelizeBranches = s1.parallelizeBranches), h, a2, returnSnap, pve, v2, analysisInfo)((s3, h1, t1, consumedChunks, v3) => { v3.symbExLog.closeScope(scopeUid) - QB(s3, (h1, t1), v3) + QB(s3, (h1, t1, consumedChunks), v3) }) case None => v2.symbExLog.closeScope(scopeUid) - QB(s2.copy(parallelizeBranches = s1.parallelizeBranches), (h, if (returnSnap) Some(Unit) else None), v2) + QB(s2.copy(parallelizeBranches = s1.parallelizeBranches), (h, if (returnSnap) Some(Unit) else None, Seq.empty), v2) }) })(entries => { val s2 = entries match { @@ -591,7 +590,8 @@ object consumer extends ConsumptionRules { case (Some(t1), Some(t2)) if returnSnap => Some(Ite(And(entry1.pathConditions.branchConditions), t1, t2)) case (None, None) if !returnSnap => None case (_, _) => sys.error(s"Unexpected join data entries: $entries") - } + }, + entry1.data._3 ++ entry2.data._3 ) (entry1.pathConditionAwareMergeWithoutConsolidation(entry2, v1), mergedData) case _ => @@ -599,7 +599,7 @@ object consumer extends ConsumptionRules { } s2 })((s4, data, v4) => { - Q(s4, data._1, data._2, Seq.empty, v4) // TODO ake: get chunks + Q(s4, data._1, data._2, data._3, v4) }) ) } diff --git a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala index b5a972a15..01e47c4ea 100644 --- a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala +++ b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala @@ -173,7 +173,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { } val (s1, taggedSnap, snapDefs, permSum, permSumExp) = summariseOnly(s, relevantChunks, resource, args, argsExp, knownValue, v) - v.decider.assumeDefinition(And(snapDefs), Option.when(withExp)(DebugExp.createInstance("Snapshot", true)), AssumptionType.Internal) + v.decider.assumeDefinition(And(snapDefs), Option.when(withExp)(DebugExp.createInstance("Snapshot", isInternal_ = true)), AssumptionType.Internal) // v.decider.assume(PermAtMost(permSum, FullPerm())) /* Done in StateConsolidator instead */ val s2 = @@ -314,7 +314,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { } else { if (!terms.utils.consumeExactRead(perms, s.constrainableARPs)) { actualConsumeCompleteConstrainable(s, relevantChunks, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, analysisInfo)((s1, updatedChunks, optSnap, v2) => { - Q(s1, Heap(updatedChunks ++ otherChunks), optSnap, updatedChunks, v2) // TODO ake: verify returned chunk list (updatedChunks) + Q(s1, Heap(updatedChunks ++ otherChunks), optSnap, updatedChunks, v2) // TODO ake: updatedChunks != consumedChunks }) } else { var pNeeded = perms @@ -322,6 +322,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { var pSum: Term = NoPerm var pSumExp: Option[ast.Exp] = permsExp.map(pe => ast.NoPerm()(pe.pos, pe.info, pe.errT)) val newChunks = ListBuffer[NonQuantifiedChunk]() + val consumedChunks = ListBuffer[NonQuantifiedChunk]() var moreNeeded = true val definiteAlias = chunkSupporter.findChunk[NonQuantifiedChunk](relevantChunks, id, args, v).filter(c => @@ -372,6 +373,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { if (!v.decider.check(IsNonPositive(newChunk.perm), Verifier.config.splitTimeout())) { newChunks.append(newChunk) + consumedChunks.append(ch) } moreNeeded = !v.decider.check(pNeeded === NoPerm, Verifier.config.splitTimeout()) @@ -387,7 +389,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { newChunks foreach { ch => val resource = Resources.resourceDescriptions(ch.resourceID) val pathCond = interpreter.buildPathConditionsForChunk(ch, resource.instanceProperties(s.mayAssumeUpperBounds)) - pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), AssumptionType.PathCondition)) + pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), AssumptionType.Unknown)) } val newHeap = Heap(allChunks) @@ -403,12 +405,12 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { Ite(IsPositive(perms), snap.convert(sorts.Snap), Unit) }) if (!moreNeeded) { - Q(s1, newHeap, condSnap, relevantChunks, v1) // FIXME ake: return chunks + Q(s1, newHeap, condSnap, consumedChunks, v1) } else { val assertExp = pNeededExp.map(pn => ast.EqCmp(pn, ast.NoPerm()())(pn.pos, pn.info, pn.errT)) v1.decider.assert(pNeeded === NoPerm, assertExp) { case true => - Q(s1, newHeap, condSnap, relevantChunks, v1) // FIXME ake: return chunks + Q(s1, newHeap, condSnap, consumedChunks, v1) case false => createFailure(ve, v1, s1, pNeeded === NoPerm, assertExp) } @@ -416,12 +418,12 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { }) } else { if (!moreNeeded) { - Q(s0, newHeap, None, Seq.empty, v) // FIXME ake: return chunks + Q(s0, newHeap, None, consumedChunks, v) } else { val assertExp = pNeededExp.map(pn => ast.EqCmp(pn, ast.NoPerm()())(pn.pos, pn.info, pn.errT)) v.decider.assert(pNeeded === NoPerm, assertExp) { case true => - Q(s0, newHeap, None, Seq.empty, v) // FIXME ake: return chunks + Q(s0, newHeap, None, consumedChunks, v) case false => createFailure(ve, v, s0, pNeeded === NoPerm, assertExp) } @@ -507,7 +509,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { v.decider.assume(perms === totalPermTaken, Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp)), AssumptionType.Unknown) if (returnSnap) { summarise(s1, relevantChunks.toSeq, resource, args, argsExp, None, v)((s2, snap, _, _, v1) => - Q(s2, updatedChunks, Some(snap), v1)) + Q(s2, updatedChunks, Some(snap), v1)) // TODO ake: updatedChunks are the new chunks, not the old consumed ones! } else { Q(s1, updatedChunks, None, v) } diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index a3b4ca746..629f3c5e9 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -1587,6 +1587,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { else Incomplete(PermMinus(permsAvailable, perms), permsAvailableExp.map(pa => ast.PermSub(pa, permsExp.get)())) + // TODO ake: add permission assert node? result } From a453e411a4d0d6ab085142891efc6c4837ad7b00 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 20 May 2025 16:01:11 +0200 Subject: [PATCH 055/474] minor fixes --- .../AssumptionAnalysisGraph.scala | 3 +- .../AssumptionAnalyzer.scala | 18 ++--- src/main/scala/rules/Executor.scala | 6 +- src/main/scala/rules/HavocSupporter.scala | 2 +- src/test/resources/andrea/magicWands.vpr | 75 +++++++++++++++++++ src/test/resources/andrea/permissions.vpr | 11 ++- src/test/resources/andrea/predicates-fold.vpr | 7 ++ src/test/resources/andrea/quickTest.vpr | 27 +++++-- 8 files changed, 128 insertions(+), 21 deletions(-) create mode 100644 src/test/resources/andrea/magicWands.vpr diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index 2376dab14..9d4c86eef 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -120,7 +120,8 @@ class DefaultAssumptionAnalysisGraph extends AssumptionAnalysisGraph { override def addEdges(source: Int, targets: Iterable[Int]): Unit = { val oldTargets = edges.getOrElse(source, Set.empty) val newTargets = targets filter(t => t > source) // only forward edges - edges.update(source, oldTargets ++ newTargets) + if(newTargets.nonEmpty) + edges.update(source, oldTargets ++ newTargets) } override def addEdges(sources: Iterable[Int], target: Int): Unit = { diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index bc4360d35..f1d878413 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -29,7 +29,7 @@ trait AssumptionAnalyzer { val assumptionGraph: AssumptionAnalysisGraph = new DefaultAssumptionAnalysisGraph() - private var currentSourceInfo: AnalysisSourceInfo = NoAnalysisSourceInfo() + private var coarseGrainedSourceStack: List[AnalysisSourceInfo] = List.empty private var currentExpStack: List[ast.Exp] = List.empty def getAnalysisInfo: AnalysisInfo = getAnalysisInfo(AssumptionType.Implicit) @@ -38,21 +38,21 @@ trait AssumptionAnalyzer { def getFullSourceInfo: AnalysisSourceInfo = { if(currentExpStack.isEmpty){ - currentSourceInfo - }else if(currentSourceInfo.isInstanceOf[NoAnalysisSourceInfo]){ + coarseGrainedSourceStack.headOption.getOrElse(NoAnalysisSourceInfo()) + }else if(coarseGrainedSourceStack.isEmpty){ ExpAnalysisSourceInfo(currentExpStack.head) }else{ - CompositeAnalysisSourceInfo(currentSourceInfo, ExpAnalysisSourceInfo(currentExpStack.head)) + CompositeAnalysisSourceInfo(coarseGrainedSourceStack.head, ExpAnalysisSourceInfo(currentExpStack.head)) } } - def setCurrentSourceInfo(analysisSourceInfo: AnalysisSourceInfo): AnalysisSourceInfo = { - currentSourceInfo = analysisSourceInfo - currentSourceInfo + def addAnalysisSourceInfo(analysisSourceInfo: AnalysisSourceInfo): AnalysisSourceInfo = { + coarseGrainedSourceStack = analysisSourceInfo +: coarseGrainedSourceStack + analysisSourceInfo } - def clearCurrentAnalysisInfo(): Unit = { - currentSourceInfo = NoAnalysisSourceInfo() + def popAnalysisSourceInfo(): Unit = { + coarseGrainedSourceStack = coarseGrainedSourceStack.tail } def addExpToStack(e: ast.Exp): Unit = { diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index ac228abb8..e85476ab0 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -279,7 +279,9 @@ object executor extends ExecutionRules { val s2 = s1.copy(invariantContexts = sLeftover.h +: s1.invariantContexts) intermediateResult combine executionFlowController.locally(s2, v1)((s3, v2) => { v2.decider.declareAndRecordAsFreshFunctions(ff1 -- v2.decider.freshFunctions) /* [BRANCH-PARALLELISATION] */ + v2.decider.assumptionAnalyzer.addAnalysisSourceInfo(ExpAnalysisSourceInfo(BigAnd(pcs.assumptionExps.filter(_.originalExp.isDefined).map(_.originalExp.get)))) v2.decider.assume(pcs.assumptions, Option.when(withExp)(DebugExp.createInstance("Loop invariant", pcs.assumptionExps)), false, assumptionType=AssumptionType.LoopInvariant) + v2.decider.assumptionAnalyzer.popAnalysisSourceInfo() v2.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) if (v2.decider.checkSmoke()) Success() @@ -326,9 +328,9 @@ object executor extends ExecutionRules { (Q: (State, Verifier) => VerificationResult) : VerificationResult = { val sepIdentifier = v.symbExLog.openScope(new ExecuteRecord(stmt, s, v.decider.pcs)) - v.decider.assumptionAnalyzer.setCurrentSourceInfo(StmtAnalysisSourceInfo(stmt)) + v.decider.assumptionAnalyzer.addAnalysisSourceInfo(StmtAnalysisSourceInfo(stmt)) exec2(s, stmt, v)((s1, v1) => { - v.decider.assumptionAnalyzer.clearCurrentAnalysisInfo() + v.decider.assumptionAnalyzer.popAnalysisSourceInfo() v1.symbExLog.closeScope(sepIdentifier) Q(s1, v1)}) } diff --git a/src/main/scala/rules/HavocSupporter.scala b/src/main/scala/rules/HavocSupporter.scala index 5d05b7cb7..3d815c40e 100644 --- a/src/main/scala/rules/HavocSupporter.scala +++ b/src/main/scala/rules/HavocSupporter.scala @@ -128,7 +128,7 @@ object havocSupporter extends SymbolicExecutionRules { val comment = "QP receiver injectivity check is well-defined" val injectivityDebugExp = Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)) v.decider.assume(FunctionPreconditionTransformer.transform(receiverInjectivityCheck, s.program), injectivityDebugExp, AssumptionType.Internal) - v.decider.assert(receiverInjectivityCheck, comment, Verifier.config.checkTimeout.toOption) { + v.decider.assert(receiverInjectivityCheck, comment, Verifier.config.assertTimeout.toOption) { case false => createFailure(pve dueTo notInjectiveReason, v, s1, receiverInjectivityCheck, "QP receiver injective") case true => // Generate the inverse axioms diff --git a/src/test/resources/andrea/magicWands.vpr b/src/test/resources/andrea/magicWands.vpr new file mode 100644 index 000000000..aa15eafca --- /dev/null +++ b/src/test/resources/andrea/magicWands.vpr @@ -0,0 +1,75 @@ +field next : Ref +field val : Int + +predicate list(start : Ref) +{ + acc(start.val) && acc(start.next) && + (start.next != null && list(start.next)) +} + + +method basicApply() +{ + var x: Int + var y: Int + inhale x > 0 + inhale x > 0 --* y > 0 + apply x > 0 --* y > 0 + assert y > 0 +} + + +method basicPackage(l: Ref) + requires list(l) + ensures list(l) + { + unfold list(l) + var tmp : Ref := l.next + + package list(tmp) --* list(l) + { + fold list(l) + } + + apply list(tmp) --* list(l) +} + +method appendit_wand(l1 : Ref, l2: Ref) + requires list(l1) && list(l2) && l2 != null + ensures list(l1) // && elems(l1) == old(elems(l1) ++ elems(l2)) + { + unfold list(l1) + if(l1.next == null) { // easy case + l1.next := l2; fold list(l1) + } else { + var tmp : Ref := l1.next + var index : Int := 1 + + // package the magic wand required in the loop invariant below + package list(tmp) --* list(l1) + { // show how to get from list(tmp) to list(l1): + fold list(l1) // also requires acc(l1.val) && acc(l1.next) + } + + while(unfolding list(tmp) in tmp.next != null) + invariant index >= 0 + invariant list(tmp)// && elems(tmp) == old(elems(l1))[index..] + invariant list(tmp) --* list(l1) // magic wand instance + { + unfold list(tmp) + var prev : Ref := tmp + tmp := tmp.next + index := index + 1 + + package list(tmp) --* list(l1) // package new magic wand + { // we get from list(tmp) to list(l1) by ... + fold list(prev) + apply list(prev) --* list(l1) + } + } + unfold list(tmp) + tmp.next := l2 + fold list(tmp) + apply list(tmp) --* list(l1) // regain predicate for whole list + } + } \ No newline at end of file diff --git a/src/test/resources/andrea/permissions.vpr b/src/test/resources/andrea/permissions.vpr index 53150fcfe..0f1f85cde 100644 --- a/src/test/resources/andrea/permissions.vpr +++ b/src/test/resources/andrea/permissions.vpr @@ -54,7 +54,16 @@ method permAmount(x: Ref, p: Perm) foo(x) } -method quantifiedPerm(xs: Seq[Ref]) +method quantifiedPerm1(xs: Seq[Ref]) +{ + assume |xs| > 5 + inhale forall x: Ref :: x in xs ==> acc(x.f) + + var a : Ref := xs[1] + a.f := 0 +} + +method quantifiedPerm2(xs: Seq[Ref]) { assume |xs| > 5 inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) diff --git a/src/test/resources/andrea/predicates-fold.vpr b/src/test/resources/andrea/predicates-fold.vpr index 87aea9790..f830ccd3b 100644 --- a/src/test/resources/andrea/predicates-fold.vpr +++ b/src/test/resources/andrea/predicates-fold.vpr @@ -33,4 +33,11 @@ method unfoldFoldP(a: Int, b: Int) unfold greater0(a) unfold greater5(b) fold greater5(a + b) +} + +method callWithPredicate(a: Int, b: Int) + requires greater0(a) && greater5(b) + ensures greater5(a + b) +{ + unfoldFoldP(a, b) } \ No newline at end of file diff --git a/src/test/resources/andrea/quickTest.vpr b/src/test/resources/andrea/quickTest.vpr index fc2148be2..53bacd557 100644 --- a/src/test/resources/andrea/quickTest.vpr +++ b/src/test/resources/andrea/quickTest.vpr @@ -1,12 +1,25 @@ field f: Int +field next : Ref +field val : Int -method maybeAlias(a: Ref, b: Ref, c: Bool, n: Int) - requires acc(a.f) && acc(b.f, 1/2) - requires c ==> a == b - requires a.f > 0 && n > 0 +predicate list(start : Ref) { - if(c){ - a.f := n + 1 + acc(start.val) && acc(start.next) && + (start.next != null && list(start.next)) +} + + +method basicPackage(l: Ref) + requires list(l) + ensures list(l) + { + unfold list(l) + var tmp : Ref := l.next + + package list(tmp) --* list(l) + { + fold list(l) } - assert a.f >= 0 + + apply list(tmp) --* list(l) } \ No newline at end of file From eae0cd6ec32321c58a50dcd363dbe5b3eaa2bc0e Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 22 May 2025 08:21:40 +0200 Subject: [PATCH 056/474] add transitive edges for checks --- .../AssumptionAnalysisGraph.scala | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index 9d4c86eef..bb5da50fc 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -63,22 +63,25 @@ trait AssumptionAnalysisGraph { res } - def addTransitiveEdges(source: Int, targets: Iterable[Int]): Unit = { - val oldTargets = transitiveEdges.getOrElse(source, Set.empty) - val newTargets = targets filter(t => t > source) // we only want forward edges - if(newTargets.nonEmpty) transitiveEdges.update(source, oldTargets ++ newTargets) + def addTransitiveEdges(source: AssumptionAnalysisNode, targets: Iterable[AssumptionAnalysisNode]): Unit = { + val oldTargets = transitiveEdges.getOrElse(source.id, Set.empty) + val newTargets = targets filter(t => t.id > source.id) map(_.id) // we only want forward edges + if(newTargets.nonEmpty) transitiveEdges.update(source.id, oldTargets ++ newTargets) } - def addTransitiveEdges(source: Iterable[Int], targets: Iterable[Int]): Unit = { + def addTransitiveEdges(source: Iterable[AssumptionAnalysisNode], targets: Iterable[AssumptionAnalysisNode]): Unit = { source foreach (s => addTransitiveEdges(s, targets)) } def addTransitiveEdges(): Unit = { val nodesPerSourceInfo = getNodesPerSourceInfo() nodesPerSourceInfo foreach {nodes => - val asserts = nodes._2.filter(_.isInstanceOf[GeneralAssertionNode]).map(_.id) - val assumes = nodes._2.filter(_.isInstanceOf[GeneralAssumptionNode]).map(_.id) + val asserts = nodes._2.filter(_.isInstanceOf[GeneralAssertionNode]) + val assumes = nodes._2.filter(_.isInstanceOf[GeneralAssumptionNode]) addTransitiveEdges(asserts, assumes) + val checks = asserts.filter(_.isInstanceOf[SimpleCheckNode]) + val notChecks = asserts.filter(!_.isInstanceOf[SimpleCheckNode]) + addTransitiveEdges(checks, notChecks) } } // def findDependentAssumptions(assertion, enableTransitivity=false) From 95bf7a36aa7981297825d42084fcc245d0647b4c Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 22 May 2025 08:45:03 +0200 Subject: [PATCH 057/474] introduce rewrite assumption type --- .../assumptionAnalysis/AnalysisInfo.scala | 2 +- src/main/scala/rules/Evaluator.scala | 4 ++-- src/main/scala/rules/MagicWandSupporter.scala | 10 ++++---- src/main/scala/rules/PredicateSupporter.scala | 8 +++---- .../functions/FunctionVerificationUnit.scala | 2 +- src/test/resources/andrea/quickTest.vpr | 24 +++++-------------- 6 files changed, 19 insertions(+), 31 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AnalysisInfo.scala b/src/main/scala/assumptionAnalysis/AnalysisInfo.scala index 79048c3f8..983491cde 100644 --- a/src/main/scala/assumptionAnalysis/AnalysisInfo.scala +++ b/src/main/scala/assumptionAnalysis/AnalysisInfo.scala @@ -2,7 +2,7 @@ package viper.silicon.assumptionAnalysis object AssumptionType extends Enumeration { type AssumptionType = Value - val Assertion, Explicit, LoopInvariant, PathCondition, Implicit, Internal, Unknown = Value + val Explicit, LoopInvariant, PathCondition, Rewrite, Implicit, Internal, Unknown = Value } import viper.silicon.assumptionAnalysis.AssumptionType._ diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index aa88c586e..3babfedd5 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -933,7 +933,7 @@ object evaluator extends EvaluationRules { moreJoins = JoinMode.Off, assertReadAccessOnly = if (Verifier.config.respectFunctionPrePermAmounts()) s2.assertReadAccessOnly /* should currently always be false */ else true) - consumes(s3, pres, true, _ => pvePre, v2, AnalysisInfo(v2.decider.assumptionAnalyzer, ExpAnalysisSourceInfo(fapp), AssumptionType.Assertion))((s4, snap, consumedChunks, v3) => { // TODO ake: add edges from consumedChunks + consumes(s3, pres, true, _ => pvePre, v2, v2.decider.assumptionAnalyzer.getAnalysisInfo)((s4, snap, consumedChunks, v3) => { // TODO ake: add edges from consumedChunks val snap1 = snap.get.convert(sorts.Snap) val preFApp = App(functionSupporter.preconditionVersion(v3.symbolConverter.toFunction(func)), snap1 :: tArgs) val preExp = Option.when(withExp)({ @@ -1051,7 +1051,7 @@ object evaluator extends EvaluationRules { => Q(s4, r4._1, r4._2, v4)) case ast.Asserting(eAss, eIn) => - consume(s, eAss, false, pve, v, AnalysisInfo(v.decider.assumptionAnalyzer, ExpAnalysisSourceInfo(eAss), AssumptionType.Assertion))((s2, _, consumedChunks, v2) => { // TODO ake: what to do with chunks here? + consume(s, eAss, false, pve, v, v.decider.assumptionAnalyzer.getAnalysisInfo)((s2, _, consumedChunks, v2) => { // TODO ake: what to do with chunks here? val s3 = s2.copy(g = s.g, h = s.h) eval(s3, eIn, pve, v2)(Q) }) diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 3a33ba936..0ce3b7d95 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -368,7 +368,7 @@ object magicWandSupporter extends SymbolicExecutionRules { val freshSnapRoot = freshSnap(sorts.Snap, v1) // Produce the wand's LHS. - produce(s1.copy(conservingSnapshotGeneration = true), toSf(freshSnapRoot), wand.left, pve, v1, AssumptionType.Explicit)((sLhs, v2) => { + produce(s1.copy(conservingSnapshotGeneration = true), toSf(freshSnapRoot), wand.left, pve, v1, AssumptionType.Internal)((sLhs, v2) => { // TODO ake: assumption type? val proofScriptCfg = proofScript.toCfg() /* Expected shape of reserveHeaps is either @@ -398,7 +398,7 @@ object magicWandSupporter extends SymbolicExecutionRules { // Execute proof script, i.e. the part written after the magic wand wrapped by curly braces. // The proof script should transform the current state such that we can consume the wand's RHS. - executor.exec(s2, proofScriptCfg, v2)((proofScriptState, proofScriptVerifier) => { + executor.exec(s2, proofScriptCfg, v2)((proofScriptState, proofScriptVerifier) => { // TODO ake: assumption types? // Consume the wand's RHS and produce a snapshot which records all the values of variables on the RHS. // This part indirectly calls the methods `this.transfer` and `this.consumeFromMultipleHeaps`. consume( @@ -406,7 +406,7 @@ object magicWandSupporter extends SymbolicExecutionRules { wand.right, true, pve, proofScriptVerifier, proofScriptVerifier.decider.assumptionAnalyzer.getAnalysisInfo )((s3, snapRhs, consumedChunks, v3) => { // TODO ake: what to do with consumedChunks? - createWandChunkAndRecordResults(s3.copy(exhaleExt = false, oldHeaps = s.oldHeaps), freshSnapRoot, snapRhs.get, v3, AssumptionType.Explicit) + createWandChunkAndRecordResults(s3.copy(exhaleExt = false, oldHeaps = s.oldHeaps), freshSnapRoot, snapRhs.get, v3, AssumptionType.Rewrite) }) }) }) @@ -417,7 +417,7 @@ object magicWandSupporter extends SymbolicExecutionRules { // and thus, that no wand chunk was created. In order to continue, we create one now. // Moreover, we need to set reserveHeaps to structurally match [State RHS] below. val s1 = sEmp.copy(reserveHeaps = Heap() +: Heap() +: Heap() +: s.reserveHeaps.tail) - createWandChunkAndRecordResults(s1, freshSnap(sorts.Snap, v), freshSnap(sorts.Snap, v), v, AssumptionType.Explicit) + createWandChunkAndRecordResults(s1, freshSnap(sorts.Snap, v), freshSnap(sorts.Snap, v), v, AssumptionType.Rewrite) } recordedBranches.foldLeft(tempResult)((prevRes, recordedState) => { @@ -487,7 +487,7 @@ object magicWandSupporter extends SymbolicExecutionRules { } // Produce the wand's RHS. - produce(s3.copy(conservingSnapshotGeneration = true), toSf(magicWandSnapshotLookup), wand.right, pve, v2, AssumptionType.Explicit)((s4, v3) => { // TODO ake: add edges from consumedChunks, explicit assumption? + produce(s3.copy(conservingSnapshotGeneration = true), toSf(magicWandSnapshotLookup), wand.right, pve, v2, AssumptionType.Rewrite)((s4, v3) => { // Recreate old state without the magic wand, and the state with the oldHeap called lhs. val s5 = s4.copy(g = s1.g, conservingSnapshotGeneration = s3.conservingSnapshotGeneration) diff --git a/src/main/scala/rules/PredicateSupporter.scala b/src/main/scala/rules/PredicateSupporter.scala index 03cf4052a..7699867c8 100644 --- a/src/main/scala/rules/PredicateSupporter.scala +++ b/src/main/scala/rules/PredicateSupporter.scala @@ -92,7 +92,7 @@ object predicateSupporter extends PredicateSupportRules { v1.decider.assumeDefinition(smValueDef, debugExp, AssumptionType.Internal) val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk( - formalArgs, Option.when(withExp)(predicate.formalArgs), predicate, tArgs, eArgs, tPerm, ePerm, sm, s.program, v1, AssumptionType.Unknown) + formalArgs, Option.when(withExp)(predicate.formalArgs), predicate, tArgs, eArgs, tPerm, ePerm, sm, s.program, v1, AssumptionType.Rewrite) val h3 = s2.h + ch val smDef = SnapshotMapDefinition(predicate, sm, Seq(smValueDef), Seq()) val smCache = if (s2.heapDependentTriggers.contains(predicate)) { @@ -117,7 +117,7 @@ object predicateSupporter extends PredicateSupportRules { functionRecorder = s2.functionRecorder.recordFvfAndDomain(smDef)) Q(s3, v1) } else { - val ch = BasicChunk(PredicateID, BasicChunkIdentifier(predicate.name), tArgs, eArgs, snap.get.convert(sorts.Snap), None, tPerm, ePerm, v1.decider.assumptionAnalyzer.getAnalysisInfo(AssumptionType.Explicit)) + val ch = BasicChunk(PredicateID, BasicChunkIdentifier(predicate.name), tArgs, eArgs, snap.get.convert(sorts.Snap), None, tPerm, ePerm, v1.decider.assumptionAnalyzer.getAnalysisInfo(AssumptionType.Rewrite)) val s3 = s2.copy(g = s.g, smDomainNeeded = s.smDomainNeeded, permissionScalingFactor = s.permissionScalingFactor, @@ -167,7 +167,7 @@ object predicateSupporter extends PredicateSupportRules { )((s2, h2, snap, consumedChunks, v1) => { val s3 = s2.copy(g = gIns, h = h2) .setConstrainable(constrainableWildcards, false) - produce(s3, toSf(snap.get), body, pve, v1, AssumptionType.Explicit)((s4, v2) => { // TODO ake: add edge from consumedChunks to new assumptions, explicit assumption? + produce(s3, toSf(snap.get), body, pve, v1, AssumptionType.Rewrite)((s4, v2) => { // TODO ake: add edge from consumedChunks to new assumptions v2.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterUnfold) if (!Verifier.config.disableFunctionUnfoldTrigger()) { val predicateTrigger = @@ -187,7 +187,7 @@ object predicateSupporter extends PredicateSupportRules { chunkSupporter.consume(s1, s1.h, predicate, tArgs, eArgs, s1.permissionScalingFactor, s1.permissionScalingFactorExp, true, ve, v, description, v.decider.assumptionAnalyzer.getAnalysisInfo)((s2, h1, snap, consumedChunks, v1) => { // TODO ake: add edges val s3 = s2.copy(g = gIns, h = h1) .setConstrainable(constrainableWildcards, false) - produce(s3, toSf(snap.get), body, pve, v1, AssumptionType.Explicit)((s4, v2) => { // TODO ake: explicit assumption + produce(s3, toSf(snap.get), body, pve, v1, AssumptionType.Rewrite)((s4, v2) => { v2.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterUnfold) if (!Verifier.config.disableFunctionUnfoldTrigger()) { val predicateTrigger = diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index 8cba3aea8..0c47fce20 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -270,7 +270,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver Some(DebugExp.createInstance(e, eNew)) } else { None } decider.assume(BuiltinEquals(data.formalResult, tBody), debugExp, AssumptionType.Implicit) - consumes(s2, posts, false, postconditionViolated, v, AnalysisInfo(v.decider.assumptionAnalyzer, if(posts.isEmpty) StringAnalysisSourceInfo("no postcondition", ast.NoPosition) else ExpAnalysisSourceInfo(posts.head), AssumptionType.Assertion))((s3, _, _, _) => { + consumes(s2, posts, false, postconditionViolated, v, v.decider.assumptionAnalyzer.getAnalysisInfo)((s3, _, _, _) => { recorders :+= s3.functionRecorder Success()})})})} diff --git a/src/test/resources/andrea/quickTest.vpr b/src/test/resources/andrea/quickTest.vpr index 53bacd557..c4101e38c 100644 --- a/src/test/resources/andrea/quickTest.vpr +++ b/src/test/resources/andrea/quickTest.vpr @@ -1,25 +1,13 @@ field f: Int -field next : Ref -field val : Int -predicate list(start : Ref) -{ - acc(start.val) && acc(start.next) && - (start.next != null && list(start.next)) -} +method foo(){ + var x: Ref + inhale acc(x.f) + package true --* acc(x.f, 1/2){ -method basicPackage(l: Ref) - requires list(l) - ensures list(l) - { - unfold list(l) - var tmp : Ref := l.next - - package list(tmp) --* list(l) - { - fold list(l) } - apply list(tmp) --* list(l) + apply true --* acc(x.f, 1/2) + assert acc(x.f) } \ No newline at end of file From 974261201d3fea66cc1f07a600dd4918fe358714 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 22 May 2025 09:05:28 +0200 Subject: [PATCH 058/474] remove analysis info from consume function arguments --- src/main/scala/rules/ChunkSupporter.scala | 35 ++++----- src/main/scala/rules/Consumer.scala | 76 ++++++++----------- src/main/scala/rules/Evaluator.scala | 18 ++--- src/main/scala/rules/Executor.scala | 21 +++-- src/main/scala/rules/MagicWandSupporter.scala | 10 +-- .../rules/MoreCompleteExhaleSupporter.scala | 19 ++--- src/main/scala/rules/PredicateSupporter.scala | 18 ++--- .../scala/rules/QuantifiedChunkSupport.scala | 23 ++---- .../scala/supporters/MethodSupporter.scala | 2 +- .../functions/FunctionVerificationUnit.scala | 28 +++---- 10 files changed, 108 insertions(+), 142 deletions(-) diff --git a/src/main/scala/rules/ChunkSupporter.scala b/src/main/scala/rules/ChunkSupporter.scala index 9f4f068fe..0b59b8df0 100644 --- a/src/main/scala/rules/ChunkSupporter.scala +++ b/src/main/scala/rules/ChunkSupporter.scala @@ -6,7 +6,7 @@ package viper.silicon.rules -import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType, PermissionInhaleNode} +import viper.silicon.assumptionAnalysis.AssumptionType import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.state._ import viper.silicon.interfaces.{Success, VerificationResult} @@ -33,8 +33,7 @@ trait ChunkSupportRules extends SymbolicExecutionRules { returnSnap: Boolean, ve: VerificationError, v: Verifier, - description: String, - analysisInfo: AnalysisInfo) + description: String) (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult @@ -77,13 +76,10 @@ object chunkSupporter extends ChunkSupportRules { returnSnap: Boolean, ve: VerificationError, v: Verifier, - description: String, - analysisInfo0: AnalysisInfo) + description: String) (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { - - val analysisInfo = v.decider.assumptionAnalyzer.getAnalysisInfo - consume2(s, h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, analysisInfo)((s2, h2, optSnap, consumedChunks, v2) => + consume2(s, h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v)((s2, h2, optSnap, consumedChunks, v2) => optSnap match { case Some(snap) => Q(s2, h2, Some(snap.convert(sorts.Snap)), consumedChunks, v2) @@ -111,16 +107,13 @@ object chunkSupporter extends ChunkSupportRules { permsExp: Option[ast.Exp], returnSnap: Boolean, ve: VerificationError, - v: Verifier, - analysisInfo0: AnalysisInfo) + v: Verifier) (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { - - val analysisInfo = v.decider.assumptionAnalyzer.getAnalysisInfo val id = ChunkIdentifier(resource, s.program) if (s.exhaleExt) { val failure = createFailure(ve, v, s, "chunk consume in package") - magicWandSupporter.transfer(s, perms, permsExp, failure, Seq(), v)(consumeGreedy(_, _, id, args, _, _, _, analysisInfo))((s1, optCh, v1) => + magicWandSupporter.transfer(s, perms, permsExp, failure, Seq(), v)(consumeGreedy(_, _, id, args, _, _, _))((s1, optCh, v1) => if (returnSnap){ // TODO ake: check that optCh is indeed the consumed chunks Q(s1, h, optCh.flatMap(ch => Some(ch.snap)), optCh, v1) } else { @@ -129,11 +122,11 @@ object chunkSupporter extends ChunkSupportRules { } else { executionFlowController.tryOrFail3[Heap, Option[Term], Iterable[Chunk]](s.copy(h = h), v)((s1, v1, QS) => if (s1.moreCompleteExhale) { - moreCompleteExhaleSupporter.consumeComplete(s1, s1.h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v1, analysisInfo)((s2, h2, snap2, consumedChunks, v2) => { + moreCompleteExhaleSupporter.consumeComplete(s1, s1.h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v1)((s2, h2, snap2, consumedChunks, v2) => { QS(s2.copy(h = s.h), h2, snap2, consumedChunks, v2) }) } else { - consumeGreedy(s1, s1.h, id, args, perms, permsExp, v1, analysisInfo) match { + consumeGreedy(s1, s1.h, id, args, perms, permsExp, v1) match { case (Complete(), s2, h2, optCh2) => val snap = optCh2 match { case Some(ch) if returnSnap => @@ -161,10 +154,8 @@ object chunkSupporter extends ChunkSupportRules { args: Seq[Term], perms: Term, permsExp: Option[ast.Exp], - v: Verifier, - analysisInfo0: AnalysisInfo) + v: Verifier) : (ConsumptionResult, State, Heap, Option[NonQuantifiedChunk]) = { - val analysisInfo = v.decider.assumptionAnalyzer.getAnalysisInfo val consumeExact = terms.utils.consumeExactRead(perms, s.constrainableARPs) def assumeProperties(chunk: NonQuantifiedChunk, heap: Heap): Unit = { @@ -188,8 +179,8 @@ object chunkSupporter extends ChunkSupportRules { val toTake = PermMin(ch.perm, perms) val toTakeExp = permsExp.map(pe => buildMinExp(Seq(ch.permExp.get, pe), ast.Perm)) val newPermExp = permsExp.map(pe => ast.PermSub(ch.permExp.get, toTakeExp.get)(pe.pos, pe.info, pe.errT)) - val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, toTake), newPermExp, analysisInfo).asInstanceOf[NonQuantifiedChunk] - val takenChunk = Some(GeneralChunk.withPerm(ch, toTake, toTakeExp, analysisInfo, isExhale=true).asInstanceOf[NonQuantifiedChunk]) + val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, toTake), newPermExp, v.decider.assumptionAnalyzer.getAnalysisInfo).asInstanceOf[NonQuantifiedChunk] + val takenChunk = Some(GeneralChunk.withPerm(ch, toTake, toTakeExp, v.decider.assumptionAnalyzer.getAnalysisInfo, isExhale=true).asInstanceOf[NonQuantifiedChunk]) var newHeap = h - ch if (!v.decider.check(newChunk.perm === NoPerm, Verifier.config.checkTimeout())) { newHeap = newHeap + newChunk @@ -202,8 +193,8 @@ object chunkSupporter extends ChunkSupportRules { val constraintExp = permsExp.map(pe => ast.PermLtCmp(pe, ch.permExp.get)(pe.pos, pe.info, pe.errT)) v.decider.assume(PermLess(perms, ch.perm), Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp)), AssumptionType.Implicit) val newPermExp = permsExp.map(pe => ast.PermSub(ch.permExp.get, pe)(pe.pos, pe.info, pe.errT)) - val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, perms), newPermExp, analysisInfo).asInstanceOf[NonQuantifiedChunk] - val takenChunk = GeneralChunk.withPerm(ch, perms, permsExp, analysisInfo, isExhale=true).asInstanceOf[NonQuantifiedChunk] + val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, perms), newPermExp, v.decider.assumptionAnalyzer.getAnalysisInfo).asInstanceOf[NonQuantifiedChunk] + val takenChunk = GeneralChunk.withPerm(ch, perms, permsExp, v.decider.assumptionAnalyzer.getAnalysisInfo, isExhale=true).asInstanceOf[NonQuantifiedChunk] val newHeap = h - ch + newChunk assumeProperties(newChunk, newHeap) (Complete(), s, newHeap, Some(takenChunk)) diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index 03b5f0c47..ee54b6f45 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -39,7 +39,7 @@ trait ConsumptionRules extends SymbolicExecutionRules { * consumed partial heap, and the chunks (3rd argument) that were consumed * @return The result of the continuation. */ - def consume(s: State, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, analysisInfo: AnalysisInfo) + def consume(s: State, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier) (Q: (State, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult @@ -62,7 +62,7 @@ trait ConsumptionRules extends SymbolicExecutionRules { as: Seq[ast.Exp], returnSnap: Boolean, pvef: ast.Exp => PartialVerificationError, - v: Verifier, analysisInfo: AnalysisInfo) + v: Verifier) (Q: (State, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult } @@ -76,11 +76,11 @@ object consumer extends ConsumptionRules { */ /** @inheritdoc */ - def consume(s: State, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, analysisInfo: AnalysisInfo) + def consume(s: State, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier) (Q: (State, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { - consumeR(s, s.h, a.whenExhaling, returnSnap, pve, v, analysisInfo)((s1, h1, snap, consumedChunks, v1) => { + consumeR(s, s.h, a.whenExhaling, returnSnap, pve, v)((s1, h1, snap, consumedChunks, v1) => { val s2 = s1.copy(h = h1, partiallyConsumedHeap = s.partiallyConsumedHeap) Q(s2, snap, consumedChunks, v1)}) @@ -91,8 +91,7 @@ object consumer extends ConsumptionRules { as: Seq[ast.Exp], returnSnap: Boolean, pvef: ast.Exp => PartialVerificationError, - v: Verifier, - analysisInfo: AnalysisInfo) + v: Verifier) (Q: (State, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { @@ -107,7 +106,7 @@ object consumer extends ConsumptionRules { allPves ++= pves }) - consumeTlcs(s, s.h, allTlcs.result(), returnSnap, allPves.result(), v, analysisInfo)((s1, h1, snap1, chunks, v1) => { + consumeTlcs(s, s.h, allTlcs.result(), returnSnap, allPves.result(), v)((s1, h1, snap1, chunks, v1) => { val s2 = s1.copy(h = h1, partiallyConsumedHeap = s.partiallyConsumedHeap) Q(s2, snap1, chunks, v1) @@ -119,8 +118,7 @@ object consumer extends ConsumptionRules { tlcs: Seq[ast.Exp], returnSnap: Boolean, pves: Seq[PartialVerificationError], - v: Verifier, - analysisInfo: AnalysisInfo) + v: Verifier) (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { @@ -131,10 +129,10 @@ object consumer extends ConsumptionRules { val pve = pves.head if (tlcs.tail.isEmpty) - wrappedConsumeTlc(s, h, a, returnSnap, pve, v, analysisInfo)(Q) + wrappedConsumeTlc(s, h, a, returnSnap, pve, v)(Q) else - wrappedConsumeTlc(s, h, a, returnSnap, pve, v, analysisInfo)((s1, h1, snap1, consumedChunksHead, v1) => { - consumeTlcs(s1, h1, tlcs.tail, returnSnap, pves.tail, v1, analysisInfo)((s2, h2, snap2, consumedChunksTail, v2) => + wrappedConsumeTlc(s, h, a, returnSnap, pve, v)((s1, h1, snap1, consumedChunksHead, v1) => { + consumeTlcs(s1, h1, tlcs.tail, returnSnap, pves.tail, v1)((s2, h2, snap2, consumedChunksTail, v2) => (snap1, snap2) match { case (Some(sn1), Some(sn2)) if returnSnap => Q(s2, h2, Some(Combine(sn1, sn2)), consumedChunksHead ++ consumedChunksTail, v2) @@ -145,14 +143,14 @@ object consumer extends ConsumptionRules { } } - private def consumeR(s: State, h: Heap, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, analysisInfo: AnalysisInfo) + private def consumeR(s: State, h: Heap, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier) (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { val tlcs = a.topLevelConjuncts val pves = Seq.fill(tlcs.length)(pve) - consumeTlcs(s, h, tlcs, returnSnap, pves, v, analysisInfo)(Q) + consumeTlcs(s, h, tlcs, returnSnap, pves, v)(Q) } /** Wrapper/decorator for consume that injects the following operations: @@ -164,8 +162,7 @@ object consumer extends ConsumptionRules { a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, - v: Verifier, - analysisInfo: AnalysisInfo) + v: Verifier) (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { @@ -181,14 +178,14 @@ object consumer extends ConsumptionRules { val sepIdentifier = v1.symbExLog.openScope(new ConsumeRecord(a, s1, v.decider.pcs)) v.decider.assumptionAnalyzer.addExpToStack(a) - consumeTlc(s1, h0, a, returnSnap, pve, v1, analysisInfo)((s2, h2, snap2, consumedChunks, v2) => { + consumeTlc(s1, h0, a, returnSnap, pve, v1)((s2, h2, snap2, consumedChunks, v2) => { v.decider.assumptionAnalyzer.popExpFromStack() v2.symbExLog.closeScope(sepIdentifier) QS(s2, h2, snap2, consumedChunks, v2)}) })(Q) } - private def consumeTlc(s: State, h: Heap, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, analysisInfo: AnalysisInfo) + private def consumeTlc(s: State, h: Heap, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier) (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { @@ -209,7 +206,7 @@ object consumer extends ConsumptionRules { case imp @ ast.Implies(e0, a0) if !a.isPure && s.moreJoins.id >= JoinMode.Impure.id => val impliesRecord = new ImpliesRecord(imp, s, v.decider.pcs, "consume") val uidImplies = v.symbExLog.openScope(impliesRecord) - consumeConditionalTlcMoreJoins(s, h, e0, a0, None, uidImplies, returnSnap, pve, v, analysisInfo)(Q) + consumeConditionalTlcMoreJoins(s, h, e0, a0, None, uidImplies, returnSnap, pve, v)(Q) case imp @ ast.Implies(e0, a0) if !a.isPure => val impliesRecord = new ImpliesRecord(imp, s, v.decider.pcs, "consume") @@ -217,7 +214,7 @@ object consumer extends ConsumptionRules { evaluator.eval(s, e0, pve, v)((s1, t0, e0New, v1) => branch(s1, t0, (e0, e0New), v1)( - (s2, v2) => consumeR(s2, h, a0, returnSnap, pve, v2, analysisInfo)((s3, h1, t1, consumedChunks, v3) => { + (s2, v2) => consumeR(s2, h, a0, returnSnap, pve, v2)((s3, h1, t1, consumedChunks, v3) => { v3.symbExLog.closeScope(uidImplies) Q(s3, h1, t1, consumedChunks, v3) }), @@ -229,7 +226,7 @@ object consumer extends ConsumptionRules { case ite @ ast.CondExp(e0, a1, a2) if !a.isPure && s.moreJoins.id >= JoinMode.Impure.id => val condExpRecord = new CondExpRecord(ite, s, v.decider.pcs, "consume") val uidCondExp = v.symbExLog.openScope(condExpRecord) - consumeConditionalTlcMoreJoins(s, h, e0, a1, Some(a2), uidCondExp, returnSnap, pve, v, analysisInfo)(Q) + consumeConditionalTlcMoreJoins(s, h, e0, a1, Some(a2), uidCondExp, returnSnap, pve, v)(Q) case ite @ ast.CondExp(e0, a1, a2) if !a.isPure => val condExpRecord = new CondExpRecord(ite, s, v.decider.pcs, "consume") @@ -237,11 +234,11 @@ object consumer extends ConsumptionRules { eval(s, e0, pve, v)((s1, t0, e0New, v1) => branch(s1, t0, (e0, e0New), v1)( - (s2, v2) => consumeR(s2, h, a1, returnSnap, pve, v2, analysisInfo)((s3, h1, t1, consumedChunks, v3) => { + (s2, v2) => consumeR(s2, h, a1, returnSnap, pve, v2)((s3, h1, t1, consumedChunks, v3) => { v3.symbExLog.closeScope(uidCondExp) Q(s3, h1, t1, consumedChunks, v3) }), - (s2, v2) => consumeR(s2, h, a2, returnSnap, pve, v2, analysisInfo)((s3, h1, t1, consumedChunks, v3) => { + (s2, v2) => consumeR(s2, h, a2, returnSnap, pve, v2)((s3, h1, t1, consumedChunks, v3) => { v3.symbExLog.closeScope(uidCondExp) Q(s3, h1, t1, consumedChunks, v3) }))) @@ -284,8 +281,7 @@ object consumer extends ConsumptionRules { negativePermissionReason = NegativePermission(acc.perm), notInjectiveReason = QPAssertionNotInjective(acc.loc), insufficientPermissionReason = InsufficientPermission(acc.loc), - v1, - analysisInfo)((s2, h2, snap, consumedChunks, v2) => Q(s2.copy(constrainableARPs = s.constrainableARPs), h2, snap, consumedChunks, v2)) + v1)((s2, h2, snap, consumedChunks, v2) => Q(s2.copy(constrainableARPs = s.constrainableARPs), h2, snap, consumedChunks, v2)) case (s1, _, _, _, _, None, v1) => Q(s1, h, if (returnSnap) Some(Unit) else None, Seq.empty, v1) } @@ -331,8 +327,7 @@ object consumer extends ConsumptionRules { negativePermissionReason = NegativePermission(acc.perm), notInjectiveReason = QPAssertionNotInjective(acc.loc), insufficientPermissionReason = InsufficientPermission(acc.loc), - v1, - analysisInfo)((s2, h2, snap, consumedChunks, v2) => Q(s2.copy(constrainableARPs = s.constrainableARPs), h2, snap, consumedChunks, v2)) + v1)((s2, h2, snap, consumedChunks, v2) => Q(s2.copy(constrainableARPs = s.constrainableARPs), h2, snap, consumedChunks, v2)) case (s1, _, _, _, _, None, v1) => Q(s1, h, if (returnSnap) Some(Unit) else None, Seq.empty, v1) } @@ -374,8 +369,7 @@ object consumer extends ConsumptionRules { negativePermissionReason = NegativePermission(ePerm), notInjectiveReason = sys.error("Quantified wand not injective"), /*ReceiverNotInjective(...)*/ insufficientPermissionReason = MagicWandChunkNotFound(wand), /*InsufficientPermission(...)*/ - v1, - analysisInfo)((s2, h2, snap, consumedChunks, v2) => Q(s2.copy(constrainableARPs = s.constrainableARPs), h2, snap, consumedChunks, v2)) + v1)((s2, h2, snap, consumedChunks, v2) => Q(s2.copy(constrainableARPs = s.constrainableARPs), h2, snap, consumedChunks, v2)) case (s1, _, _, _, _, None, v1) => Q(s1.copy(constrainableARPs = s.constrainableARPs), h, if (returnSnap) Some(Unit) else None, Seq.empty, v1) } @@ -415,8 +409,7 @@ object consumer extends ConsumptionRules { returnSnap, None, pve, - v2, - analysisInfo + v2 )((s3, h3, snap, consumedChunks, v3) => { val s4 = s3.copy(constrainableARPs = s1.constrainableARPs, partiallyConsumedHeap = Some(h3)) @@ -461,8 +454,7 @@ object consumer extends ConsumptionRules { returnSnap, None, pve, - v2, - analysisInfo + v2 )((s3, h3, snap, consumedChunks, v3) => { val s4 = s3.copy(constrainableARPs = s1.constrainableARPs, partiallyConsumedHeap = Some(h3)) @@ -471,7 +463,7 @@ object consumer extends ConsumptionRules { case let: ast.Let if !let.isPure => letSupporter.handle[ast.Exp](s, let, pve, v)((s1, g1, body, v1) => { val s2 = s1.copy(g = s1.g + g1) - consumeR(s2, h, body, returnSnap, pve, v1, analysisInfo)(Q)}) + consumeR(s2, h, body, returnSnap, pve, v1)(Q)}) case ast.AccessPredicate(locacc: ast.LocationAccess, perm) => eval(s, perm, pve, v)((s1, tPerm, permNew, v1) => @@ -485,7 +477,7 @@ object consumer extends ConsumptionRules { val lossExp = permNew.map(p => ast.PermMul(p, s3.permissionScalingFactorExp.get)(p.pos, p.info, p.errT)) val ve = pve dueTo InsufficientPermission(locacc) val description = s"consume ${a.pos}: $a" - chunkSupporter.consume(s3, h, resource, tArgs, eArgs, loss, lossExp, returnSnap, ve, v3, description, analysisInfo)((s4, h1, snap1, consumedChunks, v4) => { + chunkSupporter.consume(s3, h, resource, tArgs, eArgs, loss, lossExp, returnSnap, ve, v3, description)((s4, h1, snap1, consumedChunks, v4) => { val s5 = s4.copy(partiallyConsumedHeap = Some(h1), constrainableARPs = s.constrainableARPs) Q(s5, h1, snap1, consumedChunks, v4)})}))) @@ -528,8 +520,7 @@ object consumer extends ConsumptionRules { returnSnap, None, pve, - v1, - analysisInfo + v1 )((s3, h3, snap, consumedChunks, v3) => { val s4 = s3.copy(constrainableARPs = s1.constrainableARPs, partiallyConsumedHeap = Some(h3)) @@ -539,7 +530,7 @@ object consumer extends ConsumptionRules { magicWandSupporter.evaluateWandArguments(s, wand, pve, v)((s1, tArgs, eArgs, v1) => { val ve = pve dueTo MagicWandChunkNotFound(wand) val description = s"consume wand $wand" - chunkSupporter.consume(s1, h, wand, tArgs, eArgs, FullPerm, Option.when(withExp)(ast.FullPerm()(wand.pos, wand.info, wand.errT)), returnSnap, ve, v1, description, analysisInfo)(Q) + chunkSupporter.consume(s1, h, wand, tArgs, eArgs, FullPerm, Option.when(withExp)(ast.FullPerm()(wand.pos, wand.info, wand.errT)), returnSnap, ve, v1, description)(Q) }) case _ => @@ -552,21 +543,20 @@ object consumer extends ConsumptionRules { private def consumeConditionalTlcMoreJoins(s: State, h: Heap, e0: ast.Exp, a1: ast.Exp, a2: Option[ast.Exp], scopeUid: Int, returnSnap: Boolean, - pve: PartialVerificationError, v: Verifier, - analysisInfo: AnalysisInfo) + pve: PartialVerificationError, v: Verifier) (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { eval(s, e0, pve, v)((s1, t0, e0New, v1) => joiner.join[(Heap, Option[Term], Iterable[Chunk]), (Heap, Option[Term], Iterable[Chunk])](s1, v1, resetState = false)((s1, v1, QB) => { branch(s1.copy(parallelizeBranches = false), t0, (e0, e0New), v1)( (s2, v2) => - consumeR(s2.copy(parallelizeBranches = s1.parallelizeBranches), h, a1, returnSnap, pve, v2, analysisInfo)((s3, h1, t1, consumedChunks, v3) => { + consumeR(s2.copy(parallelizeBranches = s1.parallelizeBranches), h, a1, returnSnap, pve, v2)((s3, h1, t1, consumedChunks, v3) => { v3.symbExLog.closeScope(scopeUid) QB(s3, (h1, t1, consumedChunks), v3) }), (s2, v2) => a2 match { - case Some(a2) => consumeR(s2.copy(parallelizeBranches = s1.parallelizeBranches), h, a2, returnSnap, pve, v2, analysisInfo)((s3, h1, t1, consumedChunks, v3) => { + case Some(a2) => consumeR(s2.copy(parallelizeBranches = s1.parallelizeBranches), h, a2, returnSnap, pve, v2)((s3, h1, t1, consumedChunks, v3) => { v3.symbExLog.closeScope(scopeUid) QB(s3, (h1, t1, consumedChunks), v3) }) @@ -583,7 +573,7 @@ object consumer extends ConsumptionRules { State.mergeHeap( entry1.data._1, And(entry1.pathConditions.branchConditions), Option.when(withExp)(BigAnd(entry1.pathConditions.branchConditionExps.map(_._2.get))), entry2.data._1, And(entry2.pathConditions.branchConditions), Option.when(withExp)(BigAnd(entry2.pathConditions.branchConditionExps.map(_._2.get))), - AnalysisInfo(v.decider.assumptionAnalyzer, StringAnalysisSourceInfo("conditional join", e0.pos), AssumptionType.Implicit) + AnalysisInfo(v.decider.assumptionAnalyzer, StringAnalysisSourceInfo("conditional join", e0.pos), AssumptionType.Implicit) // TODO ake ), // Assume that entry1.pcs is inverse of entry2.pcs (entry1.data._2, entry2.data._2) match { diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 3babfedd5..1817105ac 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -6,14 +6,10 @@ package viper.silicon.rules -import viper.silicon.debugger.DebugExp import viper.silicon.Config.JoinMode -import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType, ExpAnalysisSourceInfo} -import viper.silver.ast -import viper.silver.verifier.{CounterexampleTransformer, PartialVerificationError, VerifierWarning} -import viper.silver.verifier.errors.{ErrorWrapperWithExampleTransformer, PreconditionInAppFalse} -import viper.silver.verifier.reasons._ +import viper.silicon.assumptionAnalysis.AssumptionType import viper.silicon.common.collections.immutable.InsertionOrderedSet +import viper.silicon.debugger.DebugExp import viper.silicon.interfaces._ import viper.silicon.interfaces.state.{ChunkIdentifer, NonQuantifiedChunk} import viper.silicon.logger.records.data.{CondExpRecord, EvaluateRecord, ImpliesRecord} @@ -26,9 +22,13 @@ import viper.silicon.utils.ast._ import viper.silicon.utils.toSf import viper.silicon.verifier.Verifier import viper.silicon.{Map, TriggerSets} +import viper.silver.ast import viper.silver.ast.{AnnotationInfo, LocalVarWithVersion, TrueLit, WeightedQuantifier} import viper.silver.reporter.{AnnotationWarning, WarningsDuringVerification} import viper.silver.utility.Common.Rational +import viper.silver.verifier.errors.{ErrorWrapperWithExampleTransformer, PreconditionInAppFalse} +import viper.silver.verifier.reasons._ +import viper.silver.verifier.{CounterexampleTransformer, PartialVerificationError, VerifierWarning} /* TODO: With the current design w.r.t. parallelism, eval should never "move" an execution @@ -933,7 +933,7 @@ object evaluator extends EvaluationRules { moreJoins = JoinMode.Off, assertReadAccessOnly = if (Verifier.config.respectFunctionPrePermAmounts()) s2.assertReadAccessOnly /* should currently always be false */ else true) - consumes(s3, pres, true, _ => pvePre, v2, v2.decider.assumptionAnalyzer.getAnalysisInfo)((s4, snap, consumedChunks, v3) => { // TODO ake: add edges from consumedChunks + consumes(s3, pres, true, _ => pvePre, v2)((s4, snap, consumedChunks, v3) => { // TODO ake: add edges from consumedChunks val snap1 = snap.get.convert(sorts.Snap) val preFApp = App(functionSupporter.preconditionVersion(v3.symbolConverter.toFunction(func)), snap1 :: tArgs) val preExp = Option.when(withExp)({ @@ -996,7 +996,7 @@ object evaluator extends EvaluationRules { // val c4 = c3.decCycleCounter(predicate) // eval(σ1, eIn, pve, c4)((tIn, c5) => // QB(tIn, c5))}) - consume(s4, acc, true, pve, v3, AnalysisInfo(v3.decider.assumptionAnalyzer, ExpAnalysisSourceInfo(acc), AssumptionType.Implicit))((s5, snap, consumedChunks, v4) => { + consume(s4, acc, true, pve, v3)((s5, snap, consumedChunks, v4) => { val fr6 = s5.functionRecorder.recordSnapshot(pa, v4.decider.pcs.branchConditions, snap.get) .changeDepthBy(+1) @@ -1051,7 +1051,7 @@ object evaluator extends EvaluationRules { => Q(s4, r4._1, r4._2, v4)) case ast.Asserting(eAss, eIn) => - consume(s, eAss, false, pve, v, v.decider.assumptionAnalyzer.getAnalysisInfo)((s2, _, consumedChunks, v2) => { // TODO ake: what to do with chunks here? + consume(s, eAss, false, pve, v)((s2, _, consumedChunks, v2) => { // TODO ake: what to do with chunks here? val s3 = s2.copy(g = s.g, h = s.h) eval(s3, eIn, pve, v2)(Q) }) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index e85476ab0..9dae0a2f1 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -271,7 +271,7 @@ object executor extends ExecutionRules { combine executionFlowController.locally(s, v)((s0, v0) => { v0.decider.prover.comment("Loop head block: Establish invariant") // TODO ake: set source - consumes(s0, invs, false, LoopInvariantNotEstablished, v0, v0.decider.assumptionAnalyzer.getAnalysisInfo)((sLeftover, _, consumedChunks, v1) => { // TODO ake: add edges from consumedChunks + consumes(s0, invs, false, LoopInvariantNotEstablished, v0)((sLeftover, _, consumedChunks, v1) => { // TODO ake: add edges from consumedChunks v1.decider.prover.comment("Loop head block: Execute statements of loop head block (in invariant state)") phase1data.foldLeft(Success(): VerificationResult) { case (result, _) if !result.continueVerification => result @@ -308,7 +308,7 @@ object executor extends ExecutionRules { */ v.decider.prover.comment("Loop head block: Re-establish invariant") // TODO ake: set source - consumes(s, invs, false, e => LoopInvariantNotPreserved(e), v, v.decider.assumptionAnalyzer.getAnalysisInfo)((_, _, _, _) => + consumes(s, invs, false, e => LoopInvariantNotPreserved(e), v)((_, _, _, _) => Success()) } } @@ -414,8 +414,7 @@ object executor extends ExecutionRules { FullPerm, Option.when(withExp)(ast.FullPerm()()), chunkOrderHeuristics, - v2, - AnalysisInfo(v2.decider.assumptionAnalyzer, ExpAnalysisSourceInfo(fa), AssumptionType.Implicit) + v2 ) result match { case (Complete(), s3, remainingChunks, consumedChunks) => // TODO ake: what to do with consumedChunks? @@ -446,7 +445,7 @@ object executor extends ExecutionRules { val resource = fa.res(s.program) val ve = pve dueTo InsufficientPermission(fa) val description = s"consume ${ass.pos}: $ass" - chunkSupporter.consume(s2, s2.h, resource, Seq(tRcvr), eRcvrNew.map(Seq(_)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), false, ve, v2, description, v2.decider.assumptionAnalyzer.getAnalysisInfo)((s3, h3, _, consumedChunks, v3) => { + chunkSupporter.consume(s2, s2.h, resource, Seq(tRcvr), eRcvrNew.map(Seq(_)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), false, ve, v2, description)((s3, h3, _, consumedChunks, v3) => { val (tSnap, _) = ssaifyRhs(tRhs, rhs, rhsNew, field.name, field.typ, v3, s3) val id = BasicChunkIdentifier(field.name) val newChunk = BasicChunk.createDerivedChunk(consumedChunks.toSet, FieldID, id, Seq(tRcvr), eRcvrNew.map(Seq(_)), tSnap, rhsNew, FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), @@ -507,7 +506,7 @@ object executor extends ExecutionRules { case exhale @ ast.Exhale(a) => val pve = ExhaleFailed(exhale) - consume(s, a, false, pve, v, v.decider.assumptionAnalyzer.getAnalysisInfo)((s1, _, consumedChunks, v1) => + consume(s, a, false, pve, v)((s1, _, consumedChunks, v1) => Q(s1, v1)) case assert @ ast.Assert(a: ast.FalseLit) if !s.isInPackage => @@ -521,7 +520,7 @@ object executor extends ExecutionRules { case assert @ ast.Assert(a) if Verifier.config.disableSubsumption() => val r = - consume(s, a, false, AssertFailed(assert), v, v.decider.assumptionAnalyzer.getAnalysisInfo)((_, _, _, _) => + consume(s, a, false, AssertFailed(assert), v)((_, _, _, _) => Success()) r combine Q(s, v) @@ -537,11 +536,11 @@ object executor extends ExecutionRules { * hUsed (reserveHeaps.head) instead of consuming them. hUsed is later discarded and replaced * by s.h. By copying hUsed to s.h the contained permissions remain available inside the wand. */ - consume(s, a, false, pve, v, v.decider.assumptionAnalyzer.getAnalysisInfo)((s2, _, _, v1) => { + consume(s, a, false, pve, v)((s2, _, _, v1) => { Q(s2.copy(h = s2.reserveHeaps.head), v1) }) } else - consume(s, a, false, pve, v, v.decider.assumptionAnalyzer.getAnalysisInfo)((s1, _, _, v1) => { + consume(s, a, false, pve, v)((s1, _, _, v1) => { val s2 = s1.copy(h = s.h, reserveHeaps = s.reserveHeaps) Q(s2, v1)}) @@ -600,7 +599,7 @@ object executor extends ExecutionRules { tArgs zip Seq.fill(tArgs.size)(None) val s2 = s1.copy(g = Store(fargs.zip(argsWithExp)), recordVisited = true) - consumes(s2, meth.pres, false, _ => pvePre, v1, v1.decider.assumptionAnalyzer.getAnalysisInfo)((s3, _, consumedChunks, v2) => { // TODO ake: add edges from consumedChunks + consumes(s2, meth.pres, false, _ => pvePre, v1)((s3, _, consumedChunks, v2) => { // TODO ake: add edges from consumedChunks v2.symbExLog.closeScope(preCondId) val postCondLog = new CommentRecord("Postcondition", s3, v2.decider.pcs) val postCondId = v2.symbExLog.openScope(postCondLog) @@ -628,7 +627,7 @@ object executor extends ExecutionRules { eval(s1, ePerm, pve, v1)((s2, tPerm, ePermNew, v2) => permissionSupporter.assertPositive(s2, tPerm, if (withExp) ePermNew.get else ePerm, pve, v2)((s3, v3) => { val wildcards = s3.constrainableARPs -- s1.constrainableARPs - predicateSupporter.fold(s3, predicate, tArgs, eArgsNew, tPerm, ePermNew, wildcards, pve, v3, v3.decider.assumptionAnalyzer.getAnalysisInfo)((s4, v4) => { + predicateSupporter.fold(s3, predicate, tArgs, eArgsNew, tPerm, ePermNew, wildcards, pve, v3)((s4, v4) => { v3.decider.finishDebugSubExp(s"folded ${predAcc.toString}") Q(s4, v4) } diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 0ce3b7d95..d9f8fd44d 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -6,11 +6,11 @@ package viper.silicon.rules -import viper.silicon.debugger.DebugExp import viper.silicon._ +import viper.silicon.assumptionAnalysis.AssumptionType import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType -import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType, ExpAnalysisSourceInfo, PermissionInhaleNode} import viper.silicon.common.collections.immutable.InsertionOrderedSet +import viper.silicon.debugger.DebugExp import viper.silicon.decider.RecordedPathConditions import viper.silicon.interfaces._ import viper.silicon.interfaces.state._ @@ -403,7 +403,7 @@ object magicWandSupporter extends SymbolicExecutionRules { // This part indirectly calls the methods `this.transfer` and `this.consumeFromMultipleHeaps`. consume( proofScriptState.copy(oldHeaps = s2.oldHeaps, reserveCfgs = proofScriptState.reserveCfgs.tail), - wand.right, true, pve, proofScriptVerifier, proofScriptVerifier.decider.assumptionAnalyzer.getAnalysisInfo + wand.right, true, pve, proofScriptVerifier )((s3, snapRhs, consumedChunks, v3) => { // TODO ake: what to do with consumedChunks? createWandChunkAndRecordResults(s3.copy(exhaleExt = false, oldHeaps = s.oldHeaps), freshSnapRoot, snapRhs.get, v3, AssumptionType.Rewrite) @@ -462,9 +462,9 @@ object magicWandSupporter extends SymbolicExecutionRules { (Q: (State, Verifier) => VerificationResult) : VerificationResult = { // Consume the magic wand instance "A --* B". - consume(s, wand, true, pve, v, v.decider.assumptionAnalyzer.getAnalysisInfo)((s1, snapWand, consumedChunksWand, v1) => { + consume(s, wand, true, pve, v)((s1, snapWand, consumedChunksWand, v1) => { // Consume the wand's LHS "A". - consume(s1, wand.left, true, pve, v1, v1.decider.assumptionAnalyzer.getAnalysisInfo)((s2, snapLhs, consumedChunksLeft, v2) => { + consume(s1, wand.left, true, pve, v1)((s2, snapLhs, consumedChunksLeft, v2) => { /* It is assumed that snap and MagicWandSnapshot.abstractLhs are structurally the same. * Equating the two snapshots is sound iff a wand is applied only once. * The old solution in this case did use this assumption: diff --git a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala index 01e47c4ea..d409a9e43 100644 --- a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala +++ b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala @@ -6,7 +6,7 @@ package viper.silicon.rules -import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType} +import viper.silicon.assumptionAnalysis.AssumptionType import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.state._ import viper.silicon.interfaces.{Success, VerificationResult} @@ -236,13 +236,12 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { permsExp: Option[ast.Exp], returnSnap: Boolean, ve: VerificationError, - v: Verifier, - analysisInfo: AnalysisInfo) + v: Verifier) (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { if (!s.assertReadAccessOnly) - actualConsumeComplete(s, h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, analysisInfo)(Q) + actualConsumeComplete(s, h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v)(Q) else summariseHeapAndAssertReadAccess(s, h, resource, perms, permsExp, args, argsExp, returnSnap, ve, v)(Q) } @@ -291,8 +290,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { permsExp: Option[ast.Exp], returnSnap: Boolean, ve: VerificationError, - v: Verifier, - analysisInfo: AnalysisInfo) + v: Verifier) (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { @@ -313,7 +311,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { } } else { if (!terms.utils.consumeExactRead(perms, s.constrainableARPs)) { - actualConsumeCompleteConstrainable(s, relevantChunks, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, analysisInfo)((s1, updatedChunks, optSnap, v2) => { + actualConsumeCompleteConstrainable(s, relevantChunks, resource, args, argsExp, perms, permsExp, returnSnap, ve, v)((s1, updatedChunks, optSnap, v2) => { Q(s1, Heap(updatedChunks ++ otherChunks), optSnap, updatedChunks, v2) // TODO ake: updatedChunks != consumedChunks }) } else { @@ -367,7 +365,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { pSum = PermPlus(pSum, Ite(eq, ch.perm, NoPerm)) pSumExp = eqExp.map(eq => ast.PermAdd(pSumExp.get, ast.CondExp(eq, ch.permExp.get, ast.NoPerm()())(eq.pos, eq.info, eq.errT))()) - val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, pTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, pTakenExp.get)(pe.pos, pe.info, pe.errT)), analysisInfo).asInstanceOf[NonQuantifiedChunk] + val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, pTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, pTakenExp.get)(pe.pos, pe.info, pe.errT)), v.decider.assumptionAnalyzer.getAnalysisInfo).asInstanceOf[NonQuantifiedChunk] pNeeded = PermMinus(pNeeded, pTaken) pNeededExp = permsExp.map(pe => ast.PermSub(pNeededExp.get, pTakenExp.get)(pe.pos, pe.info, pe.errT)) @@ -442,8 +440,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { permsExp: Option[ast.Exp], returnSnap: Boolean, ve: VerificationError, - v: Verifier, - analysisInfo: AnalysisInfo) + v: Verifier) (Q: (State, ListBuffer[NonQuantifiedChunk], Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -484,7 +481,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { newFr = newFr.recordPathSymbol(permTaken.applicable.asInstanceOf[Function]).recordConstraint(constraint) - GeneralChunk.withPerm(ch, PermMinus(ch.perm, permTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, permTakenExp.get)(pe.pos, pe.info, pe.errT)), analysisInfo).asInstanceOf[NonQuantifiedChunk] + GeneralChunk.withPerm(ch, PermMinus(ch.perm, permTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, permTakenExp.get)(pe.pos, pe.info, pe.errT)), v.decider.assumptionAnalyzer.getAnalysisInfo).asInstanceOf[NonQuantifiedChunk] }) val totalTakenBounds = diff --git a/src/main/scala/rules/PredicateSupporter.scala b/src/main/scala/rules/PredicateSupporter.scala index 7699867c8..b8dd8e15c 100644 --- a/src/main/scala/rules/PredicateSupporter.scala +++ b/src/main/scala/rules/PredicateSupporter.scala @@ -6,9 +6,9 @@ package viper.silicon.rules -import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType, ExpAnalysisSourceInfo, PermissionInhaleNode} -import viper.silicon.debugger.DebugExp +import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType} import viper.silicon.common.collections.immutable.InsertionOrderedSet +import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.VerificationResult import viper.silicon.resources.PredicateID import viper.silicon.state._ @@ -16,7 +16,6 @@ import viper.silicon.state.terms._ import viper.silicon.utils.toSf import viper.silicon.verifier.Verifier import viper.silver.ast -import viper.silver.ast.{NoInfo, NoPosition, NoTrafos, PredicateAccess} import viper.silver.verifier.PartialVerificationError import viper.silver.verifier.reasons.InsufficientPermission @@ -29,8 +28,7 @@ trait PredicateSupportRules extends SymbolicExecutionRules { ePerm: Option[ast.Exp], constrainableWildcards: InsertionOrderedSet[Var], pve: PartialVerificationError, - v: Verifier, - analysisInfo: AnalysisInfo) + v: Verifier) (Q: (State, Verifier) => VerificationResult) : VerificationResult @@ -60,8 +58,7 @@ object predicateSupporter extends PredicateSupportRules { ePerm: Option[ast.Exp], constrainableWildcards: InsertionOrderedSet[Var], pve: PartialVerificationError, - v: Verifier, - analysisInfo: AnalysisInfo) + v: Verifier) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -74,7 +71,7 @@ object predicateSupporter extends PredicateSupportRules { val s1 = s.copy(g = gIns, smDomainNeeded = true) .scalePermissionFactor(tPerm, ePerm) - consume(s1, body, true, pve, v, analysisInfo)((s1a, snap, consumedChunks, v1) => { // TODO ake: add edges from consumedChunks + consume(s1, body, true, pve, v)((s1a, snap, consumedChunks, v1) => { // TODO ake: add edges from consumedChunks if (!Verifier.config.disableFunctionUnfoldTrigger()) { val predTrigger = App(s1a.predicateData(predicate).triggerFunction, snap.get.convert(terms.sorts.Snap) +: tArgs) @@ -162,8 +159,7 @@ object predicateSupporter extends PredicateSupportRules { true, None, pve, - v, - v.decider.assumptionAnalyzer.getAnalysisInfo + v )((s2, h2, snap, consumedChunks, v1) => { val s3 = s2.copy(g = gIns, h = h2) .setConstrainable(constrainableWildcards, false) @@ -184,7 +180,7 @@ object predicateSupporter extends PredicateSupportRules { } else { val ve = pve dueTo InsufficientPermission(pa) val description = s"consume ${pa.pos}: $pa" - chunkSupporter.consume(s1, s1.h, predicate, tArgs, eArgs, s1.permissionScalingFactor, s1.permissionScalingFactorExp, true, ve, v, description, v.decider.assumptionAnalyzer.getAnalysisInfo)((s2, h1, snap, consumedChunks, v1) => { // TODO ake: add edges + chunkSupporter.consume(s1, s1.h, predicate, tArgs, eArgs, s1.permissionScalingFactor, s1.permissionScalingFactorExp, true, ve, v, description)((s2, h1, snap, consumedChunks, v1) => { // TODO ake: add edges val s3 = s2.copy(g = gIns, h = h1) .setConstrainable(constrainableWildcards, false) produce(s3, toSf(snap.get), body, pve, v1, AssumptionType.Rewrite)((s4, v2) => { diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 629f3c5e9..ad173a0a1 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -7,8 +7,8 @@ package viper.silicon.rules import viper.silicon.Map +import viper.silicon.assumptionAnalysis.AssumptionType import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType -import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.VerificationResult @@ -1179,8 +1179,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { negativePermissionReason: => ErrorReason, notInjectiveReason: => ErrorReason, insufficientPermissionReason: => ErrorReason, - v: Verifier, - analysisInfo: AnalysisInfo) + v: Verifier) (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { @@ -1329,8 +1328,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { rPerm, rPermExp, chunkOrderHeuristics, - v2, - analysisInfo) + v2) val optSmDomainDefinitionCondition2 = if (s3.smDomainNeeded) Some(And(condOfInvOfLoc, IsPositive(lossOfInvOfLoc), And(imagesOfFormalQVars))) else None @@ -1396,8 +1394,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { lossOfInvOfLoc, lossExp, chunkOrderHeuristics, - v, - analysisInfo + v ) permissionRemovalResult match { case (Complete(), s2, remainingChunks, consumedChunks) => @@ -1439,8 +1436,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { returnSnap: Boolean, optChunkOrderHeuristic: Option[Seq[QuantifiedBasicChunk] => Seq[QuantifiedBasicChunk]], pve: PartialVerificationError, - v: Verifier, - analysisInfo: AnalysisInfo) + v: Verifier) (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { @@ -1476,8 +1472,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { rPerm, rPermExp, chunkOrderHeuristics, - v, - analysisInfo + v ) val h2 = Heap(remainingChunks ++ otherChunks) val (smDef1, smCache1) = @@ -1526,8 +1521,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { permissions, permissionsExp, chunkOrderHeuristics, - v, - analysisInfo + v ) result match { case (Complete(), s1, remainingChunks, consumedChunks) => @@ -1606,8 +1600,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { perms: Term, // p(rs) permsExp: Option[ast.Exp], // p(rs) chunkOrderHeuristic: Seq[QuantifiedBasicChunk] => Seq[QuantifiedBasicChunk], - v: Verifier, - analysisInfo: AnalysisInfo) + v: Verifier) : (ConsumptionResult, State, Seq[QuantifiedBasicChunk], Seq[QuantifiedBasicChunk]) = { val rmPermRecord = new CommentRecord("removePermissions", s, v.decider.pcs) diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index 60843cca9..ffd3a6ecb 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -117,7 +117,7 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif executionFlowController.locally(s2a, v2)((s3, v3) => { exec(s3, body, v3)((s4, v4) =>{ // TODO ake: set source - consumes(s4, posts, false, postViolated, v4, v4.decider.assumptionAnalyzer.getAnalysisInfo(AssumptionType.Explicit))((_, _, _, _) => { + consumes(s4, posts, false, postViolated, v4)((_, _, _, _) => { Success() })})}) } )})}) diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index 0c47fce20..8ce6c1556 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -7,28 +7,28 @@ package viper.silicon.supporters.functions import com.typesafe.scalalogging.Logger -import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType, ExpAnalysisSourceInfo, StringAnalysisSourceInfo} +import viper.silicon.assumptionAnalysis.AssumptionType +import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp -import viper.silver.ast -import viper.silver.ast.utility.Functions -import viper.silver.components.StatefulComponent -import viper.silver.verifier.errors.{ContractNotWellformed, FunctionNotWellformed, PostconditionViolated} -import viper.silicon.{Map, Stack, toMap} -import viper.silicon.interfaces.decider.ProverLike +import viper.silicon.decider.Decider import viper.silicon.interfaces._ -import viper.silicon.state._ +import viper.silicon.interfaces.decider.ProverLike +import viper.silicon.rules.{consumer, evaluator, executionFlowController, producer} import viper.silicon.state.State.OldHeaps +import viper.silicon.state._ import viper.silicon.state.terms._ import viper.silicon.state.terms.predef.`?s` -import viper.silicon.common.collections.immutable.InsertionOrderedSet -import viper.silicon.decider.Decider -import viper.silicon.rules.{consumer, evaluator, executionFlowController, producer} import viper.silicon.supporters.PredicateData import viper.silicon.utils.ast.{BigAnd, simplifyVariableName} -import viper.silicon.verifier.{Verifier, VerifierComponent} import viper.silicon.utils.{freshSnap, toSf} +import viper.silicon.verifier.{Verifier, VerifierComponent} +import viper.silicon.{Map, Stack, toMap} +import viper.silver.ast import viper.silver.ast.LocalVarWithVersion +import viper.silver.ast.utility.Functions +import viper.silver.components.StatefulComponent import viper.silver.parser.PType +import viper.silver.verifier.errors.{ContractNotWellformed, FunctionNotWellformed, PostconditionViolated} import scala.annotation.unused @@ -46,9 +46,9 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver extends FunctionVerificationUnit[Sort, Decl, Term] with StatefulComponent { - import producer._ import consumer._ import evaluator._ + import producer._ @unused private var program: ast.Program = _ /*private*/ var functionData: Map[ast.Function, FunctionData] = Map.empty @@ -270,7 +270,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver Some(DebugExp.createInstance(e, eNew)) } else { None } decider.assume(BuiltinEquals(data.formalResult, tBody), debugExp, AssumptionType.Implicit) - consumes(s2, posts, false, postconditionViolated, v, v.decider.assumptionAnalyzer.getAnalysisInfo)((s3, _, _, _) => { + consumes(s2, posts, false, postconditionViolated, v)((s3, _, _, _) => { recorders :+= s3.functionRecorder Success()})})})} From 524224e81cd52b1469e669e16852cc10661c24b8 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 22 May 2025 09:18:03 +0200 Subject: [PATCH 059/474] remove assumption type from assert nodes --- .../AssumptionAnalysisGraph.scala | 28 +++++++++---------- .../AssumptionAnalyzer.scala | 26 ++++++++--------- 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index bb5da50fc..d66cb0b43 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -84,10 +84,6 @@ trait AssumptionAnalysisGraph { addTransitiveEdges(checks, notChecks) } } - // def findDependentAssumptions(assertion, enableTransitivity=false) - // def findDependentAssertions(assumption, enableTransitivity=false) - // def findUnnecessaryAssumptions(enableTransitivity=false) - // def mergeIdenticalNodes() def getNodeExportString(node: AssumptionAnalysisNode): String = { node.id + " | " + node.getClass.getSimpleName + " | " + node.assumptionType + " | " + node.getNodeString + " | " + node.sourceInfo.toString @@ -145,12 +141,9 @@ trait AssumptionAnalysisNode { def getNodeString: String - def isIncludedInAnalysis: Boolean = assumptionType match { - case Explicit => true - case PathCondition => true - case Internal => true - case Implicit => true - case Unknown => true + def isIncludedInAnalysis: Boolean = assumptionType match {// TODO ake + case Internal => false + case _ => true } } @@ -173,15 +166,18 @@ case class StringAssumptionNode(description: String, sourceInfo: AnalysisSourceI override def getNodeString: String = "assume " + description } -case class SimpleAssertionNode(assertion: ast.Exp, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Explicit) extends GeneralAssertionNode { +case class SimpleAssertionNode(assertion: ast.Exp, sourceInfo: AnalysisSourceInfo) extends GeneralAssertionNode { + val assumptionType: AssumptionType = Explicit override def getNodeString: String = "assert " + assertion.toString } -case class StringAssertionNode(description: String, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Explicit) extends GeneralAssertionNode { +case class StringAssertionNode(description: String, sourceInfo: AnalysisSourceInfo) extends GeneralAssertionNode { + val assumptionType: AssumptionType = Explicit override def getNodeString: String = "assert " + description } -case class SimpleCheckNode(t: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Unknown) extends GeneralAssertionNode { +case class SimpleCheckNode(t: Term, sourceInfo: AnalysisSourceInfo) extends GeneralAssertionNode { + val assumptionType: AssumptionType = Explicit override def getNodeString: String = "check " + t } @@ -189,13 +185,15 @@ case class PermissionInhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourc override def getNodeString: String = "inhale " + chunk.getAnalysisInfo } -case class PermissionExhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Explicit) extends GeneralAssertionNode with ChunkAnalysisInfo { +case class PermissionExhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo) extends GeneralAssertionNode with ChunkAnalysisInfo { isAsserted = true + val assumptionType: AssumptionType = Explicit override def getNodeString: String = "exhale " + chunk.getAnalysisInfo } -case class PermissionAssertNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Explicit) extends GeneralAssertionNode with ChunkAnalysisInfo { +case class PermissionAssertNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo) extends GeneralAssertionNode with ChunkAnalysisInfo { isAsserted = true + val assumptionType: AssumptionType = Explicit override def getNodeString: String = "assert " + permAmount.getOrElse("") + " for " + chunk.getAnalysisInfo } diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index f1d878413..380d424e6 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -11,8 +11,8 @@ import viper.silver.ast._ trait AssumptionAnalyzer { def addPermissionInhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] - def addPermissionAssertNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Explicit): Option[Int] - def addPermissionExhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Explicit): Option[Int] + def addPermissionAssertNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo): Option[Int] + def addPermissionExhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo): Option[Int] def addPermissionNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Explicit, isExhale: Boolean=false): Option[Int] @@ -115,11 +115,11 @@ class DefaultAssumptionAnalyzer(member: Member) extends AssumptionAnalyzer { } override def createAssertOrCheckNode(term: Term, assertion: Either[String, ast.Exp], analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] = { - if(isCheck) return Some(SimpleCheckNode(term, analysisSourceInfo, AssumptionType.Implicit)) + if(isCheck) return Some(SimpleCheckNode(term, analysisSourceInfo)) Some(assertion match { - case Left(description) => StringAssertionNode(description, analysisSourceInfo, AssumptionType.Explicit) - case Right(exp) => SimpleAssertionNode(exp, analysisSourceInfo, AssumptionType.Explicit) + case Left(description) => StringAssertionNode(description, analysisSourceInfo) + case Right(exp) => SimpleAssertionNode(exp, analysisSourceInfo) }) } @@ -137,23 +137,23 @@ class DefaultAssumptionAnalyzer(member: Member) extends AssumptionAnalyzer { Some(node.id) } - override def addPermissionAssertNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Explicit): Option[Int] = { - val node = PermissionAssertNode(chunk, permAmount, sourceInfo, AssumptionType.Explicit) + override def addPermissionAssertNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo): Option[Int] = { + val node = PermissionAssertNode(chunk, permAmount, sourceInfo) addNode(node) addPermissionDependencies(Set(chunk), Some(node.id)) Some(node.id) } - override def addPermissionExhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Explicit): Option[Int] = { - val node = PermissionExhaleNode(chunk, permAmount, sourceInfo, assumptionType) + override def addPermissionExhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo): Option[Int] = { + val node = PermissionExhaleNode(chunk, permAmount, sourceInfo) addNode(node) addPermissionDependencies(Set(chunk), Some(node.id)) Some(node.id) } override def addPermissionNode(chunk: Chunk, permAmount: Option[Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Explicit, isExhale: Boolean=false): Option[Int] = { - val nodeFunction = if(isExhale) addPermissionExhaleNode _ else addPermissionInhaleNode _ - nodeFunction(chunk, permAmount, sourceInfo, assumptionType) + if(isExhale) addPermissionExhaleNode(chunk, permAmount, sourceInfo) + else addPermissionInhaleNode(chunk, permAmount, sourceInfo, assumptionType) } override def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNodeId: Option[Int]): Unit = { @@ -198,8 +198,8 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { } override def addPermissionInhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None - override def addPermissionExhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None - override def addPermissionAssertNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None + override def addPermissionExhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo): Option[Int] = None + override def addPermissionAssertNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo): Option[Int] = None override def addPermissionNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isExhale: Boolean=false): Option[Int] = None override def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNode: Option[Int]): Unit = { From afd2cf8cec554f8541254ce25bb131660ea65ab4 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 22 May 2025 10:46:51 +0200 Subject: [PATCH 060/474] minor fixes, loop invariant separation --- .../AssumptionAnalysisGraph.scala | 7 ++--- .../AssumptionAnalyzer.scala | 28 +++++++++++-------- src/main/scala/decider/Decider.scala | 23 +++++++++++++++ src/main/scala/rules/Brancher.scala | 8 +++--- src/main/scala/rules/Consumer.scala | 4 +-- src/main/scala/rules/Evaluator.scala | 4 +-- src/main/scala/rules/Executor.scala | 9 +++--- src/main/scala/rules/Producer.scala | 6 ++-- src/main/scala/rules/StateConsolidator.scala | 11 ++++---- 9 files changed, 63 insertions(+), 37 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index d66cb0b43..eec6d0906 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -80,7 +80,7 @@ trait AssumptionAnalysisGraph { val assumes = nodes._2.filter(_.isInstanceOf[GeneralAssumptionNode]) addTransitiveEdges(asserts, assumes) val checks = asserts.filter(_.isInstanceOf[SimpleCheckNode]) - val notChecks = asserts.filter(!_.isInstanceOf[SimpleCheckNode]) + val notChecks = nodes._2.filter(!_.isInstanceOf[SimpleCheckNode]) addTransitiveEdges(checks, notChecks) } } @@ -177,7 +177,7 @@ case class StringAssertionNode(description: String, sourceInfo: AnalysisSourceIn } case class SimpleCheckNode(t: Term, sourceInfo: AnalysisSourceInfo) extends GeneralAssertionNode { - val assumptionType: AssumptionType = Explicit + val assumptionType: AssumptionType = Internal override def getNodeString: String = "check " + t } @@ -197,6 +197,3 @@ case class PermissionAssertNode(chunk: Chunk, permAmount: Option[ast.Exp], sourc override def getNodeString: String = "assert " + permAmount.getOrElse("") + " for " + chunk.getAnalysisInfo } - - - diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 380d424e6..973e5674f 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -30,19 +30,19 @@ trait AssumptionAnalyzer { val assumptionGraph: AssumptionAnalysisGraph = new DefaultAssumptionAnalysisGraph() private var coarseGrainedSourceStack: List[AnalysisSourceInfo] = List.empty - private var currentExpStack: List[ast.Exp] = List.empty + private var fineGrainedSourceStack: List[AnalysisSourceInfo] = List.empty def getAnalysisInfo: AnalysisInfo = getAnalysisInfo(AssumptionType.Implicit) def getAnalysisInfo(assumptionType: AssumptionType): AnalysisInfo = AnalysisInfo(this, getFullSourceInfo, assumptionType) def getFullSourceInfo: AnalysisSourceInfo = { - if(currentExpStack.isEmpty){ + if(fineGrainedSourceStack.isEmpty){ coarseGrainedSourceStack.headOption.getOrElse(NoAnalysisSourceInfo()) }else if(coarseGrainedSourceStack.isEmpty){ - ExpAnalysisSourceInfo(currentExpStack.head) + fineGrainedSourceStack.head }else{ - CompositeAnalysisSourceInfo(coarseGrainedSourceStack.head, ExpAnalysisSourceInfo(currentExpStack.head)) + CompositeAnalysisSourceInfo(coarseGrainedSourceStack.head, fineGrainedSourceStack.head) } } @@ -55,13 +55,17 @@ trait AssumptionAnalyzer { coarseGrainedSourceStack = coarseGrainedSourceStack.tail } - def addExpToStack(e: ast.Exp): Unit = { - currentExpStack = e +: currentExpStack + def addFineGrainedSource(e: ast.Exp): Unit = { + fineGrainedSourceStack = ExpAnalysisSourceInfo(e) +: fineGrainedSourceStack } - def popExpFromStack(): Unit = { - if(currentExpStack.nonEmpty) - currentExpStack = currentExpStack.tail + def addFineGrainedSource(analysisSourceInfo: AnalysisSourceInfo): Unit = { + fineGrainedSourceStack = analysisSourceInfo +: fineGrainedSourceStack + } + + def popFineGrainedSource(): Unit = { + if(fineGrainedSourceStack.nonEmpty) + fineGrainedSourceStack = fineGrainedSourceStack.tail } def getMember: Option[Member] @@ -108,8 +112,10 @@ class DefaultAssumptionAnalyzer(member: Member) extends AssumptionAnalyzer { } override def addAssumptions(assumptions: Iterable[DebugExp], analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Seq[Int] = { - val newNodes = assumptions.filter(_.originalExp.isDefined) - .map(a => SimpleAssumptionNode(a.originalExp.get, analysisSourceInfo, assumptionType)) + val newNodes = assumptions.toSeq.map(a => + if (a.originalExp.isDefined) SimpleAssumptionNode(a.originalExp.get, analysisSourceInfo, assumptionType) + else StringAssumptionNode(a.description.getOrElse("unknown"), analysisSourceInfo, AssumptionType.Internal) + ) newNodes foreach addNode newNodes.map(_.id).toSeq } diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 610379b1f..b35cc8e5a 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -62,6 +62,7 @@ trait Decider { def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp], assumptionType: AssumptionType): Unit def assume(t: Term, debugExp: Option[DebugExp], assumptionType: AssumptionType): Unit + def assume(assumptions: Iterable[Term], debugExps: Option[Iterable[DebugExp]], description: String, enforceAssumption: Boolean, assumptionType: AssumptionType): Unit def assume(terms: Seq[Term], debugExps: Option[Seq[DebugExp]], assumptionType: AssumptionType): Unit def assumeDefinition(t: Term, debugExp: Option[DebugExp], assumptionType: AssumptionType): Unit def assume(terms: Iterable[Term], debugExp: Option[DebugExp], enforceAssumption: Boolean, assumptionType: AssumptionType): Unit @@ -342,6 +343,28 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } } + // TODO ake: review this + def assume(assumptions: Iterable[Term], debugExps: Option[Iterable[DebugExp]], description: String, enforceAssumption: Boolean, assumptionType: AssumptionType): Unit = { + val debugExp = Option.when(debugExps.isDefined)(DebugExp.createInstance(description, InsertionOrderedSet(debugExps.get))) + val assumptionIds = if(debugExps.isDefined) assumptionAnalyzer.addAssumptions(debugExps.get, assumptionAnalyzer.getFullSourceInfo, assumptionType) else Seq.empty + + val assumptionsWithLabels = + if(assumptions.size == assumptionIds.size) assumptions.zip(assumptionIds).map{case (t, id) => (t, AssumptionAnalyzer.createAssumptionLabel(Some(id)))} + else assumptions map (t => (t, "")) + + val filteredTerms = + if (enforceAssumption) assumptionsWithLabels + else assumptionsWithLabels filterNot(t => isKnownToBeTrue(t._1)) + + if(filteredTerms.isEmpty) return + + assumeWithoutSmokeChecks(InsertionOrderedSet(assumptionsWithLabels)) + + if (debugMode && debugExp.isDefined) { + addDebugExp(debugExp.get) + } + } + def assume(terms: Iterable[Term], debugExp: Option[DebugExp], enforceAssumption: Boolean, assumptionType: AssumptionType): Unit = { val filteredTerms = diff --git a/src/main/scala/rules/Brancher.scala b/src/main/scala/rules/Brancher.scala index dd1a08b43..d74370221 100644 --- a/src/main/scala/rules/Brancher.scala +++ b/src/main/scala/rules/Brancher.scala @@ -145,9 +145,9 @@ object brancher extends BranchingRules { executionFlowController.locally(s, v0)((s1, v1) => { v1.decider.prover.comment(s"[else-branch: $cnt | $negatedCondition]") - v1.decider.assumptionAnalyzer.addExpToStack(conditionExp._1) + v1.decider.assumptionAnalyzer.addFineGrainedSource(conditionExp._1) v1.decider.setCurrentBranchCondition(negatedCondition, (negatedConditionExp, negatedConditionExpNew)) - v1.decider.assumptionAnalyzer.popExpFromStack() + v1.decider.assumptionAnalyzer.popFineGrainedSource() var functionsOfElseBranchdDeciderBefore: Set[FunctionDecl] = null var nMacrosOfElseBranchDeciderBefore: Int = 0 @@ -197,9 +197,9 @@ object brancher extends BranchingRules { v.symbExLog.markReachable(uidBranchPoint) executionFlowController.locally(s, v)((s1, v1) => { v1.decider.prover.comment(s"[then-branch: $cnt | $condition]") - v1.decider.assumptionAnalyzer.addExpToStack(conditionExp._1) + v1.decider.assumptionAnalyzer.addFineGrainedSource(conditionExp._1) v1.decider.setCurrentBranchCondition(condition, conditionExp) - v1.decider.assumptionAnalyzer.popExpFromStack() + v1.decider.assumptionAnalyzer.popFineGrainedSource() fThen(v1.stateConsolidator(s1).consolidateOptionally(s1, v1), v1) }) diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index ee54b6f45..e86d48eb8 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -176,10 +176,10 @@ object consumer extends ConsumptionRules { val s1 = s0.copy(h = s.h) /* s1 is s, but the retrying flag might be set */ val sepIdentifier = v1.symbExLog.openScope(new ConsumeRecord(a, s1, v.decider.pcs)) - v.decider.assumptionAnalyzer.addExpToStack(a) + v.decider.assumptionAnalyzer.addFineGrainedSource(a) consumeTlc(s1, h0, a, returnSnap, pve, v1)((s2, h2, snap2, consumedChunks, v2) => { - v.decider.assumptionAnalyzer.popExpFromStack() + v.decider.assumptionAnalyzer.popFineGrainedSource() v2.symbExLog.closeScope(sepIdentifier) QS(s2, h2, snap2, consumedChunks, v2)}) })(Q) diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 1817105ac..c86f749f5 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -93,9 +93,9 @@ object evaluator extends EvaluationRules { : VerificationResult = { val sepIdentifier = v.symbExLog.openScope(new EvaluateRecord(e, s, v.decider.pcs)) - v.decider.assumptionAnalyzer.addExpToStack(e) + v.decider.assumptionAnalyzer.addFineGrainedSource(e) eval3(s, e, pve, v)((s1, t, eNew, v1) => { - v.decider.assumptionAnalyzer.popExpFromStack() + v.decider.assumptionAnalyzer.popFineGrainedSource() v1.symbExLog.closeScope(sepIdentifier) Q(s1, t, eNew, v1)}) } diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 9dae0a2f1..c4444f7eb 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -262,7 +262,7 @@ object executor extends ExecutionRules { (executionFlowController.locally(sBody, v)((s0, v0) => { v0.decider.prover.comment("Loop head block: Check well-definedness of invariant") val mark = v0.decider.setPathConditionMark() - produces(s0, freshSnap, invs, ContractNotWellformed, v0, AssumptionType.LoopInvariant)((s1, v1) => { // TODO ake: set source + produces(s0, freshSnap, invs, ContractNotWellformed, v0, AssumptionType.LoopInvariant)((s1, v1) => { phase1data = phase1data :+ (s1, v1.decider.pcs.after(mark), v1.decider.freshFunctions /* [BRANCH-PARALLELISATION] */) @@ -270,7 +270,6 @@ object executor extends ExecutionRules { })}) combine executionFlowController.locally(s, v)((s0, v0) => { v0.decider.prover.comment("Loop head block: Establish invariant") - // TODO ake: set source consumes(s0, invs, false, LoopInvariantNotEstablished, v0)((sLeftover, _, consumedChunks, v1) => { // TODO ake: add edges from consumedChunks v1.decider.prover.comment("Loop head block: Execute statements of loop head block (in invariant state)") phase1data.foldLeft(Success(): VerificationResult) { @@ -280,7 +279,7 @@ object executor extends ExecutionRules { intermediateResult combine executionFlowController.locally(s2, v1)((s3, v2) => { v2.decider.declareAndRecordAsFreshFunctions(ff1 -- v2.decider.freshFunctions) /* [BRANCH-PARALLELISATION] */ v2.decider.assumptionAnalyzer.addAnalysisSourceInfo(ExpAnalysisSourceInfo(BigAnd(pcs.assumptionExps.filter(_.originalExp.isDefined).map(_.originalExp.get)))) - v2.decider.assume(pcs.assumptions, Option.when(withExp)(DebugExp.createInstance("Loop invariant", pcs.assumptionExps)), false, assumptionType=AssumptionType.LoopInvariant) + v2.decider.assume(pcs.assumptions, Some(pcs.assumptionExps), "Loop invariant", enforceAssumption=false, assumptionType=AssumptionType.LoopInvariant) v2.decider.assumptionAnalyzer.popAnalysisSourceInfo() v2.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) if (v2.decider.checkSmoke()) @@ -441,7 +440,7 @@ object executor extends ExecutionRules { val pve = AssignmentFailed(ass) eval(s, eRcvr, pve, v)((s1, tRcvr, eRcvrNew, v1) => eval(s1, rhs, pve, v1)((s2, tRhs, rhsNew, v2) => { - v2.decider.assumptionAnalyzer.addExpToStack(fa) + v2.decider.assumptionAnalyzer.addFineGrainedSource(fa) val resource = fa.res(s.program) val ve = pve dueTo InsufficientPermission(fa) val description = s"consume ${ass.pos}: $ass" @@ -454,7 +453,7 @@ object executor extends ExecutionRules { val s5 = s4.copy(h = h4) val (debugHeapName, _) = v4.getDebugOldLabel(s5, fa.pos) val s6 = if (withExp) s5.copy(oldHeaps = s5.oldHeaps + (debugHeapName -> magicWandSupporter.getEvalHeap(s5))) else s5 - v4.decider.assumptionAnalyzer.popExpFromStack() + v4.decider.assumptionAnalyzer.popFineGrainedSource() Q(s6, v4) }) }) diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index 2f420f7b8..26bcce280 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -155,10 +155,10 @@ object producer extends ProductionRules { val pve = pves.head - v.decider.assumptionAnalyzer.addExpToStack(a) + v.decider.assumptionAnalyzer.addFineGrainedSource(a) if (as.tail.isEmpty) wrappedProduceTlc(s, sf, a, pve, v, assumptionType)((s1, v1) => { - v.decider.assumptionAnalyzer.popExpFromStack() + v.decider.assumptionAnalyzer.popFineGrainedSource() Q(s1, v1) }) else { @@ -172,7 +172,7 @@ object producer extends ProductionRules { */ wrappedProduceTlc(s, sf0, a, pve, v, assumptionType)((s1, v1) => { - v1.decider.assumptionAnalyzer.popExpFromStack() + v1.decider.assumptionAnalyzer.popFineGrainedSource() produceTlcs(s1, sf1, as.tail, pves.tail, v1, assumptionType)(Q) }) } catch { diff --git a/src/main/scala/rules/StateConsolidator.scala b/src/main/scala/rules/StateConsolidator.scala index b064388a0..7af32bb0d 100644 --- a/src/main/scala/rules/StateConsolidator.scala +++ b/src/main/scala/rules/StateConsolidator.scala @@ -61,6 +61,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol def consolidate(s: State, v: Verifier): State = { val comLog = new CommentRecord("state consolidation", s, v.decider.pcs) val sepIdentifier = v.symbExLog.openScope(comLog) + v.decider.assumptionAnalyzer.addFineGrainedSource(StringAnalysisSourceInfo("state consolidation", NoPosition)) v.decider.prover.comment("[state consolidation]") v.decider.prover.saturate(config.proverSaturationTimeouts.beforeIteration) @@ -108,6 +109,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), AssumptionType.PathCondition)) } + v.decider.assumptionAnalyzer.popFineGrainedSource() v.symbExLog.closeScope(sepIdentifier) (functionRecorder, hs :+ Heap(mergedChunks)) } @@ -208,12 +210,11 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol } private def mergeChunks1(fr1: FunctionRecorder, chunk1: Chunk, chunk2: Chunk, qvars: Seq[Var], v: Verifier): Option[(FunctionRecorder, Chunk, Term)] = { - val analysisInfo = AnalysisInfo(v.decider.assumptionAnalyzer, StringAnalysisSourceInfo("state consolidation", NoPosition), AssumptionType.Internal) - (chunk1, chunk2) match { + (chunk1, chunk2) match { case (BasicChunk(rid1, id1, args1, args1Exp, snap1, snap1Exp, perm1, perm1Exp), BasicChunk(_, _, _, _, snap2, _, perm2, perm2Exp)) => val (fr2, combinedSnap, snapEq) = combineSnapshots(fr1, snap1, snap2, perm1, perm2, qvars, v) val newExp = perm1Exp.map(p1 => ast.PermAdd(p1, perm2Exp.get)()) - Some(fr2, BasicChunk(rid1, id1, args1, args1Exp, combinedSnap, snap1Exp, PermPlus(perm1, perm2), newExp, analysisInfo), snapEq) + Some(fr2, BasicChunk(rid1, id1, args1, args1Exp, combinedSnap, snap1Exp, PermPlus(perm1, perm2), newExp, v.decider.assumptionAnalyzer.getAnalysisInfo(AssumptionType.Internal)), snapEq) case (l@QuantifiedFieldChunk(id1, fvf1, condition1, condition1Exp, perm1, perm1Exp, invs1, singletonRcvr1, singletonRcvr1Exp, hints1), r@QuantifiedFieldChunk(_, fvf2, _, _, perm2, perm2Exp, _, _, _, hints2)) => assert(l.quantifiedVars == Seq(`?r`)) @@ -223,14 +224,14 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol val permSum = PermPlus(perm1, perm2) val permSumExp = perm1Exp.map(p1 => ast.PermAdd(p1, perm2Exp.get)()) val bestHints = if (hints1.nonEmpty) hints1 else hints2 - Some(fr2, QuantifiedFieldChunk(id1, combinedSnap, condition1, condition1Exp, permSum, permSumExp, invs1, singletonRcvr1, singletonRcvr1Exp, bestHints, analysisInfo), snapEq) + Some(fr2, QuantifiedFieldChunk(id1, combinedSnap, condition1, condition1Exp, permSum, permSumExp, invs1, singletonRcvr1, singletonRcvr1Exp, bestHints, v.decider.assumptionAnalyzer.getAnalysisInfo(AssumptionType.Internal)), snapEq) case (l@QuantifiedPredicateChunk(id1, qVars1, qVars1Exp, psf1, _, _, perm1, perm1Exp, _, _, _, _), r@QuantifiedPredicateChunk(_, qVars2, qVars2Exp, psf2, condition2, condition2Exp, perm2, perm2Exp, invs2, singletonArgs2, singletonArgs2Exp, hints2)) => val (fr2, combinedSnap, snapEq) = quantifiedChunkSupporter.combinePredicateSnapshotMaps(fr1, id1.name, qVars2, psf1, psf2, l.perm.replace(qVars1, qVars2), r.perm, v) val permSum = PermPlus(perm1.replace(qVars1, qVars2), perm2) val permSumExp = perm1Exp.map(p1 => ast.PermAdd(p1.replace(qVars1Exp.get.zip(qVars2Exp.get).toMap), perm2Exp.get)()) - Some(fr2, QuantifiedPredicateChunk(id1, qVars2, qVars2Exp, combinedSnap, condition2, condition2Exp, permSum, permSumExp, invs2, singletonArgs2, singletonArgs2Exp, hints2, analysisInfo), snapEq) + Some(fr2, QuantifiedPredicateChunk(id1, qVars2, qVars2Exp, combinedSnap, condition2, condition2Exp, permSum, permSumExp, invs2, singletonArgs2, singletonArgs2Exp, hints2, v.decider.assumptionAnalyzer.getAnalysisInfo(AssumptionType.Internal)), snapEq) case _ => None } From c7a78a80dbb4e22e20cb8e4434edf24bc6e73004 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 22 May 2025 17:32:28 +0200 Subject: [PATCH 061/474] update graph export format --- .../AnalysisSourceInfo.scala | 2 +- .../AssumptionAnalysisGraph.scala | 40 +++++++++++++------ .../AssumptionAnalyzer.scala | 4 +- 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala index cba835b94..507762d78 100644 --- a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala +++ b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala @@ -57,7 +57,7 @@ case class StringAnalysisSourceInfo(description: String, position: Position) ext } case class CompositeAnalysisSourceInfo(coarseGrainedSource: AnalysisSourceInfo, fineGrainedSource: AnalysisSourceInfo) extends AnalysisSourceInfo { - override def toString: String = coarseGrainedSource.toString + " -> " + fineGrainedSource.toString + override def toString: String = coarseGrainedSource.toString // TODO ake: + " -> " + fineGrainedSource.toString override def getPosition: Position = coarseGrainedSource.getPosition override def equals(obj: Any): Boolean = coarseGrainedSource.equals(obj) // TODO ake diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index eec6d0906..7a96c11d9 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -5,7 +5,7 @@ import viper.silicon.interfaces.state.Chunk import viper.silicon.state.terms.Term import viper.silver.ast -import java.io.PrintWriter +import java.io.{File, PrintWriter} import java.util.concurrent.atomic.AtomicInteger import scala.collection.mutable @@ -85,20 +85,29 @@ trait AssumptionAnalysisGraph { } } - def getNodeExportString(node: AssumptionAnalysisNode): String = { - node.id + " | " + node.getClass.getSimpleName + " | " + node.assumptionType + " | " + node.getNodeString + " | " + node.sourceInfo.toString + def exportGraph(fileName: String): Unit = { + val directory = new File(fileName) + directory.mkdir() + exportNodes(fileName) + exportEdges(fileName + "/edges.csv") } - def exportGraph(fileName: String): Unit = { + def exportEdges(fileName: String): Unit = { val writer = new PrintWriter(fileName) - writer.println("====== Nodes ======") - writer.println("id | node type | assumption type | node info | source info") + writer.println("source,target,label") + edges foreach (e => e._2 foreach (t => writer.println(e._1 + "," + t + ",direct"))) + transitiveEdges foreach (e => e._2 foreach (t => writer.println(e._1 + "," + t + ",transitive"))) + writer.close() + } + + private def exportNodes(fileName: String): Unit = { + def getNodeExportString(node: AssumptionAnalysisNode): String = { + node.id + " | " + node.getNodeType + " | " + node.assumptionType + " | " + node.getNodeString + + " | " + node.sourceInfo.toString + " | " + node.sourceInfo.getStringForExport + } + val writer = new PrintWriter(fileName + "/nodes.csv") + writer.println("id | node type | assumption type | node info | source info | position") nodes foreach (n => writer.println(getNodeExportString(n).replace("\n", " "))) - writer.println("====== Edges ======") - edges foreach (e => writer.println(e._1 + " -> " + e._2.mkString(","))) - writer.println("====== Transitive Edges ======") - transitiveEdges foreach (e => writer.println(e._1 + " -> " + e._2.mkString(","))) - writer.println("====== End ======") writer.close() } } @@ -140,6 +149,7 @@ trait AssumptionAnalysisNode { override def toString: String = id.toString + " | " + getNodeString + " | " + sourceInfo.toString def getNodeString: String + def getNodeType: String def isIncludedInAnalysis: Boolean = assumptionType match {// TODO ake case Internal => false @@ -147,8 +157,11 @@ trait AssumptionAnalysisNode { } } -trait GeneralAssumptionNode extends AssumptionAnalysisNode {} +trait GeneralAssumptionNode extends AssumptionAnalysisNode { + override def getNodeType: String = "Assumption" +} trait GeneralAssertionNode extends AssumptionAnalysisNode { + override def getNodeType: String = "Assertion" var isAsserted = false } @@ -179,15 +192,18 @@ case class StringAssertionNode(description: String, sourceInfo: AnalysisSourceIn case class SimpleCheckNode(t: Term, sourceInfo: AnalysisSourceInfo) extends GeneralAssertionNode { val assumptionType: AssumptionType = Internal override def getNodeString: String = "check " + t + override def getNodeType: String = "Check" } case class PermissionInhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Unknown) extends GeneralAssumptionNode with ChunkAnalysisInfo { override def getNodeString: String = "inhale " + chunk.getAnalysisInfo + override def getNodeType: String = "Inhale" } case class PermissionExhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo) extends GeneralAssertionNode with ChunkAnalysisInfo { isAsserted = true val assumptionType: AssumptionType = Explicit + override def getNodeType: String = "Exhale" override def getNodeString: String = "exhale " + chunk.getAnalysisInfo } diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 973e5674f..e64c52987 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -181,7 +181,7 @@ class DefaultAssumptionAnalyzer(member: Member) extends AssumptionAnalyzer { override def getMember: Option[Member] = Some(member) override def exportGraph(): Unit = { - val filename: Option[String] = getMember map { + val foldername: Option[String] = getMember map { case Method(name, _, _, _, _, _) => name case ast.Function(name, _, _, _, _, _) => name case Domain(name, _, _, _, _) => name @@ -189,7 +189,7 @@ class DefaultAssumptionAnalyzer(member: Member) extends AssumptionAnalyzer { case location: Location => location.pos.toString case member: ExtensionMember => member.pos.toString } - assumptionGraph.exportGraph("graphExports/" + filename.getOrElse("latestExport") + ".txt") + assumptionGraph.exportGraph("graphExports/" + foldername.getOrElse("latestExport")) } } From d3a9232294ac1fe44125465e42d2aa4056acf22d Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 24 May 2025 11:49:17 +0200 Subject: [PATCH 062/474] update and add examples --- src/test/resources/andrea/assert-assume.vpr | 15 -- src/test/resources/andrea/branches.vpr | 150 ++++++++++++++++-- src/test/resources/andrea/copy-inc.vpr | 23 --- src/test/resources/andrea/domains.vpr | 38 +++++ src/test/resources/andrea/framing.vpr | 20 --- src/test/resources/andrea/gaussian.vpr | 51 +++++- src/test/resources/andrea/list.vpr | 37 +++++ src/test/resources/andrea/lseg.vpr | 30 ++++ src/test/resources/andrea/magicWands.vpr | 1 + src/test/resources/andrea/method-sum.vpr | 11 +- src/test/resources/andrea/permissions.vpr | 69 +------- .../resources/andrea/predicates-unfold.vpr | 21 --- .../{predicates-fold.vpr => predicates.vpr} | 0 src/test/resources/andrea/quantified-perm.vpr | 36 ++++- src/test/resources/andrea/tuple.vpr | 25 +++ 15 files changed, 365 insertions(+), 162 deletions(-) delete mode 100644 src/test/resources/andrea/assert-assume.vpr delete mode 100644 src/test/resources/andrea/copy-inc.vpr create mode 100644 src/test/resources/andrea/domains.vpr delete mode 100644 src/test/resources/andrea/framing.vpr create mode 100644 src/test/resources/andrea/list.vpr create mode 100644 src/test/resources/andrea/lseg.vpr delete mode 100644 src/test/resources/andrea/predicates-unfold.vpr rename src/test/resources/andrea/{predicates-fold.vpr => predicates.vpr} (100%) create mode 100644 src/test/resources/andrea/tuple.vpr diff --git a/src/test/resources/andrea/assert-assume.vpr b/src/test/resources/andrea/assert-assume.vpr deleted file mode 100644 index 272fef7c6..000000000 --- a/src/test/resources/andrea/assert-assume.vpr +++ /dev/null @@ -1,15 +0,0 @@ - -function foo(n: Int): Int - requires n > 0 - ensures result >= 0 - - -method test(n: Int) - requires n > 0 -{ - var a: Int - a := foo(n) - assert a >= 0 - assert a < 0 -} - diff --git a/src/test/resources/andrea/branches.vpr b/src/test/resources/andrea/branches.vpr index d14b2cfa4..a8519941d 100644 --- a/src/test/resources/andrea/branches.vpr +++ b/src/test/resources/andrea/branches.vpr @@ -2,12 +2,14 @@ field f: Int -method foo(a: Int, b: Int) +method branches2(a: Int, b: Int) { var n:Int, c: Bool - assume a > 0 - assume b > 0 + assume 0 < a && a < 100 + assume 0 < b + assume b < 50 + assume c ==> a > 5 if(c){ n := a + 1 @@ -20,32 +22,156 @@ method foo(a: Int, b: Int) x := a + b }else{ x := n + 1 - assert x >= 1 + assert x > 1 } + assert n > 1 assert x > n } -method fooPerm(a: Ref, b: Ref) +method branchesPerm(a: Ref, b: Ref) requires acc(a.f) && acc(b.f) { var n:Int, c: Bool - assume a.f > 0 - assume b.f > 0 + assume 0 < a.f && a.f < 100 + assume 0 < b.f + assume b.f < 50 + assume c ==> a.f > 5 if(c){ - n := a.f + n := a.f + 1 }else{ - n := b.f + n := b.f + 1 } var x: Int := 0 if(a.f > n){ x := a.f + b.f }else{ - x := n - assert x >= 1 + x := n + 1 + assert x > 1 + } + assert n > 1 + assert x > n +} + +method nestedBranches1(a: Int, b: Int) +{ + var n:Int, c: Bool + + assume 0 < a && a < 100 + assume 0 < b + assume b < 50 + assume c ==> a > 5 + + if(c){ + if(a > b){ + n := a - b + }else{ + n := a + b + assert n > 0 + } + n := n - 1 + }else{ + n := a + b + } + + assert n >= 0 + assert n <= a + b + assert c ==> n < a + b +} + +function sum(a: Int, b: Int): Int + requires a >= 0 && b >= 0 + ensures result == a + b + ensures result >= 0 + ensures result >= a && result >= b +{ + a + b +} + +function diff(a: Int, b: Int): Int + requires a >= 0 && b >= 0 + requires a >= b + ensures result == a - b + ensures result >= 0 + ensures result <= a +{ + a - b +} + +method branchesFunctions(a: Int, b: Int) +{ + var n:Int, c: Bool + + assume 0 < a && a < 100 + assume 0 < b + assume b < 50 + assume c ==> a > 5 + + if(c){ + if(a > b){ + n := diff(a, b) + }else{ + n := sum(a, b) + assert n > 0 + } + n := n - 1 + }else{ + n := sum(a, b) } - assert x >= n + + assert n >= 0 + assert n <= a + b + assert c ==> n < a + b +} + + +function sumPerm(a: Ref, b: Ref): Int + requires acc(a.f, wildcard) && acc(b.f, wildcard) + requires a.f >= 0 && b.f >= 0 + ensures result == a.f + b.f + ensures result >= 0 + ensures result >= a.f && result >= b.f +{ + a.f + b.f +} + +function diffPerm(a: Ref, b: Ref): Int + requires acc(a.f, wildcard) && acc(b.f, wildcard) + requires a.f >= 0 && b.f >= 0 + requires a.f >= b.f + ensures result == a.f - b.f + ensures result >= 0 + ensures result <= a.f +{ + a.f - b.f +} + +method branchesFunctionsPerm(a: Ref, b: Ref) + requires acc(a.f) && acc(b.f) +{ + var n:Int, c: Bool + + assume 0 < a.f && a.f < 100 + assume 0 < b.f + assume b.f < 50 + assume c ==> a.f > 5 + + if(c){ + if(a.f > b.f){ + n := diffPerm(a, b) + }else{ + n := sumPerm(a, b) + assert n > 0 + } + n := n - 1 + }else{ + n := sumPerm(a, b) + } + + assert n >= 0 + assert n <= a.f + b.f + assert c ==> n < a.f + b.f } \ No newline at end of file diff --git a/src/test/resources/andrea/copy-inc.vpr b/src/test/resources/andrea/copy-inc.vpr deleted file mode 100644 index e8ad3b94b..000000000 --- a/src/test/resources/andrea/copy-inc.vpr +++ /dev/null @@ -1,23 +0,0 @@ -field f: Int - -method copyAndInc(x: Ref, y: Ref) - requires acc(x.f) && acc(y.f, 1/2) - ensures acc(x.f) && acc(y.f, 1/2) - ensures x.f == y.f + 1 -{ - x.f := y.f + 1 -} - -method client(a: Ref, b: Ref) { - var L0: Bool, L1: Bool - inhale L0 ==> acc(a.f) - inhale L1 ==> acc(b.f) - assume L0 && L1 - - a.f := 1 - b.f := 3 - - copyAndInc(a, b) - - assert b.f == 3 && a.f == 4 -} diff --git a/src/test/resources/andrea/domains.vpr b/src/test/resources/andrea/domains.vpr new file mode 100644 index 000000000..4897d27e1 --- /dev/null +++ b/src/test/resources/andrea/domains.vpr @@ -0,0 +1,38 @@ +field val: Int +define access(a) + (forall j: Int :: 0 <= j && j < len(a) ==> acc(slot(a,j).val)) + +domain IArray { + function slot(a: IArray, i: Int): Ref + function len(a: IArray): Int + function first(r: Ref): IArray + function second(r: Ref): Int + + axiom all_diff { + forall a: IArray, i: Int :: { slot(a,i) } + first(slot(a,i)) == a && second(slot(a,i)) == i + } + + axiom len_nonneg { + forall a: IArray :: { len(a) } + len(a) >= 0 + } +} + +method client() +{ + // Create an integer array with three elements + var a: IArray + inhale len(a) == 3 + inhale access(a) // access to all array slots + + // Initialize the elements of an array + var i: Int := 0 + while ( i < len(a) ) + invariant access(a) + invariant 0 <= i && i <= len(a) + { + slot(a,i).val := -i // models a[i] := -i + i := i + 1 + } +} \ No newline at end of file diff --git a/src/test/resources/andrea/framing.vpr b/src/test/resources/andrea/framing.vpr deleted file mode 100644 index bc028121e..000000000 --- a/src/test/resources/andrea/framing.vpr +++ /dev/null @@ -1,20 +0,0 @@ - - -field f: Int - -method foo(a: Ref) returns(n:Int) - requires acc(a.f, 1/2) - ensures acc(a.f, 1/2) && n == a.f -{ - n := a.f -} - -method framing1(a: Ref) - requires acc(a.f) && a.f > 0 - ensures acc(a.f) -{ - var n: Int - n := foo(a) - assert n > 0 - a.f := 5 -} diff --git a/src/test/resources/andrea/gaussian.vpr b/src/test/resources/andrea/gaussian.vpr index eadc69710..a31fa12db 100644 --- a/src/test/resources/andrea/gaussian.vpr +++ b/src/test/resources/andrea/gaussian.vpr @@ -1,5 +1,6 @@ +field f: Int -method sum(n: Int) returns (res: Int) +method gaussianSimple(n: Int) returns (res: Int) requires 0 <= n requires n <= 5 ensures res == n * (n + 1) / 2 @@ -15,3 +16,51 @@ method sum(n: Int) returns (res: Int) i := i + 1 } } + +method gaussianPerm(a: Ref, p: Perm) returns (res: Int) + requires none < p && p < write + requires acc(a.f, p) + requires 0 <= a.f + requires a.f <= 5 + ensures acc(a.f, p) + ensures res == a.f * (a.f + 1) / 2 +{ + res := 0 + var i: Int := 0 + while(i <= a.f) + invariant acc(a.f, p) + invariant 0 <= a.f && a.f <= 5 + invariant i <= (a.f + 1) + invariant i <= 6 + invariant res == (i - 1) * i / 2 + { + res := res + i + i := i + 1 + } +} + +predicate gaussianEq(res: Int, n: Int){ + res == (n - 1) * n / 2 && n >= 0 +} + +method gaussianPred(n: Int) returns (res: Int) + requires 0 <= n + requires n <= 5 + ensures gaussianEq(res, n+1) +{ + res := 0 + var i: Int := 0 + fold gaussianEq(res, i) + while(i <= n) + invariant i <= (n + 1) + invariant i <= 6 + invariant gaussianEq(res, i) + { + unfold gaussianEq(res, i) + res := res + i + i := i + 1 + fold gaussianEq(res, i) + } + unfold gaussianEq(res, i) + fold gaussianEq(res, n+1) +} diff --git a/src/test/resources/andrea/list.vpr b/src/test/resources/andrea/list.vpr new file mode 100644 index 000000000..c763ab30f --- /dev/null +++ b/src/test/resources/andrea/list.vpr @@ -0,0 +1,37 @@ +field elem: Int +field next: Ref + +predicate list(this: Ref) { + acc(this.elem) && acc(this.next) && + (this.next != null ==> list(this.next)) +} + +function listLength(l:Ref) : Int + requires list(l) + ensures result > 0 +{ + unfolding list(l) in l.next == null ? 1 : 1 + listLength(l.next) +} + +method appendList(this: Ref, e: Int) + requires list(this) + requires 0 <= e && e < 100 + ensures list(this) +{ + unfold list(this) + assume 0 <= this.elem && this.elem < 100 + + if (this.next == null) { + var n: Ref + + n := new(elem, next) + n.elem := e + n.next := null + this.next := n + + fold list(n) + } else { + appendList(this.next, e) + } + fold list(this) +} diff --git a/src/test/resources/andrea/lseg.vpr b/src/test/resources/andrea/lseg.vpr new file mode 100644 index 000000000..b696523f7 --- /dev/null +++ b/src/test/resources/andrea/lseg.vpr @@ -0,0 +1,30 @@ +field elem: Int +field next: Ref + +predicate lseg(this: Ref, last: Ref) { + this != last ==> + acc(this.elem) && acc(this.next) && + this.next != null && + lseg(this.next, last) +} + +function values(this: Ref, last: Ref): Seq[Int] + requires lseg(this, last) +{ + unfolding lseg(this, last) in + this == last + ? Seq[Int]() + : Seq(this.elem) ++ values(this.next, last) +} + +method removeFirst(this: Ref, last: Ref) returns (first: Int, rest: Ref) + requires lseg(this, last) + requires this != last + ensures lseg(rest, last) + ensures values(rest, last) == old(values(this, last)[1..]) +{ + unfold lseg(this, last) + + first := this.elem + rest := this.next +} diff --git a/src/test/resources/andrea/magicWands.vpr b/src/test/resources/andrea/magicWands.vpr index aa15eafca..347d1cb98 100644 --- a/src/test/resources/andrea/magicWands.vpr +++ b/src/test/resources/andrea/magicWands.vpr @@ -39,6 +39,7 @@ method appendit_wand(l1 : Ref, l2: Ref) ensures list(l1) // && elems(l1) == old(elems(l1) ++ elems(l2)) { unfold list(l1) + assume l1.val >= 0 if(l1.next == null) { // easy case l1.next := l2; fold list(l1) } else { diff --git a/src/test/resources/andrea/method-sum.vpr b/src/test/resources/andrea/method-sum.vpr index 79ce75bf3..426e74ede 100644 --- a/src/test/resources/andrea/method-sum.vpr +++ b/src/test/resources/andrea/method-sum.vpr @@ -3,12 +3,12 @@ field f: Int method sum(x: Int, y: Int) returns(res: Int) requires x >= 0 && y >= 0 ensures res == x + y -//{ -// res := x + y -//} +{ + res := x + y +} -method methodSum(x: Int, y: Int) +method sumClient(x: Int, y: Int) { assume x >= 0 assume y >= 0 @@ -18,6 +18,5 @@ method methodSum(x: Int, y: Int) n := n + n2 assert n >= x + 2*y - assert n < 0 - + assert n >= 0 } \ No newline at end of file diff --git a/src/test/resources/andrea/permissions.vpr b/src/test/resources/andrea/permissions.vpr index 0f1f85cde..6b46d60ce 100644 --- a/src/test/resources/andrea/permissions.vpr +++ b/src/test/resources/andrea/permissions.vpr @@ -5,16 +5,9 @@ function id(n: Int): Int method foo(a: Ref) requires acc(a.f, 1/2) && a.f >= 0 - ensures acc(a.f, 1/2) + ensures acc(a.f, 1/2) -method simple(x: Ref, y: Ref, p: Perm) - requires acc(x.f) && acc(y.f) - ensures acc(x.f) && acc(y.f) -{ - x.f := y.f + 1 -} - -method call1(x: Ref) +method call(x: Ref) requires acc(x.f) ensures acc(x.f) { @@ -23,58 +16,7 @@ method call1(x: Ref) assert x.f >= 0 } -method call2(x: Ref) - requires acc(x.f) - ensures acc(x.f) -{ - assume x.f > 0 - x.f := id(x.f) - assert x.f >= 0 -} - -method inhaleExhalePartial(x: Ref) -{ - inhale acc(x.f) && x.f > 0 - foo(x) - exhale acc(x.f, 1/2) -} - -method inhaleExhaleFull(x: Ref) -{ - inhale acc(x.f) && x.f > 0 - foo(x) - exhale acc(x.f) -} - -method permAmount(x: Ref, p: Perm) -{ - assume p > none - inhale acc(x.f, p) && x.f > 0 - assume p > 1/2 - foo(x) -} - -method quantifiedPerm1(xs: Seq[Ref]) -{ - assume |xs| > 5 - inhale forall x: Ref :: x in xs ==> acc(x.f) - - var a : Ref := xs[1] - a.f := 0 -} - -method quantifiedPerm2(xs: Seq[Ref]) -{ - assume |xs| > 5 - inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) - - assert xs[2].f > 0 - - var a : Ref := xs[0] - a.f := 0 -} - -method bar(x: Ref, y: Ref, p: Perm) +method permAmount(x: Ref, y: Ref, p: Perm) requires p > none requires acc(x.f) && acc(y.f, p) ensures acc(x.f) && acc(y.f, p) @@ -99,10 +41,13 @@ method transitivity(a: Ref, n: Int) method maybeAlias(a: Ref, b: Ref, c: Bool, n: Int) requires acc(a.f, 1/2) && acc(b.f, 1/2) requires c ==> a == b - requires a.f > 0 && n > 0 + requires a.f > 0 && n > 0 && b.f >= 0 + requires a.f < 100 + requires !c ==> a.f < b.f { if(c){ a.f := n + 1 } assert a.f >= 0 + assert !c ==> a.f <= 100 } \ No newline at end of file diff --git a/src/test/resources/andrea/predicates-unfold.vpr b/src/test/resources/andrea/predicates-unfold.vpr deleted file mode 100644 index 0efe58040..000000000 --- a/src/test/resources/andrea/predicates-unfold.vpr +++ /dev/null @@ -1,21 +0,0 @@ - -predicate greater0(n: Int) -{ - n > 0 -} - -method foo(n: Int, b: Bool) - requires b ==> greater0(n) // this is not reported in any unsat core! - // requires n > 0 -{ - var x: Int - if(b){ - unfold greater0(n) - x := n + 1 - fold greater0(n) - }else{ - x := 19 - } - - assert x > 1 -} \ No newline at end of file diff --git a/src/test/resources/andrea/predicates-fold.vpr b/src/test/resources/andrea/predicates.vpr similarity index 100% rename from src/test/resources/andrea/predicates-fold.vpr rename to src/test/resources/andrea/predicates.vpr diff --git a/src/test/resources/andrea/quantified-perm.vpr b/src/test/resources/andrea/quantified-perm.vpr index 76bbe0c48..b447239bb 100644 --- a/src/test/resources/andrea/quantified-perm.vpr +++ b/src/test/resources/andrea/quantified-perm.vpr @@ -1,11 +1,43 @@ field f: Int +field first : Ref +field second : Ref -method foo(xs: Seq[Ref]) { +method quantifiedPerm(xs: Seq[Ref]) { assume |xs| > 5 inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) + assert xs[2].f > 0 var a : Ref := xs[0] a.f := 0 +} - assert xs[2].f > 0 +method quantifiedWritePerm(nodes: Set[Ref], x: Ref) + requires forall n:Ref :: { n.first } n in nodes ==> + acc(n.first) && + (n.first != null ==> n.first in nodes) + requires forall n:Ref :: { n.second } n in nodes ==> + acc(n.second) && + (n.second != null ==> n.second in nodes) + requires x in nodes +{ + var y : Ref + if(x.second != null) { + y := x.second // permissions covered by preconditions + y.second := y + } +} + +method quantifiedSum(nodes: Set[Ref], x: Ref) + requires forall n:Ref :: { n.first } n in nodes ==> + acc(n.first) && + (n.first != null ==> n.first in nodes) + requires forall n:Ref :: { n.f } n in nodes ==> + acc(n.f) && 0 <= n.f && n.f <= 100 + requires x in nodes +{ + var a: Int := x.f + if(x.first != null) { + a := a + x.first.f + } + assert a >= 0 } \ No newline at end of file diff --git a/src/test/resources/andrea/tuple.vpr b/src/test/resources/andrea/tuple.vpr new file mode 100644 index 000000000..45982c815 --- /dev/null +++ b/src/test/resources/andrea/tuple.vpr @@ -0,0 +1,25 @@ +field left: Int +field right: Int + +predicate tuple(this: Ref) { + acc(this.left) && acc(this.right) +} + +method setTuple(this: Ref, l: Int, r: Int) + requires tuple(this) + ensures tuple(this) +{ + unfold tuple(this) + this.left := l + this.right := r + fold tuple(this) +} + +method addTuple(this: Ref) returns (sum: Int) + requires acc(tuple(this), 1/2) + ensures acc(tuple(this), 1/2) +{ + unfold acc(tuple(this), 1/2) + sum := this.left + this.right + fold acc(tuple(this), 1/2) +} \ No newline at end of file From e7beabf481d5739825018e7f86440407bd4e4eff Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 26 May 2025 16:25:34 +0200 Subject: [PATCH 063/474] minor fixes --- .../AssumptionAnalysisGraph.scala | 8 +++-- .../AssumptionAnalyzer.scala | 30 +++++++------------ src/main/scala/rules/Brancher.scala | 16 ++++++---- src/main/scala/rules/Consumer.scala | 4 +-- src/main/scala/rules/Evaluator.scala | 4 +-- src/main/scala/rules/Executor.scala | 6 ++-- src/main/scala/rules/Producer.scala | 6 ++-- src/main/scala/rules/StateConsolidator.scala | 4 +-- src/test/resources/andrea/branches.vpr | 8 ----- 9 files changed, 38 insertions(+), 48 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index 7a96c11d9..7dcafbad8 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -101,12 +101,14 @@ trait AssumptionAnalysisGraph { } private def exportNodes(fileName: String): Unit = { + val sep = "#" def getNodeExportString(node: AssumptionAnalysisNode): String = { - node.id + " | " + node.getNodeType + " | " + node.assumptionType + " | " + node.getNodeString + - " | " + node.sourceInfo.toString + " | " + node.sourceInfo.getStringForExport + val parts = Seq(node.id.toString, node.getNodeType, node.assumptionType.toString, node.getNodeString, node.sourceInfo.toString, node.sourceInfo.getStringForExport) + parts.map(_.replace("#", "@")).mkString(sep) } val writer = new PrintWriter(fileName + "/nodes.csv") - writer.println("id | node type | assumption type | node info | source info | position") + val headerParts = Seq("id", "node type", "assumption type", "node info", "source info", "position") + writer.println(headerParts.mkString(sep)) nodes foreach (n => writer.println(getNodeExportString(n).replace("\n", " "))) writer.close() } diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index e64c52987..33f8b8742 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -29,43 +29,33 @@ trait AssumptionAnalyzer { val assumptionGraph: AssumptionAnalysisGraph = new DefaultAssumptionAnalysisGraph() - private var coarseGrainedSourceStack: List[AnalysisSourceInfo] = List.empty - private var fineGrainedSourceStack: List[AnalysisSourceInfo] = List.empty + var sourceInfoes: List[AnalysisSourceInfo] = List.empty // TODO ake: make private def getAnalysisInfo: AnalysisInfo = getAnalysisInfo(AssumptionType.Implicit) def getAnalysisInfo(assumptionType: AssumptionType): AnalysisInfo = AnalysisInfo(this, getFullSourceInfo, assumptionType) def getFullSourceInfo: AnalysisSourceInfo = { - if(fineGrainedSourceStack.isEmpty){ - coarseGrainedSourceStack.headOption.getOrElse(NoAnalysisSourceInfo()) - }else if(coarseGrainedSourceStack.isEmpty){ - fineGrainedSourceStack.head - }else{ - CompositeAnalysisSourceInfo(coarseGrainedSourceStack.head, fineGrainedSourceStack.head) + if(sourceInfoes.size <= 1){ + sourceInfoes.headOption.getOrElse(NoAnalysisSourceInfo()) + } else{ + CompositeAnalysisSourceInfo(sourceInfoes.last, sourceInfoes.head) } } def addAnalysisSourceInfo(analysisSourceInfo: AnalysisSourceInfo): AnalysisSourceInfo = { - coarseGrainedSourceStack = analysisSourceInfo +: coarseGrainedSourceStack + sourceInfoes = analysisSourceInfo +: sourceInfoes analysisSourceInfo } def popAnalysisSourceInfo(): Unit = { - coarseGrainedSourceStack = coarseGrainedSourceStack.tail + sourceInfoes = sourceInfoes.tail } - def addFineGrainedSource(e: ast.Exp): Unit = { - fineGrainedSourceStack = ExpAnalysisSourceInfo(e) +: fineGrainedSourceStack - } - - def addFineGrainedSource(analysisSourceInfo: AnalysisSourceInfo): Unit = { - fineGrainedSourceStack = analysisSourceInfo +: fineGrainedSourceStack - } + def getAnalysisSourceInfoes: List[AnalysisSourceInfo] = sourceInfoes - def popFineGrainedSource(): Unit = { - if(fineGrainedSourceStack.nonEmpty) - fineGrainedSourceStack = fineGrainedSourceStack.tail + def addAnalysisSourceInfo(e: ast.Exp): Unit = { + sourceInfoes = ExpAnalysisSourceInfo(e) +: sourceInfoes } def getMember: Option[Member] diff --git a/src/main/scala/rules/Brancher.scala b/src/main/scala/rules/Brancher.scala index d74370221..bcced1715 100644 --- a/src/main/scala/rules/Brancher.scala +++ b/src/main/scala/rules/Brancher.scala @@ -6,7 +6,8 @@ package viper.silicon.rules -import viper.silicon.assumptionAnalysis.{AssumptionType, ExpAnalysisSourceInfo} +import viper.silicon.assumptionAnalysis.{AnalysisSourceInfo, AssumptionType, ExpAnalysisSourceInfo} +import viper.silicon.common.collections.immutable.InsertionOrderedSet import java.util.concurrent._ import viper.silicon.common.concurrency._ @@ -58,6 +59,7 @@ object brancher extends BranchingRules { && s.quantifiedVariables.map(_._1).exists(condition.freeVariables.contains)) ) + v.decider.assumptionAnalyzer.addAnalysisSourceInfo(ExpAnalysisSourceInfo(conditionExp._1)) /* True if the then-branch is to be explored */ val executeThenBranch = ( skipPathFeasibilityCheck @@ -68,6 +70,7 @@ object brancher extends BranchingRules { !executeThenBranch /* Assumes that ast least one branch is feasible */ || skipPathFeasibilityCheck || !v.decider.check(condition, Verifier.config.checkTimeout())) + v.decider.assumptionAnalyzer.popAnalysisSourceInfo() val parallelizeElseBranch = s.parallelizeBranches && executeThenBranch && executeElseBranch @@ -97,6 +100,7 @@ object brancher extends BranchingRules { var macrosOfElseBranchDecider: Seq[MacroDecl] = null var pcsForElseBranch: PathConditionStack = null var noOfErrors = 0 + val currentStmtStack = v.decider.assumptionAnalyzer.getAnalysisSourceInfoes val elseBranchVerificationTask: Verifier => VerificationResult = if (executeElseBranch) { @@ -143,11 +147,12 @@ object brancher extends BranchingRules { } elseBranchVerifier = v0.uniqueId + v0.decider.assumptionAnalyzer.sourceInfoes = currentStmtStack executionFlowController.locally(s, v0)((s1, v1) => { v1.decider.prover.comment(s"[else-branch: $cnt | $negatedCondition]") - v1.decider.assumptionAnalyzer.addFineGrainedSource(conditionExp._1) + v1.decider.assumptionAnalyzer.addAnalysisSourceInfo(conditionExp._1) v1.decider.setCurrentBranchCondition(negatedCondition, (negatedConditionExp, negatedConditionExpNew)) - v1.decider.assumptionAnalyzer.popFineGrainedSource() + v1.decider.assumptionAnalyzer.popAnalysisSourceInfo() var functionsOfElseBranchdDeciderBefore: Set[FunctionDecl] = null var nMacrosOfElseBranchDeciderBefore: Int = 0 @@ -195,11 +200,12 @@ object brancher extends BranchingRules { val res = { val thenRes = if (executeThenBranch) { v.symbExLog.markReachable(uidBranchPoint) + v.decider.assumptionAnalyzer.sourceInfoes = currentStmtStack executionFlowController.locally(s, v)((s1, v1) => { v1.decider.prover.comment(s"[then-branch: $cnt | $condition]") - v1.decider.assumptionAnalyzer.addFineGrainedSource(conditionExp._1) + v1.decider.assumptionAnalyzer.addAnalysisSourceInfo(conditionExp._1) v1.decider.setCurrentBranchCondition(condition, conditionExp) - v1.decider.assumptionAnalyzer.popFineGrainedSource() + v1.decider.assumptionAnalyzer.popAnalysisSourceInfo() fThen(v1.stateConsolidator(s1).consolidateOptionally(s1, v1), v1) }) diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index e86d48eb8..c2d016591 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -176,10 +176,10 @@ object consumer extends ConsumptionRules { val s1 = s0.copy(h = s.h) /* s1 is s, but the retrying flag might be set */ val sepIdentifier = v1.symbExLog.openScope(new ConsumeRecord(a, s1, v.decider.pcs)) - v.decider.assumptionAnalyzer.addFineGrainedSource(a) + v.decider.assumptionAnalyzer.addAnalysisSourceInfo(a) consumeTlc(s1, h0, a, returnSnap, pve, v1)((s2, h2, snap2, consumedChunks, v2) => { - v.decider.assumptionAnalyzer.popFineGrainedSource() + v.decider.assumptionAnalyzer.popAnalysisSourceInfo() v2.symbExLog.closeScope(sepIdentifier) QS(s2, h2, snap2, consumedChunks, v2)}) })(Q) diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index c86f749f5..a9ab70fbe 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -93,9 +93,9 @@ object evaluator extends EvaluationRules { : VerificationResult = { val sepIdentifier = v.symbExLog.openScope(new EvaluateRecord(e, s, v.decider.pcs)) - v.decider.assumptionAnalyzer.addFineGrainedSource(e) + v.decider.assumptionAnalyzer.addAnalysisSourceInfo(e) eval3(s, e, pve, v)((s1, t, eNew, v1) => { - v.decider.assumptionAnalyzer.popFineGrainedSource() + v.decider.assumptionAnalyzer.popAnalysisSourceInfo() v1.symbExLog.closeScope(sepIdentifier) Q(s1, t, eNew, v1)}) } diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index c4444f7eb..8aa284edf 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -440,7 +440,7 @@ object executor extends ExecutionRules { val pve = AssignmentFailed(ass) eval(s, eRcvr, pve, v)((s1, tRcvr, eRcvrNew, v1) => eval(s1, rhs, pve, v1)((s2, tRhs, rhsNew, v2) => { - v2.decider.assumptionAnalyzer.addFineGrainedSource(fa) + v2.decider.assumptionAnalyzer.addAnalysisSourceInfo(fa) val resource = fa.res(s.program) val ve = pve dueTo InsufficientPermission(fa) val description = s"consume ${ass.pos}: $ass" @@ -453,7 +453,7 @@ object executor extends ExecutionRules { val s5 = s4.copy(h = h4) val (debugHeapName, _) = v4.getDebugOldLabel(s5, fa.pos) val s6 = if (withExp) s5.copy(oldHeaps = s5.oldHeaps + (debugHeapName -> magicWandSupporter.getEvalHeap(s5))) else s5 - v4.decider.assumptionAnalyzer.popFineGrainedSource() + v4.decider.assumptionAnalyzer.popAnalysisSourceInfo() Q(s6, v4) }) }) @@ -480,7 +480,7 @@ object executor extends ExecutionRules { field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), p, pExp, sm, s.program, v, AssumptionType.Implicit) } else { val newChunk = BasicChunk(FieldID, BasicChunkIdentifier(field.name), Seq(tRcvr), Option.when(withExp)(Seq(x)), snap, snapExp, p, pExp, - AnalysisInfo(v.decider.assumptionAnalyzer, ExpAnalysisSourceInfo(ast.FieldAccess(x, field)(field.pos, field.info, field.errT)), AssumptionType.Explicit)) + v.decider.assumptionAnalyzer.getAnalysisInfo(AssumptionType.Implicit)) newChunk } }) diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index 26bcce280..b917b65dd 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -155,10 +155,10 @@ object producer extends ProductionRules { val pve = pves.head - v.decider.assumptionAnalyzer.addFineGrainedSource(a) + v.decider.assumptionAnalyzer.addAnalysisSourceInfo(a) if (as.tail.isEmpty) wrappedProduceTlc(s, sf, a, pve, v, assumptionType)((s1, v1) => { - v.decider.assumptionAnalyzer.popFineGrainedSource() + v.decider.assumptionAnalyzer.popAnalysisSourceInfo() Q(s1, v1) }) else { @@ -172,7 +172,7 @@ object producer extends ProductionRules { */ wrappedProduceTlc(s, sf0, a, pve, v, assumptionType)((s1, v1) => { - v1.decider.assumptionAnalyzer.popFineGrainedSource() + v1.decider.assumptionAnalyzer.popAnalysisSourceInfo() produceTlcs(s1, sf1, as.tail, pves.tail, v1, assumptionType)(Q) }) } catch { diff --git a/src/main/scala/rules/StateConsolidator.scala b/src/main/scala/rules/StateConsolidator.scala index 7af32bb0d..c0977cee1 100644 --- a/src/main/scala/rules/StateConsolidator.scala +++ b/src/main/scala/rules/StateConsolidator.scala @@ -61,7 +61,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol def consolidate(s: State, v: Verifier): State = { val comLog = new CommentRecord("state consolidation", s, v.decider.pcs) val sepIdentifier = v.symbExLog.openScope(comLog) - v.decider.assumptionAnalyzer.addFineGrainedSource(StringAnalysisSourceInfo("state consolidation", NoPosition)) + v.decider.assumptionAnalyzer.addAnalysisSourceInfo(StringAnalysisSourceInfo("state consolidation", NoPosition)) v.decider.prover.comment("[state consolidation]") v.decider.prover.saturate(config.proverSaturationTimeouts.beforeIteration) @@ -109,7 +109,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), AssumptionType.PathCondition)) } - v.decider.assumptionAnalyzer.popFineGrainedSource() + v.decider.assumptionAnalyzer.popAnalysisSourceInfo() v.symbExLog.closeScope(sepIdentifier) (functionRecorder, hs :+ Heap(mergedChunks)) } diff --git a/src/test/resources/andrea/branches.vpr b/src/test/resources/andrea/branches.vpr index a8519941d..dc1363de5 100644 --- a/src/test/resources/andrea/branches.vpr +++ b/src/test/resources/andrea/branches.vpr @@ -85,8 +85,6 @@ method nestedBranches1(a: Int, b: Int) function sum(a: Int, b: Int): Int requires a >= 0 && b >= 0 ensures result == a + b - ensures result >= 0 - ensures result >= a && result >= b { a + b } @@ -95,8 +93,6 @@ function diff(a: Int, b: Int): Int requires a >= 0 && b >= 0 requires a >= b ensures result == a - b - ensures result >= 0 - ensures result <= a { a - b } @@ -132,8 +128,6 @@ function sumPerm(a: Ref, b: Ref): Int requires acc(a.f, wildcard) && acc(b.f, wildcard) requires a.f >= 0 && b.f >= 0 ensures result == a.f + b.f - ensures result >= 0 - ensures result >= a.f && result >= b.f { a.f + b.f } @@ -143,8 +137,6 @@ function diffPerm(a: Ref, b: Ref): Int requires a.f >= 0 && b.f >= 0 requires a.f >= b.f ensures result == a.f - b.f - ensures result >= 0 - ensures result <= a.f { a.f - b.f } From 1551884641406298effec08cbf736e7ebf779188 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 27 May 2025 15:25:38 +0200 Subject: [PATCH 064/474] minor fixes --- .../assumptionAnalysis/AssumptionAnalyzer.scala | 4 ++-- src/main/scala/rules/Evaluator.scala | 7 ++++--- src/main/scala/supporters/MethodSupporter.scala | 3 --- .../functions/FunctionVerificationUnit.scala | 6 ++++-- src/test/resources/andrea/branches.vpr | 14 ++++++++++++++ src/test/resources/andrea/gaussian.vpr | 3 +-- src/test/resources/andrea/predicates.vpr | 2 ++ 7 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 33f8b8742..92bb8e18f 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -103,7 +103,7 @@ class DefaultAssumptionAnalyzer(member: Member) extends AssumptionAnalyzer { override def addAssumptions(assumptions: Iterable[DebugExp], analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Seq[Int] = { val newNodes = assumptions.toSeq.map(a => - if (a.originalExp.isDefined) SimpleAssumptionNode(a.originalExp.get, analysisSourceInfo, assumptionType) + if (a.originalExp.isDefined) SimpleAssumptionNode(a.originalExp.get, if(analysisSourceInfo.isInstanceOf[NoAnalysisSourceInfo]) ExpAnalysisSourceInfo(a.originalExp.get) else analysisSourceInfo, assumptionType) else StringAssumptionNode(a.description.getOrElse("unknown"), analysisSourceInfo, AssumptionType.Internal) ) newNodes foreach addNode @@ -156,7 +156,7 @@ class DefaultAssumptionAnalyzer(member: Member) extends AssumptionAnalyzer { if(newChunkNodeId.isEmpty) return val oldChunkNodeIds = assumptionGraph.nodes - .filter(c => c.id != newChunkNodeId.get && !c.isInstanceOf[PermissionAssertNode] && c.isInstanceOf[ChunkAnalysisInfo] && oldChunks.contains(c.asInstanceOf[ChunkAnalysisInfo].getChunk)) + .filter(c => c.id != newChunkNodeId.get && c.isInstanceOf[PermissionInhaleNode] && oldChunks.contains(c.asInstanceOf[ChunkAnalysisInfo].getChunk)) .map(_.id).toSet assumptionGraph.addEdges(oldChunkNodeIds, newChunkNodeId.get) } diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index a9ab70fbe..168052521 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -186,7 +186,8 @@ object evaluator extends EvaluationRules { case _: ast.WildcardPerm => val (tVar, tConstraints, eVar) = v.decider.freshARP() - val constraintExp = Option.when(withExp)(DebugExp.createInstance(s"${eVar.get.toString} > none", true)) + val eConstraints = Some(ast.LocalVar("wildcardConstraints", ast.Bool)(e.pos, e.info, e.errT)) + val constraintExp = Option.when(withExp)(DebugExp.createInstance(None, eConstraints, eConstraints, Some(tConstraints), isInternal_ = true, children = InsertionOrderedSet())) v.decider.assumeDefinition(tConstraints, constraintExp, AssumptionType.Implicit) /* TODO: Only record wildcards in State.constrainableARPs that are used in exhale * position. Currently, wildcards used in inhale position (only) may not be removed @@ -202,7 +203,7 @@ object evaluator extends EvaluationRules { val s1 = s.copy(functionRecorder = s.functionRecorder.recordConstrainedVar(tVar, tConstraints)) .setConstrainable(Seq(tVar), true) - Q(s1, tVar, eVar, v) + Q(s1, tVar, eVar.map(eV => ast.LocalVarWithVersion(eV.name, eV.typ)(e.pos, e.info, e.errT)), v) case fa: ast.FieldAccess if s.qpFields.contains(fa.field) => eval(s, fa.rcv, pve, v)((s1, tRcvr, eRcvr, v1) => { @@ -590,7 +591,7 @@ object evaluator extends EvaluationRules { val currentPermAmount = PermLookup(field.name, pmDef.pm, args.head) v1.decider.prover.comment(s"perm($resacc) ~~> assume upper permission bound") val (debugHeapName, debugLabel) = v1.getDebugOldLabel(s2, resacc.pos, Some(h)) - val exp = Option.when(withExp)(ast.PermLeCmp(ast.DebugLabelledOld(ast.CurrentPerm(resacc)(), debugLabel)(), ast.FullPerm()())()) + val exp = Option.when(withExp)(ast.PermLeCmp(ast.DebugLabelledOld(ast.CurrentPerm(resacc)(), debugLabel)(), ast.FullPerm()())(resacc.pos, resacc.info, resacc.errT)) v1.decider.assume(PermAtMost(currentPermAmount, FullPerm), exp, exp.map(s2.substituteVarsInExp(_)), AssumptionType.Internal) val s3 = if (Verifier.config.enableDebugging()) s2.copy(oldHeaps = s2.oldHeaps + (debugHeapName -> h)) else s2 (s3, currentPermAmount) diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index ffd3a6ecb..e455dc03a 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -101,7 +101,6 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif * rules in Smans' paper. */ executionFlowController.locally(s, v)((s1, v1) => { - // TODO ake: set source produces(s1, freshSnap, pres, ContractNotWellformed, v1, AssumptionType.Explicit)((s2, v2) => { v2.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) val s2a = s2.copy(oldHeaps = s2.oldHeaps + (Verifier.PRE_STATE_LABEL -> s2.h)) @@ -109,14 +108,12 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif val s4 = s3.copy(h = Heap()) val impLog = new WellformednessCheckRecord(posts, s, v.decider.pcs) val sepIdentifier = symbExLog.openScope(impLog) - // TODO ake: set source produces(s4, freshSnap, posts, ContractNotWellformed, v3, AssumptionType.Internal)((_, _) => { symbExLog.closeScope(sepIdentifier) Success()})}) && { executionFlowController.locally(s2a, v2)((s3, v3) => { exec(s3, body, v3)((s4, v4) =>{ - // TODO ake: set source consumes(s4, posts, false, postViolated, v4)((_, _, _, _) => { Success() })})}) } )})}) diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index 8ce6c1556..3e706946f 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -7,7 +7,7 @@ package viper.silicon.supporters.functions import com.typesafe.scalalogging.Logger -import viper.silicon.assumptionAnalysis.AssumptionType +import viper.silicon.assumptionAnalysis.{AssumptionType, ExpAnalysisSourceInfo} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.decider.Decider @@ -261,8 +261,9 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver case (intermediateResult, Phase1Data(sPre, bcsPre, bcsPreExp, pcsPre, pcsPreExp)) => intermediateResult && executionFlowController.locally(sPre, v)((s1, _) => { decider.setCurrentBranchCondition(And(bcsPre), (BigAnd(bcsPreExp.map(_._1)), Option.when(wExp)(BigAnd(bcsPreExp.map(_._2.get))))) - decider.assume(pcsPre, Option.when(wExp)(DebugExp.createInstance(s"precondition of ${function.name}", pcsPreExp.get)), enforceAssumption = false, assumptionType=AssumptionType.Explicit) + decider.assume(pcsPre, pcsPreExp, s"precondition of ${function.name}", enforceAssumption=false, assumptionType=AssumptionType.Explicit) v.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) + v.decider.assumptionAnalyzer.addAnalysisSourceInfo(ExpAnalysisSourceInfo(body)) eval(s1, body, FunctionNotWellformed(function), v)((s2, tBody, bodyNew, _) => { val debugExp = if (wExp) { val e = ast.EqCmp(ast.Result(function.typ)(), body)(function.pos, function.info, function.errT) @@ -270,6 +271,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver Some(DebugExp.createInstance(e, eNew)) } else { None } decider.assume(BuiltinEquals(data.formalResult, tBody), debugExp, AssumptionType.Implicit) + v.decider.assumptionAnalyzer.popAnalysisSourceInfo() consumes(s2, posts, false, postconditionViolated, v)((s3, _, _, _) => { recorders :+= s3.functionRecorder Success()})})})} diff --git a/src/test/resources/andrea/branches.vpr b/src/test/resources/andrea/branches.vpr index dc1363de5..4b8390753 100644 --- a/src/test/resources/andrea/branches.vpr +++ b/src/test/resources/andrea/branches.vpr @@ -166,4 +166,18 @@ method branchesFunctionsPerm(a: Ref, b: Ref) assert n >= 0 assert n <= a.f + b.f assert c ==> n < a.f + b.f +} + + +method infeasibleBranch(a: Int, b: Int) + requires a > 0 && b > 0 +{ + var res: Int := a + 1 + if(a < 0){ + res := b + 1 + assert res >= 0 + } + + assert res >= 0 + assert res >= a } \ No newline at end of file diff --git a/src/test/resources/andrea/gaussian.vpr b/src/test/resources/andrea/gaussian.vpr index a31fa12db..2dc9ba39f 100644 --- a/src/test/resources/andrea/gaussian.vpr +++ b/src/test/resources/andrea/gaussian.vpr @@ -61,6 +61,5 @@ method gaussianPred(n: Int) returns (res: Int) i := i + 1 fold gaussianEq(res, i) } - unfold gaussianEq(res, i) - fold gaussianEq(res, n+1) + assert i == n+1 } diff --git a/src/test/resources/andrea/predicates.vpr b/src/test/resources/andrea/predicates.vpr index f830ccd3b..c7b430cff 100644 --- a/src/test/resources/andrea/predicates.vpr +++ b/src/test/resources/andrea/predicates.vpr @@ -30,8 +30,10 @@ method unfoldFoldP(a: Int, b: Int) requires greater0(a) && greater5(b) ensures greater5(a + b) { + assume b < 100 unfold greater0(a) unfold greater5(b) + assume a > 60 fold greater5(a + b) } From a4bbc5eb5ab0375fb2ed335a17a68f4083dbf73b Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 28 May 2025 10:27:26 +0200 Subject: [PATCH 065/474] cleanup TODOs --- .../AnalysisSourceInfo.scala | 7 +++++-- .../AssumptionAnalysisGraph.scala | 9 ++------- .../AssumptionAnalyzer.scala | 5 ++++- src/main/scala/decider/ProverStdIO.scala | 1 - src/main/scala/rules/Brancher.scala | 4 ++-- src/main/scala/rules/ChunkSupporter.scala | 1 - src/main/scala/rules/Consumer.scala | 4 ++-- src/main/scala/rules/Evaluator.scala | 18 +++++++++--------- src/main/scala/rules/Executor.scala | 2 +- src/main/scala/rules/HavocSupporter.scala | 2 +- src/main/scala/rules/MagicWandSupporter.scala | 6 +++--- .../rules/MoreCompleteExhaleSupporter.scala | 8 ++++---- src/main/scala/rules/Producer.scala | 1 - .../scala/rules/QuantifiedChunkSupport.scala | 15 +++++++-------- src/main/scala/rules/StateConsolidator.scala | 8 ++++---- 15 files changed, 44 insertions(+), 47 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala index 507762d78..707fcfe2d 100644 --- a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala +++ b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala @@ -18,6 +18,8 @@ abstract class AnalysisSourceInfo { def getPosition: Position def getTopLevelSource: AnalysisSourceInfo = this + + def getFineGrainedSource: AnalysisSourceInfo = this } case class NoAnalysisSourceInfo() extends AnalysisSourceInfo { @@ -57,10 +59,11 @@ case class StringAnalysisSourceInfo(description: String, position: Position) ext } case class CompositeAnalysisSourceInfo(coarseGrainedSource: AnalysisSourceInfo, fineGrainedSource: AnalysisSourceInfo) extends AnalysisSourceInfo { - override def toString: String = coarseGrainedSource.toString // TODO ake: + " -> " + fineGrainedSource.toString + override def toString: String = coarseGrainedSource.toString override def getPosition: Position = coarseGrainedSource.getPosition - override def equals(obj: Any): Boolean = coarseGrainedSource.equals(obj) // TODO ake + override def equals(obj: Any): Boolean = coarseGrainedSource.equals(obj) override def getTopLevelSource: AnalysisSourceInfo = coarseGrainedSource + override def getFineGrainedSource: AnalysisSourceInfo = fineGrainedSource } diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index 7dcafbad8..6f9190517 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -103,11 +103,11 @@ trait AssumptionAnalysisGraph { private def exportNodes(fileName: String): Unit = { val sep = "#" def getNodeExportString(node: AssumptionAnalysisNode): String = { - val parts = Seq(node.id.toString, node.getNodeType, node.assumptionType.toString, node.getNodeString, node.sourceInfo.toString, node.sourceInfo.getStringForExport) + val parts = Seq(node.id.toString, node.getNodeType, node.assumptionType.toString, node.getNodeString, node.sourceInfo.toString, node.sourceInfo.getStringForExport, node.sourceInfo.getFineGrainedSource.toString) parts.map(_.replace("#", "@")).mkString(sep) } val writer = new PrintWriter(fileName + "/nodes.csv") - val headerParts = Seq("id", "node type", "assumption type", "node info", "source info", "position") + val headerParts = Seq("id", "node type", "assumption type", "node info", "source info", "position", "fine grained source") writer.println(headerParts.mkString(sep)) nodes foreach (n => writer.println(getNodeExportString(n).replace("\n", " "))) writer.close() @@ -152,11 +152,6 @@ trait AssumptionAnalysisNode { def getNodeString: String def getNodeType: String - - def isIncludedInAnalysis: Boolean = assumptionType match {// TODO ake - case Internal => false - case _ => true - } } trait GeneralAssumptionNode extends AssumptionAnalysisNode { diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 92bb8e18f..613f6741c 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -29,7 +29,7 @@ trait AssumptionAnalyzer { val assumptionGraph: AssumptionAnalysisGraph = new DefaultAssumptionAnalysisGraph() - var sourceInfoes: List[AnalysisSourceInfo] = List.empty // TODO ake: make private + protected var sourceInfoes: List[AnalysisSourceInfo] = List.empty def getAnalysisInfo: AnalysisInfo = getAnalysisInfo(AssumptionType.Implicit) @@ -53,6 +53,9 @@ trait AssumptionAnalyzer { } def getAnalysisSourceInfoes: List[AnalysisSourceInfo] = sourceInfoes + def setAnalysisSourceInfoes(infoes: List[AnalysisSourceInfo]): Unit = { + sourceInfoes = infoes + } def addAnalysisSourceInfo(e: ast.Exp): Unit = { sourceInfoes = ExpAnalysisSourceInfo(e) +: sourceInfoes diff --git a/src/main/scala/decider/ProverStdIO.scala b/src/main/scala/decider/ProverStdIO.scala index d8a79c353..9a3d4acdc 100644 --- a/src/main/scala/decider/ProverStdIO.scala +++ b/src/main/scala/decider/ProverStdIO.scala @@ -227,7 +227,6 @@ abstract class ProverStdIO(uniqueId: String, // private val quantificationLogger = bookkeeper.logfiles("quantification-problems") - // TODO ake: we should always have a label? def assume(term: Term): Unit = { assume(term, "prover_" + proverLabelId) proverLabelId += 1 diff --git a/src/main/scala/rules/Brancher.scala b/src/main/scala/rules/Brancher.scala index bcced1715..c8c7651b4 100644 --- a/src/main/scala/rules/Brancher.scala +++ b/src/main/scala/rules/Brancher.scala @@ -147,7 +147,7 @@ object brancher extends BranchingRules { } elseBranchVerifier = v0.uniqueId - v0.decider.assumptionAnalyzer.sourceInfoes = currentStmtStack + v0.decider.assumptionAnalyzer.setAnalysisSourceInfoes(currentStmtStack) executionFlowController.locally(s, v0)((s1, v1) => { v1.decider.prover.comment(s"[else-branch: $cnt | $negatedCondition]") v1.decider.assumptionAnalyzer.addAnalysisSourceInfo(conditionExp._1) @@ -200,7 +200,7 @@ object brancher extends BranchingRules { val res = { val thenRes = if (executeThenBranch) { v.symbExLog.markReachable(uidBranchPoint) - v.decider.assumptionAnalyzer.sourceInfoes = currentStmtStack + v.decider.assumptionAnalyzer.setAnalysisSourceInfoes(currentStmtStack) executionFlowController.locally(s, v)((s1, v1) => { v1.decider.prover.comment(s"[then-branch: $cnt | $condition]") v1.decider.assumptionAnalyzer.addAnalysisSourceInfo(conditionExp._1) diff --git a/src/main/scala/rules/ChunkSupporter.scala b/src/main/scala/rules/ChunkSupporter.scala index 0b59b8df0..4c474914a 100644 --- a/src/main/scala/rules/ChunkSupporter.scala +++ b/src/main/scala/rules/ChunkSupporter.scala @@ -169,7 +169,6 @@ object chunkSupporter extends ChunkSupportRules { case Some(ch) => if (s.assertReadAccessOnly) { if (v.decider.check(Implies(IsPositive(perms), IsPositive(ch.perm)), Verifier.config.assertTimeout.getOrElse(0))) { - // TODO ake: add edge from check to permission assert node v.decider.assumptionAnalyzer.addPermissionAssertNode(ch, ch.permExp.map(pe => IsPositive(pe)(pe.pos, pe.info, pe.errT)), v.decider.assumptionAnalyzer.getFullSourceInfo) (Complete(), s, h, Some(ch)) } else { diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index c2d016591..ed65f7cc9 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -624,12 +624,12 @@ object consumer extends ConsumptionRules { } v2.decider.assert(termToAssert, Some(e)) { case true => - v2.decider.assume(t, Option.when(withExp)(e), eNew, AssumptionType.Unknown) + v2.decider.assume(t, Option.when(withExp)(e), eNew, AssumptionType.Internal) QS(s3, v2) case false => val failure = createFailure(pve dueTo AssertionFalse(e), v2, s3, termToAssert, eNew) if (s3.retryLevel == 0 && v2.reportFurtherErrors()){ - v2.decider.assume(t, Option.when(withExp)(e), eNew, AssumptionType.Unknown) + v2.decider.assume(t, Option.when(withExp)(e), eNew, AssumptionType.Internal) failure combine QS(s3, v2) } else failure}}) })((s4, v4) => { diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 168052521..46591d9c4 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -1083,21 +1083,21 @@ object evaluator extends EvaluationRules { case false => val failure = createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, idxLtLength, idxLtLengthExp) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { - v1.decider.assume(idxLtLength, idxLtLengthExp, idxLtLengthExpNew, AssumptionType.Unknown) + v1.decider.assume(idxLtLength, idxLtLengthExp, idxLtLengthExpNew, AssumptionType.Internal) failure combine Q(s1, SeqAt(t0, t1), eNew, v1) } else failure} case false => val failure1 = createFailure(pve dueTo SeqIndexNegative(e0, e1), v1, s1, idxGe0, idxGe0ExpNew) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { - v1.decider.assume(idxGe0, idxGe0Exp, idxGe0ExpNew, AssumptionType.Unknown) + v1.decider.assume(idxGe0, idxGe0Exp, idxGe0ExpNew, AssumptionType.Internal) v1.decider.assert(idxLtLength, idxLtLengthExp) { case true => failure1 combine Q(s1, SeqAt(t0, t1), eNew, v1) case false => val failure2 = failure1 combine createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, idxLtLength, idxLtLengthExpNew) if (v1.reportFurtherErrors()) { - v1.decider.assume(idxLtLength, idxLtLengthExp, idxLtLengthExpNew, AssumptionType.Unknown) + v1.decider.assume(idxLtLength, idxLtLengthExp, idxLtLengthExpNew, AssumptionType.Internal) failure2 combine Q(s1, SeqAt(t0, t1), eNew, v1) } else failure2} } else failure1}}}) @@ -1134,20 +1134,20 @@ object evaluator extends EvaluationRules { case false => val failure = createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, idxLtLength, idxLtLengthExpNew) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { - v1.decider.assume(idxLtLength, idxLtLengthExp, idxLtLengthExpNew, AssumptionType.Unknown) + v1.decider.assume(idxLtLength, idxLtLengthExp, idxLtLengthExpNew, AssumptionType.Internal) failure combine Q(s1, SeqUpdate(t0, t1, t2), eNew, v1)} else failure} case false => val failure1 = createFailure(pve dueTo SeqIndexNegative(e0, e1), v1, s1, idxGe0, idxGe0ExpNew) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { - v1.decider.assume(idxGe0, idxGe0Exp, idxGe0ExpNew, AssumptionType.Unknown) + v1.decider.assume(idxGe0, idxGe0Exp, idxGe0ExpNew, AssumptionType.Internal) v1.decider.assert(idxLtLength, idxLtLengthExp) { case true => failure1 combine Q(s1, SeqUpdate(t0, t1, t2), eNew, v1) case false => val failure2 = failure1 combine createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, idxLtLength, idxLtLengthExpNew) if (v1.reportFurtherErrors()) { - v1.decider.assume(idxLtLength, idxLtLengthExp, idxLtLengthExpNew, AssumptionType.Unknown) + v1.decider.assume(idxLtLength, idxLtLengthExp, idxLtLengthExpNew, AssumptionType.Internal) failure2 combine Q(s1, SeqUpdate(t0, t1, t2), eNew, v1) } else failure2} } else failure1}}}) @@ -1266,7 +1266,7 @@ object evaluator extends EvaluationRules { case false => val failure1 = createFailure(pve dueTo MapKeyNotContained(base, key), v1, s1, SetIn(keyT, MapDomain(baseT)), assertExpNew) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { - v1.decider.assume(SetIn(keyT, MapDomain(baseT)), assertExp, assertExpNew, AssumptionType.Unknown) + v1.decider.assume(SetIn(keyT, MapDomain(baseT)), assertExp, assertExpNew, AssumptionType.Internal) failure1 combine Q(s1, MapLookup(baseT, keyT), eNew, v1) } else { failure1 @@ -1520,7 +1520,7 @@ object evaluator extends EvaluationRules { case false => val failure = createFailure(pve dueTo DivisionByZero(eDivisor), v, s, tDivisor !== tZero, notZeroExpNew) if (s.retryLevel == 0 && v.reportFurtherErrors()) { - v.decider.assume(tDivisor !== tZero, notZeroExp, notZeroExpNew, AssumptionType.Unknown) + v.decider.assume(tDivisor !== tZero, notZeroExp, notZeroExpNew, AssumptionType.Internal) failure combine Q(s, t, v) } else failure } @@ -1706,7 +1706,7 @@ object evaluator extends EvaluationRules { var sJoined = entries.tail.foldLeft(entries.head.s)((sAcc, entry) => sAcc.merge(entry.s)) sJoined = sJoined.copy(functionRecorder = sJoined.functionRecorder.recordPathSymbol(joinSymbol)) - joinDefEqs foreach { case (t, exp, expNew) => v.decider.assume(t, exp, expNew, AssumptionType.Unknown)} + joinDefEqs foreach { case (t, exp, expNew) => v.decider.assume(t, exp, expNew, AssumptionType.Internal)} (sJoined, (joinTerm, joinExp)) } diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 8aa284edf..9826cea60 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -423,7 +423,7 @@ object executor extends ExecutionRules { val debugExp = Option.when(withExp)(DebugExp.createInstance("Definitional axioms for singleton-FVF's value", isInternal_ = true)) v1.decider.assumeDefinition(smValueDef, debugExp, AssumptionType.Internal) val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(Seq(`?r`), Option.when(withExp)(Seq(ast.LocalVarDecl("r", ast.Ref)(ass.pos, ass.info, ass.errT))), - field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), sm, s.program, v1, AssumptionType.Implicit) // TODO ake: or internal? + field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), sm, s.program, v1, AssumptionType.Implicit) if (s3.heapDependentTriggers.contains(field)) { val debugExp2 = Option.when(withExp)(DebugExp.createInstance(s"FieldTrigger(${eRcvrNew.toString()}.${field.name})")) v1.decider.assume(FieldTrigger(field.name, sm, tRcvr), debugExp2, AssumptionType.Internal) diff --git a/src/main/scala/rules/HavocSupporter.scala b/src/main/scala/rules/HavocSupporter.scala index 3d815c40e..8789c3701 100644 --- a/src/main/scala/rules/HavocSupporter.scala +++ b/src/main/scala/rules/HavocSupporter.scala @@ -41,7 +41,7 @@ object havocSupporter extends SymbolicExecutionRules { def execHavoc(havoc: ast.Quasihavoc, v: Verifier, s: State) - (Q: (State, Verifier) => VerificationResult) + (Q: (State, Verifier) => VerificationResult) // TODO ake: add argument AssumptionType? : VerificationResult = { val pve = QuasihavocFailed(havoc) diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index d9f8fd44d..9328e9884 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -368,7 +368,7 @@ object magicWandSupporter extends SymbolicExecutionRules { val freshSnapRoot = freshSnap(sorts.Snap, v1) // Produce the wand's LHS. - produce(s1.copy(conservingSnapshotGeneration = true), toSf(freshSnapRoot), wand.left, pve, v1, AssumptionType.Internal)((sLhs, v2) => { // TODO ake: assumption type? + produce(s1.copy(conservingSnapshotGeneration = true), toSf(freshSnapRoot), wand.left, pve, v1, AssumptionType.Internal)((sLhs, v2) => { val proofScriptCfg = proofScript.toCfg() /* Expected shape of reserveHeaps is either @@ -398,7 +398,7 @@ object magicWandSupporter extends SymbolicExecutionRules { // Execute proof script, i.e. the part written after the magic wand wrapped by curly braces. // The proof script should transform the current state such that we can consume the wand's RHS. - executor.exec(s2, proofScriptCfg, v2)((proofScriptState, proofScriptVerifier) => { // TODO ake: assumption types? + executor.exec(s2, proofScriptCfg, v2)((proofScriptState, proofScriptVerifier) => { // Consume the wand's RHS and produce a snapshot which records all the values of variables on the RHS. // This part indirectly calls the methods `this.transfer` and `this.consumeFromMultipleHeaps`. consume( @@ -436,7 +436,7 @@ object magicWandSupporter extends SymbolicExecutionRules { v1.decider.setCurrentBranchCondition(And(branchConditions), (exp, expNew)) // Recreate all path conditions in the Z3 proof script that we recorded for that branch - v1.decider.assume(conservedPcs._1, conservedPcs._2, AssumptionType.Unknown) + v1.decider.assume(conservedPcs._1, conservedPcs._2, AssumptionType.Internal) // Execute the continuation Q Q(s2, magicWandChunk, v1) diff --git a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala index d409a9e43..a1cb6ed1c 100644 --- a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala +++ b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala @@ -387,7 +387,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { newChunks foreach { ch => val resource = Resources.resourceDescriptions(ch.resourceID) val pathCond = interpreter.buildPathConditionsForChunk(ch, resource.instanceProperties(s.mayAssumeUpperBounds)) - pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), AssumptionType.Unknown)) + pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), AssumptionType.Internal)) } val newHeap = Heap(allChunks) @@ -477,7 +477,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { ast.Implies(ast.Not(eqExp.get)(), ast.EqCmp(permTakenExp.get, ast.NoPerm()())())(pe.pos, pe.info, pe.errT)))) - v.decider.assume(constraint, Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp)), AssumptionType.Unknown) + v.decider.assume(constraint, Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp)), AssumptionType.Internal) newFr = newFr.recordPathSymbol(permTaken.applicable.asInstanceOf[Function]).recordConstraint(constraint) @@ -493,7 +493,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { val constraintExp = permsExp.map(pe => ast.Implies(ast.NeCmp(totalPermSumExp.get, ast.NoPerm()())(), ast.And(ast.PermLeCmp(ast.NoPerm()(), totalPermTakenExp.get)(), ast.PermLeCmp(totalPermTakenExp.get, totalPermSumExp.get)())(pe.pos, pe.info, pe.errT))()) - v.decider.assume(totalTakenBounds, constraintExp, constraintExp, AssumptionType.Unknown) + v.decider.assume(totalTakenBounds, constraintExp, constraintExp, AssumptionType.Internal) newFr = newFr.recordConstraint(totalTakenBounds) @@ -503,7 +503,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { v.decider.assert(Implies(PermLess(NoPerm, perms), totalPermTaken !== NoPerm), totalPermTakenImplExp) { case true => val constraintExp = permsExp.map(pe => ast.EqCmp(pe, totalPermTakenExp.get)()) - v.decider.assume(perms === totalPermTaken, Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp)), AssumptionType.Unknown) + v.decider.assume(perms === totalPermTaken, Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp)), AssumptionType.Internal) if (returnSnap) { summarise(s1, relevantChunks.toSeq, resource, args, argsExp, None, v)((s2, snap, _, _, v1) => Q(s2, updatedChunks, Some(snap), v1)) // TODO ake: updatedChunks are the new chunks, not the old consumed ones! diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index b917b65dd..b54e3d65f 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -230,7 +230,6 @@ object producer extends ProductionRules { : VerificationResult = { v.logger.debug(s"\nPRODUCE ${viper.silicon.utils.ast.sourceLineColumn(a)}: $a") -// v.decider.assumptionAnalyzer.updateCurrentAnalysisInfo(ExpAnalysisSourceInfo(a)) TODO ake v.logger.debug(v.stateFormatter.format(s, v.decider.pcs)) val Q: (State, Verifier) => VerificationResult = (state, verifier) => diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index ad173a0a1..87dde968a 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -1053,7 +1053,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { triggers = effectiveTriggers, qidPrefix = qid ) - v.decider.assume(pcsForChunk, pcsForChunkExp, pcsForChunkExp, AssumptionType.Unknown) + v.decider.assume(pcsForChunk, pcsForChunkExp, pcsForChunkExp, AssumptionType.Internal) }) val (fr1, h1) = v.stateConsolidator(s).merge(s.functionRecorder, s, s.h, Heap(Seq(ch)), v) @@ -1491,7 +1491,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { } val consumedChunk = quantifiedChunkSupporter.createSingletonQuantifiedChunk( - codomainQVars, codomainQVarsExp, resource, arguments, argumentsExp, permsTaken, permsTakenExp, smDef1.sm, s.program, v1, AssumptionType.Unknown) + codomainQVars, codomainQVarsExp, resource, arguments, argumentsExp, permsTaken, permsTakenExp, smDef1.sm, s.program, v1, AssumptionType.Unknown) // TODO ake: exhale val s3 = s2.copy(functionRecorder = s2.functionRecorder.recordFvfAndDomain(smDef1), smCache = smCache1) (result, s3, h2, Some(consumedChunk)) // TODO ake: or consumedChunks? @@ -1581,7 +1581,6 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { else Incomplete(PermMinus(permsAvailable, perms), permsAvailableExp.map(pa => ast.PermSub(pa, permsExp.get)())) - // TODO ake: add permission assert node? result } @@ -1681,9 +1680,9 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { if (constrainPermissions) { v.decider.prover.comment(s"Constrain original permissions $perms") - v.decider.assume(permissionConstraint, permissionConstraintExp, permissionConstraintExp, AssumptionType.Unknown) + v.decider.assume(permissionConstraint, permissionConstraintExp, permissionConstraintExp, AssumptionType.Internal) remainingChunks = - remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.assumptionAnalyzer.getAnalysisInfo) // TODO ake: assumptionType? + remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.assumptionAnalyzer.getAnalysisInfo(AssumptionType.Implicit)) } else { v.decider.prover.comment(s"Chunk depleted?") val chunkDepleted = v.decider.check(depletedCheck, Verifier.config.splitTimeout()) @@ -1694,7 +1693,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { remainingChunks = remainingChunks :+ ithChunk } else { remainingChunks = - remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.assumptionAnalyzer.getAnalysisInfo)// TODO ake: assumptionType? + remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.assumptionAnalyzer.getAnalysisInfo(AssumptionType.Implicit)) } } } @@ -2077,7 +2076,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { if (result) { // Learn the equality val debugExp = Option.when(withExp)(DebugExp.createInstance("Chunks alias", isInternal_ = true)) - v.decider.assume(equalityTerm, debugExp, AssumptionType.Unknown) + v.decider.assume(equalityTerm, debugExp, AssumptionType.Internal) } result case _ => false @@ -2103,7 +2102,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { if (result) { // Learn the equality val debugExp = Option.when(withExp)(DebugExp.createInstance("Chunks alias", isInternal_ = true)) - v.decider.assume(equalityTerm, debugExp, AssumptionType.Unknown) + v.decider.assume(equalityTerm, debugExp, AssumptionType.Internal) } result } else { diff --git a/src/main/scala/rules/StateConsolidator.scala b/src/main/scala/rules/StateConsolidator.scala index c0977cee1..641be41b4 100644 --- a/src/main/scala/rules/StateConsolidator.scala +++ b/src/main/scala/rules/StateConsolidator.scala @@ -109,7 +109,6 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), AssumptionType.PathCondition)) } - v.decider.assumptionAnalyzer.popAnalysisSourceInfo() v.symbExLog.closeScope(sepIdentifier) (functionRecorder, hs :+ Heap(mergedChunks)) } @@ -119,6 +118,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol reserveHeaps = mergedHeaps.tail) val s2 = assumeUpperPermissionBoundForQPFields(s1, v) + v.decider.assumptionAnalyzer.popAnalysisSourceInfo() s2 } @@ -300,7 +300,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol Some(DebugExp.createInstance(exp, exp)) } else { None } v.decider.assume( - Forall(receiver, PermAtMost(currentPermAmount, FullPerm), Trigger(trigger), "qp-fld-prm-bnd"), debugExp, AssumptionType.Unknown) + Forall(receiver, PermAtMost(currentPermAmount, FullPerm), Trigger(trigger), "qp-fld-prm-bnd"), debugExp, AssumptionType.Internal) } else { /* If we don't use heap-dependent triggers, the trigger x.f does not work. Instead, we assume the permission @@ -317,7 +317,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol val exp = ast.PermLeCmp(permExp, ast.FullPerm()())() Some(DebugExp.createInstance(exp, exp)) } else { None } - v.decider.assume(PermAtMost(PermLookup(field.name, pmDef.pm, chunk.singletonRcvr.get), FullPerm), debugExp, AssumptionType.Unknown) + v.decider.assume(PermAtMost(PermLookup(field.name, pmDef.pm, chunk.singletonRcvr.get), FullPerm), debugExp, AssumptionType.Internal) } else { val chunkReceivers = chunk.invs.get.inverses.map(i => App(i, chunk.invs.get.additionalArguments ++ chunk.quantifiedVars)) val triggers = chunkReceivers.map(r => Trigger(r)).toSeq @@ -333,7 +333,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol Some(DebugExp.createInstance(exp, exp)) } else { None } v.decider.assume( - Forall(chunk.quantifiedVars, PermAtMost(currentPermAmount, FullPerm), triggers, "qp-fld-prm-bnd"), debugExp, AssumptionType.Unknown) + Forall(chunk.quantifiedVars, PermAtMost(currentPermAmount, FullPerm), triggers, "qp-fld-prm-bnd"), debugExp, AssumptionType.Internal) } } From d66b88cdb76a4435bedace000ab30b7f512706a7 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 29 May 2025 11:55:00 +0200 Subject: [PATCH 066/474] add support for assumption type annotations --- .../assumptionAnalysis/AnalysisInfo.scala | 2 + .../AssumptionAnalyzer.scala | 19 ++++++++ src/main/scala/rules/Executor.scala | 47 +++++++++++-------- src/main/scala/rules/HavocSupporter.scala | 29 +++++++----- src/main/scala/rules/MagicWandSupporter.scala | 12 +++-- src/main/scala/rules/PredicateSupporter.scala | 21 +++++---- .../scala/supporters/MethodSupporter.scala | 14 ++++-- .../PredicateVerificationUnit.scala | 13 +++-- .../functions/FunctionVerificationUnit.scala | 13 +++-- src/test/resources/andrea/annotations.vpr | 26 ++++++++++ src/test/resources/andrea/branches.vpr | 2 + src/test/resources/andrea/method-sum.vpr | 15 +++++- src/test/resources/andrea/predicates.vpr | 7 +++ src/test/resources/andrea/quickTest.vpr | 16 ++----- 14 files changed, 166 insertions(+), 70 deletions(-) create mode 100644 src/test/resources/andrea/annotations.vpr diff --git a/src/main/scala/assumptionAnalysis/AnalysisInfo.scala b/src/main/scala/assumptionAnalysis/AnalysisInfo.scala index 983491cde..270b2a604 100644 --- a/src/main/scala/assumptionAnalysis/AnalysisInfo.scala +++ b/src/main/scala/assumptionAnalysis/AnalysisInfo.scala @@ -3,6 +3,8 @@ package viper.silicon.assumptionAnalysis object AssumptionType extends Enumeration { type AssumptionType = Value val Explicit, LoopInvariant, PathCondition, Rewrite, Implicit, Internal, Unknown = Value + + def fromString(s: String): Option[Value] = values.find(_.toString == s) } import viper.silicon.assumptionAnalysis.AssumptionType._ diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 613f6741c..8be4da3e4 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -68,8 +68,27 @@ trait AssumptionAnalyzer { } object AssumptionAnalyzer { + val assumptionTypeAnnotationKey = "assumptionType" + val enableAssumptionAnalysisAnnotationKey = "enableAssumptionAnalysis" val noAssumptionAnalyzerSingelton = new NoAssumptionAnalyzer() + private def extractAnnotationFromInfo(info: Info, annotationKey: String): Option[Seq[String]] = { + info.getAllInfos[AnnotationInfo] + .filter(_.values.contains(annotationKey)) + .map(_.values(annotationKey)).headOption + } + + def extractAssumptionTypeFromInfo(info: Info): Option[AssumptionType] = { + val annotation = extractAnnotationFromInfo(info, assumptionTypeAnnotationKey) + if(annotation.isDefined && annotation.get.nonEmpty) AssumptionType.fromString(annotation.get.head) else None + } + + + def extractEnableAnalysisFromInfo(info: Info): Option[Boolean] = { + val annotation = extractAnnotationFromInfo(info, enableAssumptionAnalysisAnnotationKey) + if(annotation.isDefined && annotation.get.nonEmpty) annotation.get.head.toBooleanOption else None + } + def createAssumptionLabel(id: Option[Int], offset: Int = 0): String = { createLabel("assumption", id, offset) } diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 9826cea60..7c1d76eed 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -9,7 +9,8 @@ package viper.silicon.rules import viper.silicon.debugger.DebugExp import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.Config.JoinMode -import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType, ExpAnalysisSourceInfo, PermissionInhaleNode, StmtAnalysisSourceInfo, StringAnalysisSourceInfo} +import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType +import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionAnalyzer, AssumptionType, ExpAnalysisSourceInfo, PermissionInhaleNode, StmtAnalysisSourceInfo, StringAnalysisSourceInfo} import scala.annotation.unused import viper.silver.cfg.silver.SilverCfg @@ -353,6 +354,7 @@ object executor extends ExecutionRules { v.decider.prover.comment("[exec]") v.decider.prover.comment(stmt.toString()) } + val annotatedAssumptionTypeOpt = AssumptionAnalyzer.extractAssumptionTypeFromInfo(stmt.info) val executed = stmt match { case ast.Seqn(stmts, _) => @@ -369,7 +371,7 @@ object executor extends ExecutionRules { case ass @ ast.LocalVarAssign(x, rhs) => eval(s, rhs, AssignmentFailed(ass), v)((s1, tRhs, rhsNew, v1) => { - val (t, e) = ssaifyRhs(tRhs, rhs, rhsNew, x.name, x.typ, v, s1) + val (t, e) = ssaifyRhs(tRhs, rhs, rhsNew, x.name, x.typ, v, s1, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit)) Q(s1.copy(g = s1.g + (x, (t, e))), v1)}) /* TODO: Encode assignments e1.f := e2 as @@ -423,7 +425,7 @@ object executor extends ExecutionRules { val debugExp = Option.when(withExp)(DebugExp.createInstance("Definitional axioms for singleton-FVF's value", isInternal_ = true)) v1.decider.assumeDefinition(smValueDef, debugExp, AssumptionType.Internal) val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(Seq(`?r`), Option.when(withExp)(Seq(ast.LocalVarDecl("r", ast.Ref)(ass.pos, ass.info, ass.errT))), - field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), sm, s.program, v1, AssumptionType.Implicit) + field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), sm, s.program, v1, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit)) if (s3.heapDependentTriggers.contains(field)) { val debugExp2 = Option.when(withExp)(DebugExp.createInstance(s"FieldTrigger(${eRcvrNew.toString()}.${field.name})")) v1.decider.assume(FieldTrigger(field.name, sm, tRcvr), debugExp2, AssumptionType.Internal) @@ -445,10 +447,10 @@ object executor extends ExecutionRules { val ve = pve dueTo InsufficientPermission(fa) val description = s"consume ${ass.pos}: $ass" chunkSupporter.consume(s2, s2.h, resource, Seq(tRcvr), eRcvrNew.map(Seq(_)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), false, ve, v2, description)((s3, h3, _, consumedChunks, v3) => { - val (tSnap, _) = ssaifyRhs(tRhs, rhs, rhsNew, field.name, field.typ, v3, s3) + val (tSnap, _) = ssaifyRhs(tRhs, rhs, rhsNew, field.name, field.typ, v3, s3, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit)) val id = BasicChunkIdentifier(field.name) val newChunk = BasicChunk.createDerivedChunk(consumedChunks.toSet, FieldID, id, Seq(tRcvr), eRcvrNew.map(Seq(_)), tSnap, rhsNew, FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), - v3.decider.assumptionAnalyzer.getAnalysisInfo(AssumptionType.Implicit)) + v3.decider.assumptionAnalyzer.getAnalysisInfo(annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit))) chunkSupporter.produce(s3, h3, newChunk, v3)((s4, h4, v4) => { val s5 = s4.copy(h = h4) val (debugHeapName, _) = v4.getDebugOldLabel(s5, fa.pos) @@ -477,10 +479,10 @@ object executor extends ExecutionRules { val debugExp = Option.when(withExp)(DebugExp.createInstance("Definitional axioms for singleton-FVF's value", isInternal_ = true)) v.decider.assumeDefinition(smValueDef, debugExp, AssumptionType.Internal) quantifiedChunkSupporter.createSingletonQuantifiedChunk(Seq(`?r`), Option.when(withExp)(Seq(ast.LocalVarDecl("r", ast.Ref)(stmt.pos, stmt.info, stmt.errT))), - field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), p, pExp, sm, s.program, v, AssumptionType.Implicit) + field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), p, pExp, sm, s.program, v, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit)) } else { val newChunk = BasicChunk(FieldID, BasicChunkIdentifier(field.name), Seq(tRcvr), Option.when(withExp)(Seq(x)), snap, snapExp, p, pExp, - v.decider.assumptionAnalyzer.getAnalysisInfo(AssumptionType.Implicit)) + v.decider.assumptionAnalyzer.getAnalysisInfo(annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit))) newChunk } }) @@ -497,7 +499,7 @@ object executor extends ExecutionRules { /* We're done */ Success() case _ => - produce(s, freshSnap, a, InhaleFailed(inhale), v, AssumptionType.Explicit)((s1, v1) => { + produce(s, freshSnap, a, InhaleFailed(inhale), v, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit))((s1, v1) => { v1.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterInhale) Q(s1, v1)}) } @@ -548,7 +550,7 @@ object executor extends ExecutionRules { case ast.MethodCall(methodName, _, _) if !Verifier.config.disableHavocHack407() && methodName.startsWith(hack407_method_name_prefix) => - val analysisInfo = v.decider.assumptionAnalyzer.getAnalysisInfo(AssumptionType.Explicit) + val analysisInfo = v.decider.assumptionAnalyzer.getAnalysisInfo(annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit)) val resourceName = methodName.stripPrefix(hack407_method_name_prefix) val member = s.program.collectFirst { case m: ast.Field if m.name == resourceName => m @@ -568,7 +570,7 @@ object executor extends ExecutionRules { // Calling hack510() triggers a state consolidation. // See also Silicon issue #510. case ast.MethodCall(`hack510_method_name`, _, _) => - val s1 = v.stateConsolidator(s).consolidate(s, v) // TODO ake: AssumptionType.Explicit + val s1 = v.stateConsolidator(s).consolidate(s, v) // TODO ake: assumption Type Q(s1, v) case call @ ast.MethodCall(methodName, eArgs, lhs) => @@ -579,6 +581,9 @@ object executor extends ExecutionRules { val pveCall = CallFailed(call) val pveCallTransformed = pveCall.withReasonNodeTransformed(reasonTransformer) + val methodAnnotatedAssumptionType = AssumptionAnalyzer.extractAssumptionTypeFromInfo(meth.info) + val finalAssumptionType = annotatedAssumptionTypeOpt.getOrElse(methodAnnotatedAssumptionType.getOrElse(AssumptionType.Explicit)) + val mcLog = new MethodCallRecord(call, s, v.decider.pcs) val sepIdentifier = v.symbExLog.openScope(mcLog) val paramLog = new CommentRecord("Parameters", s, v.decider.pcs) @@ -605,7 +610,7 @@ object executor extends ExecutionRules { val outs = meth.formalReturns.map(_.localVar) val gOuts = Store(outs.map(x => (x, v2.decider.fresh(x))).toMap) val s4 = s3.copy(g = s3.g + gOuts, oldHeaps = s3.oldHeaps + (Verifier.PRE_STATE_LABEL -> magicWandSupporter.getEvalHeap(s1))) - produces(s4, freshSnap, meth.posts, _ => pveCallTransformed, v2, AssumptionType.Explicit)((s5, v3) => { + produces(s4, freshSnap, meth.posts, _ => pveCallTransformed, v2, finalAssumptionType)((s5, v3) => { v3.symbExLog.closeScope(postCondId) v3.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) val gLhs = Store(lhs.zip(outs) @@ -621,12 +626,14 @@ object executor extends ExecutionRules { v.decider.startDebugSubExp() val ePerm = pap.perm val predicate = s.program.findPredicate(predicateName) + val predicateAnnotatedAssumptionType = AssumptionAnalyzer.extractAssumptionTypeFromInfo(predicate.info) + val finalAssumptionType = annotatedAssumptionTypeOpt.getOrElse(predicateAnnotatedAssumptionType.getOrElse(AssumptionType.Rewrite)) val pve = FoldFailed(fold) evals(s, eArgs, _ => pve, v)((s1, tArgs, eArgsNew, v1) => eval(s1, ePerm, pve, v1)((s2, tPerm, ePermNew, v2) => permissionSupporter.assertPositive(s2, tPerm, if (withExp) ePermNew.get else ePerm, pve, v2)((s3, v3) => { val wildcards = s3.constrainableARPs -- s1.constrainableARPs - predicateSupporter.fold(s3, predicate, tArgs, eArgsNew, tPerm, ePermNew, wildcards, pve, v3)((s4, v4) => { + predicateSupporter.fold(s3, predicate, tArgs, eArgsNew, tPerm, ePermNew, wildcards, pve, v3, finalAssumptionType)((s4, v4) => { v3.decider.finishDebugSubExp(s"folded ${predAcc.toString}") Q(s4, v4) } @@ -637,6 +644,8 @@ object executor extends ExecutionRules { v.decider.startDebugSubExp() val ePerm = pap.perm val predicate = s.program.findPredicate(predicateName) + val predicateAnnotatedAssumptionType = AssumptionAnalyzer.extractAssumptionTypeFromInfo(predicate.info) + val finalAssumptionType = annotatedAssumptionTypeOpt.getOrElse(predicateAnnotatedAssumptionType.getOrElse(AssumptionType.Rewrite)) val pve = UnfoldFailed(unfold) evals(s, eArgs, _ => pve, v)((s1, tArgs, eArgsNew, v1) => eval(s1, ePerm, pve, v1)((s2, tPerm, ePermNew, v2) => { @@ -658,7 +667,7 @@ object executor extends ExecutionRules { permissionSupporter.assertPositive(s2, tPerm, if (withExp) ePermNew.get else ePerm, pve, v2)((s3, v3) => { val wildcards = s3.constrainableARPs -- s1.constrainableARPs - predicateSupporter.unfold(s3.copy(smCache = smCache1), predicate, tArgs, eArgsNew, tPerm, ePermNew, wildcards, pve, v3, pa)( + predicateSupporter.unfold(s3.copy(smCache = smCache1), predicate, tArgs, eArgsNew, tPerm, ePermNew, wildcards, pve, v3, pa, finalAssumptionType)( (s4, v4) => { v2.decider.finishDebugSubExp(s"unfolded ${pa.toString}") Q(s4, v4) @@ -668,7 +677,7 @@ object executor extends ExecutionRules { case pckg @ ast.Package(wand, proofScript) => val pve = PackageFailed(pckg) - magicWandSupporter.packageWand(s.copy(isInPackage = true), wand, proofScript, pve, v)((s1, chWand, v1) => { + magicWandSupporter.packageWand(s.copy(isInPackage = true), wand, proofScript, pve, v, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Rewrite))((s1, chWand, v1) => { val hOps = s1.reserveHeaps.head + chWand assert(s.exhaleExt || s1.reserveHeaps.length == 1) @@ -712,13 +721,13 @@ object executor extends ExecutionRules { case apply @ ast.Apply(e) => val pve = ApplyFailed(apply) - magicWandSupporter.applyWand(s, e, pve, v)(Q) + magicWandSupporter.applyWand(s, e, pve, v, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Rewrite))(Q) case havoc: ast.Quasihavoc => - havocSupporter.execHavoc(havoc, v, s)(Q) + havocSupporter.execHavoc(havoc, v, s, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit))(Q) case havocall: ast.Quasihavocall => - havocSupporter.execHavocall(havocall, v, s)(Q) + havocSupporter.execHavocall(havocall, v, s, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit))(Q) case viper.silicon.extensions.TryBlock(body) => var bodySucceeded = false @@ -742,7 +751,7 @@ object executor extends ExecutionRules { executed } - private def ssaifyRhs(rhs: Term, rhsExp: ast.Exp, rhsExpNew: Option[ast.Exp], name: String, typ: ast.Type, v: Verifier, s : State): (Term, Option[ast.Exp]) = { + private def ssaifyRhs(rhs: Term, rhsExp: ast.Exp, rhsExpNew: Option[ast.Exp], name: String, typ: ast.Type, v: Verifier, s : State, assumptionType: AssumptionType): (Term, Option[ast.Exp]) = { rhs match { case _: Var | _: Literal => (rhs, rhsExpNew) @@ -769,7 +778,7 @@ object executor extends ExecutionRules { } else { (None, None) } - v.decider.assumeDefinition(BuiltinEquals(t, rhs), debugExp, AssumptionType.Implicit) + v.decider.assumeDefinition(BuiltinEquals(t, rhs), debugExp, assumptionType) (t, eNew) } } diff --git a/src/main/scala/rules/HavocSupporter.scala b/src/main/scala/rules/HavocSupporter.scala index 8789c3701..592f72e28 100644 --- a/src/main/scala/rules/HavocSupporter.scala +++ b/src/main/scala/rules/HavocSupporter.scala @@ -8,6 +8,7 @@ package viper.silicon.rules import viper.silicon.debugger.DebugExp import viper.silicon.Map +import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType, StmtAnalysisSourceInfo} import viper.silicon.interfaces.VerificationResult import viper.silicon.interfaces.state.{Chunk, NonQuantifiedChunk, QuantifiedChunk} @@ -40,8 +41,9 @@ object havocSupporter extends SymbolicExecutionRules { */ def execHavoc(havoc: ast.Quasihavoc, v: Verifier, - s: State) - (Q: (State, Verifier) => VerificationResult) // TODO ake: add argument AssumptionType? + s: State, + assumptionType: AssumptionType) + (Q: (State, Verifier) => VerificationResult) : VerificationResult = { val pve = QuasihavocFailed(havoc) @@ -57,9 +59,9 @@ object havocSupporter extends SymbolicExecutionRules { // the HavocHelperData inside of a HavocOneData case (as opposed to HavocAllData). val newChunks = if (usesQPChunks(s1, resource)) - havocQuantifiedResource(s1, lhsTerm, resource, HavocOneData(tRcvrs), v1) + havocQuantifiedResource(s1, lhsTerm, resource, HavocOneData(tRcvrs), v1, assumptionType) else - havocNonQuantifiedResource(s1, lhsTerm, resource, HavocOneData(tRcvrs), v1) + havocNonQuantifiedResource(s1, lhsTerm, resource, HavocOneData(tRcvrs), v1, assumptionType) Q(s1.copy(h = Heap(newChunks)), v1) }) @@ -79,7 +81,8 @@ object havocSupporter extends SymbolicExecutionRules { */ def execHavocall(havocall: ast.Quasihavocall, v: Verifier, - s: State) + s: State, + assumptionType: AssumptionType) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { val pve = HavocallFailed(havocall) @@ -155,9 +158,9 @@ object havocSupporter extends SymbolicExecutionRules { // the HavocHelperData inside of a HavocAllData case. val newChunks = if (usesQPChunks(s1, resource)) - havocQuantifiedResource(s1, tCond, resource, HavocallData(inverseFunctions, codomainQVars, imagesOfCodomain), v1) + havocQuantifiedResource(s1, tCond, resource, HavocallData(inverseFunctions, codomainQVars, imagesOfCodomain), v1, assumptionType) else - havocNonQuantifiedResource(s1, tCond, resource, HavocallData(inverseFunctions, codomainQVars, imagesOfCodomain), v1) + havocNonQuantifiedResource(s1, tCond, resource, HavocallData(inverseFunctions, codomainQVars, imagesOfCodomain), v1, assumptionType) Q(s1.copy(h = Heap(newChunks)), v1) } @@ -182,7 +185,8 @@ object havocSupporter extends SymbolicExecutionRules { lhs: Term, resource: ast.Resource, condInfo: HavocHelperData, - v: Verifier) + v: Verifier, + assumptionType: AssumptionType) : Seq[Chunk] = { val id = ChunkIdentifier(resource, s.program) @@ -193,12 +197,12 @@ object havocSupporter extends SymbolicExecutionRules { val havockedSnap = v.decider.fresh("mwsf", sorts.MagicWandSnapFunction, Option.when(withExp)(PUnknown())) val cond = replacementCond(lhs, ch.args, condInfo) val magicWandSnapshot = MagicWandSnapshot(Ite(cond, havockedSnap, ch.snap.mwsf)) - NonQuantifiedChunk.withSnap(ch, magicWandSnapshot, None, v.decider.assumptionAnalyzer.getAnalysisInfo) // TODO ake: assumptionType? + NonQuantifiedChunk.withSnap(ch, magicWandSnapshot, None, v.decider.assumptionAnalyzer.getAnalysisInfo(assumptionType)) case ch => val havockedSnap = freshSnap(ch.snap.sort, v) val cond = replacementCond(lhs, ch.args, condInfo) - NonQuantifiedChunk.withSnap(ch, Ite(cond, havockedSnap, ch.snap), None, v.decider.assumptionAnalyzer.getAnalysisInfo) + NonQuantifiedChunk.withSnap(ch, Ite(cond, havockedSnap, ch.snap), None, v.decider.assumptionAnalyzer.getAnalysisInfo(assumptionType)) } otherChunks ++ newChunks } @@ -224,7 +228,8 @@ object havocSupporter extends SymbolicExecutionRules { lhs: Term, resource: ast.Resource, condInfo: HavocHelperData, - v: Verifier) + v: Verifier, + assumptionType: AssumptionType) : Seq[Chunk] = { // Quantified field chunks are of the form R(r; sm, pm). @@ -281,7 +286,7 @@ object havocSupporter extends SymbolicExecutionRules { val debugExp = Option.when(withExp)(DebugExp.createInstance("havoc new axiom", isInternal_ = true)) v.decider.assume(newAxiom, debugExp, AssumptionType.Internal) - QuantifiedChunk.withSnapshotMap(ch, newSm, v.decider.assumptionAnalyzer.getAnalysisInfo) // TODO ake: assumption type? + QuantifiedChunk.withSnapshotMap(ch, newSm, v.decider.assumptionAnalyzer.getAnalysisInfo(assumptionType)) } newChunks ++ otherChunks } diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 9328e9884..1eee46057 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -238,7 +238,8 @@ object magicWandSupporter extends SymbolicExecutionRules { wand: ast.MagicWand, proofScript: ast.Seqn, pve: PartialVerificationError, - v: Verifier) + v: Verifier, + assumptionType: AssumptionType = AssumptionType.Rewrite) (Q: (State, Chunk, Verifier) => VerificationResult) : VerificationResult = { @@ -406,7 +407,7 @@ object magicWandSupporter extends SymbolicExecutionRules { wand.right, true, pve, proofScriptVerifier )((s3, snapRhs, consumedChunks, v3) => { // TODO ake: what to do with consumedChunks? - createWandChunkAndRecordResults(s3.copy(exhaleExt = false, oldHeaps = s.oldHeaps), freshSnapRoot, snapRhs.get, v3, AssumptionType.Rewrite) + createWandChunkAndRecordResults(s3.copy(exhaleExt = false, oldHeaps = s.oldHeaps), freshSnapRoot, snapRhs.get, v3, assumptionType) }) }) }) @@ -417,7 +418,7 @@ object magicWandSupporter extends SymbolicExecutionRules { // and thus, that no wand chunk was created. In order to continue, we create one now. // Moreover, we need to set reserveHeaps to structurally match [State RHS] below. val s1 = sEmp.copy(reserveHeaps = Heap() +: Heap() +: Heap() +: s.reserveHeaps.tail) - createWandChunkAndRecordResults(s1, freshSnap(sorts.Snap, v), freshSnap(sorts.Snap, v), v, AssumptionType.Rewrite) + createWandChunkAndRecordResults(s1, freshSnap(sorts.Snap, v), freshSnap(sorts.Snap, v), v, assumptionType) } recordedBranches.foldLeft(tempResult)((prevRes, recordedState) => { @@ -458,7 +459,8 @@ object magicWandSupporter extends SymbolicExecutionRules { def applyWand(s: State, wand: ast.MagicWand, pve: PartialVerificationError, - v: Verifier) + v: Verifier, + assumptionType: AssumptionType = AssumptionType.Rewrite) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { // Consume the magic wand instance "A --* B". @@ -487,7 +489,7 @@ object magicWandSupporter extends SymbolicExecutionRules { } // Produce the wand's RHS. - produce(s3.copy(conservingSnapshotGeneration = true), toSf(magicWandSnapshotLookup), wand.right, pve, v2, AssumptionType.Rewrite)((s4, v3) => { + produce(s3.copy(conservingSnapshotGeneration = true), toSf(magicWandSnapshotLookup), wand.right, pve, v2, assumptionType)((s4, v3) => { // Recreate old state without the magic wand, and the state with the oldHeap called lhs. val s5 = s4.copy(g = s1.g, conservingSnapshotGeneration = s3.conservingSnapshotGeneration) diff --git a/src/main/scala/rules/PredicateSupporter.scala b/src/main/scala/rules/PredicateSupporter.scala index b8dd8e15c..cf1ec69f7 100644 --- a/src/main/scala/rules/PredicateSupporter.scala +++ b/src/main/scala/rules/PredicateSupporter.scala @@ -6,6 +6,7 @@ package viper.silicon.rules +import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp @@ -28,7 +29,8 @@ trait PredicateSupportRules extends SymbolicExecutionRules { ePerm: Option[ast.Exp], constrainableWildcards: InsertionOrderedSet[Var], pve: PartialVerificationError, - v: Verifier) + v: Verifier, + assumptionType: AssumptionType = AssumptionType.Rewrite) (Q: (State, Verifier) => VerificationResult) : VerificationResult @@ -41,7 +43,8 @@ trait PredicateSupportRules extends SymbolicExecutionRules { constrainableWildcards: InsertionOrderedSet[Var], pve: PartialVerificationError, v: Verifier, - pa: ast.PredicateAccess) + pa: ast.PredicateAccess, + assumptionType: AssumptionType = AssumptionType.Rewrite) (Q: (State, Verifier) => VerificationResult) : VerificationResult } @@ -58,7 +61,8 @@ object predicateSupporter extends PredicateSupportRules { ePerm: Option[ast.Exp], constrainableWildcards: InsertionOrderedSet[Var], pve: PartialVerificationError, - v: Verifier) + v: Verifier, + assumptionType: AssumptionType = AssumptionType.Rewrite) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -89,7 +93,7 @@ object predicateSupporter extends PredicateSupportRules { v1.decider.assumeDefinition(smValueDef, debugExp, AssumptionType.Internal) val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk( - formalArgs, Option.when(withExp)(predicate.formalArgs), predicate, tArgs, eArgs, tPerm, ePerm, sm, s.program, v1, AssumptionType.Rewrite) + formalArgs, Option.when(withExp)(predicate.formalArgs), predicate, tArgs, eArgs, tPerm, ePerm, sm, s.program, v1, assumptionType) val h3 = s2.h + ch val smDef = SnapshotMapDefinition(predicate, sm, Seq(smValueDef), Seq()) val smCache = if (s2.heapDependentTriggers.contains(predicate)) { @@ -114,7 +118,7 @@ object predicateSupporter extends PredicateSupportRules { functionRecorder = s2.functionRecorder.recordFvfAndDomain(smDef)) Q(s3, v1) } else { - val ch = BasicChunk(PredicateID, BasicChunkIdentifier(predicate.name), tArgs, eArgs, snap.get.convert(sorts.Snap), None, tPerm, ePerm, v1.decider.assumptionAnalyzer.getAnalysisInfo(AssumptionType.Rewrite)) + val ch = BasicChunk(PredicateID, BasicChunkIdentifier(predicate.name), tArgs, eArgs, snap.get.convert(sorts.Snap), None, tPerm, ePerm, v1.decider.assumptionAnalyzer.getAnalysisInfo(assumptionType)) val s3 = s2.copy(g = s.g, smDomainNeeded = s.smDomainNeeded, permissionScalingFactor = s.permissionScalingFactor, @@ -134,7 +138,8 @@ object predicateSupporter extends PredicateSupportRules { constrainableWildcards: InsertionOrderedSet[Var], pve: PartialVerificationError, v: Verifier, - pa: ast.PredicateAccess) + pa: ast.PredicateAccess, + assumptionType: AssumptionType = AssumptionType.Rewrite) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { val tArgsWithE = if (withExp) @@ -163,7 +168,7 @@ object predicateSupporter extends PredicateSupportRules { )((s2, h2, snap, consumedChunks, v1) => { val s3 = s2.copy(g = gIns, h = h2) .setConstrainable(constrainableWildcards, false) - produce(s3, toSf(snap.get), body, pve, v1, AssumptionType.Rewrite)((s4, v2) => { // TODO ake: add edge from consumedChunks to new assumptions + produce(s3, toSf(snap.get), body, pve, v1, assumptionType)((s4, v2) => { // TODO ake: add edge from consumedChunks to new assumptions v2.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterUnfold) if (!Verifier.config.disableFunctionUnfoldTrigger()) { val predicateTrigger = @@ -183,7 +188,7 @@ object predicateSupporter extends PredicateSupportRules { chunkSupporter.consume(s1, s1.h, predicate, tArgs, eArgs, s1.permissionScalingFactor, s1.permissionScalingFactorExp, true, ve, v, description)((s2, h1, snap, consumedChunks, v1) => { // TODO ake: add edges val s3 = s2.copy(g = gIns, h = h1) .setConstrainable(constrainableWildcards, false) - produce(s3, toSf(snap.get), body, pve, v1, AssumptionType.Rewrite)((s4, v2) => { + produce(s3, toSf(snap.get), body, pve, v1, assumptionType)((s4, v2) => { v2.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterUnfold) if (!Verifier.config.disableFunctionUnfoldTrigger()) { val predicateTrigger = diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index e455dc03a..d9cbe8285 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -7,7 +7,7 @@ package viper.silicon.supporters import com.typesafe.scalalogging.Logger -import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType, DefaultAssumptionAnalyzer, ExpAnalysisSourceInfo, NoAssumptionAnalyzer, StringAnalysisSourceInfo} +import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionAnalyzer, AssumptionType, DefaultAssumptionAnalyzer, ExpAnalysisSourceInfo, NoAssumptionAnalyzer, StringAnalysisSourceInfo} import viper.silver.ast import viper.silver.components.StatefulComponent import viper.silver.verifier.errors._ @@ -47,7 +47,9 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif logger.debug("\n\n" + "-" * 10 + " METHOD " + method.name + "-" * 10 + "\n") decider.prover.comment("%s %s %s".format("-" * 10, method.name, "-" * 10)) - v.decider.initAssumptionAnalyzer(method) + val isAnalysisEnabled = AssumptionAnalyzer.extractEnableAnalysisFromInfo(method.info).getOrElse(Verifier.config.enableAssumptionAnalysis()) + if(isAnalysisEnabled) v.decider.initAssumptionAnalyzer(method) + else v.decider.removeAssumptionAnalyzer() val proverOptions: Map[String, String] = method.info.getUniqueInfo[ast.AnnotationInfo] match { case Some(ai) if ai.values.contains("proverArgs") => @@ -95,13 +97,15 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif new java.io.File(s"${Verifier.config.tempDirectory()}/${method.name}.dot")) } + val annotatedAssumptionTypeOpt = AssumptionAnalyzer.extractAssumptionTypeFromInfo(method.info) + errorsReportedSoFar.set(0) val result = /* Combined the well-formedness check and the execution of the body, which are two separate * rules in Smans' paper. */ executionFlowController.locally(s, v)((s1, v1) => { - produces(s1, freshSnap, pres, ContractNotWellformed, v1, AssumptionType.Explicit)((s2, v2) => { + produces(s1, freshSnap, pres, ContractNotWellformed, v1, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit))((s2, v2) => { v2.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) val s2a = s2.copy(oldHeaps = s2.oldHeaps + (Verifier.PRE_STATE_LABEL -> s2.h)) ( executionFlowController.locally(s2a, v2)((s3, v3) => { @@ -113,12 +117,12 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif Success()})}) && { executionFlowController.locally(s2a, v2)((s3, v3) => { - exec(s3, body, v3)((s4, v4) =>{ + exec(s3, body, v3)((s4, v4) =>{ // TODO ake: assumption type? consumes(s4, posts, false, postViolated, v4)((_, _, _, _) => { Success() })})}) } )})}) - result.assumptionAnalyzer = v.decider.assumptionAnalyzer + if(isAnalysisEnabled) result.assumptionAnalyzer = v.decider.assumptionAnalyzer v.decider.removeAssumptionAnalyzer() v.decider.resetProverOptions() diff --git a/src/main/scala/supporters/PredicateVerificationUnit.scala b/src/main/scala/supporters/PredicateVerificationUnit.scala index 548048da7..1ac4385f9 100644 --- a/src/main/scala/supporters/PredicateVerificationUnit.scala +++ b/src/main/scala/supporters/PredicateVerificationUnit.scala @@ -7,7 +7,7 @@ package viper.silicon.supporters import com.typesafe.scalalogging.Logger -import viper.silicon.assumptionAnalysis.AssumptionType +import viper.silicon.assumptionAnalysis.{AssumptionAnalyzer, AssumptionType} import viper.silver.ast import viper.silver.ast.Program import viper.silver.components.StatefulComponent @@ -83,9 +83,12 @@ trait DefaultPredicateVerificationUnitProvider extends VerifierComponent { v: Ve def verify(sInit: State, predicate: ast.Predicate): Seq[VerificationResult] = { logger.debug("\n\n" + "-" * 10 + " PREDICATE " + predicate.name + "-" * 10 + "\n") - v.decider.initAssumptionAnalyzer(predicate) decider.prover.comment("%s %s %s".format("-" * 10, predicate.name, "-" * 10)) + val isAnalysisEnabled = AssumptionAnalyzer.extractEnableAnalysisFromInfo(predicate.info).getOrElse(Verifier.config.enableAssumptionAnalysis()) + if(isAnalysisEnabled) v.decider.initAssumptionAnalyzer(predicate) + else v.decider.removeAssumptionAnalyzer() + openSymbExLogger(predicate) val ins = predicate.formalArgs.map(_.localVar) @@ -94,6 +97,8 @@ trait DefaultPredicateVerificationUnitProvider extends VerifierComponent { v: Ve oldHeaps = OldHeaps()) val err = PredicateNotWellformed(predicate) + val assumptionType = AssumptionAnalyzer.extractAssumptionTypeFromInfo(predicate.info).getOrElse(AssumptionType.Internal) // TODO ake: internal? + val result = predicate.body match { case None => Success() @@ -101,12 +106,12 @@ trait DefaultPredicateVerificationUnitProvider extends VerifierComponent { v: Ve /* locallyXXX { magicWandSupporter.checkWandsAreSelfFraming(σ.γ, σ.h, predicate, c)} &&*/ executionFlowController.locally(s, v)((s1, _) => { - produce(s1, freshSnap, body, err, v, AssumptionType.Internal)((_, _) => + produce(s1, freshSnap, body, err, v, assumptionType)((_, _) => Success())}) } symbExLog.closeMemberScope() - result.assumptionAnalyzer = v.decider.assumptionAnalyzer + if(isAnalysisEnabled) result.assumptionAnalyzer = v.decider.assumptionAnalyzer v.decider.removeAssumptionAnalyzer() Seq(result) } diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index 3e706946f..d1c3aee2d 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -7,7 +7,7 @@ package viper.silicon.supporters.functions import com.typesafe.scalalogging.Logger -import viper.silicon.assumptionAnalysis.{AssumptionType, ExpAnalysisSourceInfo} +import viper.silicon.assumptionAnalysis.{AssumptionAnalyzer, AssumptionType, ExpAnalysisSourceInfo} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.decider.Decider @@ -30,6 +30,7 @@ import viper.silver.components.StatefulComponent import viper.silver.parser.PType import viper.silver.verifier.errors.{ContractNotWellformed, FunctionNotWellformed, PostconditionViolated} +import java.io.ObjectInputFilter.Config import scala.annotation.unused trait FunctionVerificationUnit[SO, SY, AX] @@ -148,7 +149,10 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver def verify(sInit: State, function: ast.Function): Seq[VerificationResult] = { val comment = ("-" * 10) + " FUNCTION " + function.name + ("-" * 10) - v.decider.initAssumptionAnalyzer(function) + val isAnalysisEnabled = AssumptionAnalyzer.extractEnableAnalysisFromInfo(function.info).getOrElse(Verifier.config.enableAssumptionAnalysis()) + if(isAnalysisEnabled) v.decider.initAssumptionAnalyzer(function) + else v.decider.removeAssumptionAnalyzer() + logger.debug(s"\n\n$comment\n") decider.prover.comment(comment) @@ -160,7 +164,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver val res = handleFunction(sInit, function) symbExLog.closeMemberScope() - res.assumptionAnalyzer = v.decider.assumptionAnalyzer + if(isAnalysisEnabled) res.assumptionAnalyzer = v.decider.assumptionAnalyzer v.decider.removeAssumptionAnalyzer() Seq(res) } @@ -255,13 +259,14 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver var recorders: Seq[FunctionRecorder] = Vector.empty val wExp = evaluator.withExp + val annotatedAssumptionTypeOpt = AssumptionAnalyzer.extractAssumptionTypeFromInfo(function.info) val result = phase1data.foldLeft(Success(): VerificationResult) { case (fatalResult: FatalResult, _) => fatalResult case (intermediateResult, Phase1Data(sPre, bcsPre, bcsPreExp, pcsPre, pcsPreExp)) => intermediateResult && executionFlowController.locally(sPre, v)((s1, _) => { decider.setCurrentBranchCondition(And(bcsPre), (BigAnd(bcsPreExp.map(_._1)), Option.when(wExp)(BigAnd(bcsPreExp.map(_._2.get))))) - decider.assume(pcsPre, pcsPreExp, s"precondition of ${function.name}", enforceAssumption=false, assumptionType=AssumptionType.Explicit) + decider.assume(pcsPre, pcsPreExp, s"precondition of ${function.name}", enforceAssumption=false, assumptionType=annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit)) v.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) v.decider.assumptionAnalyzer.addAnalysisSourceInfo(ExpAnalysisSourceInfo(body)) eval(s1, body, FunctionNotWellformed(function), v)((s2, tBody, bodyNew, _) => { diff --git a/src/test/resources/andrea/annotations.vpr b/src/test/resources/andrea/annotations.vpr new file mode 100644 index 000000000..b498ee01f --- /dev/null +++ b/src/test/resources/andrea/annotations.vpr @@ -0,0 +1,26 @@ + +@assumptionType("Implicit") +method annotationFunc(n: Int) returns (res: Int) + requires 0 <= n && n <= 100 + ensures res > 1000 +{ + res := 1001 +} + + +method annotationBasic(x: Int) + requires x > 0 +{ + var res2: Int + @assumptionType("Implicit") + inhale x < 100 + + var res: Int := annotationFunc(x) + assert res >= 50 + + @assumptionType("Explicit") + res2 := annotationFunc(x) + + assert 2*x <= 200 + assert res2 >= 100 +} \ No newline at end of file diff --git a/src/test/resources/andrea/branches.vpr b/src/test/resources/andrea/branches.vpr index 4b8390753..408055bdd 100644 --- a/src/test/resources/andrea/branches.vpr +++ b/src/test/resources/andrea/branches.vpr @@ -173,6 +173,8 @@ method infeasibleBranch(a: Int, b: Int) requires a > 0 && b > 0 { var res: Int := a + 1 + + if(a < 0){ res := b + 1 assert res >= 0 diff --git a/src/test/resources/andrea/method-sum.vpr b/src/test/resources/andrea/method-sum.vpr index 426e74ede..77054ebfe 100644 --- a/src/test/resources/andrea/method-sum.vpr +++ b/src/test/resources/andrea/method-sum.vpr @@ -2,8 +2,9 @@ field f: Int method sum(x: Int, y: Int) returns(res: Int) requires x >= 0 && y >= 0 - ensures res == x + y + ensures res == x + y && res > 100 { + assume x > 100 res := x + y } @@ -19,4 +20,16 @@ method sumClient(x: Int, y: Int) assert n >= x + 2*y assert n >= 0 +} + +method sumClient2(x: Int, y: Int) +{ + assume x >= 0 + assume y >= 0 + assume x < y + var n: Int := sum(x, x) + assert n >= 100 + + var n2: Int := sum(y, y) + assert n2 == 2*y } \ No newline at end of file diff --git a/src/test/resources/andrea/predicates.vpr b/src/test/resources/andrea/predicates.vpr index c7b430cff..b47473ed4 100644 --- a/src/test/resources/andrea/predicates.vpr +++ b/src/test/resources/andrea/predicates.vpr @@ -5,11 +5,14 @@ predicate greater0(n: Int) n > 0 } +@assumptionType("Internal") predicate greater5(n: Int) { n > 5 } +@enableAssumptionAnalysis("True") +@assumptionType("Internal") method unfoldP(n: Int) requires greater0(n) { @@ -19,6 +22,7 @@ method unfoldP(n: Int) assert x > 1 } +@enableAssumptionAnalysis("false") method foldP(n: Int) requires n > 10 { @@ -31,9 +35,12 @@ method unfoldFoldP(a: Int, b: Int) ensures greater5(a + b) { assume b < 100 + @assumptionType("Implicit") unfold greater0(a) unfold greater5(b) + @assumptionType("Implicit") assume a > 60 + @assumptionType("Implicit") fold greater5(a + b) } diff --git a/src/test/resources/andrea/quickTest.vpr b/src/test/resources/andrea/quickTest.vpr index c4101e38c..c1f53e8fb 100644 --- a/src/test/resources/andrea/quickTest.vpr +++ b/src/test/resources/andrea/quickTest.vpr @@ -1,13 +1,5 @@ -field f: Int - -method foo(){ - var x: Ref - inhale acc(x.f) - - package true --* acc(x.f, 1/2){ - - } - - apply true --* acc(x.f, 1/2) - assert acc(x.f) +method quickTest(a: Int) +{ + @assumptionType("Implicit") + assume a > 0 } \ No newline at end of file From 8a4fec81ef7fd250d24ca4a30ac4ec76074319ab Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 29 May 2025 11:56:38 +0200 Subject: [PATCH 067/474] fix info propagation for rewriteInhale --- silver | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/silver b/silver index 78f08c750..9fd9d555b 160000 --- a/silver +++ b/silver @@ -1 +1 @@ -Subproject commit 78f08c750641ee7642bcabbbb7db2e38b78e4e4f +Subproject commit 9fd9d555bce951044b80f01b18e489059c6e2f62 From 555341f7dea81c34b54a6c94fecdce6c68b85dab Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 29 May 2025 16:19:22 +0200 Subject: [PATCH 068/474] add support for domain axioms --- .../assumptionAnalysis/AnalysisInfo.scala | 2 +- .../AssumptionAnalysisGraph.scala | 4 +- .../AssumptionAnalyzer.scala | 40 ++++++++++++++++--- src/main/scala/decider/Decider.scala | 12 ++++-- src/main/scala/decider/ProverStdIO.scala | 14 +++---- .../scala/interfaces/decider/Prover.scala | 18 ++++++++- src/main/scala/rules/MagicWandSupporter.scala | 2 +- src/main/scala/supporters/Domains.scala | 8 ++-- .../scala/supporters/MethodSupporter.scala | 7 +--- .../PredicateVerificationUnit.scala | 7 +--- .../functions/FunctionVerificationUnit.scala | 6 +-- .../scala/verifier/DefaultMainVerifier.scala | 30 ++++++++------ .../verifier/VerificationPoolManager.scala | 2 + src/test/resources/andrea/branches.vpr | 3 ++ src/test/resources/andrea/magicWands.vpr | 6 ++- 15 files changed, 105 insertions(+), 56 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AnalysisInfo.scala b/src/main/scala/assumptionAnalysis/AnalysisInfo.scala index 270b2a604..1bb6fcf97 100644 --- a/src/main/scala/assumptionAnalysis/AnalysisInfo.scala +++ b/src/main/scala/assumptionAnalysis/AnalysisInfo.scala @@ -2,7 +2,7 @@ package viper.silicon.assumptionAnalysis object AssumptionType extends Enumeration { type AssumptionType = Value - val Explicit, LoopInvariant, PathCondition, Rewrite, Implicit, Internal, Unknown = Value + val Explicit, LoopInvariant, PathCondition, Rewrite, Implicit, Internal, Unknown, Axiom = Value def fromString(s: String): Option[Value] = values.find(_.toString == s) } diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index 6f9190517..bc7dfc807 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -24,7 +24,7 @@ trait AssumptionAnalysisGraph { var transitiveEdges: mutable.Map[Int, Set[Int]] = mutable.Map.empty def addNode(node: AssumptionAnalysisNode): Unit - def addNodes(nodes: Set[AssumptionAnalysisNode]): Unit + def addNodes(nodes: Iterable[AssumptionAnalysisNode]): Unit def addEdges(source: Int, targets: Iterable[Int]): Unit def addEdges(sources: Iterable[Int], target: Int): Unit def addEdges(sources: Iterable[Int], targets: Iterable[Int]): Unit @@ -123,7 +123,7 @@ class DefaultAssumptionAnalysisGraph extends AssumptionAnalysisGraph { nodes = nodes :+ node } - override def addNodes(nodes: Set[AssumptionAnalysisNode]): Unit = { + override def addNodes(nodes: Iterable[AssumptionAnalysisNode]): Unit = { nodes foreach addNode } diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 8be4da3e4..f28a35040 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -1,7 +1,6 @@ package viper.silicon.assumptionAnalysis import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType -import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.state.Chunk import viper.silicon.state.terms.Term @@ -10,15 +9,20 @@ import viper.silver.ast._ trait AssumptionAnalyzer { + val assumptionGraph: AssumptionAnalysisGraph = new DefaultAssumptionAnalysisGraph() + def addPermissionInhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] def addPermissionAssertNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo): Option[Int] def addPermissionExhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo): Option[Int] def addPermissionNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Explicit, isExhale: Boolean=false): Option[Int] - + def addNodes(nodes: Iterable[AssumptionAnalysisNode]): Unit + def addNode(node: AssumptionAnalysisNode): Unit + def getNodes: Iterable[AssumptionAnalysisNode] def addSingleAssumption(assumption: DebugExp, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] def addAssumptions(assumptions: Iterable[DebugExp], analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Seq[Int] + def addExpAssumption(assumption: ast.Exp, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] def createAssertOrCheckNode(term: Term, assertion: Either[String, ast.Exp], analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] @@ -27,8 +31,6 @@ trait AssumptionAnalyzer { def processUnsatCoreAndAddDependencies(dep: String): Unit - val assumptionGraph: AssumptionAnalysisGraph = new DefaultAssumptionAnalysisGraph() - protected var sourceInfoes: List[AnalysisSourceInfo] = List.empty def getAnalysisInfo: AnalysisInfo = getAnalysisInfo(AssumptionType.Implicit) @@ -97,6 +99,10 @@ object AssumptionAnalyzer { createLabel("assertion", id, offset) } + def createAxiomLabel(id: Option[Int]): String = { + createLabel("axiom", id) + } + private def createLabel(description: String, id: Option[Int], offset: Int = 0): String = { if (id.isDefined) description + "_" + id.get + "_" + offset else "" @@ -109,10 +115,19 @@ object AssumptionAnalyzer { def isAssertionLabel(label: String): Boolean = label.startsWith("assertion_") def isAssumptionLabel(label: String): Boolean = label.startsWith("assumption_") + + def isAxiomLabel(label: String): Boolean = label.startsWith("axiom_") } class DefaultAssumptionAnalyzer(member: Member) extends AssumptionAnalyzer { - def addNode(node: AssumptionAnalysisNode): Unit = { + + override def addNodes(nodes: Iterable[AssumptionAnalysisNode]): Unit = { + assumptionGraph.addNodes(nodes) + } + + override def getNodes: Iterable[AssumptionAnalysisNode] = assumptionGraph.nodes + + override def addNode(node: AssumptionAnalysisNode): Unit = { assumptionGraph.addNode(node) } @@ -123,6 +138,13 @@ class DefaultAssumptionAnalyzer(member: Member) extends AssumptionAnalyzer { Some(node.id) } + + override def addExpAssumption(assumption: ast.Exp, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { + val node = SimpleAssumptionNode(assumption, analysisSourceInfo, assumptionType) + addNode(node) + Some(node.id) + } + override def addAssumptions(assumptions: Iterable[DebugExp], analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Seq[Int] = { val newNodes = assumptions.toSeq.map(a => if (a.originalExp.isDefined) SimpleAssumptionNode(a.originalExp.get, if(analysisSourceInfo.isInstanceOf[NoAnalysisSourceInfo]) ExpAnalysisSourceInfo(a.originalExp.get) else analysisSourceInfo, assumptionType) @@ -147,6 +169,8 @@ class DefaultAssumptionAnalyzer(member: Member) extends AssumptionAnalyzer { val assumptionIds = assumptionLabels.filter(AssumptionAnalyzer.isAssumptionLabel).map(AssumptionAnalyzer.getIdFromLabel) val assertionIds = assumptionLabels.filter(AssumptionAnalyzer.isAssertionLabel).map(AssumptionAnalyzer.getIdFromLabel) assumptionGraph.addEdges(assumptionIds, assertionIds) + val axiomIds = assumptionLabels.filter(AssumptionAnalyzer.isAxiomLabel).map(AssumptionAnalyzer.getIdFromLabel) + assumptionGraph.addEdges(axiomIds, assertionIds) } override def addPermissionInhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { @@ -208,6 +232,10 @@ class DefaultAssumptionAnalyzer(member: Member) extends AssumptionAnalyzer { class NoAssumptionAnalyzer extends AssumptionAnalyzer { + override def getNodes: Iterable[AssumptionAnalysisNode] = Seq() + override def addNode(node: AssumptionAnalysisNode): Unit = {} + override def addNodes(nodes: Iterable[AssumptionAnalysisNode]): Unit = {} + override def addAssumptions(assumptions: Iterable[DebugExp], analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Seq[Int] = Seq.empty override def createAssertOrCheckNode(term: Term, assertion: Either[String, ast.Exp], analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] = None @@ -229,5 +257,7 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { override def addSingleAssumption(assumption: DebugExp, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None + override def addExpAssumption(assumption: ast.Exp, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None + override def exportGraph(): Unit = {} } \ No newline at end of file diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index b35cc8e5a..3ce47df35 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -104,7 +104,7 @@ trait Decider { def statistics(): Map[String, String] var assumptionAnalyzer: AssumptionAnalyzer - def initAssumptionAnalyzer(member: Member): Unit + def initAssumptionAnalyzer(member: Member, preambleNodes: Iterable[AssumptionAnalysisNode]): Unit def removeAssumptionAnalyzer(): Unit } @@ -137,10 +137,14 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => var assumptionAnalyzer: AssumptionAnalyzer = new NoAssumptionAnalyzer() - override def initAssumptionAnalyzer(member: Member): Unit = { - if(Verifier.config.enableAssumptionAnalysis()){ + override def initAssumptionAnalyzer(member: Member, preambleNodes: Iterable[AssumptionAnalysisNode]): Unit = { + val isAnalysisEnabled = AssumptionAnalyzer.extractEnableAnalysisFromInfo(member.info).getOrElse(Verifier.config.enableAssumptionAnalysis()) + if(isAnalysisEnabled) { assumptionAnalyzer = new DefaultAssumptionAnalyzer(member) + assumptionAnalyzer.addNodes(preambleNodes) prover.setAssumptionAnalyzer(assumptionAnalyzer) + }else{ + removeAssumptionAnalyzer() } } @@ -458,7 +462,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => val result = asserted || proverAssert(t, timeout, AssumptionAnalyzer.createAssertionLabel(assertNode map (_.id))) assertNode foreach (_.isAsserted = result) - if(result || !isCheck) assertNode foreach assumptionAnalyzer.assumptionGraph.addNode + if(result || !isCheck) assertNode foreach assumptionAnalyzer.addNode diff --git a/src/main/scala/decider/ProverStdIO.scala b/src/main/scala/decider/ProverStdIO.scala index 9a3d4acdc..bf73af689 100644 --- a/src/main/scala/decider/ProverStdIO.scala +++ b/src/main/scala/decider/ProverStdIO.scala @@ -6,23 +6,21 @@ package viper.silicon.decider -import viper.silicon.assumptionAnalysis.{AssumptionAnalyzer, NoAssumptionAnalyzer} - -import java.io._ -import java.nio.file.Path -import java.util.concurrent.TimeUnit import com.typesafe.scalalogging.LazyLogging +import viper.silicon.assumptionAnalysis.{AssumptionAnalyzer, NoAssumptionAnalyzer} import viper.silicon.common.config.Version -import viper.silicon.interfaces.decider.{Prover, Result, Sat, Unknown, Unsat} +import viper.silicon.interfaces.decider._ import viper.silicon.reporting.{ExternalToolError, ProverInteractionFailed} import viper.silicon.state.IdentifierFactory import viper.silicon.state.terms._ import viper.silicon.verifier.Verifier -import viper.silver.verifier.{DefaultDependency => SilDefaultDependency} import viper.silicon.{Config, Map, toMap} import viper.silver.reporter.{ConfigurationConfirmation, InternalWarningMessage, QuantifierInstantiationsMessage, Reporter} -import viper.silver.verifier.Model +import viper.silver.verifier.{Model, DefaultDependency => SilDefaultDependency} +import java.io._ +import java.nio.file.Path +import java.util.concurrent.TimeUnit import scala.collection.mutable abstract class ProverStdIO(uniqueId: String, diff --git a/src/main/scala/interfaces/decider/Prover.scala b/src/main/scala/interfaces/decider/Prover.scala index 67ee216ac..a4f3b66d7 100644 --- a/src/main/scala/interfaces/decider/Prover.scala +++ b/src/main/scala/interfaces/decider/Prover.scala @@ -6,7 +6,7 @@ package viper.silicon.interfaces.decider -import viper.silicon.assumptionAnalysis.AssumptionAnalyzer +import viper.silicon.assumptionAnalysis.{AssumptionAnalysisNode, AssumptionAnalyzer, AssumptionType, DefaultAssumptionAnalyzer, ExpAnalysisSourceInfo, NoAssumptionAnalyzer} import viper.silicon.debugger.DebugAxiom import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.common.config.Version @@ -15,6 +15,7 @@ import viper.silicon.{Config, Map} import viper.silicon.state.terms._ import viper.silicon.verifier.Verifier import viper.silver.verifier.Model +import viper.silver.ast sealed abstract class Result object Sat extends Result @@ -25,6 +26,9 @@ object Unknown extends Result trait ProverLike { protected val debugMode = Verifier.config.enableDebugging() var preambleAssumptions: Seq[DebugAxiom] = Seq() + protected var preambleAssumptionAnalyzer: AssumptionAnalyzer = + if(Verifier.config.enableAssumptionAnalysis()) new DefaultAssumptionAnalyzer(ast.Method("none", Seq(), Seq(), Seq(), Seq(), None)()) + else new NoAssumptionAnalyzer() def emit(content: String): Unit def emit(contents: Iterable[String]): Unit = { contents foreach emit } def emitSettings(contents: Iterable[String]): Unit @@ -33,6 +37,18 @@ trait ProverLike { preambleAssumptions :+= new DebugAxiom(description, terms) terms foreach assume } + def assumeAxiomsWithAnalysis(axioms: InsertionOrderedSet[(Term, ast.Exp)], description: String): Unit = { + if (debugMode) { + preambleAssumptions :+= new DebugAxiom(description, axioms.map(_._1)) + axioms.foreach(axiom => { + val id = preambleAssumptionAnalyzer.addExpAssumption(axiom._2, ExpAnalysisSourceInfo(axiom._2), AssumptionType.Axiom) + assume(axiom._1, AssumptionAnalyzer.createAxiomLabel(id)) + }) + }else{ + axioms.foreach(t => assume(t._1)) + } + } + def getPreambleAnalysisNodes: Iterable[AssumptionAnalysisNode] = preambleAssumptionAnalyzer.getNodes def setOption(name: String, value: String): String def assume(term: Term): Unit def assume(term: Term, label: String): Unit diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 1eee46057..ed0dd513c 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -399,7 +399,7 @@ object magicWandSupporter extends SymbolicExecutionRules { // Execute proof script, i.e. the part written after the magic wand wrapped by curly braces. // The proof script should transform the current state such that we can consume the wand's RHS. - executor.exec(s2, proofScriptCfg, v2)((proofScriptState, proofScriptVerifier) => { + executor.exec(s2, proofScriptCfg, v2)((proofScriptState, proofScriptVerifier) => { // TODO ake: propagate assumption type! // Consume the wand's RHS and produce a snapshot which records all the values of variables on the RHS. // This part indirectly calls the methods `this.transfer` and `this.consumeFromMultipleHeaps`. consume( diff --git a/src/main/scala/supporters/Domains.scala b/src/main/scala/supporters/Domains.scala index bb8916f77..cbf7581ae 100644 --- a/src/main/scala/supporters/Domains.scala +++ b/src/main/scala/supporters/Domains.scala @@ -27,7 +27,7 @@ class DefaultDomainsContributor(symbolConverter: SymbolConverter, private var collectedSorts = InsertionOrderedSet[Sort]() private var collectedFunctions = InsertionOrderedSet[terms.DomainFun]() - private var collectedAxioms = InsertionOrderedSet[Term]() + private var collectedAxioms = InsertionOrderedSet[(Term, ast.Exp)]() private var uniqueSymbols = MultiMap.empty[Sort, DomainFun] /* Lifetime */ @@ -101,7 +101,7 @@ class DefaultDomainsContributor(symbolConverter: SymbolConverter, domain.axioms foreach (axiom => { val tAx = domainTranslator.translateAxiom(axiom, symbolConverter.toSort) val tAxPres = FunctionPreconditionTransformer.transform(tAx, program) - collectedAxioms += terms.And(tAxPres, tAx) + collectedAxioms = collectedAxioms.incl(terms.And(tAxPres, tAx), axiom.exp) }) }) } @@ -118,10 +118,10 @@ class DefaultDomainsContributor(symbolConverter: SymbolConverter, collectedFunctions foreach (f => sink.declare(terms.FunctionDecl(f))) } - def axiomsAfterAnalysis: Iterable[terms.Term] = collectedAxioms + def axiomsAfterAnalysis: Iterable[terms.Term] = collectedAxioms.map(_._1) def emitAxiomsAfterAnalysis(sink: ProverLike): Unit = { - sink.assumeAxioms(collectedAxioms, "Domain axioms") + sink.assumeAxiomsWithAnalysis(collectedAxioms, "Domain axioms") } def uniquenessAssumptionsAfterAnalysis: Iterable[Term] = diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index d9cbe8285..1e4b47a86 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -47,10 +47,6 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif logger.debug("\n\n" + "-" * 10 + " METHOD " + method.name + "-" * 10 + "\n") decider.prover.comment("%s %s %s".format("-" * 10, method.name, "-" * 10)) - val isAnalysisEnabled = AssumptionAnalyzer.extractEnableAnalysisFromInfo(method.info).getOrElse(Verifier.config.enableAssumptionAnalysis()) - if(isAnalysisEnabled) v.decider.initAssumptionAnalyzer(method) - else v.decider.removeAssumptionAnalyzer() - val proverOptions: Map[String, String] = method.info.getUniqueInfo[ast.AnnotationInfo] match { case Some(ai) if ai.values.contains("proverArgs") => toMap(ai.values("proverArgs").flatMap(o => { @@ -122,8 +118,7 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif Success() })})}) } )})}) - if(isAnalysisEnabled) result.assumptionAnalyzer = v.decider.assumptionAnalyzer - v.decider.removeAssumptionAnalyzer() + result.assumptionAnalyzer = v.decider.assumptionAnalyzer v.decider.resetProverOptions() symbExLog.closeMemberScope() diff --git a/src/main/scala/supporters/PredicateVerificationUnit.scala b/src/main/scala/supporters/PredicateVerificationUnit.scala index 1ac4385f9..a7a3d3f26 100644 --- a/src/main/scala/supporters/PredicateVerificationUnit.scala +++ b/src/main/scala/supporters/PredicateVerificationUnit.scala @@ -85,10 +85,6 @@ trait DefaultPredicateVerificationUnitProvider extends VerifierComponent { v: Ve logger.debug("\n\n" + "-" * 10 + " PREDICATE " + predicate.name + "-" * 10 + "\n") decider.prover.comment("%s %s %s".format("-" * 10, predicate.name, "-" * 10)) - val isAnalysisEnabled = AssumptionAnalyzer.extractEnableAnalysisFromInfo(predicate.info).getOrElse(Verifier.config.enableAssumptionAnalysis()) - if(isAnalysisEnabled) v.decider.initAssumptionAnalyzer(predicate) - else v.decider.removeAssumptionAnalyzer() - openSymbExLogger(predicate) val ins = predicate.formalArgs.map(_.localVar) @@ -111,8 +107,7 @@ trait DefaultPredicateVerificationUnitProvider extends VerifierComponent { v: Ve } symbExLog.closeMemberScope() - if(isAnalysisEnabled) result.assumptionAnalyzer = v.decider.assumptionAnalyzer - v.decider.removeAssumptionAnalyzer() + result.assumptionAnalyzer = v.decider.assumptionAnalyzer Seq(result) } diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index d1c3aee2d..e2ad4d507 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -149,9 +149,6 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver def verify(sInit: State, function: ast.Function): Seq[VerificationResult] = { val comment = ("-" * 10) + " FUNCTION " + function.name + ("-" * 10) - val isAnalysisEnabled = AssumptionAnalyzer.extractEnableAnalysisFromInfo(function.info).getOrElse(Verifier.config.enableAssumptionAnalysis()) - if(isAnalysisEnabled) v.decider.initAssumptionAnalyzer(function) - else v.decider.removeAssumptionAnalyzer() logger.debug(s"\n\n$comment\n") decider.prover.comment(comment) @@ -164,8 +161,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver val res = handleFunction(sInit, function) symbExLog.closeMemberScope() - if(isAnalysisEnabled) res.assumptionAnalyzer = v.decider.assumptionAnalyzer - v.decider.removeAssumptionAnalyzer() + res.assumptionAnalyzer = v.decider.assumptionAnalyzer Seq(res) } diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index c9526b469..75a850cd6 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -6,19 +6,11 @@ package viper.silicon.verifier -import viper.silicon.debugger.SiliconDebugger import viper.silicon.Config.{ExhaleMode, JoinMode} - -import java.text.SimpleDateFormat -import java.util.concurrent._ -import scala.annotation.unused -import scala.collection.mutable -import scala.util.Random -import viper.silver.ast -import viper.silver.components.StatefulComponent import viper.silicon._ import viper.silicon.assumptionAnalysis.DefaultAssumptionAnalyzer import viper.silicon.common.collections.immutable.InsertionOrderedSet +import viper.silicon.debugger.SiliconDebugger import viper.silicon.decider.SMTLib2PreambleReader import viper.silicon.extensions.ConditionalPermissionRewriter import viper.silicon.interfaces._ @@ -27,17 +19,25 @@ import viper.silicon.logger.{MemberSymbExLogger, SymbExLogger} import viper.silicon.reporting.{MultiRunRecorders, condenseToViperResult} import viper.silicon.state._ import viper.silicon.state.terms.{Decl, Sort, Term, sorts} -import viper.silicon.supporters.{DefaultDomainsContributor, DefaultMapsContributor, DefaultMultisetsContributor, DefaultPredicateVerificationUnitProvider, DefaultSequencesContributor, DefaultSetsContributor, MagicWandSnapFunctionsContributor, PredicateData} -import viper.silicon.supporters.qps._ import viper.silicon.supporters.functions.{DefaultFunctionVerificationUnitProvider, FunctionData} +import viper.silicon.supporters.qps._ +import viper.silicon.supporters._ import viper.silicon.utils.Counter +import viper.silver.ast import viper.silver.ast.utility.rewriter.Traverse import viper.silver.ast.{BackendType, Member} import viper.silver.cfg.silver.SilverCfg +import viper.silver.components.StatefulComponent import viper.silver.frontend.FrontendStateCache import viper.silver.reporter._ import viper.silver.verifier.VerifierWarning +import java.text.SimpleDateFormat +import java.util.concurrent._ +import scala.annotation.unused +import scala.collection.mutable +import scala.util.Random + /* TODO: Extract a suitable MainVerifier interface, probably including * - def verificationPoolManager: VerificationPoolManager) * - def uniqueIdCounter: String) @@ -229,8 +229,10 @@ class DefaultMainVerifier(config: Config, */ val functionVerificationResults = functionsSupporter.units.toList flatMap (function => { val startTime = System.currentTimeMillis() + decider.initAssumptionAnalyzer(function, allProvers.getPreambleAnalysisNodes) val results = functionsSupporter.verify(createInitialState(function, program, functionData, predicateData), function) .flatMap(extractAllVerificationResults) + decider.removeAssumptionAnalyzer() val elapsed = System.currentTimeMillis() - startTime reporter report VerificationResultMessage(s"silicon", function, elapsed, condenseToViperResult(results)) logger debug s"Silicon finished verification of function `${function.name}` in ${viper.silver.reporter.format.formatMillisReadably(elapsed)} seconds with the following result: ${condenseToViperResult(results).toString}" @@ -239,8 +241,10 @@ class DefaultMainVerifier(config: Config, val predicateVerificationResults = predicateSupporter.units.toList flatMap (predicate => { val startTime = System.currentTimeMillis() + decider.initAssumptionAnalyzer(predicate, allProvers.getPreambleAnalysisNodes) val results = predicateSupporter.verify(createInitialState(predicate, program, functionData, predicateData), predicate) .flatMap(extractAllVerificationResults) + decider.removeAssumptionAnalyzer() val elapsed = System.currentTimeMillis() - startTime reporter report VerificationResultMessage(s"silicon", predicate, elapsed, condenseToViperResult(results)) logger debug s"Silicon finished verification of predicate `${predicate.name}` in ${viper.silver.reporter.format.formatMillisReadably(elapsed)} seconds with the following result: ${condenseToViperResult(results).toString}" @@ -268,8 +272,10 @@ class DefaultMainVerifier(config: Config, _verificationPoolManager.queueVerificationTask(v => { val startTime = System.currentTimeMillis() + v.decider.initAssumptionAnalyzer(method, allProvers.getPreambleAnalysisNodes) val results = v.methodSupporter.verify(s, method) .flatMap(extractAllVerificationResults) + v.decider.removeAssumptionAnalyzer() val elapsed = System.currentTimeMillis() - startTime reporter report VerificationResultMessage(s"silicon", method, elapsed, condenseToViperResult(results)) @@ -282,7 +288,7 @@ class DefaultMainVerifier(config: Config, _verificationPoolManager.queueVerificationTask(v => { val startTime = System.currentTimeMillis() - val results = v.cfgSupporter.verify(s, cfg) + val results = v.cfgSupporter.verify(s, cfg) // TODO ake: assumption analysis .flatMap(extractAllVerificationResults) val elapsed = System.currentTimeMillis() - startTime diff --git a/src/main/scala/verifier/VerificationPoolManager.scala b/src/main/scala/verifier/VerificationPoolManager.scala index 094dc8c19..e35978561 100644 --- a/src/main/scala/verifier/VerificationPoolManager.scala +++ b/src/main/scala/verifier/VerificationPoolManager.scala @@ -15,6 +15,7 @@ import viper.silicon.interfaces.VerificationResult import viper.silver.components.StatefulComponent import viper.silicon.interfaces.decider.ProverLike import viper.silicon.state.terms.{Decl, Term} +import viper.silver.ast class VerificationPoolManager(mainVerifier: MainVerifier) extends StatefulComponent { private val numberOfWorkers: Int = Verifier.config.numberOfParallelVerifiers() @@ -29,6 +30,7 @@ class VerificationPoolManager(mainVerifier: MainVerifier) extends StatefulCompon def assume(term: Term): Unit = workerVerifiers foreach (_.decider.prover.assume(term)) def assume(term: Term, label: String): Unit = workerVerifiers foreach (_.decider.prover.assume(term, label)) override def assumeAxioms(terms: InsertionOrderedSet[Term], description: String): Unit = workerVerifiers foreach (_.decider.prover.assumeAxioms(terms, description)) + override def assumeAxiomsWithAnalysis(axioms: InsertionOrderedSet[(Term, ast.Exp)], description: String): Unit = workerVerifiers foreach (_.decider.prover.assumeAxiomsWithAnalysis(axioms, description)) def declare(decl: Decl): Unit = workerVerifiers foreach (_.decider.prover.declare(decl)) def comment(content: String): Unit = workerVerifiers foreach (_.decider.prover.comment(content)) diff --git a/src/test/resources/andrea/branches.vpr b/src/test/resources/andrea/branches.vpr index 408055bdd..51d88d471 100644 --- a/src/test/resources/andrea/branches.vpr +++ b/src/test/resources/andrea/branches.vpr @@ -146,11 +146,13 @@ method branchesFunctionsPerm(a: Ref, b: Ref) { var n:Int, c: Bool + //@assumptionType("Implicit") assume 0 < a.f && a.f < 100 assume 0 < b.f assume b.f < 50 assume c ==> a.f > 5 + //@assumptionType("Implicit") if(c){ if(a.f > b.f){ n := diffPerm(a, b) @@ -160,6 +162,7 @@ method branchesFunctionsPerm(a: Ref, b: Ref) } n := n - 1 }else{ + @assumptionType("Implicit") n := sumPerm(a, b) } diff --git a/src/test/resources/andrea/magicWands.vpr b/src/test/resources/andrea/magicWands.vpr index 347d1cb98..4bb87866e 100644 --- a/src/test/resources/andrea/magicWands.vpr +++ b/src/test/resources/andrea/magicWands.vpr @@ -24,13 +24,17 @@ method basicPackage(l: Ref) ensures list(l) { unfold list(l) - var tmp : Ref := l.next + var tmp : Ref + //@assumptionType("Explicit") + tmp := l.next + //@assumptionType("Implicit") package list(tmp) --* list(l) { fold list(l) } + //@assumptionType("Implicit") apply list(tmp) --* list(l) } From 34839f991b81f710a41700d81ba361a1b3ee9eb4 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 30 May 2025 09:53:10 +0200 Subject: [PATCH 069/474] add support for function axioms --- .../AssumptionAnalyzer.scala | 25 ++++++++--- src/main/scala/decider/ProverStdIO.scala | 2 +- .../scala/interfaces/decider/Prover.scala | 14 +++--- src/main/scala/rules/Brancher.scala | 5 ++- src/main/scala/rules/Evaluator.scala | 2 +- src/main/scala/rules/Joiner.scala | 2 +- .../BuiltinDomainsContributor.scala | 1 + src/main/scala/supporters/Domains.scala | 13 +++--- .../supporters/functions/FunctionData.scala | 43 +++++++++++-------- .../functions/FunctionVerificationUnit.scala | 20 ++++----- .../scala/verifier/DefaultMainVerifier.scala | 2 +- .../verifier/VerificationPoolManager.scala | 11 ++--- src/test/resources/andrea/axioms.vpr | 26 +++++++++++ src/test/resources/andrea/branches.vpr | 33 ++++++++++++++ src/test/resources/andrea/quickTest.vpr | 27 ++++++++++-- 15 files changed, 165 insertions(+), 61 deletions(-) create mode 100644 src/test/resources/andrea/axioms.vpr diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index f28a35040..66e99622b 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -22,14 +22,15 @@ trait AssumptionAnalyzer { def addSingleAssumption(assumption: DebugExp, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] def addAssumptions(assumptions: Iterable[DebugExp], analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Seq[Int] - def addExpAssumption(assumption: ast.Exp, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] + def addAssumption(assumption: ast.Exp, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] + def addAssumption(assumption: String, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] def createAssertOrCheckNode(term: Term, assertion: Either[String, ast.Exp], analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNodeId: Option[Int]): Unit def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNodeId: Chunk): Unit - def processUnsatCoreAndAddDependencies(dep: String): Unit + def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit protected var sourceInfoes: List[AnalysisSourceInfo] = List.empty @@ -139,12 +140,19 @@ class DefaultAssumptionAnalyzer(member: Member) extends AssumptionAnalyzer { } - override def addExpAssumption(assumption: ast.Exp, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { + override def addAssumption(assumption: ast.Exp, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { val node = SimpleAssumptionNode(assumption, analysisSourceInfo, assumptionType) addNode(node) Some(node.id) } + + override def addAssumption(assumption: String, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { + val node = StringAssumptionNode(assumption, analysisSourceInfo, assumptionType) + addNode(node) + Some(node.id) + } + override def addAssumptions(assumptions: Iterable[DebugExp], analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Seq[Int] = { val newNodes = assumptions.toSeq.map(a => if (a.originalExp.isDefined) SimpleAssumptionNode(a.originalExp.get, if(analysisSourceInfo.isInstanceOf[NoAnalysisSourceInfo]) ExpAnalysisSourceInfo(a.originalExp.get) else analysisSourceInfo, assumptionType) @@ -163,11 +171,13 @@ class DefaultAssumptionAnalyzer(member: Member) extends AssumptionAnalyzer { }) } - override def processUnsatCoreAndAddDependencies(dep: String): Unit = { + override def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit = { val assumptionLabels = dep.replace("(", "").replace(")", "").split(" ") if(assumptionLabels.size < 2) return val assumptionIds = assumptionLabels.filter(AssumptionAnalyzer.isAssumptionLabel).map(AssumptionAnalyzer.getIdFromLabel) - val assertionIds = assumptionLabels.filter(AssumptionAnalyzer.isAssertionLabel).map(AssumptionAnalyzer.getIdFromLabel) + val assertionIdsFromUnsatCore = assumptionLabels.filter(AssumptionAnalyzer.isAssertionLabel).map(AssumptionAnalyzer.getIdFromLabel) + val assertionIdFromLabel = AssumptionAnalyzer.getIdFromLabel(assertionLabel) + val assertionIds = assertionIdFromLabel +: assertionIdsFromUnsatCore // TODO ake: add check (not already contained) assumptionGraph.addEdges(assumptionIds, assertionIds) val axiomIds = assumptionLabels.filter(AssumptionAnalyzer.isAxiomLabel).map(AssumptionAnalyzer.getIdFromLabel) assumptionGraph.addEdges(axiomIds, assertionIds) @@ -240,7 +250,7 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { override def createAssertOrCheckNode(term: Term, assertion: Either[String, ast.Exp], analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] = None - override def processUnsatCoreAndAddDependencies(dep: String): Unit = { + override def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit = { } override def addPermissionInhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None @@ -257,7 +267,8 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { override def addSingleAssumption(assumption: DebugExp, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None - override def addExpAssumption(assumption: ast.Exp, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None + override def addAssumption(assumption: ast.Exp, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None + override def addAssumption(assumption: String, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None override def exportGraph(): Unit = {} } \ No newline at end of file diff --git a/src/main/scala/decider/ProverStdIO.scala b/src/main/scala/decider/ProverStdIO.scala index bf73af689..678f9b1d9 100644 --- a/src/main/scala/decider/ProverStdIO.scala +++ b/src/main/scala/decider/ProverStdIO.scala @@ -303,7 +303,7 @@ abstract class ProverStdIO(uniqueId: String, retrieveReasonUnknown() }else if(Verifier.config.enableAssumptionAnalysis()){ val unsatCore = extractUnsatCore() - assumptionAnalyzer.processUnsatCoreAndAddDependencies(unsatCore) + assumptionAnalyzer.processUnsatCoreAndAddDependencies(unsatCore, label) } pop() diff --git a/src/main/scala/interfaces/decider/Prover.scala b/src/main/scala/interfaces/decider/Prover.scala index a4f3b66d7..e4fa5bc85 100644 --- a/src/main/scala/interfaces/decider/Prover.scala +++ b/src/main/scala/interfaces/decider/Prover.scala @@ -6,16 +6,16 @@ package viper.silicon.interfaces.decider -import viper.silicon.assumptionAnalysis.{AssumptionAnalysisNode, AssumptionAnalyzer, AssumptionType, DefaultAssumptionAnalyzer, ExpAnalysisSourceInfo, NoAssumptionAnalyzer} -import viper.silicon.debugger.DebugAxiom +import viper.silicon.assumptionAnalysis._ import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.common.config.Version -import viper.silver.components.StatefulComponent -import viper.silicon.{Config, Map} +import viper.silicon.debugger.DebugAxiom import viper.silicon.state.terms._ import viper.silicon.verifier.Verifier -import viper.silver.verifier.Model +import viper.silicon.{Config, Map} import viper.silver.ast +import viper.silver.components.StatefulComponent +import viper.silver.verifier.Model sealed abstract class Result object Sat extends Result @@ -37,11 +37,11 @@ trait ProverLike { preambleAssumptions :+= new DebugAxiom(description, terms) terms foreach assume } - def assumeAxiomsWithAnalysis(axioms: InsertionOrderedSet[(Term, ast.Exp)], description: String): Unit = { + def assumeAxiomsWithAnalysisInfo(axioms: InsertionOrderedSet[(Term, AnalysisSourceInfo)], description: String): Unit = { if (debugMode) { preambleAssumptions :+= new DebugAxiom(description, axioms.map(_._1)) axioms.foreach(axiom => { - val id = preambleAssumptionAnalyzer.addExpAssumption(axiom._2, ExpAnalysisSourceInfo(axiom._2), AssumptionType.Axiom) + val id = preambleAssumptionAnalyzer.addAssumption(axiom._2.toString, axiom._2, AssumptionType.Axiom) assume(axiom._1, AssumptionAnalyzer.createAxiomLabel(id)) }) }else{ diff --git a/src/main/scala/rules/Brancher.scala b/src/main/scala/rules/Brancher.scala index c8c7651b4..75c9d6312 100644 --- a/src/main/scala/rules/Brancher.scala +++ b/src/main/scala/rules/Brancher.scala @@ -54,8 +54,9 @@ object brancher extends BranchingRules { * (2) the branch condition contains a quantified variable */ val skipPathFeasibilityCheck = ( - fromShortCircuitingAnd - || ( s.quantifiedVariables.nonEmpty + Verifier.config.enableAssumptionAnalysis() + || fromShortCircuitingAnd + || ( s.quantifiedVariables.nonEmpty && s.quantifiedVariables.map(_._1).exists(condition.freeVariables.contains)) ) diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 46591d9c4..f1c55166e 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -940,7 +940,7 @@ object evaluator extends EvaluationRules { val preExp = Option.when(withExp)({ DebugExp.createInstance(Some(s"precondition of ${func.name}(${eArgsNew.get.mkString(", ")}) holds"), None, None, InsertionOrderedSet.empty) }) - v3.decider.assume(preFApp, preExp, AssumptionType.Implicit) + v3.decider.assume(preFApp, preExp, AssumptionType.Internal) val funcAnn = func.info.getUniqueInfo[AnnotationInfo] val tFApp = funcAnn match { case Some(a) if a.values.contains("opaque") => diff --git a/src/main/scala/rules/Joiner.scala b/src/main/scala/rules/Joiner.scala index a408a99bb..7d322772b 100644 --- a/src/main/scala/rules/Joiner.scala +++ b/src/main/scala/rules/Joiner.scala @@ -105,7 +105,7 @@ object joiner extends JoiningRules { val pcsExp = Option.when(withExp)(entry.pathConditions.conditionalizedExp) val comment = "Joined path conditions" v.decider.prover.comment(comment) - v.decider.assume(pcs, Option.when(withExp)(DebugExp.createInstance(comment, InsertionOrderedSet(pcsExp.get))), enforceAssumption = false, assumptionType=AssumptionType.Implicit) + v.decider.assume(pcs, pcsExp, comment, enforceAssumption = false, assumptionType=AssumptionType.Internal) feasibleBranches = And(entry.pathConditions.branchConditions) :: feasibleBranches feasibleBranchesExp = feasibleBranchesExp.map(fbe => BigAnd(entry.pathConditions.branchConditionExps.map(_._1)) :: fbe) feasibleBranchesExpNew = feasibleBranchesExpNew.map(fbe => BigAnd(entry.pathConditions.branchConditionExps.map(_._2.get)) :: fbe) diff --git a/src/main/scala/supporters/BuiltinDomainsContributor.scala b/src/main/scala/supporters/BuiltinDomainsContributor.scala index b6ef014d2..8820a81ac 100644 --- a/src/main/scala/supporters/BuiltinDomainsContributor.scala +++ b/src/main/scala/supporters/BuiltinDomainsContributor.scala @@ -16,6 +16,7 @@ import viper.silicon.interfaces.PreambleContributor import viper.silicon.interfaces.decider.ProverLike import viper.silicon.state.DefaultSymbolConverter import viper.silicon.state.terms._ +import viper.silver.ast.NoPosition abstract class BuiltinDomainsContributor extends PreambleContributor[Sort, DomainFun, Term] { type BuiltinDomainType <: ast.GenericType diff --git a/src/main/scala/supporters/Domains.scala b/src/main/scala/supporters/Domains.scala index cbf7581ae..35f8702df 100644 --- a/src/main/scala/supporters/Domains.scala +++ b/src/main/scala/supporters/Domains.scala @@ -6,14 +6,15 @@ package viper.silicon.supporters -import viper.silver.ast +import viper.silicon.assumptionAnalysis.{AnalysisSourceInfo, ExpAnalysisSourceInfo} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.common.collections.immutable.MultiMap._ -import viper.silicon.toMap import viper.silicon.interfaces.PreambleContributor import viper.silicon.interfaces.decider.ProverLike -import viper.silicon.state.{FunctionPreconditionTransformer, SymbolConverter, terms} import viper.silicon.state.terms.{Distinct, DomainFun, Sort, Term} +import viper.silicon.state.{FunctionPreconditionTransformer, SymbolConverter, terms} +import viper.silicon.toMap +import viper.silver.ast import viper.silver.ast.NamedDomainAxiom trait DomainsContributor[SO, SY, AX, UA] extends PreambleContributor[SO, SY, AX] { @@ -27,7 +28,7 @@ class DefaultDomainsContributor(symbolConverter: SymbolConverter, private var collectedSorts = InsertionOrderedSet[Sort]() private var collectedFunctions = InsertionOrderedSet[terms.DomainFun]() - private var collectedAxioms = InsertionOrderedSet[(Term, ast.Exp)]() + private var collectedAxioms = InsertionOrderedSet[(Term, AnalysisSourceInfo)]() private var uniqueSymbols = MultiMap.empty[Sort, DomainFun] /* Lifetime */ @@ -101,7 +102,7 @@ class DefaultDomainsContributor(symbolConverter: SymbolConverter, domain.axioms foreach (axiom => { val tAx = domainTranslator.translateAxiom(axiom, symbolConverter.toSort) val tAxPres = FunctionPreconditionTransformer.transform(tAx, program) - collectedAxioms = collectedAxioms.incl(terms.And(tAxPres, tAx), axiom.exp) + collectedAxioms = collectedAxioms.incl(terms.And(tAxPres, tAx), ExpAnalysisSourceInfo(axiom.exp)) }) }) } @@ -121,7 +122,7 @@ class DefaultDomainsContributor(symbolConverter: SymbolConverter, def axiomsAfterAnalysis: Iterable[terms.Term] = collectedAxioms.map(_._1) def emitAxiomsAfterAnalysis(sink: ProverLike): Unit = { - sink.assumeAxiomsWithAnalysis(collectedAxioms, "Domain axioms") + sink.assumeAxiomsWithAnalysisInfo(collectedAxioms, "Domain axioms") } def uniquenessAssumptionsAfterAnalysis: Iterable[Term] = diff --git a/src/main/scala/supporters/functions/FunctionData.scala b/src/main/scala/supporters/functions/FunctionData.scala index 2d7b68138..f74ffb930 100644 --- a/src/main/scala/supporters/functions/FunctionData.scala +++ b/src/main/scala/supporters/functions/FunctionData.scala @@ -6,25 +6,26 @@ package viper.silicon.supporters.functions -import scala.annotation.unused import com.typesafe.scalalogging.LazyLogging -import viper.silicon.state.FunctionPreconditionTransformer -import viper.silver.ast -import viper.silver.ast.utility.Functions +import viper.silicon.assumptionAnalysis.{AnalysisSourceInfo, ExpAnalysisSourceInfo, StringAnalysisSourceInfo} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.interfaces.FatalResult import viper.silicon.rules.{InverseFunctions, SnapshotMapDefinition, functionSupporter} import viper.silicon.state.terms._ import viper.silicon.state.terms.predef._ -import viper.silicon.state.{Identifier, IdentifierFactory, SymbolConverter} +import viper.silicon.state.{FunctionPreconditionTransformer, Identifier, IdentifierFactory, SymbolConverter} import viper.silicon.supporters.PredicateData -import viper.silicon.utils.ast.simplifyVariableName +import viper.silicon.utils.ast.{BigAnd, simplifyVariableName} import viper.silicon.verifier.Verifier import viper.silicon.{Config, Map, toMap} +import viper.silver.ast import viper.silver.ast.LocalVarWithVersion +import viper.silver.ast.utility.Functions import viper.silver.parser.PUnknown import viper.silver.reporter.Reporter +import scala.annotation.unused + /* TODO: Refactor FunctionData! * Separate computations from "storing" the final results and sharing * them with other components. Computations should probably be moved to the @@ -83,12 +84,14 @@ class FunctionData(val programFunction: ast.Function, val preconditionFunctionApplication = App(preconditionFunction, `?s` +: formalArgs.values.toSeq) val limitedAxiom = - Forall(arguments, + (Forall(arguments, BuiltinEquals(limitedFunctionApplication, functionApplication), - Trigger(functionApplication)) + Trigger(functionApplication)), + StringAnalysisSourceInfo("Limited Axiom", programFunction.pos)) val triggerAxiom = - Forall(arguments, triggerFunctionApplication, Trigger(limitedFunctionApplication)) + (Forall(arguments, triggerFunctionApplication, Trigger(limitedFunctionApplication)), + StringAnalysisSourceInfo("Trigger Axiom", programFunction.pos)) /* * Data collected during phases 1 (well-definedness checking) and 2 (verification) @@ -201,7 +204,7 @@ class FunctionData(val programFunction: ast.Function, } } - lazy val postAxiom: Option[Term] = { + lazy val postAxiom: Option[(Term, AnalysisSourceInfo)] = { assert(phase == 1, s"Postcondition axiom must be generated in phase 1, current phase is $phase") if (programFunction.posts.nonEmpty) { @@ -210,7 +213,10 @@ class FunctionData(val programFunction: ast.Function, val bodyBindings: Map[Var, Term] = Map(formalResult -> limitedFunctionApplication) val body = Let(toMap(bodyBindings), innermostBody) - Some(Forall(arguments, body, Trigger(limitedFunctionApplication))) + Some((Forall(arguments, body, Trigger(limitedFunctionApplication)), + ExpAnalysisSourceInfo(ast.Forall(argumentExps.filter(_.isDefined).map(v => ast.LocalVarDecl(v.get.name, v.get.typ)(v.get.pos)), Seq(), + ast.Implies(BigAnd(programFunction.pres), BigAnd(programFunction.posts))())(programFunction.pos, programFunction.info, programFunction.errT)) + )) } else None } @@ -266,7 +272,7 @@ class FunctionData(val programFunction: ast.Function, expressionTranslator.translate(program, programFunction, this) } - lazy val definitionalAxiom: Option[Term] = { + lazy val definitionalAxiom: Option[(Term, AnalysisSourceInfo)] = { assert(phase == 2, s"Definitional axiom must be generated in phase 2, current phase is $phase") optBody.map(translatedBody => { @@ -281,24 +287,27 @@ class FunctionData(val programFunction: ast.Function, val allTriggers = ( Seq(Trigger(functionApplication)) ++ actualPredicateTriggers) - Forall(arguments, body, allTriggers)}) + (Forall(arguments, body, allTriggers), StringAnalysisSourceInfo("definitionalAxiom", programFunction.pos)) + }) } - lazy val bodyPreconditionPropagationAxiom: Seq[Term] = { + lazy val bodyPreconditionPropagationAxiom: Seq[(Term, AnalysisSourceInfo)] = { val pre = preconditionFunctionApplication val bodyPreconditions = if (programFunction.body.isDefined) optBody.map(translatedBody => { val body = Implies(pre, FunctionPreconditionTransformer.transform(translatedBody, program)) - Forall(arguments, body, Seq(Trigger(functionApplication))) + (Forall(arguments, body, Seq(Trigger(functionApplication))), + StringAnalysisSourceInfo("bodyPreconditionPropagationAxiom", programFunction.pos)) }) else None bodyPreconditions.toSeq } - lazy val postPreconditionPropagationAxiom: Seq[Term] = { + lazy val postPreconditionPropagationAxiom: Seq[(Term, AnalysisSourceInfo)] = { val pre = preconditionFunctionApplication val postPreconditions = if (programFunction.posts.nonEmpty) { val bodyBindings: Map[Var, Term] = Map(formalResult -> limitedFunctionApplication) val bodies = translatedPosts.map(tPost => Let(bodyBindings, Implies(pre, FunctionPreconditionTransformer.transform(tPost, program)))) - bodies.map(b => Forall(arguments, b, Seq(Trigger(limitedFunctionApplication)))) + bodies.map(b => (Forall(arguments, b, Seq(Trigger(limitedFunctionApplication))), + StringAnalysisSourceInfo("postPreconditionPropagationAxiom", programFunction.pos))) } else Seq() postPreconditions } diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index e2ad4d507..39d76920e 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -7,7 +7,7 @@ package viper.silicon.supporters.functions import com.typesafe.scalalogging.Logger -import viper.silicon.assumptionAnalysis.{AssumptionAnalyzer, AssumptionType, ExpAnalysisSourceInfo} +import viper.silicon.assumptionAnalysis.{AnalysisSourceInfo, AssumptionAnalyzer, AssumptionType, ExpAnalysisSourceInfo} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.decider.Decider @@ -30,7 +30,6 @@ import viper.silver.components.StatefulComponent import viper.silver.parser.PType import viper.silver.verifier.errors.{ContractNotWellformed, FunctionNotWellformed, PostconditionViolated} -import java.io.ObjectInputFilter.Config import scala.annotation.unused trait FunctionVerificationUnit[SO, SY, AX] @@ -53,9 +52,9 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver @unused private var program: ast.Program = _ /*private*/ var functionData: Map[ast.Function, FunctionData] = Map.empty - private var emittedFunctionAxioms: Vector[Term] = Vector.empty + private var emittedFunctionAxioms: Vector[(Term, AnalysisSourceInfo)] = Vector.empty private var freshVars: Vector[Var] = Vector.empty - private var postConditionAxioms: Vector[Term] = Vector.empty + private var postConditionAxioms: Vector[(Term, AnalysisSourceInfo)] = Vector.empty private val expressionTranslator = { def resolutionFailureMessage(exp: ast.Positioned, data: FunctionData): String = ( @@ -143,7 +142,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver val axiomsAfterAnalysis: Iterable[Term] = Seq.empty def emitAxiomsAfterAnalysis(sink: ProverLike): Unit = () - def getPostConditionAxioms() = this.postConditionAxioms + def getPostConditionAxioms() = this.postConditionAxioms.map(_._1) /* Verification and subsequent preamble contribution */ @@ -262,7 +261,8 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver case (intermediateResult, Phase1Data(sPre, bcsPre, bcsPreExp, pcsPre, pcsPreExp)) => intermediateResult && executionFlowController.locally(sPre, v)((s1, _) => { decider.setCurrentBranchCondition(And(bcsPre), (BigAnd(bcsPreExp.map(_._1)), Option.when(wExp)(BigAnd(bcsPreExp.map(_._2.get))))) - decider.assume(pcsPre, pcsPreExp, s"precondition of ${function.name}", enforceAssumption=false, assumptionType=annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit)) + // TODO ake: pcsPreExp are missing position infos sometimes (e.g. Snapshots) + decider.assume(pcsPre, pcsPreExp, s"precondition of ${function.name}", enforceAssumption=false, assumptionType=annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit)) // TODO ake: assumption type? v.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) v.decider.assumptionAnalyzer.addAnalysisSourceInfo(ExpAnalysisSourceInfo(body)) eval(s1, body, FunctionNotWellformed(function), v)((s2, tBody, bodyNew, _) => { @@ -282,8 +282,8 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver result } - private def emitAndRecordFunctionAxioms(axiom: Term*): Unit = { - decider.prover.assumeAxioms(InsertionOrderedSet(axiom), "Function axioms") + private def emitAndRecordFunctionAxioms(axiom: (Term, AnalysisSourceInfo)*): Unit = { + decider.prover.assumeAxiomsWithAnalysisInfo(InsertionOrderedSet(axiom), "Function axioms") emittedFunctionAxioms = emittedFunctionAxioms ++ axiom } @@ -316,10 +316,10 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver freshVars foreach (x => sink.declare(ConstDecl(x))) } - val axiomsAfterVerification: Iterable[Term] = emittedFunctionAxioms + val axiomsAfterVerification: Iterable[Term] = emittedFunctionAxioms.map(_._1) def emitAxiomsAfterVerification(sink: ProverLike): Unit = { - sink.assumeAxioms(InsertionOrderedSet(emittedFunctionAxioms), "Function axioms") + sink.assumeAxiomsWithAnalysisInfo(InsertionOrderedSet(emittedFunctionAxioms), "Function axioms") } /* Lifetime */ diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index 75a850cd6..c37ea4b10 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -272,7 +272,7 @@ class DefaultMainVerifier(config: Config, _verificationPoolManager.queueVerificationTask(v => { val startTime = System.currentTimeMillis() - v.decider.initAssumptionAnalyzer(method, allProvers.getPreambleAnalysisNodes) + v.decider.initAssumptionAnalyzer(method, allProvers.getPreambleAnalysisNodes ++ v.decider.prover.getPreambleAnalysisNodes) val results = v.methodSupporter.verify(s, method) .flatMap(extractAllVerificationResults) v.decider.removeAssumptionAnalyzer() diff --git a/src/main/scala/verifier/VerificationPoolManager.scala b/src/main/scala/verifier/VerificationPoolManager.scala index e35978561..663905256 100644 --- a/src/main/scala/verifier/VerificationPoolManager.scala +++ b/src/main/scala/verifier/VerificationPoolManager.scala @@ -6,16 +6,17 @@ package viper.silicon.verifier -import java.util.concurrent._ -import org.apache.commons.pool2.{BasePooledObjectFactory, ObjectPool, PoolUtils, PooledObject} import org.apache.commons.pool2.impl.{DefaultPooledObject, GenericObjectPool, GenericObjectPoolConfig} +import org.apache.commons.pool2.{BasePooledObjectFactory, ObjectPool, PoolUtils, PooledObject} import viper.silicon.Config +import viper.silicon.assumptionAnalysis.AnalysisSourceInfo import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.interfaces.VerificationResult -import viper.silver.components.StatefulComponent import viper.silicon.interfaces.decider.ProverLike import viper.silicon.state.terms.{Decl, Term} -import viper.silver.ast +import viper.silver.components.StatefulComponent + +import java.util.concurrent._ class VerificationPoolManager(mainVerifier: MainVerifier) extends StatefulComponent { private val numberOfWorkers: Int = Verifier.config.numberOfParallelVerifiers() @@ -30,7 +31,7 @@ class VerificationPoolManager(mainVerifier: MainVerifier) extends StatefulCompon def assume(term: Term): Unit = workerVerifiers foreach (_.decider.prover.assume(term)) def assume(term: Term, label: String): Unit = workerVerifiers foreach (_.decider.prover.assume(term, label)) override def assumeAxioms(terms: InsertionOrderedSet[Term], description: String): Unit = workerVerifiers foreach (_.decider.prover.assumeAxioms(terms, description)) - override def assumeAxiomsWithAnalysis(axioms: InsertionOrderedSet[(Term, ast.Exp)], description: String): Unit = workerVerifiers foreach (_.decider.prover.assumeAxiomsWithAnalysis(axioms, description)) + override def assumeAxiomsWithAnalysisInfo(axioms: InsertionOrderedSet[(Term, AnalysisSourceInfo)], description: String): Unit = workerVerifiers foreach (_.decider.prover.assumeAxiomsWithAnalysisInfo(axioms, description)) def declare(decl: Decl): Unit = workerVerifiers foreach (_.decider.prover.declare(decl)) def comment(content: String): Unit = workerVerifiers foreach (_.decider.prover.comment(content)) diff --git a/src/test/resources/andrea/axioms.vpr b/src/test/resources/andrea/axioms.vpr new file mode 100644 index 000000000..dd09d12b1 --- /dev/null +++ b/src/test/resources/andrea/axioms.vpr @@ -0,0 +1,26 @@ + +function sum(a: Int, b: Int): Int + ensures result == a + b + +function rand(): Int + ensures result > 0 + +function func(a: Int, b: Int): Int + requires a > 0 + ensures result >= a && result >= b && result > 0 +{ + a>b? a : b +} + +function useless(c: Bool): Int + requires c + ensures result == 5 + +method funcClient(a: Int, b: Int) + requires a > 0 +{ + var res: Int + res := func(a, b) + assert res >= 0 + assert rand() + rand() > 0 +} \ No newline at end of file diff --git a/src/test/resources/andrea/branches.vpr b/src/test/resources/andrea/branches.vpr index 51d88d471..3563def9e 100644 --- a/src/test/resources/andrea/branches.vpr +++ b/src/test/resources/andrea/branches.vpr @@ -183,6 +183,39 @@ method infeasibleBranch(a: Int, b: Int) assert res >= 0 } + assert res >= 0 + assert res >= a +} + +method infeasibleBranchPerm(a: Ref, b: Ref) + requires acc(a.f) + requires a.f > 0 +{ + var res: Int := a.f + 1 + + + if(a.f < 0){ + res := b.f + 1 + assert res >= 0 + assert a.f == 0 + } + + assert res >= 0 + assert res >= a.f +} + + +method infeasibleBranchNoPerm(a: Int, b: Ref) + requires a > 0 +{ + var res: Int := a + 1 + + + if(a < 0){ + res := b.f + 1 + assert res >= 0 + } + assert res >= 0 assert res >= a } \ No newline at end of file diff --git a/src/test/resources/andrea/quickTest.vpr b/src/test/resources/andrea/quickTest.vpr index c1f53e8fb..dd09d12b1 100644 --- a/src/test/resources/andrea/quickTest.vpr +++ b/src/test/resources/andrea/quickTest.vpr @@ -1,5 +1,26 @@ -method quickTest(a: Int) + +function sum(a: Int, b: Int): Int + ensures result == a + b + +function rand(): Int + ensures result > 0 + +function func(a: Int, b: Int): Int + requires a > 0 + ensures result >= a && result >= b && result > 0 { - @assumptionType("Implicit") - assume a > 0 + a>b? a : b +} + +function useless(c: Bool): Int + requires c + ensures result == 5 + +method funcClient(a: Int, b: Int) + requires a > 0 +{ + var res: Int + res := func(a, b) + assert res >= 0 + assert rand() + rand() > 0 } \ No newline at end of file From 6b1f0866df65003cbeeefdaef16669ab1cf282cf Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 30 May 2025 10:55:58 +0200 Subject: [PATCH 070/474] cleanup and minor fixes --- src/main/scala/rules/Executor.scala | 4 +--- src/main/scala/rules/QuantifiedChunkSupport.scala | 2 +- src/main/scala/supporters/PredicateVerificationUnit.scala | 2 +- src/main/scala/supporters/functions/FunctionData.scala | 2 +- .../scala/supporters/functions/FunctionVerificationUnit.scala | 4 +--- src/main/scala/verifier/DefaultMainVerifier.scala | 4 ++-- 6 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 7c1d76eed..bbf936af8 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -279,9 +279,8 @@ object executor extends ExecutionRules { val s2 = s1.copy(invariantContexts = sLeftover.h +: s1.invariantContexts) intermediateResult combine executionFlowController.locally(s2, v1)((s3, v2) => { v2.decider.declareAndRecordAsFreshFunctions(ff1 -- v2.decider.freshFunctions) /* [BRANCH-PARALLELISATION] */ - v2.decider.assumptionAnalyzer.addAnalysisSourceInfo(ExpAnalysisSourceInfo(BigAnd(pcs.assumptionExps.filter(_.originalExp.isDefined).map(_.originalExp.get)))) + // TODO ake: pcs.assumptionExps without exps do not have a source, but setting the source here will result in all invariants having the same source v2.decider.assume(pcs.assumptions, Some(pcs.assumptionExps), "Loop invariant", enforceAssumption=false, assumptionType=AssumptionType.LoopInvariant) - v2.decider.assumptionAnalyzer.popAnalysisSourceInfo() v2.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) if (v2.decider.checkSmoke()) Success() @@ -307,7 +306,6 @@ object executor extends ExecutionRules { * attempting to re-establish the invariant. */ v.decider.prover.comment("Loop head block: Re-establish invariant") - // TODO ake: set source consumes(s, invs, false, e => LoopInvariantNotPreserved(e), v)((_, _, _, _) => Success()) } diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 87dde968a..37e68ca29 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -998,7 +998,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val nonNegTerm = Forall(qvars, Implies(FunctionPreconditionTransformer.transform(nonNegImplication, s.program), nonNegImplication), Nil) val nonNegExp = qvarExps.map(qv => ast.Forall(qv, Nil, nonNegImplicationExp.get)()) // TODO: Replace by QP-analogue of permissionSupporter.assertNotNegative - v.decider.assert(nonNegTerm, nonNegExp) { // FIXME ake: verify this + v.decider.assert(nonNegTerm, nonNegExp) { // TODO ake: verify this case true => /* TODO: Can we omit/simplify the injectivity check in certain situations? */ diff --git a/src/main/scala/supporters/PredicateVerificationUnit.scala b/src/main/scala/supporters/PredicateVerificationUnit.scala index a7a3d3f26..7549bacde 100644 --- a/src/main/scala/supporters/PredicateVerificationUnit.scala +++ b/src/main/scala/supporters/PredicateVerificationUnit.scala @@ -93,7 +93,7 @@ trait DefaultPredicateVerificationUnitProvider extends VerifierComponent { v: Ve oldHeaps = OldHeaps()) val err = PredicateNotWellformed(predicate) - val assumptionType = AssumptionAnalyzer.extractAssumptionTypeFromInfo(predicate.info).getOrElse(AssumptionType.Internal) // TODO ake: internal? + val assumptionType = AssumptionAnalyzer.extractAssumptionTypeFromInfo(predicate.info).getOrElse(AssumptionType.Internal) val result = predicate.body match { case None => diff --git a/src/main/scala/supporters/functions/FunctionData.scala b/src/main/scala/supporters/functions/FunctionData.scala index f74ffb930..4b13f18ce 100644 --- a/src/main/scala/supporters/functions/FunctionData.scala +++ b/src/main/scala/supporters/functions/FunctionData.scala @@ -209,7 +209,7 @@ class FunctionData(val programFunction: ast.Function, if (programFunction.posts.nonEmpty) { val pre = preconditionFunctionApplication - val innermostBody = And(generateNestedDefinitionalAxioms ++ List(Implies(pre, And(translatedPosts)))) + val innermostBody = And(generateNestedDefinitionalAxioms ++ List(Implies(pre, And(translatedPosts)))) // TODO ake: why not introduce one axiom per postcondition? val bodyBindings: Map[Var, Term] = Map(formalResult -> limitedFunctionApplication) val body = Let(toMap(bodyBindings), innermostBody) diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index 39d76920e..cd245a3ec 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -262,9 +262,8 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver intermediateResult && executionFlowController.locally(sPre, v)((s1, _) => { decider.setCurrentBranchCondition(And(bcsPre), (BigAnd(bcsPreExp.map(_._1)), Option.when(wExp)(BigAnd(bcsPreExp.map(_._2.get))))) // TODO ake: pcsPreExp are missing position infos sometimes (e.g. Snapshots) - decider.assume(pcsPre, pcsPreExp, s"precondition of ${function.name}", enforceAssumption=false, assumptionType=annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit)) // TODO ake: assumption type? + decider.assume(pcsPre, pcsPreExp, s"precondition of ${function.name}", enforceAssumption=false, assumptionType=annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit)) v.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) - v.decider.assumptionAnalyzer.addAnalysisSourceInfo(ExpAnalysisSourceInfo(body)) eval(s1, body, FunctionNotWellformed(function), v)((s2, tBody, bodyNew, _) => { val debugExp = if (wExp) { val e = ast.EqCmp(ast.Result(function.typ)(), body)(function.pos, function.info, function.errT) @@ -272,7 +271,6 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver Some(DebugExp.createInstance(e, eNew)) } else { None } decider.assume(BuiltinEquals(data.formalResult, tBody), debugExp, AssumptionType.Implicit) - v.decider.assumptionAnalyzer.popAnalysisSourceInfo() consumes(s2, posts, false, postconditionViolated, v)((s3, _, _, _) => { recorders :+= s3.functionRecorder Success()})})})} diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index c37ea4b10..3a48093ca 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -229,7 +229,7 @@ class DefaultMainVerifier(config: Config, */ val functionVerificationResults = functionsSupporter.units.toList flatMap (function => { val startTime = System.currentTimeMillis() - decider.initAssumptionAnalyzer(function, allProvers.getPreambleAnalysisNodes) + decider.initAssumptionAnalyzer(function, allProvers.getPreambleAnalysisNodes ++ decider.prover.getPreambleAnalysisNodes) val results = functionsSupporter.verify(createInitialState(function, program, functionData, predicateData), function) .flatMap(extractAllVerificationResults) decider.removeAssumptionAnalyzer() @@ -241,7 +241,7 @@ class DefaultMainVerifier(config: Config, val predicateVerificationResults = predicateSupporter.units.toList flatMap (predicate => { val startTime = System.currentTimeMillis() - decider.initAssumptionAnalyzer(predicate, allProvers.getPreambleAnalysisNodes) + decider.initAssumptionAnalyzer(predicate, allProvers.getPreambleAnalysisNodes ++ decider.prover.getPreambleAnalysisNodes) val results = predicateSupporter.verify(createInitialState(predicate, program, functionData, predicateData), predicate) .flatMap(extractAllVerificationResults) decider.removeAssumptionAnalyzer() From af83b28417b70db713e64580bdbea6a049cff3c9 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 31 May 2025 10:13:16 +0200 Subject: [PATCH 071/474] add impreciseness example --- src/test/resources/andrea/impreciseness.vpr | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/test/resources/andrea/impreciseness.vpr diff --git a/src/test/resources/andrea/impreciseness.vpr b/src/test/resources/andrea/impreciseness.vpr new file mode 100644 index 000000000..3187a6405 --- /dev/null +++ b/src/test/resources/andrea/impreciseness.vpr @@ -0,0 +1,15 @@ +field f: Int + +method getValue(x: Ref) returns (res: Int) + requires acc(x.f) + ensures acc(x.f) + +method permTest(a: Ref, b: Ref, n: Int) + requires acc(a.f) && acc(b.f) && b.f > 0 + requires n > 0 +{ + a.f := b.f + 2 + assert a.f > 2 + a.f := n + assert a.f >= 0 // incorrectly depends on acc(b.f) +} \ No newline at end of file From 3ae894731dd6bbdb527f8b7998ec040641a8a996 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 31 May 2025 11:08:17 +0200 Subject: [PATCH 072/474] fix impressiceness for field assigns --- src/main/scala/rules/Executor.scala | 41 ++++++++++++++++------------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index bbf936af8..8eb1e88c0 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -438,26 +438,31 @@ object executor extends ExecutionRules { case ass @ ast.FieldAssign(fa @ ast.FieldAccess(eRcvr, field), rhs) => assert(!s.exhaleExt) val pve = AssignmentFailed(ass) - eval(s, eRcvr, pve, v)((s1, tRcvr, eRcvrNew, v1) => - eval(s1, rhs, pve, v1)((s2, tRhs, rhsNew, v2) => { - v2.decider.assumptionAnalyzer.addAnalysisSourceInfo(fa) - val resource = fa.res(s.program) - val ve = pve dueTo InsufficientPermission(fa) - val description = s"consume ${ass.pos}: $ass" - chunkSupporter.consume(s2, s2.h, resource, Seq(tRcvr), eRcvrNew.map(Seq(_)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), false, ve, v2, description)((s3, h3, _, consumedChunks, v3) => { - val (tSnap, _) = ssaifyRhs(tRhs, rhs, rhsNew, field.name, field.typ, v3, s3, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit)) - val id = BasicChunkIdentifier(field.name) - val newChunk = BasicChunk.createDerivedChunk(consumedChunks.toSet, FieldID, id, Seq(tRcvr), eRcvrNew.map(Seq(_)), tSnap, rhsNew, FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), - v3.decider.assumptionAnalyzer.getAnalysisInfo(annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit))) - chunkSupporter.produce(s3, h3, newChunk, v3)((s4, h4, v4) => { - val s5 = s4.copy(h = h4) - val (debugHeapName, _) = v4.getDebugOldLabel(s5, fa.pos) - val s6 = if (withExp) s5.copy(oldHeaps = s5.oldHeaps + (debugHeapName -> magicWandSupporter.getEvalHeap(s5))) else s5 - v4.decider.assumptionAnalyzer.popAnalysisSourceInfo() - Q(s6, v4) + eval(s, eRcvr, pve, v)((s1, tRcvr, eRcvrNew, v1) =>{ + eval(s1, rhs, pve, v1)((s2, tRhs, rhsNew, v2) => { + val resource = fa.res(s.program) + val ve = pve dueTo InsufficientPermission(fa) + val description = s"consume ${ass.pos}: $ass" + v2.decider.assumptionAnalyzer.popAnalysisSourceInfo() // lhs and rhs should have different analysis sources + v2.decider.assumptionAnalyzer.addAnalysisSourceInfo(fa) + chunkSupporter.consume(s2, s2.h, resource, Seq(tRcvr), eRcvrNew.map(Seq(_)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), false, ve, v2, description)((s3, h3, _, consumedChunks, v3) => { + v3.decider.assumptionAnalyzer.popAnalysisSourceInfo() + v3.decider.assumptionAnalyzer.addAnalysisSourceInfo(StmtAnalysisSourceInfo(ass)) + val (tSnap, _) = ssaifyRhs(tRhs, rhs, rhsNew, field.name, field.typ, v3, s3, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit)) + val id = BasicChunkIdentifier(field.name) + v3.decider.assumptionAnalyzer.popAnalysisSourceInfo() // lhs and rhs should have different analysis sources + v3.decider.assumptionAnalyzer.addAnalysisSourceInfo(fa) + val newChunk = BasicChunk.createDerivedChunk(Set.empty, FieldID, id, Seq(tRcvr), eRcvrNew.map(Seq(_)), tSnap, rhsNew, FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), + v3.decider.assumptionAnalyzer.getAnalysisInfo(annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit))) + chunkSupporter.produce(s3, h3, newChunk, v3)((s4, h4, v4) => { + val s5 = s4.copy(h = h4) + val (debugHeapName, _) = v4.getDebugOldLabel(s5, fa.pos) + val s6 = if (withExp) s5.copy(oldHeaps = s5.oldHeaps + (debugHeapName -> magicWandSupporter.getEvalHeap(s5))) else s5 + Q(s6, v4) + }) }) }) - }) + } ) case stmt@ast.NewStmt(x, fields) => From 09f5820206eaa9f4659b625d7a01026922014ecc Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 31 May 2025 11:23:24 +0200 Subject: [PATCH 073/474] add isExhale flag to quantified chunk creation --- src/main/scala/rules/Executor.scala | 4 +- src/main/scala/rules/MagicWandSupporter.scala | 2 +- src/main/scala/rules/PredicateSupporter.scala | 2 +- src/main/scala/rules/Producer.scala | 2 +- .../scala/rules/QuantifiedChunkSupport.scala | 40 ++++++++++++------- 5 files changed, 31 insertions(+), 19 deletions(-) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 8eb1e88c0..b7bca4056 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -423,7 +423,7 @@ object executor extends ExecutionRules { val debugExp = Option.when(withExp)(DebugExp.createInstance("Definitional axioms for singleton-FVF's value", isInternal_ = true)) v1.decider.assumeDefinition(smValueDef, debugExp, AssumptionType.Internal) val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(Seq(`?r`), Option.when(withExp)(Seq(ast.LocalVarDecl("r", ast.Ref)(ass.pos, ass.info, ass.errT))), - field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), sm, s.program, v1, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit)) + field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), sm, s.program, v1, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit), isExhale=false) if (s3.heapDependentTriggers.contains(field)) { val debugExp2 = Option.when(withExp)(DebugExp.createInstance(s"FieldTrigger(${eRcvrNew.toString()}.${field.name})")) v1.decider.assume(FieldTrigger(field.name, sm, tRcvr), debugExp2, AssumptionType.Internal) @@ -482,7 +482,7 @@ object executor extends ExecutionRules { val debugExp = Option.when(withExp)(DebugExp.createInstance("Definitional axioms for singleton-FVF's value", isInternal_ = true)) v.decider.assumeDefinition(smValueDef, debugExp, AssumptionType.Internal) quantifiedChunkSupporter.createSingletonQuantifiedChunk(Seq(`?r`), Option.when(withExp)(Seq(ast.LocalVarDecl("r", ast.Ref)(stmt.pos, stmt.info, stmt.errT))), - field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), p, pExp, sm, s.program, v, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit)) + field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), p, pExp, sm, s.program, v, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit), isExhale=false) } else { val newChunk = BasicChunk(FieldID, BasicChunkIdentifier(field.name), Seq(tRcvr), Option.when(withExp)(Seq(x)), snap, snapExp, p, pExp, v.decider.assumptionAnalyzer.getAnalysisInfo(annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit))) diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index ed0dd513c..2328212e8 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -330,7 +330,7 @@ object magicWandSupporter extends SymbolicExecutionRules { val debugExp = Option.when(withExp)(DebugExp.createInstance("Definitional axioms for singleton-SM's value", isInternal_ = true)) v4.decider.assumeDefinition(smValueDef, debugExp, AssumptionType.Internal) val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(formalVars, formalVarExps, wand, args, - Option.when(withExp)(bodyVars), FullPerm, Option.when(withExp)(ast.FullPerm()()), sm, s.program, v4, assumptionType) + Option.when(withExp)(bodyVars), FullPerm, Option.when(withExp)(ast.FullPerm()()), sm, s.program, v4, assumptionType, isExhale=false) val conservedPcs = s5.conservedPcs.head :+ v4.decider.pcs.after(preMark).definitionsOnly appendToResults(s5, ch, v4.decider.pcs.after(preMark), (conservedPcs.flatMap(_.conditionalized), Option.when(withExp)(conservedPcs.flatMap(_.conditionalizedExp))), v4) Success() diff --git a/src/main/scala/rules/PredicateSupporter.scala b/src/main/scala/rules/PredicateSupporter.scala index cf1ec69f7..ca0f27181 100644 --- a/src/main/scala/rules/PredicateSupporter.scala +++ b/src/main/scala/rules/PredicateSupporter.scala @@ -93,7 +93,7 @@ object predicateSupporter extends PredicateSupportRules { v1.decider.assumeDefinition(smValueDef, debugExp, AssumptionType.Internal) val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk( - formalArgs, Option.when(withExp)(predicate.formalArgs), predicate, tArgs, eArgs, tPerm, ePerm, sm, s.program, v1, assumptionType) + formalArgs, Option.when(withExp)(predicate.formalArgs), predicate, tArgs, eArgs, tPerm, ePerm, sm, s.program, v1, assumptionType, isExhale=false) val h3 = s2.h + ch val smDef = SnapshotMapDefinition(predicate, sm, Seq(smValueDef), Seq()) val smCache = if (s2.heapDependentTriggers.contains(predicate)) { diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index b54e3d65f..5809f00cf 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -411,7 +411,7 @@ object producer extends ProductionRules { else s1.conservedPcs val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(formalVars, formalVarExps, wand, args, bodyVarsNew, - FullPerm, Option.when(withExp)(ast.FullPerm()(wand.pos, wand.info, wand.errT)), sm, s.program, v1, assumptionType) + FullPerm, Option.when(withExp)(ast.FullPerm()(wand.pos, wand.info, wand.errT)), sm, s.program, v1, assumptionType, isExhale=false) val h2 = s1.h + ch val smCache1 = if (s1.heapDependentTriggers.contains(MagicWandIdentifier(wand, s1.program))){ val (relevantChunks, _) = diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 37e68ca29..cfd97fa8b 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -180,7 +180,8 @@ trait QuantifiedChunkSupport extends SymbolicExecutionRules { sm: Term, program: ast.Program, v: Verifier, - assumptionType: AssumptionType) + assumptionType: AssumptionType, + isExhale: Boolean) : QuantifiedBasicChunk /** Creates a quantified chunk corresponding to the assertion @@ -219,7 +220,8 @@ trait QuantifiedChunkSupport extends SymbolicExecutionRules { qidPrefix: String, v: Verifier, program: ast.Program, - assumptionType: AssumptionType) + assumptionType: AssumptionType, + isExhale: Boolean) : (QuantifiedBasicChunk, InverseFunctions) def splitHeap[CH <: QuantifiedBasicChunk : NotNothing : ClassTag] @@ -275,7 +277,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { sm: Term, program: ast.Program, v: Verifier, - assumptionType: AssumptionType) + assumptionType: AssumptionType, + isExhale: Boolean) : QuantifiedBasicChunk = { val condition = @@ -305,7 +308,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { hints, program, v, - assumptionType) + assumptionType, + isExhale) } /** @inheritdoc [[QuantifiedChunkSupport.createQuantifiedChunk]] */ @@ -327,7 +331,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { qidPrefix: String, v: Verifier, program: ast.Program, - assumptionType: AssumptionType) + assumptionType: AssumptionType, + isExhale: Boolean) : (QuantifiedBasicChunk, InverseFunctions) = { val (inverseFunctions, imagesOfCodomain) = @@ -369,7 +374,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { hints, program, v, - assumptionType) + assumptionType, + isExhale) (ch, inverseFunctions) } @@ -413,7 +419,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { hints: Seq[Term], program: ast.Program, v: Verifier, - assumptionType: AssumptionType) + assumptionType: AssumptionType, + isExhale: Boolean) : QuantifiedBasicChunk = { resource match { @@ -433,7 +440,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optSingletonArguments.map(_.head), optSingletonArgumentsExp.map(_.head), hints, - v.decider.assumptionAnalyzer.getAnalysisInfo(assumptionType)) + v.decider.assumptionAnalyzer.getAnalysisInfo(assumptionType), + isExhale) case predicate: ast.Predicate => QuantifiedPredicateChunk( @@ -449,7 +457,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optSingletonArguments, optSingletonArgumentsExp, hints, - v.decider.assumptionAnalyzer.getAnalysisInfo(assumptionType)) + v.decider.assumptionAnalyzer.getAnalysisInfo(assumptionType), + isExhale) case wand: ast.MagicWand => val conditionalizedPermissions = Ite(condition, permissions, NoPerm) @@ -465,7 +474,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optSingletonArguments, optSingletonArgumentsExp, hints, - v.decider.assumptionAnalyzer.getAnalysisInfo(assumptionType)) + v.decider.assumptionAnalyzer.getAnalysisInfo(assumptionType), + isExhale) case other => sys.error(s"Found yet unsupported resource $other (${other.getClass.getSimpleName})") @@ -937,7 +947,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { qidPrefix = qid, v = v, program = s.program, - assumptionType = assumptionType) + assumptionType = assumptionType, + isExhale = false) val (effectiveTriggers, effectiveTriggersQVars, effectiveTriggersQVarExps) = optTrigger match { case Some(_) => @@ -1121,7 +1132,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val conservedPcs = if (s.recordPcs) (s.conservedPcs.head :+ v.decider.pcs.after(definitionalAxiomMark)) +: s.conservedPcs.tail else s.conservedPcs - val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(formalQVars, formalQVarsExp, resource, tArgs, eArgs, tPerm, ePerm, sm, s.program, v, assumptionType) + val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(formalQVars, formalQVarsExp, resource, tArgs, eArgs, tPerm, ePerm, sm, s.program, v, assumptionType, isExhale=false) val (fr1, h1) = v.stateConsolidator(s).merge(s.functionRecorder, s, s.h, Heap(Seq(ch)), v) val interpreter = new NonQuantifiedPropertyInterpreter(h1.values, v) @@ -1359,7 +1370,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { qid, v2, s.program, - AssumptionType.Unknown // TODO ake: should be exhale + AssumptionType.Explicit, + isExhale = true ) val debugExp = Option.when(withExp)(DebugExp.createInstance("Inverse functions for quantified permission", isInternal_ = true)) v.decider.assume(FunctionPreconditionTransformer.transform(inverseFunctions.axiomInvertiblesOfInverses, s3.program), debugExp, AssumptionType.Internal) @@ -1491,7 +1503,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { } val consumedChunk = quantifiedChunkSupporter.createSingletonQuantifiedChunk( - codomainQVars, codomainQVarsExp, resource, arguments, argumentsExp, permsTaken, permsTakenExp, smDef1.sm, s.program, v1, AssumptionType.Unknown) // TODO ake: exhale + codomainQVars, codomainQVarsExp, resource, arguments, argumentsExp, permsTaken, permsTakenExp, smDef1.sm, s.program, v1, AssumptionType.Explicit, isExhale=true) val s3 = s2.copy(functionRecorder = s2.functionRecorder.recordFvfAndDomain(smDef1), smCache = smCache1) (result, s3, h2, Some(consumedChunk)) // TODO ake: or consumedChunks? From 6473c50c332b28fc9157fcac83cb7ef64d78b258 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 31 May 2025 11:38:31 +0200 Subject: [PATCH 074/474] Chunks: add explicit exhale node for permMinus --- src/main/scala/interfaces/state/Chunks.scala | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/scala/interfaces/state/Chunks.scala b/src/main/scala/interfaces/state/Chunks.scala index 2378e80a1..37f60f79f 100644 --- a/src/main/scala/interfaces/state/Chunks.scala +++ b/src/main/scala/interfaces/state/Chunks.scala @@ -6,9 +6,9 @@ package viper.silicon.interfaces.state -import viper.silicon.assumptionAnalysis.{AnalysisInfo, PermissionInhaleNode} +import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType, PermissionInhaleNode} import viper.silicon.resources.ResourceID -import viper.silicon.state.terms.{Term, Var} +import viper.silicon.state.terms.{PermMinus, Term, Var} import viper.silver.ast trait Chunk { @@ -39,9 +39,15 @@ object GeneralChunk { } def permMinus(chunk: GeneralChunk, newPerm: Term, newPermExp: Option[ast.Exp], analysisInfo: AnalysisInfo, isExhale: Boolean=false): GeneralChunk = { + // TODO ake: review and test val newChunk = chunk.permMinus(newPerm, newPermExp) - val newNodeId = analysisInfo.assumptionAnalyzer.addPermissionNode(newChunk, newPermExp, analysisInfo.sourceInfo, analysisInfo.assumptionType, isExhale) + val newNodeId = analysisInfo.assumptionAnalyzer.addPermissionNode(newChunk, + Option.when(chunk.permExp.isDefined && newPermExp.isDefined)(ast.PermSub(chunk.permExp.get, newPermExp.get)(newPermExp.get.pos, newPermExp.get.info)), + analysisInfo.sourceInfo, analysisInfo.assumptionType, isExhale) analysisInfo.assumptionAnalyzer.addPermissionDependencies(Set(chunk), newNodeId) + val exhaledChunk = chunk.withPerm(newPerm, newPermExp) + val exhaledNodeId = analysisInfo.assumptionAnalyzer.addPermissionNode(exhaledChunk, newPermExp, analysisInfo.sourceInfo, AssumptionType.Implicit, isExhale=true) + analysisInfo.assumptionAnalyzer.addPermissionDependencies(Set(chunk), exhaledNodeId) newChunk } From abb9d0cc00bc66f0046c81c663dd477a6d3de5b5 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 2 Jun 2025 09:45:55 +0200 Subject: [PATCH 075/474] summarizing heap: set dedicated source --- .../assumptionAnalysis/AssumptionAnalyzer.scala | 13 +++++++++++++ .../scala/rules/QuantifiedChunkSupport.scala | 17 ++++++++++------- src/test/resources/andrea/quantified-perm.vpr | 5 +++++ 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 66e99622b..f50b2896a 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -33,12 +33,15 @@ trait AssumptionAnalyzer { def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit protected var sourceInfoes: List[AnalysisSourceInfo] = List.empty + protected var forcedMainSource: Option[AnalysisSourceInfo] = None def getAnalysisInfo: AnalysisInfo = getAnalysisInfo(AssumptionType.Implicit) def getAnalysisInfo(assumptionType: AssumptionType): AnalysisInfo = AnalysisInfo(this, getFullSourceInfo, assumptionType) def getFullSourceInfo: AnalysisSourceInfo = { + if(forcedMainSource.isDefined) + return forcedMainSource.get if(sourceInfoes.size <= 1){ sourceInfoes.headOption.getOrElse(NoAnalysisSourceInfo()) } else{ @@ -53,6 +56,16 @@ trait AssumptionAnalyzer { def popAnalysisSourceInfo(): Unit = { sourceInfoes = sourceInfoes.tail + forcedMainSource = None + } + + def setForcedSource(description: String): Unit = { + if(forcedMainSource.isEmpty) + forcedMainSource = Some(StringAnalysisSourceInfo(description, getFullSourceInfo.getPosition)) + } + + def unsetForcedSource(): Unit = { + forcedMainSource = None } def getAnalysisSourceInfoes: List[AnalysisSourceInfo] = sourceInfoes diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index cfd97fa8b..85f60d175 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -732,8 +732,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { smDef: SnapshotMapDefinition, v: Verifier) : (PermMapDefinition, PmCache) = { - - Verifier.config.mapCache(s.pmCache.get(resource, relevantChunks)) match { + v.decider.assumptionAnalyzer.setForcedSource("summarizing heap") + val res = Verifier.config.mapCache(s.pmCache.get(resource, relevantChunks)) match { case Some(pmDef) => v.decider.assume(pmDef.valueDefinitions, Option.when(withExp)(DebugExp.createInstance("value definitions", isInternal_ = true)), enforceAssumption = false, assumptionType=AssumptionType.Internal) (pmDef, s.pmCache) @@ -744,6 +744,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.assume(valueDef, Option.when(withExp)(DebugExp.createInstance("value definitions", isInternal_ = true)), enforceAssumption = false, assumptionType=AssumptionType.Internal) (pmDef, s.pmCache + ((resource, relevantChunks) -> pmDef)) } + v.decider.assumptionAnalyzer.unsetForcedSource() + res } /* Snapshots */ @@ -770,6 +772,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optSmDomainDefinitionCondition: Option[Term] = None, optQVarsInstantiations: Option[Seq[Term]] = None) : (SnapshotMapDefinition, SnapshotMapCache) = { + v.decider.assumptionAnalyzer.setForcedSource("summarizing heap") def emitSnapshotMapDefinition(s: State, smDef: SnapshotMapDefinition, @@ -840,7 +843,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { } emitSnapshotMapDefinition(s, smDef, v, optQVarsInstantiations) - + v.decider.assumptionAnalyzer.unsetForcedSource() (smDef, smCache) } @@ -852,7 +855,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optSmDomainDefinitionCondition: Option[Term] = None, optQVarsInstantiations: Option[Seq[Term]] = None) : (State, SnapshotMapDefinition, PermMapDefinition) = { - + v.decider.assumptionAnalyzer.setForcedSource("summarizing heap") val (smDef, smCache) = summarisingSnapshotMap( s, resource, codomainQVars, relevantChunks, v, optSmDomainDefinitionCondition, optQVarsInstantiations) @@ -864,7 +867,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { s1, resource, codomainQVars, relevantChunks, smDef, v) val s2 = s1.copy(pmCache = pmCache) - + v.decider.assumptionAnalyzer.unsetForcedSource() (s2, smDef, pmDef) } @@ -877,14 +880,14 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { relevantChunks: Seq[QuantifiedBasicChunk], v: Verifier) : (State, PermMapDefinition) = { - + v.decider.assumptionAnalyzer.setForcedSource("summarizing heap") val s1 = s val (pmDef, pmCache) = quantifiedChunkSupporter.summarisingPermissionMap( s1, resource, codomainQVars, relevantChunks, null, v) val s2 = s1.copy(pmCache = pmCache) - + v.decider.assumptionAnalyzer.unsetForcedSource() (s2, pmDef) } diff --git a/src/test/resources/andrea/quantified-perm.vpr b/src/test/resources/andrea/quantified-perm.vpr index b447239bb..01ffd752f 100644 --- a/src/test/resources/andrea/quantified-perm.vpr +++ b/src/test/resources/andrea/quantified-perm.vpr @@ -9,6 +9,11 @@ method quantifiedPerm(xs: Seq[Ref]) { assert xs[2].f > 0 var a : Ref := xs[0] a.f := 0 + assert xs[0].f >= 0 + assert xs[0].f == 0 + assert xs[2].f >= 0 + a.f := -1 + assert xs[2] != a ==> xs[2].f > 0 } method quantifiedWritePerm(nodes: Set[Ref], x: Ref) From eb1183f5e0235c355c124fc3132d78288628a0c4 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 2 Jun 2025 10:41:47 +0200 Subject: [PATCH 076/474] Chunks: add explicit exhale node if chunk is depleted --- src/main/scala/rules/QuantifiedChunkSupport.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 85f60d175..47598bb23 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -1710,6 +1710,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { remainingChunks = remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.assumptionAnalyzer.getAnalysisInfo(AssumptionType.Implicit)) } + }else{ + v.decider.assumptionAnalyzer.addPermissionExhaleNode(ithChunk, ithPTakenExp, v.decider.assumptionAnalyzer.getFullSourceInfo) } } From 96b127e378efe23ab59a39fd40e574d222b44ccc Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 2 Jun 2025 10:57:35 +0200 Subject: [PATCH 077/474] state consolidation: set correct source --- src/main/scala/rules/StateConsolidator.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/scala/rules/StateConsolidator.scala b/src/main/scala/rules/StateConsolidator.scala index 641be41b4..086c850f8 100644 --- a/src/main/scala/rules/StateConsolidator.scala +++ b/src/main/scala/rules/StateConsolidator.scala @@ -61,7 +61,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol def consolidate(s: State, v: Verifier): State = { val comLog = new CommentRecord("state consolidation", s, v.decider.pcs) val sepIdentifier = v.symbExLog.openScope(comLog) - v.decider.assumptionAnalyzer.addAnalysisSourceInfo(StringAnalysisSourceInfo("state consolidation", NoPosition)) + v.decider.assumptionAnalyzer.setForcedSource("state consolidation") v.decider.prover.comment("[state consolidation]") v.decider.prover.saturate(config.proverSaturationTimeouts.beforeIteration) @@ -118,7 +118,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol reserveHeaps = mergedHeaps.tail) val s2 = assumeUpperPermissionBoundForQPFields(s1, v) - v.decider.assumptionAnalyzer.popAnalysisSourceInfo() + v.decider.assumptionAnalyzer.unsetForcedSource() s2 } From e4a972f25ebc98c730a7bfab8e109bc1eef32175 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 2 Jun 2025 18:28:33 +0200 Subject: [PATCH 078/474] fix source for quantified field assign --- .../AssumptionAnalyzer.scala | 5 ++++ src/main/scala/rules/Executor.scala | 16 +++++++----- src/test/resources/andrea/quantified-perm.vpr | 26 +++++++++++++++++++ 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index f50b2896a..5cafacb91 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -64,6 +64,11 @@ trait AssumptionAnalyzer { forcedMainSource = Some(StringAnalysisSourceInfo(description, getFullSourceInfo.getPosition)) } + def setForcedSource(source: AnalysisSourceInfo): Unit = { + if(forcedMainSource.isEmpty) + forcedMainSource = Some(source) + } + def unsetForcedSource(): Unit = { forcedMainSource = None } diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index b7bca4056..35fa4bc9e 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -401,6 +401,7 @@ object executor extends ExecutionRules { s2 } v2.decider.clearModel() + v2.decider.assumptionAnalyzer.setForcedSource(ExpAnalysisSourceInfo(fa)) val result = quantifiedChunkSupporter.removePermissions( s2p, relevantChunks, @@ -415,6 +416,7 @@ object executor extends ExecutionRules { chunkOrderHeuristics, v2 ) + v2.decider.assumptionAnalyzer.unsetForcedSource() result match { case (Complete(), s3, remainingChunks, consumedChunks) => // TODO ake: what to do with consumedChunks? val h3 = Heap(remainingChunks ++ otherChunks) @@ -422,10 +424,12 @@ object executor extends ExecutionRules { v1.decider.prover.comment("Definitional axioms for singleton-FVF's value") val debugExp = Option.when(withExp)(DebugExp.createInstance("Definitional axioms for singleton-FVF's value", isInternal_ = true)) v1.decider.assumeDefinition(smValueDef, debugExp, AssumptionType.Internal) + v1.decider.assumptionAnalyzer.setForcedSource(ExpAnalysisSourceInfo(fa)) val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(Seq(`?r`), Option.when(withExp)(Seq(ast.LocalVarDecl("r", ast.Ref)(ass.pos, ass.info, ass.errT))), field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), sm, s.program, v1, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit), isExhale=false) + v1.decider.assumptionAnalyzer.unsetForcedSource() if (s3.heapDependentTriggers.contains(field)) { - val debugExp2 = Option.when(withExp)(DebugExp.createInstance(s"FieldTrigger(${eRcvrNew.toString()}.${field.name})")) + val debugExp2 = Option.when(withExp)(DebugExp.createInstance(s"FieldTrigger(${eRcvrNew.toString}.${field.name})")) v1.decider.assume(FieldTrigger(field.name, sm, tRcvr), debugExp2, AssumptionType.Internal) } val s4 = s3.copy(h = h3 + ch) @@ -443,17 +447,15 @@ object executor extends ExecutionRules { val resource = fa.res(s.program) val ve = pve dueTo InsufficientPermission(fa) val description = s"consume ${ass.pos}: $ass" - v2.decider.assumptionAnalyzer.popAnalysisSourceInfo() // lhs and rhs should have different analysis sources - v2.decider.assumptionAnalyzer.addAnalysisSourceInfo(fa) + v2.decider.assumptionAnalyzer.setForcedSource(ExpAnalysisSourceInfo(fa)) chunkSupporter.consume(s2, s2.h, resource, Seq(tRcvr), eRcvrNew.map(Seq(_)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), false, ve, v2, description)((s3, h3, _, consumedChunks, v3) => { - v3.decider.assumptionAnalyzer.popAnalysisSourceInfo() - v3.decider.assumptionAnalyzer.addAnalysisSourceInfo(StmtAnalysisSourceInfo(ass)) + v3.decider.assumptionAnalyzer.setForcedSource(StmtAnalysisSourceInfo(ass)) val (tSnap, _) = ssaifyRhs(tRhs, rhs, rhsNew, field.name, field.typ, v3, s3, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit)) val id = BasicChunkIdentifier(field.name) - v3.decider.assumptionAnalyzer.popAnalysisSourceInfo() // lhs and rhs should have different analysis sources - v3.decider.assumptionAnalyzer.addAnalysisSourceInfo(fa) + v3.decider.assumptionAnalyzer.setForcedSource(ExpAnalysisSourceInfo(fa)) val newChunk = BasicChunk.createDerivedChunk(Set.empty, FieldID, id, Seq(tRcvr), eRcvrNew.map(Seq(_)), tSnap, rhsNew, FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), v3.decider.assumptionAnalyzer.getAnalysisInfo(annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit))) + v3.decider.assumptionAnalyzer.unsetForcedSource() chunkSupporter.produce(s3, h3, newChunk, v3)((s4, h4, v4) => { val s5 = s4.copy(h = h4) val (debugHeapName, _) = v4.getDebugOldLabel(s5, fa.pos) diff --git a/src/test/resources/andrea/quantified-perm.vpr b/src/test/resources/andrea/quantified-perm.vpr index 01ffd752f..3b4898d18 100644 --- a/src/test/resources/andrea/quantified-perm.vpr +++ b/src/test/resources/andrea/quantified-perm.vpr @@ -29,6 +29,7 @@ method quantifiedWritePerm(nodes: Set[Ref], x: Ref) if(x.second != null) { y := x.second // permissions covered by preconditions y.second := y + assert x.second.second == x.second } } @@ -45,4 +46,29 @@ method quantifiedSum(nodes: Set[Ref], x: Ref) a := a + x.first.f } assert a >= 0 +} + +method quantifiedPerm2Seqs(xs: Seq[Ref], ys: Seq[Ref]) { + assume |xs| > 5 && |ys| > 3 + inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) + inhale forall y: Ref :: y in ys ==> (acc(y.f) && y.f < 0) + + assert xs[0].f > 0 +} + +method quantifiedExhalePartially(xs: Seq[Ref], ys: Seq[Ref]) { + assume |xs| > 5 + inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) + + xs[0].f := 10 + exhale forall x: Ref :: x in xs ==> acc(x.f, 1/2) + assert xs[1].f > 0 +} + +method quantifiedExhaleFully(xs: Seq[Ref], ys: Seq[Ref]) { + assume |xs| > 5 + inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) + + xs[0].f := 10 + exhale forall x: Ref :: x in xs ==> acc(x.f) } \ No newline at end of file From 596fc01b0efda087c7cf1d07a599d632795515fc Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 2 Jun 2025 18:31:24 +0200 Subject: [PATCH 079/474] fix forced source --- src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 5cafacb91..8bf19309e 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -60,13 +60,11 @@ trait AssumptionAnalyzer { } def setForcedSource(description: String): Unit = { - if(forcedMainSource.isEmpty) - forcedMainSource = Some(StringAnalysisSourceInfo(description, getFullSourceInfo.getPosition)) + forcedMainSource = Some(StringAnalysisSourceInfo(description, getFullSourceInfo.getPosition)) } def setForcedSource(source: AnalysisSourceInfo): Unit = { - if(forcedMainSource.isEmpty) - forcedMainSource = Some(source) + forcedMainSource = Some(source) } def unsetForcedSource(): Unit = { From b01bb6cd2660c81d82e6fb0daf09d12c095e8c51 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 3 Jun 2025 17:05:32 +0200 Subject: [PATCH 080/474] add explicit exhale nodes for more complete exhale --- src/main/scala/interfaces/state/Chunks.scala | 1 - .../rules/MoreCompleteExhaleSupporter.scala | 4 ++- src/test/resources/andrea/impreciseness.vpr | 3 ++- src/test/resources/andrea/quantified-perm.vpr | 26 ++++++++++++++++++- 4 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/main/scala/interfaces/state/Chunks.scala b/src/main/scala/interfaces/state/Chunks.scala index 37f60f79f..e6dbdd84c 100644 --- a/src/main/scala/interfaces/state/Chunks.scala +++ b/src/main/scala/interfaces/state/Chunks.scala @@ -39,7 +39,6 @@ object GeneralChunk { } def permMinus(chunk: GeneralChunk, newPerm: Term, newPermExp: Option[ast.Exp], analysisInfo: AnalysisInfo, isExhale: Boolean=false): GeneralChunk = { - // TODO ake: review and test val newChunk = chunk.permMinus(newPerm, newPermExp) val newNodeId = analysisInfo.assumptionAnalyzer.addPermissionNode(newChunk, Option.when(chunk.permExp.isDefined && newPermExp.isDefined)(ast.PermSub(chunk.permExp.get, newPermExp.get)(newPermExp.get.pos, newPermExp.get.info)), diff --git a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala index a1cb6ed1c..fe4f884ca 100644 --- a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala +++ b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala @@ -366,13 +366,14 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { pSumExp = eqExp.map(eq => ast.PermAdd(pSumExp.get, ast.CondExp(eq, ch.permExp.get, ast.NoPerm()())(eq.pos, eq.info, eq.errT))()) val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, pTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, pTakenExp.get)(pe.pos, pe.info, pe.errT)), v.decider.assumptionAnalyzer.getAnalysisInfo).asInstanceOf[NonQuantifiedChunk] + v.decider.assumptionAnalyzer.addPermissionExhaleNode(ch, pTakenExp, v.decider.assumptionAnalyzer.getFullSourceInfo) pNeeded = PermMinus(pNeeded, pTaken) pNeededExp = permsExp.map(pe => ast.PermSub(pNeededExp.get, pTakenExp.get)(pe.pos, pe.info, pe.errT)) if (!v.decider.check(IsNonPositive(newChunk.perm), Verifier.config.splitTimeout())) { newChunks.append(newChunk) - consumedChunks.append(ch) } + consumedChunks.append(ch) moreNeeded = !v.decider.check(pNeeded === NoPerm, Verifier.config.splitTimeout()) } else { @@ -481,6 +482,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { newFr = newFr.recordPathSymbol(permTaken.applicable.asInstanceOf[Function]).recordConstraint(constraint) + v.decider.assumptionAnalyzer.addPermissionExhaleNode(ch, permTakenExp, v.decider.assumptionAnalyzer.getFullSourceInfo) GeneralChunk.withPerm(ch, PermMinus(ch.perm, permTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, permTakenExp.get)(pe.pos, pe.info, pe.errT)), v.decider.assumptionAnalyzer.getAnalysisInfo).asInstanceOf[NonQuantifiedChunk] }) diff --git a/src/test/resources/andrea/impreciseness.vpr b/src/test/resources/andrea/impreciseness.vpr index 3187a6405..ce7dea379 100644 --- a/src/test/resources/andrea/impreciseness.vpr +++ b/src/test/resources/andrea/impreciseness.vpr @@ -12,4 +12,5 @@ method permTest(a: Ref, b: Ref, n: Int) assert a.f > 2 a.f := n assert a.f >= 0 // incorrectly depends on acc(b.f) -} \ No newline at end of file +} + diff --git a/src/test/resources/andrea/quantified-perm.vpr b/src/test/resources/andrea/quantified-perm.vpr index 3b4898d18..7daaa9b90 100644 --- a/src/test/resources/andrea/quantified-perm.vpr +++ b/src/test/resources/andrea/quantified-perm.vpr @@ -71,4 +71,28 @@ method quantifiedExhaleFully(xs: Seq[Ref], ys: Seq[Ref]) { xs[0].f := 10 exhale forall x: Ref :: x in xs ==> acc(x.f) -} \ No newline at end of file +} + +method quantifiedPermBig(xs: Seq[Ref], ys: Seq[Ref], r: Ref) { + assume |xs| > 5 && |ys| > 5 + inhale acc(r.f, 1/2) + inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) + inhale forall y: Ref :: y in ys ==> (acc(y.f, 1/2) && y.f > 0) + + assert xs[0] != ys[0] + assert r != xs[0] + assert xs[2].f >= 0 + xs[0].f := xs[1].f + xs[2].f + xs[1].f := ys[0].f + + inhale ys[0] == r + + ys[0].f := xs[0].f + r.f := r.f + xs[0].f + assert r.f == 2*xs[0].f + + xs[3].f := r.f + + assert forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) + assert forall y: Ref :: y in ys ==> (acc(y.f, 1/2) && y.f > 0) + } \ No newline at end of file From 56cd3d9d9edf60a1b7ecbd1bc162f77a21d2d7c9 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 4 Jun 2025 09:35:02 +0200 Subject: [PATCH 081/474] add support for analysis info annotation to domains --- .../scala/assumptionAnalysis/AnalysisSourceInfo.scala | 8 ++++++++ .../scala/assumptionAnalysis/AssumptionAnalyzer.scala | 10 ++++++++++ src/main/scala/interfaces/decider/Prover.scala | 2 +- src/main/scala/supporters/Domains.scala | 10 +++++++--- src/test/resources/andrea/domains.vpr | 4 +++- 5 files changed, 29 insertions(+), 5 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala index 707fcfe2d..a7fdade0e 100644 --- a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala +++ b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala @@ -20,6 +20,8 @@ abstract class AnalysisSourceInfo { def getTopLevelSource: AnalysisSourceInfo = this def getFineGrainedSource: AnalysisSourceInfo = this + + def isAnalysisEnabled: Boolean = true } case class NoAnalysisSourceInfo() extends AnalysisSourceInfo { @@ -38,6 +40,8 @@ case class ExpAnalysisSourceInfo(source: ast.Exp) extends AnalysisSourceInfo { case _ => false } } + + override def isAnalysisEnabled: Boolean = AssumptionAnalyzer.extractEnableAnalysisFromInfo(source.info).getOrElse(true) } case class StmtAnalysisSourceInfo(source: ast.Stmt) extends AnalysisSourceInfo { @@ -51,6 +55,8 @@ case class StmtAnalysisSourceInfo(source: ast.Stmt) extends AnalysisSourceInfo { case _ => false } } + + override def isAnalysisEnabled: Boolean = AssumptionAnalyzer.extractEnableAnalysisFromInfo(source.info).getOrElse(true) } case class StringAnalysisSourceInfo(description: String, position: Position) extends AnalysisSourceInfo { @@ -66,4 +72,6 @@ case class CompositeAnalysisSourceInfo(coarseGrainedSource: AnalysisSourceInfo, override def getTopLevelSource: AnalysisSourceInfo = coarseGrainedSource override def getFineGrainedSource: AnalysisSourceInfo = fineGrainedSource + + override def isAnalysisEnabled: Boolean = coarseGrainedSource.isAnalysisEnabled && fineGrainedSource.isAnalysisEnabled } diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 8bf19309e..004c15b3b 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -108,6 +108,16 @@ object AssumptionAnalyzer { if(annotation.isDefined && annotation.get.nonEmpty) annotation.get.head.toBooleanOption else None } + def createEnableAnalysisInfo(enableAnalysis: Boolean): AnnotationInfo = + AnnotationInfo(Map((enableAssumptionAnalysisAnnotationKey, Seq(enableAnalysis.toString)))) + + def createAnalysisAnnotationInfo(enableAnalysis: Boolean, assumptionType: AssumptionType): AnnotationInfo = + AnnotationInfo(Map( + (enableAssumptionAnalysisAnnotationKey, Seq(enableAnalysis.toString)), + (assumptionTypeAnnotationKey, Seq(assumptionType.toString)) + )) + + def createAssumptionLabel(id: Option[Int], offset: Int = 0): String = { createLabel("assumption", id, offset) } diff --git a/src/main/scala/interfaces/decider/Prover.scala b/src/main/scala/interfaces/decider/Prover.scala index e4fa5bc85..e1d2afa04 100644 --- a/src/main/scala/interfaces/decider/Prover.scala +++ b/src/main/scala/interfaces/decider/Prover.scala @@ -41,7 +41,7 @@ trait ProverLike { if (debugMode) { preambleAssumptions :+= new DebugAxiom(description, axioms.map(_._1)) axioms.foreach(axiom => { - val id = preambleAssumptionAnalyzer.addAssumption(axiom._2.toString, axiom._2, AssumptionType.Axiom) + val id = if(axiom._2.isAnalysisEnabled) preambleAssumptionAnalyzer.addAssumption(axiom._2.toString, axiom._2, AssumptionType.Axiom) else None assume(axiom._1, AssumptionAnalyzer.createAxiomLabel(id)) }) }else{ diff --git a/src/main/scala/supporters/Domains.scala b/src/main/scala/supporters/Domains.scala index 35f8702df..6778c207f 100644 --- a/src/main/scala/supporters/Domains.scala +++ b/src/main/scala/supporters/Domains.scala @@ -6,7 +6,7 @@ package viper.silicon.supporters -import viper.silicon.assumptionAnalysis.{AnalysisSourceInfo, ExpAnalysisSourceInfo} +import viper.silicon.assumptionAnalysis.{AnalysisSourceInfo, AssumptionAnalyzer, ExpAnalysisSourceInfo} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.common.collections.immutable.MultiMap._ import viper.silicon.interfaces.PreambleContributor @@ -15,7 +15,7 @@ import viper.silicon.state.terms.{Distinct, DomainFun, Sort, Term} import viper.silicon.state.{FunctionPreconditionTransformer, SymbolConverter, terms} import viper.silicon.toMap import viper.silver.ast -import viper.silver.ast.NamedDomainAxiom +import viper.silver.ast.{MakeInfoPair, NamedDomainAxiom} trait DomainsContributor[SO, SY, AX, UA] extends PreambleContributor[SO, SY, AX] { def uniquenessAssumptionsAfterAnalysis: Iterable[UA] @@ -99,10 +99,14 @@ class DefaultDomainsContributor(symbolConverter: SymbolConverter, } }) + val isAnalysisForDomainEnabled = AssumptionAnalyzer.extractEnableAnalysisFromInfo(domain.info).getOrElse(true) + domain.axioms foreach (axiom => { val tAx = domainTranslator.translateAxiom(axiom, symbolConverter.toSort) val tAxPres = FunctionPreconditionTransformer.transform(tAx, program) - collectedAxioms = collectedAxioms.incl(terms.And(tAxPres, tAx), ExpAnalysisSourceInfo(axiom.exp)) + val enableAnalysis = AssumptionAnalyzer.extractEnableAnalysisFromInfo(axiom.info).getOrElse(isAnalysisForDomainEnabled) + val exp = axiom.exp.withMeta((axiom.exp.pos, MakeInfoPair(axiom.exp.info, AssumptionAnalyzer.createEnableAnalysisInfo(enableAnalysis)), axiom.exp.errT)) + collectedAxioms = collectedAxioms.incl(terms.And(tAxPres, tAx), ExpAnalysisSourceInfo(exp)) }) }) } diff --git a/src/test/resources/andrea/domains.vpr b/src/test/resources/andrea/domains.vpr index 4897d27e1..b473610fe 100644 --- a/src/test/resources/andrea/domains.vpr +++ b/src/test/resources/andrea/domains.vpr @@ -2,12 +2,14 @@ field val: Int define access(a) (forall j: Int :: 0 <= j && j < len(a) ==> acc(slot(a,j).val)) +@enableAssumptionAnalysis("false") domain IArray { function slot(a: IArray, i: Int): Ref function len(a: IArray): Int function first(r: Ref): IArray function second(r: Ref): Int + @enableAssumptionAnalysis("true") axiom all_diff { forall a: IArray, i: Int :: { slot(a,i) } first(slot(a,i)) == a && second(slot(a,i)) == i @@ -19,7 +21,7 @@ domain IArray { } } -method client() +method domainsClient() { // Create an integer array with three elements var a: IArray From b6d8a649a5b8121f7ee7ab722dc01991053f3208 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 4 Jun 2025 10:56:48 +0200 Subject: [PATCH 082/474] add support for expression annotation (preconditions, invariants, branch conditions) --- .../scala/assumptionAnalysis/AssumptionAnalyzer.scala | 9 +++++---- src/main/scala/rules/Producer.scala | 6 +++--- src/test/resources/andrea/branches.vpr | 2 +- src/test/resources/andrea/gaussian.vpr | 8 ++++---- src/test/resources/andrea/permissions.vpr | 2 +- 5 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 004c15b3b..ec9c8edcc 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -159,7 +159,7 @@ class DefaultAssumptionAnalyzer(member: Member) extends AssumptionAnalyzer { } override def addSingleAssumption(assumption: DebugExp, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { - val node = if(assumption.originalExp.isDefined) SimpleAssumptionNode(assumption.originalExp.get, analysisSourceInfo, assumptionType) + val node = if(assumption.originalExp.isDefined) SimpleAssumptionNode(assumption.originalExp.get, analysisSourceInfo, AssumptionAnalyzer.extractAssumptionTypeFromInfo(assumption.originalExp.get.info).getOrElse(assumptionType)) else StringAssumptionNode(assumption.description.getOrElse("unknown"), analysisSourceInfo, assumptionType) addNode(node) Some(node.id) @@ -167,7 +167,7 @@ class DefaultAssumptionAnalyzer(member: Member) extends AssumptionAnalyzer { override def addAssumption(assumption: ast.Exp, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { - val node = SimpleAssumptionNode(assumption, analysisSourceInfo, assumptionType) + val node = SimpleAssumptionNode(assumption, analysisSourceInfo, AssumptionAnalyzer.extractAssumptionTypeFromInfo(assumption.info).getOrElse(assumptionType)) addNode(node) Some(node.id) } @@ -181,11 +181,12 @@ class DefaultAssumptionAnalyzer(member: Member) extends AssumptionAnalyzer { override def addAssumptions(assumptions: Iterable[DebugExp], analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Seq[Int] = { val newNodes = assumptions.toSeq.map(a => - if (a.originalExp.isDefined) SimpleAssumptionNode(a.originalExp.get, if(analysisSourceInfo.isInstanceOf[NoAnalysisSourceInfo]) ExpAnalysisSourceInfo(a.originalExp.get) else analysisSourceInfo, assumptionType) + if (a.originalExp.isDefined) SimpleAssumptionNode(a.originalExp.get, if(analysisSourceInfo.isInstanceOf[NoAnalysisSourceInfo]) ExpAnalysisSourceInfo(a.originalExp.get) else analysisSourceInfo, + AssumptionAnalyzer.extractAssumptionTypeFromInfo(a.originalExp.get.info).getOrElse(assumptionType)) else StringAssumptionNode(a.description.getOrElse("unknown"), analysisSourceInfo, AssumptionType.Internal) ) newNodes foreach addNode - newNodes.map(_.id).toSeq + newNodes.map(_.id) } override def createAssertOrCheckNode(term: Term, assertion: Either[String, ast.Exp], analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] = { diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index 5809f00cf..0903cb969 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -7,7 +7,7 @@ package viper.silicon.rules import viper.silicon.Config.JoinMode -import viper.silicon.assumptionAnalysis.AssumptionType +import viper.silicon.assumptionAnalysis.{AssumptionAnalyzer, AssumptionType} import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.{Unreachable, VerificationResult} @@ -225,10 +225,10 @@ object producer extends ProductionRules { a: ast.Exp, pve: PartialVerificationError, v: Verifier, - assumptionType: AssumptionType) + _assumptionType: AssumptionType) (continuation: (State, Verifier) => VerificationResult) : VerificationResult = { - + val assumptionType = AssumptionAnalyzer.extractAssumptionTypeFromInfo(a.info).getOrElse(_assumptionType) v.logger.debug(s"\nPRODUCE ${viper.silicon.utils.ast.sourceLineColumn(a)}: $a") v.logger.debug(v.stateFormatter.format(s, v.decider.pcs)) diff --git a/src/test/resources/andrea/branches.vpr b/src/test/resources/andrea/branches.vpr index 3563def9e..c0b559ec8 100644 --- a/src/test/resources/andrea/branches.vpr +++ b/src/test/resources/andrea/branches.vpr @@ -11,7 +11,7 @@ method branches2(a: Int, b: Int) assume b < 50 assume c ==> a > 5 - if(c){ + if(@assumptionType("Implicit")(c)){ n := a + 1 }else{ n := b + 1 diff --git a/src/test/resources/andrea/gaussian.vpr b/src/test/resources/andrea/gaussian.vpr index 2dc9ba39f..b4d2b6769 100644 --- a/src/test/resources/andrea/gaussian.vpr +++ b/src/test/resources/andrea/gaussian.vpr @@ -21,18 +21,18 @@ method gaussianPerm(a: Ref, p: Perm) returns (res: Int) requires none < p && p < write requires acc(a.f, p) requires 0 <= a.f - requires a.f <= 5 + requires @assumptionType("Implicit")(a.f <= 5) ensures acc(a.f, p) ensures res == a.f * (a.f + 1) / 2 { res := 0 var i: Int := 0 - while(i <= a.f) + while(@assumptionType("Implicit")(i <= a.f)) invariant acc(a.f, p) invariant 0 <= a.f && a.f <= 5 invariant i <= (a.f + 1) - invariant i <= 6 - invariant res == (i - 1) * i / 2 + invariant @assumptionType("Implicit")(i <= 6) + invariant @assumptionType("Explicit")(res == (i - 1) * i / 2) { res := res + i i := i + 1 diff --git a/src/test/resources/andrea/permissions.vpr b/src/test/resources/andrea/permissions.vpr index 6b46d60ce..16ac3870b 100644 --- a/src/test/resources/andrea/permissions.vpr +++ b/src/test/resources/andrea/permissions.vpr @@ -4,7 +4,7 @@ function id(n: Int): Int ensures result == n method foo(a: Ref) - requires acc(a.f, 1/2) && a.f >= 0 + requires @assumptionType("Internal")(acc(a.f, 1/2)) && a.f >= 0 ensures acc(a.f, 1/2) method call(x: Ref) From 3992512778ed033e821f10b92e8a35f556099ea7 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 7 Jun 2025 08:39:57 +0200 Subject: [PATCH 083/474] quantifiers: add dedicated source for aux globals --- src/main/scala/rules/Evaluator.scala | 2 ++ src/main/scala/rules/Executor.scala | 2 +- .../scala/rules/QuantifiedChunkSupport.scala | 4 ++++ src/test/resources/andrea/permissions.vpr | 14 +++++++++++++- src/test/resources/andrea/quantified-perm.vpr | 19 ++++++++++++++++++- 5 files changed, 38 insertions(+), 3 deletions(-) diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index f1c55166e..335ef0b95 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -827,7 +827,9 @@ object evaluator extends EvaluationRules { val auxNonGlobalsExp = auxExps.map(_._2) val commentGlobal = "Nested auxiliary terms: globals (aux)" v1.decider.prover.comment(commentGlobal) + v1.decider.assumptionAnalyzer.setForcedSource(commentGlobal) v1.decider.assume(tAuxGlobal, Option.when(withExp)(DebugExp.createInstance(description=commentGlobal, children=auxGlobalsExp.get)), enforceAssumption = false, assumptionType=AssumptionType.Internal) + v1.decider.assumptionAnalyzer.unsetForcedSource() val commentNonGlobals = "Nested auxiliary terms: non-globals (aux)" v1.decider.prover.comment(commentNonGlobals) v1.decider.assume(tAuxHeapIndep/*tAux*/, Option.when(withExp)(DebugExp.createInstance(description=commentNonGlobals, children=auxNonGlobalsExp.get)), enforceAssumption = false, assumptionType=AssumptionType.Internal) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 35fa4bc9e..0c6bf84e9 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -518,7 +518,7 @@ object executor extends ExecutionRules { case assert @ ast.Assert(a: ast.FalseLit) if !s.isInPackage => /* "assert false" triggers a smoke check. If successful, we backtrack. */ executionFlowController.tryOrFail0(s.copy(h = magicWandSupporter.getEvalHeap(s)), v)((s1, v1, QS) => { - if (v1.decider.checkSmoke(true)) + if (v1.decider.checkSmoke(true)) // TODO ake: add node to assumption graph QS(s1.copy(h = s.h), v1) else createFailure(AssertFailed(assert) dueTo AssertionFalse(a), v1, s1, False, true, Option.when(withExp)(a)) diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 47598bb23..7959bef73 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -996,8 +996,10 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val commentGlobals = "Nested auxiliary terms: globals" v.decider.prover.comment(commentGlobals) + v.decider.assumptionAnalyzer.setForcedSource(commentGlobals) v.decider.assume(auxGlobals, Option.when(withExp)(DebugExp.createInstance(description=commentGlobals, children=auxGlobalsExp.get)), enforceAssumption = false, assumptionType=AssumptionType.Internal) + v.decider.assumptionAnalyzer.unsetForcedSource() val commentNonGlobals = "Nested auxiliary terms: non-globals" v.decider.prover.comment(commentNonGlobals) @@ -1228,7 +1230,9 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val comment = "Nested auxiliary terms: globals" v.decider.prover.comment(comment) + v.decider.assumptionAnalyzer.setForcedSource(comment) v.decider.assume(auxGlobals, Option.when(withExp)(DebugExp.createInstance(description=comment, children=auxGlobalsExp.get)), enforceAssumption = false, assumptionType=AssumptionType.Internal) + v.decider.assumptionAnalyzer.unsetForcedSource() val comment2 = "Nested auxiliary terms: non-globals" v.decider.prover.comment(comment2) diff --git a/src/test/resources/andrea/permissions.vpr b/src/test/resources/andrea/permissions.vpr index 16ac3870b..2acea8a72 100644 --- a/src/test/resources/andrea/permissions.vpr +++ b/src/test/resources/andrea/permissions.vpr @@ -50,4 +50,16 @@ method maybeAlias(a: Ref, b: Ref, c: Bool, n: Int) } assert a.f >= 0 assert !c ==> a.f <= 100 -} \ No newline at end of file +} + +method aliasing(x: Ref, n: Int) + requires acc(x.f) && x.f < n +{ + var y: Ref := x + y.f := n + 1 + assert x.f >= n + + assume y.f < n + assert false + +} diff --git a/src/test/resources/andrea/quantified-perm.vpr b/src/test/resources/andrea/quantified-perm.vpr index 7daaa9b90..f40687468 100644 --- a/src/test/resources/andrea/quantified-perm.vpr +++ b/src/test/resources/andrea/quantified-perm.vpr @@ -48,10 +48,27 @@ method quantifiedSum(nodes: Set[Ref], x: Ref) assert a >= 0 } + method quantifiedPerm2Seqs(xs: Seq[Ref], ys: Seq[Ref]) { assume |xs| > 5 && |ys| > 3 inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) - inhale forall y: Ref :: y in ys ==> (acc(y.f) && y.f < 0) + inhale forall y: Ref :: y in ys ==> (acc(y.f) && y.f > 0) + + assert xs[0].f > 0 +} + +method quantifiedPerm2Seqs2(xs: Seq[Ref], ys: Seq[Ref]) { + assume |xs| > 5 && |ys| > 3 + inhale forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0) + inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard) && y.f > 0) + + assert xs[0].f > 0 +} + +method quantifiedPerm2Seqs3(xs: Seq[Ref], ys: Seq[Ref]) { + assume |xs| > 5 && |ys| > 3 + inhale forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0) + inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard)) assert xs[0].f > 0 } From cd1a6b78ceeb42128103ea244b3d9b36b1612040 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 7 Jun 2025 09:40:53 +0200 Subject: [PATCH 084/474] state consolidation, heap summary: add incoming edges via forced dependencies --- .../AssumptionAnalyzer.scala | 25 ++++++++++++++++--- .../scala/rules/QuantifiedChunkSupport.scala | 7 ++++++ src/main/scala/rules/StateConsolidator.scala | 10 +++++--- 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index ec9c8edcc..1768f7c55 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -27,6 +27,7 @@ trait AssumptionAnalyzer { def createAssertOrCheckNode(term: Term, assertion: Either[String, ast.Exp], analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] + def getChunkNodeIds(oldChunks: Set[Chunk]): Set[Int] def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNodeId: Option[Int]): Unit def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNodeId: Chunk): Unit @@ -34,6 +35,7 @@ trait AssumptionAnalyzer { protected var sourceInfoes: List[AnalysisSourceInfo] = List.empty protected var forcedMainSource: Option[AnalysisSourceInfo] = None + protected var forcedDependencies: List[Int] = List.empty def getAnalysisInfo: AnalysisInfo = getAnalysisInfo(AssumptionType.Implicit) @@ -71,6 +73,14 @@ trait AssumptionAnalyzer { forcedMainSource = None } + def addForcedDependencies(chunks: Set[Chunk]): Unit = { + forcedDependencies = forcedDependencies ++ getChunkNodeIds(chunks).toList + } + + def unsetForcedDependencies(): Unit = { + forcedDependencies = List.empty + } + def getAnalysisSourceInfoes: List[AnalysisSourceInfo] = sourceInfoes def setAnalysisSourceInfoes(infoes: List[AnalysisSourceInfo]): Unit = { sourceInfoes = infoes @@ -150,12 +160,14 @@ class DefaultAssumptionAnalyzer(member: Member) extends AssumptionAnalyzer { override def addNodes(nodes: Iterable[AssumptionAnalysisNode]): Unit = { assumptionGraph.addNodes(nodes) + assumptionGraph.addEdges(forcedDependencies, nodes.map(_.id)) } override def getNodes: Iterable[AssumptionAnalysisNode] = assumptionGraph.nodes override def addNode(node: AssumptionAnalysisNode): Unit = { assumptionGraph.addNode(node) + assumptionGraph.addEdges(forcedDependencies, node.id) } override def addSingleAssumption(assumption: DebugExp, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { @@ -235,12 +247,16 @@ class DefaultAssumptionAnalyzer(member: Member) extends AssumptionAnalyzer { else addPermissionInhaleNode(chunk, permAmount, sourceInfo, assumptionType) } + override def getChunkNodeIds(oldChunks: Set[Chunk]): Set[Int] = { + assumptionGraph.nodes + .filter(c => c.isInstanceOf[PermissionInhaleNode] && oldChunks.contains(c.asInstanceOf[ChunkAnalysisInfo].getChunk)) + .map(_.id).toSet + } + override def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNodeId: Option[Int]): Unit = { if(newChunkNodeId.isEmpty) return - val oldChunkNodeIds = assumptionGraph.nodes - .filter(c => c.id != newChunkNodeId.get && c.isInstanceOf[PermissionInhaleNode] && oldChunks.contains(c.asInstanceOf[ChunkAnalysisInfo].getChunk)) - .map(_.id).toSet + val oldChunkNodeIds = getChunkNodeIds(oldChunks).filter(id => id != newChunkNodeId.get) assumptionGraph.addEdges(oldChunkNodeIds, newChunkNodeId.get) } @@ -285,6 +301,9 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { override def addPermissionAssertNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo): Option[Int] = None override def addPermissionNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isExhale: Boolean=false): Option[Int] = None + + override def getChunkNodeIds(oldChunks: Set[Chunk]): Set[Int] = Set.empty + override def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNode: Option[Int]): Unit = { } diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 7959bef73..589d49f01 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -733,6 +733,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v: Verifier) : (PermMapDefinition, PmCache) = { v.decider.assumptionAnalyzer.setForcedSource("summarizing heap") + v.decider.assumptionAnalyzer.addForcedDependencies(relevantChunks.toSet) val res = Verifier.config.mapCache(s.pmCache.get(resource, relevantChunks)) match { case Some(pmDef) => v.decider.assume(pmDef.valueDefinitions, Option.when(withExp)(DebugExp.createInstance("value definitions", isInternal_ = true)), enforceAssumption = false, assumptionType=AssumptionType.Internal) @@ -773,6 +774,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optQVarsInstantiations: Option[Seq[Term]] = None) : (SnapshotMapDefinition, SnapshotMapCache) = { v.decider.assumptionAnalyzer.setForcedSource("summarizing heap") + v.decider.assumptionAnalyzer.addForcedDependencies(relevantChunks.toSet) def emitSnapshotMapDefinition(s: State, smDef: SnapshotMapDefinition, @@ -843,6 +845,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { } emitSnapshotMapDefinition(s, smDef, v, optQVarsInstantiations) + v.decider.assumptionAnalyzer.unsetForcedDependencies() v.decider.assumptionAnalyzer.unsetForcedSource() (smDef, smCache) } @@ -856,6 +859,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optQVarsInstantiations: Option[Seq[Term]] = None) : (State, SnapshotMapDefinition, PermMapDefinition) = { v.decider.assumptionAnalyzer.setForcedSource("summarizing heap") + v.decider.assumptionAnalyzer.addForcedDependencies(relevantChunks.toSet) val (smDef, smCache) = summarisingSnapshotMap( s, resource, codomainQVars, relevantChunks, v, optSmDomainDefinitionCondition, optQVarsInstantiations) @@ -867,6 +871,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { s1, resource, codomainQVars, relevantChunks, smDef, v) val s2 = s1.copy(pmCache = pmCache) + v.decider.assumptionAnalyzer.unsetForcedDependencies() v.decider.assumptionAnalyzer.unsetForcedSource() (s2, smDef, pmDef) } @@ -881,12 +886,14 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v: Verifier) : (State, PermMapDefinition) = { v.decider.assumptionAnalyzer.setForcedSource("summarizing heap") + v.decider.assumptionAnalyzer.addForcedDependencies(relevantChunks.toSet) val s1 = s val (pmDef, pmCache) = quantifiedChunkSupporter.summarisingPermissionMap( s1, resource, codomainQVars, relevantChunks, null, v) val s2 = s1.copy(pmCache = pmCache) + v.decider.assumptionAnalyzer.unsetForcedDependencies() v.decider.assumptionAnalyzer.unsetForcedSource() (s2, pmDef) } diff --git a/src/main/scala/rules/StateConsolidator.scala b/src/main/scala/rules/StateConsolidator.scala index 086c850f8..f2f63422f 100644 --- a/src/main/scala/rules/StateConsolidator.scala +++ b/src/main/scala/rules/StateConsolidator.scala @@ -81,9 +81,11 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol val roundLog = new CommentRecord("Round " + fixedPointRound, s, v.decider.pcs) val roundSepIdentifier = v.symbExLog.openScope(roundLog) - val (_functionRecorder, _mergedChunks, _, snapEqs) = singleMerge(functionRecorder, destChunks, newChunks, s.functionRecorderQuantifiedVariables().map(_._1), v) + val (_functionRecorder, _mergedChunks, _newChunks, snapEqs) = singleMerge(functionRecorder, destChunks, newChunks, s.functionRecorderQuantifiedVariables().map(_._1), v) - snapEqs foreach (t => v.decider.assume(t, Option.when(withExp)(DebugExp.createInstance("Snapshot Equations", isInternal_ = true)), AssumptionType.PathCondition)) + v.decider.assumptionAnalyzer.addForcedDependencies(_newChunks.toSet) + snapEqs foreach (t => v.decider.assume(t, Option.when(withExp)(DebugExp.createInstance("Snapshot Equations", isInternal_ = true)), AssumptionType.Internal)) + v.decider.assumptionAnalyzer.unsetForcedDependencies() functionRecorder = _functionRecorder mergedChunks = _mergedChunks @@ -101,12 +103,14 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol mergedChunks.filter(_.isInstanceOf[BasicChunk]) foreach { case ch: BasicChunk => val resource = Resources.resourceDescriptions(ch.resourceID) val pathCond = interpreter.buildPathConditionsForChunk(ch, resource.instanceProperties(s.mayAssumeUpperBounds)) + v.decider.assumptionAnalyzer.addForcedDependencies(Set(ch)) pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), AssumptionType.Internal)) + v.decider.assumptionAnalyzer.unsetForcedDependencies() } Resources.resourceDescriptions foreach { case (id, desc) => val pathCond = interpreter.buildPathConditionsForResource(id, desc.delayedProperties(s.mayAssumeUpperBounds)) - pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), AssumptionType.PathCondition)) + pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), AssumptionType.Internal)) } v.symbExLog.closeScope(sepIdentifier) From b696ca0103fa989ef876a125e05e3b4f8ef1287d Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 7 Jun 2025 10:15:21 +0200 Subject: [PATCH 085/474] check smoke: add assert false node --- .../assumptionAnalysis/AssumptionAnalysisGraph.scala | 3 --- .../scala/assumptionAnalysis/AssumptionAnalyzer.scala | 11 ++++++++++- src/main/scala/decider/Decider.scala | 7 +++++-- src/main/scala/decider/ProverStdIO.scala | 11 +++++++++-- src/main/scala/decider/Z3ProverAPI.scala | 2 +- src/main/scala/interfaces/decider/Prover.scala | 2 +- src/main/scala/rules/Executor.scala | 2 +- 7 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index bc7dfc807..2b9160e48 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -159,7 +159,6 @@ trait GeneralAssumptionNode extends AssumptionAnalysisNode { } trait GeneralAssertionNode extends AssumptionAnalysisNode { override def getNodeType: String = "Assertion" - var isAsserted = false } trait ChunkAnalysisInfo { @@ -198,14 +197,12 @@ case class PermissionInhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourc } case class PermissionExhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo) extends GeneralAssertionNode with ChunkAnalysisInfo { - isAsserted = true val assumptionType: AssumptionType = Explicit override def getNodeType: String = "Exhale" override def getNodeString: String = "exhale " + chunk.getAnalysisInfo } case class PermissionAssertNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo) extends GeneralAssertionNode with ChunkAnalysisInfo { - isAsserted = true val assumptionType: AssumptionType = Explicit override def getNodeString: String = "assert " + permAmount.getOrElse("") + " for " + chunk.getAnalysisInfo } diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 1768f7c55..2ae8cd060 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -3,7 +3,7 @@ package viper.silicon.assumptionAnalysis import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.state.Chunk -import viper.silicon.state.terms.Term +import viper.silicon.state.terms.{False, Term} import viper.silver.ast import viper.silver.ast._ @@ -33,6 +33,8 @@ trait AssumptionAnalyzer { def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit + def addAssertFalseNode(isCheck: Boolean): Option[Int] + protected var sourceInfoes: List[AnalysisSourceInfo] = List.empty protected var forcedMainSource: Option[AnalysisSourceInfo] = None protected var forcedDependencies: List[Int] = List.empty @@ -210,6 +212,12 @@ class DefaultAssumptionAnalyzer(member: Member) extends AssumptionAnalyzer { }) } + override def addAssertFalseNode(isCheck: Boolean): Option[Int] = { + val node = createAssertOrCheckNode(False, Right(ast.FalseLit()()), getFullSourceInfo, isCheck) // TODO ake: set isAssert + addNode(node.get) + node.map(_.id) + } + override def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit = { val assumptionLabels = dep.replace("(", "").replace(")", "").split(" ") if(assumptionLabels.size < 2) return @@ -293,6 +301,7 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { override def createAssertOrCheckNode(term: Term, assertion: Either[String, ast.Exp], analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] = None + override def addAssertFalseNode(isCheck: Boolean): Option[Int] = None override def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit = { } diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 3ce47df35..eb43850a5 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -420,8 +420,12 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => /* Asserting facts */ def checkSmoke(isAssert: Boolean = false): Boolean = { + val label = if(Verifier.config.enableAssumptionAnalysis()){ + val nodeId = assumptionAnalyzer.addAssertFalseNode(!isAssert) + AssumptionAnalyzer.createAssertionLabel(nodeId) + }else{ "" } val timeout = if (isAssert) Verifier.config.assertTimeout.toOption else Verifier.config.checkTimeout.toOption - prover.check(timeout) == Unsat + prover.check(timeout, label) == Unsat } def check(t: Term, timeout: Int): Boolean = { @@ -461,7 +465,6 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => val result = asserted || proverAssert(t, timeout, AssumptionAnalyzer.createAssertionLabel(assertNode map (_.id))) - assertNode foreach (_.isAsserted = result) if(result || !isCheck) assertNode foreach assumptionAnalyzer.addNode diff --git a/src/main/scala/decider/ProverStdIO.scala b/src/main/scala/decider/ProverStdIO.scala index 678f9b1d9..97378dc81 100644 --- a/src/main/scala/decider/ProverStdIO.scala +++ b/src/main/scala/decider/ProverStdIO.scala @@ -383,16 +383,23 @@ abstract class ProverStdIO(uniqueId: String, (result, endTime - startTime) } - def check(timeout: Option[Int] = None): Result = { + def check(timeout: Option[Int] = None, label: String = ""): Result = { setTimeout(timeout) writeLine("(check-sat)") - readLine() match { + val result = readLine() match { case "sat" => Sat case "unsat" => Unsat case "unknown" => Unknown } + + if(result == Unsat && Verifier.config.enableAssumptionAnalysis()){ + val unsatCore = extractUnsatCore() + assumptionAnalyzer.processUnsatCoreAndAddDependencies(unsatCore, label) + } + + result } def statistics(): Map[String, String] = { diff --git a/src/main/scala/decider/Z3ProverAPI.scala b/src/main/scala/decider/Z3ProverAPI.scala index 7f31fc561..fab12009c 100644 --- a/src/main/scala/decider/Z3ProverAPI.scala +++ b/src/main/scala/decider/Z3ProverAPI.scala @@ -393,7 +393,7 @@ class Z3ProverAPI(uniqueId: String, (result, endTime - startTime) } - def check(timeout: Option[Int] = None): Result = { + def check(timeout: Option[Int] = None, label: String = ""): Result = { endPreamblePhase() setTimeout(timeout) diff --git a/src/main/scala/interfaces/decider/Prover.scala b/src/main/scala/interfaces/decider/Prover.scala index e1d2afa04..3e71050ad 100644 --- a/src/main/scala/interfaces/decider/Prover.scala +++ b/src/main/scala/interfaces/decider/Prover.scala @@ -61,7 +61,7 @@ trait ProverLike { trait Prover extends ProverLike with StatefulComponent { def start(userArgsString: Option[String]): Unit def assert(goal: Term, timeout: Option[Int] = None, label: String = ""): Boolean - def check(timeout: Option[Int] = None): Result + def check(timeout: Option[Int] = None, label: String = ""): Result def fresh(id: String, argSorts: Seq[Sort], resultSort: Sort): Function def statistics(): Map[String, String] def hasModel(): Boolean diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 0c6bf84e9..35fa4bc9e 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -518,7 +518,7 @@ object executor extends ExecutionRules { case assert @ ast.Assert(a: ast.FalseLit) if !s.isInPackage => /* "assert false" triggers a smoke check. If successful, we backtrack. */ executionFlowController.tryOrFail0(s.copy(h = magicWandSupporter.getEvalHeap(s)), v)((s1, v1, QS) => { - if (v1.decider.checkSmoke(true)) // TODO ake: add node to assumption graph + if (v1.decider.checkSmoke(true)) QS(s1.copy(h = s.h), v1) else createFailure(AssertFailed(assert) dueTo AssertionFalse(a), v1, s1, False, true, Option.when(withExp)(a)) From 357cd75e95e9b9f4a4b3506eab5dc700d9415c81 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 7 Jun 2025 10:19:21 +0200 Subject: [PATCH 086/474] update silver --- silver | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/silver b/silver index 9fd9d555b..e8521cbb1 160000 --- a/silver +++ b/silver @@ -1 +1 @@ -Subproject commit 9fd9d555bce951044b80f01b18e489059c6e2f62 +Subproject commit e8521cbb1b4277a7ac8fc210d00211593f9cd6ed From f6a6fa8f90e859f1002ce9a4baf35704a4daf6ef Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 7 Jun 2025 10:38:41 +0200 Subject: [PATCH 087/474] workaround --- src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala | 2 +- src/main/scala/rules/Evaluator.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 2ae8cd060..480a1a6ea 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -59,7 +59,7 @@ trait AssumptionAnalyzer { } def popAnalysisSourceInfo(): Unit = { - sourceInfoes = sourceInfoes.tail + if(sourceInfoes.nonEmpty) sourceInfoes = sourceInfoes.tail // TODO ake: should never be empty forcedMainSource = None } diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 335ef0b95..5d55a32d0 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -95,7 +95,7 @@ object evaluator extends EvaluationRules { val sepIdentifier = v.symbExLog.openScope(new EvaluateRecord(e, s, v.decider.pcs)) v.decider.assumptionAnalyzer.addAnalysisSourceInfo(e) eval3(s, e, pve, v)((s1, t, eNew, v1) => { - v.decider.assumptionAnalyzer.popAnalysisSourceInfo() + v.decider.assumptionAnalyzer.popAnalysisSourceInfo() // TODO ake: can return multiple times v1.symbExLog.closeScope(sepIdentifier) Q(s1, t, eNew, v1)}) } From 42de3882d374e71789588c019c5c707599590691 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 10 Jun 2025 09:09:56 +0200 Subject: [PATCH 088/474] remove workaround and fix pipeline (test) --- .../AnalysisSourceInfo.scala | 40 ++++++++++++ .../AssumptionAnalyzer.scala | 62 +++---------------- src/main/scala/decider/Decider.scala | 31 +++++++--- src/main/scala/rules/Brancher.scala | 18 +++--- src/main/scala/rules/ChunkSupporter.scala | 12 ++-- src/main/scala/rules/Consumer.scala | 4 +- src/main/scala/rules/Evaluator.scala | 8 +-- src/main/scala/rules/Executor.scala | 26 ++++---- src/main/scala/rules/HavocSupporter.scala | 6 +- src/main/scala/rules/MagicWandSupporter.scala | 4 +- .../rules/MoreCompleteExhaleSupporter.scala | 8 +-- src/main/scala/rules/PredicateSupporter.scala | 2 +- src/main/scala/rules/Producer.scala | 10 +-- .../scala/rules/QuantifiedChunkSupport.scala | 36 +++++------ src/main/scala/rules/StateConsolidator.scala | 10 +-- 15 files changed, 142 insertions(+), 135 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala index a7fdade0e..26764de9e 100644 --- a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala +++ b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala @@ -75,3 +75,43 @@ case class CompositeAnalysisSourceInfo(coarseGrainedSource: AnalysisSourceInfo, override def isAnalysisEnabled: Boolean = coarseGrainedSource.isAnalysisEnabled && fineGrainedSource.isAnalysisEnabled } + + +case class AnalysisSourceInfoStack (sourceInfoes: List[AnalysisSourceInfo] = List.empty, + forcedMainSource: Option[AnalysisSourceInfo] = None){ + + + def getFullSourceInfo: AnalysisSourceInfo = { + if(forcedMainSource.isDefined) + return forcedMainSource.get + if(sourceInfoes.size <= 1){ + sourceInfoes.headOption.getOrElse(NoAnalysisSourceInfo()) + } else{ + CompositeAnalysisSourceInfo(sourceInfoes.last, sourceInfoes.head) + } + } + + def addAnalysisSourceInfo(analysisSourceInfo: AnalysisSourceInfo): AnalysisSourceInfoStack = { + AnalysisSourceInfoStack(analysisSourceInfo +: sourceInfoes, forcedMainSource) + } + + def addAnalysisSourceInfo(e: ast.Exp): AnalysisSourceInfoStack = { + AnalysisSourceInfoStack(ExpAnalysisSourceInfo(e) +: sourceInfoes, forcedMainSource) + } + + def popAnalysisSourceInfo(): AnalysisSourceInfoStack = { + AnalysisSourceInfoStack(sourceInfoes.tail, forcedMainSource) + } + + def withForcedSource(description: String): AnalysisSourceInfoStack = { + AnalysisSourceInfoStack(sourceInfoes, Some(StringAnalysisSourceInfo(description, getFullSourceInfo.getPosition))) + } + + def withForcedSource(source: AnalysisSourceInfo): AnalysisSourceInfoStack = { + AnalysisSourceInfoStack(sourceInfoes, Some(source)) + } + + def withoutForcedSource(): AnalysisSourceInfoStack = { + AnalysisSourceInfoStack(sourceInfoes, None) + } +} \ No newline at end of file diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 480a1a6ea..be8473c29 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -10,6 +10,7 @@ import viper.silver.ast._ trait AssumptionAnalyzer { val assumptionGraph: AssumptionAnalysisGraph = new DefaultAssumptionAnalysisGraph() + var forcedDependencies: List[Int] = List.empty def addPermissionInhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] def addPermissionAssertNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo): Option[Int] @@ -33,47 +34,11 @@ trait AssumptionAnalyzer { def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit - def addAssertFalseNode(isCheck: Boolean): Option[Int] + def addAssertFalseNode(isCheck: Boolean, sourceInfo: AnalysisSourceInfo): Option[Int] - protected var sourceInfoes: List[AnalysisSourceInfo] = List.empty - protected var forcedMainSource: Option[AnalysisSourceInfo] = None - protected var forcedDependencies: List[Int] = List.empty - - def getAnalysisInfo: AnalysisInfo = getAnalysisInfo(AssumptionType.Implicit) - - def getAnalysisInfo(assumptionType: AssumptionType): AnalysisInfo = AnalysisInfo(this, getFullSourceInfo, assumptionType) - - def getFullSourceInfo: AnalysisSourceInfo = { - if(forcedMainSource.isDefined) - return forcedMainSource.get - if(sourceInfoes.size <= 1){ - sourceInfoes.headOption.getOrElse(NoAnalysisSourceInfo()) - } else{ - CompositeAnalysisSourceInfo(sourceInfoes.last, sourceInfoes.head) - } - } - - def addAnalysisSourceInfo(analysisSourceInfo: AnalysisSourceInfo): AnalysisSourceInfo = { - sourceInfoes = analysisSourceInfo +: sourceInfoes - analysisSourceInfo - } - - def popAnalysisSourceInfo(): Unit = { - if(sourceInfoes.nonEmpty) sourceInfoes = sourceInfoes.tail // TODO ake: should never be empty - forcedMainSource = None - } - - def setForcedSource(description: String): Unit = { - forcedMainSource = Some(StringAnalysisSourceInfo(description, getFullSourceInfo.getPosition)) - } - - def setForcedSource(source: AnalysisSourceInfo): Unit = { - forcedMainSource = Some(source) - } + def getMember: Option[Member] - def unsetForcedSource(): Unit = { - forcedMainSource = None - } + def exportGraph(): Unit def addForcedDependencies(chunks: Set[Chunk]): Unit = { forcedDependencies = forcedDependencies ++ getChunkNodeIds(chunks).toList @@ -83,19 +48,6 @@ trait AssumptionAnalyzer { forcedDependencies = List.empty } - def getAnalysisSourceInfoes: List[AnalysisSourceInfo] = sourceInfoes - def setAnalysisSourceInfoes(infoes: List[AnalysisSourceInfo]): Unit = { - sourceInfoes = infoes - } - - def addAnalysisSourceInfo(e: ast.Exp): Unit = { - sourceInfoes = ExpAnalysisSourceInfo(e) +: sourceInfoes - } - - def getMember: Option[Member] - - def exportGraph(): Unit - } object AssumptionAnalyzer { @@ -212,8 +164,8 @@ class DefaultAssumptionAnalyzer(member: Member) extends AssumptionAnalyzer { }) } - override def addAssertFalseNode(isCheck: Boolean): Option[Int] = { - val node = createAssertOrCheckNode(False, Right(ast.FalseLit()()), getFullSourceInfo, isCheck) // TODO ake: set isAssert + override def addAssertFalseNode(isCheck: Boolean, sourceInfo: AnalysisSourceInfo): Option[Int] = { + val node = createAssertOrCheckNode(False, Right(ast.FalseLit()()), sourceInfo, isCheck) addNode(node.get) node.map(_.id) } @@ -301,7 +253,7 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { override def createAssertOrCheckNode(term: Term, assertion: Either[String, ast.Exp], analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] = None - override def addAssertFalseNode(isCheck: Boolean): Option[Int] = None + override def addAssertFalseNode(isCheck: Boolean, sourceInfo: AnalysisSourceInfo): Option[Int] = None override def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit = { } diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index eb43850a5..bb2ee9c52 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -104,8 +104,12 @@ trait Decider { def statistics(): Map[String, String] var assumptionAnalyzer: AssumptionAnalyzer + var analysisSourceInfoStack: AnalysisSourceInfoStack def initAssumptionAnalyzer(member: Member, preambleNodes: Iterable[AssumptionAnalysisNode]): Unit def removeAssumptionAnalyzer(): Unit + def updateAnalysisSourceInfo(f: AnalysisSourceInfoStack => AnalysisSourceInfoStack): Unit + def getAnalysisInfo: AnalysisInfo + def getAnalysisInfo(assumptionType: AssumptionType): AnalysisInfo } /* @@ -136,6 +140,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => private val _debuggerAssumedTerms: mutable.Set[Term] = mutable.Set.empty var assumptionAnalyzer: AssumptionAnalyzer = new NoAssumptionAnalyzer() + var analysisSourceInfoStack: AnalysisSourceInfoStack = AnalysisSourceInfoStack() override def initAssumptionAnalyzer(member: Member, preambleNodes: Iterable[AssumptionAnalysisNode]): Unit = { val isAnalysisEnabled = AssumptionAnalyzer.extractEnableAnalysisFromInfo(member.info).getOrElse(Verifier.config.enableAssumptionAnalysis()) @@ -152,6 +157,14 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => assumptionAnalyzer = new NoAssumptionAnalyzer prover.setAssumptionAnalyzer(assumptionAnalyzer) } + + override def updateAnalysisSourceInfo(f: AnalysisSourceInfoStack => AnalysisSourceInfoStack): Unit = { + analysisSourceInfoStack = f(analysisSourceInfoStack) + } + + def getAnalysisInfo: AnalysisInfo = getAnalysisInfo(AssumptionType.Implicit) + + def getAnalysisInfo(assumptionType: AssumptionType): AnalysisInfo = AnalysisInfo(assumptionAnalyzer, analysisSourceInfoStack.getFullSourceInfo, assumptionType) def functionDecls: Set[FunctionDecl] = _declaredFreshFunctions def macroDecls: Vector[MacroDecl] = _declaredFreshMacros @@ -301,18 +314,18 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp], assumptionType: AssumptionType): Unit = { if (finalExp.isDefined) { - assume(assumptions=InsertionOrderedSet((t, Some(DebugExp.createInstance(e.get, finalExp.get)))), assumptionAnalyzer.getFullSourceInfo, enforceAssumption = false, isDefinition = false, assumptionType) + assume(assumptions=InsertionOrderedSet((t, Some(DebugExp.createInstance(e.get, finalExp.get)))), analysisSourceInfoStack.getFullSourceInfo, enforceAssumption = false, isDefinition = false, assumptionType) } else { - assume(assumptions=InsertionOrderedSet((t, None)), assumptionAnalyzer.getFullSourceInfo, enforceAssumption = false, isDefinition = false, assumptionType) + assume(assumptions=InsertionOrderedSet((t, None)), analysisSourceInfoStack.getFullSourceInfo, enforceAssumption = false, isDefinition = false, assumptionType) } } def assume(t: Term, debugExp: Option[DebugExp], assumptionType: AssumptionType): Unit = { - assume(InsertionOrderedSet(Seq((t, debugExp))), assumptionAnalyzer.getFullSourceInfo, enforceAssumption = false, isDefinition = false, assumptionType) + assume(InsertionOrderedSet(Seq((t, debugExp))), analysisSourceInfoStack.getFullSourceInfo, enforceAssumption = false, isDefinition = false, assumptionType) } def assumeDefinition(t: Term, debugExp: Option[DebugExp], assumptionType: AssumptionType): Unit = { - assume(InsertionOrderedSet(Seq((t, debugExp))), assumptionAnalyzer.getFullSourceInfo, enforceAssumption=false, isDefinition=true, assumptionType) + assume(InsertionOrderedSet(Seq((t, debugExp))), analysisSourceInfoStack.getFullSourceInfo, enforceAssumption=false, isDefinition=true, assumptionType) } def assume(assumptions: InsertionOrderedSet[(Term, Option[DebugExp])], analysisSourceInfo: AnalysisSourceInfo, enforceAssumption: Boolean, isDefinition: Boolean, assumptionType: AssumptionType): Unit = { @@ -336,7 +349,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } def assume(assumptions: Seq[Term], debugExps: Option[Seq[DebugExp]], assumptionType: AssumptionType): Unit = { - val assumptionIds = if(debugExps.isDefined) assumptionAnalyzer.addAssumptions(debugExps.get, assumptionAnalyzer.getFullSourceInfo, assumptionType) else Seq.empty + val assumptionIds = if(debugExps.isDefined) assumptionAnalyzer.addAssumptions(debugExps.get, analysisSourceInfoStack.getFullSourceInfo, assumptionType) else Seq.empty val assumptionsWithLabels = if(assumptions.size == assumptionIds.size) assumptions.zip(assumptionIds).map{case (t, id) => (t, AssumptionAnalyzer.createAssumptionLabel(Some(id)))} @@ -350,7 +363,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => // TODO ake: review this def assume(assumptions: Iterable[Term], debugExps: Option[Iterable[DebugExp]], description: String, enforceAssumption: Boolean, assumptionType: AssumptionType): Unit = { val debugExp = Option.when(debugExps.isDefined)(DebugExp.createInstance(description, InsertionOrderedSet(debugExps.get))) - val assumptionIds = if(debugExps.isDefined) assumptionAnalyzer.addAssumptions(debugExps.get, assumptionAnalyzer.getFullSourceInfo, assumptionType) else Seq.empty + val assumptionIds = if(debugExps.isDefined) assumptionAnalyzer.addAssumptions(debugExps.get, analysisSourceInfoStack.getFullSourceInfo, assumptionType) else Seq.empty val assumptionsWithLabels = if(assumptions.size == assumptionIds.size) assumptions.zip(assumptionIds).map{case (t, id) => (t, AssumptionAnalyzer.createAssumptionLabel(Some(id)))} @@ -380,7 +393,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => if (debugMode) { addDebugExp(debugExp.get.withTerm(And(filteredTerms))) - val assumptionId: Option[Int] = if(debugExp.isDefined) assumptionAnalyzer.addSingleAssumption(debugExp.get.withTerm(And(filteredTerms)), assumptionAnalyzer.getFullSourceInfo, assumptionType) else None + val assumptionId: Option[Int] = if(debugExp.isDefined) assumptionAnalyzer.addSingleAssumption(debugExp.get.withTerm(And(filteredTerms)), analysisSourceInfoStack.getFullSourceInfo, assumptionType) else None val termsWithLabel = filteredTerms.zipWithIndex.iterator.map {case (t, idx) => (t, AssumptionAnalyzer.createAssumptionLabel(assumptionId, idx))}.toSeq assumeWithoutSmokeChecks(InsertionOrderedSet(termsWithLabel)) }else{ @@ -421,7 +434,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => def checkSmoke(isAssert: Boolean = false): Boolean = { val label = if(Verifier.config.enableAssumptionAnalysis()){ - val nodeId = assumptionAnalyzer.addAssertFalseNode(!isAssert) + val nodeId = assumptionAnalyzer.addAssertFalseNode(!isAssert, analysisSourceInfoStack.getFullSourceInfo) AssumptionAnalyzer.createAssertionLabel(nodeId) }else{ "" } val timeout = if (isAssert) Verifier.config.assertTimeout.toOption else Verifier.config.checkTimeout.toOption @@ -461,7 +474,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => val sepIdentifier = symbExLog.openScope(assertRecord) val asserted = if(Verifier.config.enableAssumptionAnalysis()) t.equals(True) else isKnownToBeTrue(t) - val assertNode = if(!asserted) assumptionAnalyzer.createAssertOrCheckNode(t, e, decider.assumptionAnalyzer.getFullSourceInfo, isCheck) else None + val assertNode = if(!asserted) assumptionAnalyzer.createAssertOrCheckNode(t, e, decider.analysisSourceInfoStack.getFullSourceInfo, isCheck) else None val result = asserted || proverAssert(t, timeout, AssumptionAnalyzer.createAssertionLabel(assertNode map (_.id))) diff --git a/src/main/scala/rules/Brancher.scala b/src/main/scala/rules/Brancher.scala index 75c9d6312..cd326fc34 100644 --- a/src/main/scala/rules/Brancher.scala +++ b/src/main/scala/rules/Brancher.scala @@ -60,7 +60,7 @@ object brancher extends BranchingRules { && s.quantifiedVariables.map(_._1).exists(condition.freeVariables.contains)) ) - v.decider.assumptionAnalyzer.addAnalysisSourceInfo(ExpAnalysisSourceInfo(conditionExp._1)) + v.decider.updateAnalysisSourceInfo(_.addAnalysisSourceInfo(ExpAnalysisSourceInfo(conditionExp._1))) /* True if the then-branch is to be explored */ val executeThenBranch = ( skipPathFeasibilityCheck @@ -71,7 +71,7 @@ object brancher extends BranchingRules { !executeThenBranch /* Assumes that ast least one branch is feasible */ || skipPathFeasibilityCheck || !v.decider.check(condition, Verifier.config.checkTimeout())) - v.decider.assumptionAnalyzer.popAnalysisSourceInfo() + v.decider.updateAnalysisSourceInfo(_.popAnalysisSourceInfo()) val parallelizeElseBranch = s.parallelizeBranches && executeThenBranch && executeElseBranch @@ -101,7 +101,7 @@ object brancher extends BranchingRules { var macrosOfElseBranchDecider: Seq[MacroDecl] = null var pcsForElseBranch: PathConditionStack = null var noOfErrors = 0 - val currentStmtStack = v.decider.assumptionAnalyzer.getAnalysisSourceInfoes + val currentAnalysisSourceInfoStack = v.decider.analysisSourceInfoStack val elseBranchVerificationTask: Verifier => VerificationResult = if (executeElseBranch) { @@ -148,12 +148,12 @@ object brancher extends BranchingRules { } elseBranchVerifier = v0.uniqueId - v0.decider.assumptionAnalyzer.setAnalysisSourceInfoes(currentStmtStack) + v0.decider.analysisSourceInfoStack = currentAnalysisSourceInfoStack executionFlowController.locally(s, v0)((s1, v1) => { v1.decider.prover.comment(s"[else-branch: $cnt | $negatedCondition]") - v1.decider.assumptionAnalyzer.addAnalysisSourceInfo(conditionExp._1) + v1.decider.updateAnalysisSourceInfo(_.addAnalysisSourceInfo(conditionExp._1)) v1.decider.setCurrentBranchCondition(negatedCondition, (negatedConditionExp, negatedConditionExpNew)) - v1.decider.assumptionAnalyzer.popAnalysisSourceInfo() + v1.decider.updateAnalysisSourceInfo(_.popAnalysisSourceInfo()) var functionsOfElseBranchdDeciderBefore: Set[FunctionDecl] = null var nMacrosOfElseBranchDeciderBefore: Int = 0 @@ -201,12 +201,12 @@ object brancher extends BranchingRules { val res = { val thenRes = if (executeThenBranch) { v.symbExLog.markReachable(uidBranchPoint) - v.decider.assumptionAnalyzer.setAnalysisSourceInfoes(currentStmtStack) + v.decider.analysisSourceInfoStack = currentAnalysisSourceInfoStack executionFlowController.locally(s, v)((s1, v1) => { v1.decider.prover.comment(s"[then-branch: $cnt | $condition]") - v1.decider.assumptionAnalyzer.addAnalysisSourceInfo(conditionExp._1) + v1.decider.updateAnalysisSourceInfo(_.addAnalysisSourceInfo(conditionExp._1)) v1.decider.setCurrentBranchCondition(condition, conditionExp) - v1.decider.assumptionAnalyzer.popAnalysisSourceInfo() + v1.decider.updateAnalysisSourceInfo(_.popAnalysisSourceInfo()) fThen(v1.stateConsolidator(s1).consolidateOptionally(s1, v1), v1) }) diff --git a/src/main/scala/rules/ChunkSupporter.scala b/src/main/scala/rules/ChunkSupporter.scala index 4c474914a..87b033644 100644 --- a/src/main/scala/rules/ChunkSupporter.scala +++ b/src/main/scala/rules/ChunkSupporter.scala @@ -169,7 +169,7 @@ object chunkSupporter extends ChunkSupportRules { case Some(ch) => if (s.assertReadAccessOnly) { if (v.decider.check(Implies(IsPositive(perms), IsPositive(ch.perm)), Verifier.config.assertTimeout.getOrElse(0))) { - v.decider.assumptionAnalyzer.addPermissionAssertNode(ch, ch.permExp.map(pe => IsPositive(pe)(pe.pos, pe.info, pe.errT)), v.decider.assumptionAnalyzer.getFullSourceInfo) + v.decider.assumptionAnalyzer.addPermissionAssertNode(ch, ch.permExp.map(pe => IsPositive(pe)(pe.pos, pe.info, pe.errT)), v.decider.analysisSourceInfoStack.getFullSourceInfo) (Complete(), s, h, Some(ch)) } else { (Incomplete(perms, permsExp), s, h, None) @@ -178,8 +178,8 @@ object chunkSupporter extends ChunkSupportRules { val toTake = PermMin(ch.perm, perms) val toTakeExp = permsExp.map(pe => buildMinExp(Seq(ch.permExp.get, pe), ast.Perm)) val newPermExp = permsExp.map(pe => ast.PermSub(ch.permExp.get, toTakeExp.get)(pe.pos, pe.info, pe.errT)) - val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, toTake), newPermExp, v.decider.assumptionAnalyzer.getAnalysisInfo).asInstanceOf[NonQuantifiedChunk] - val takenChunk = Some(GeneralChunk.withPerm(ch, toTake, toTakeExp, v.decider.assumptionAnalyzer.getAnalysisInfo, isExhale=true).asInstanceOf[NonQuantifiedChunk]) + val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, toTake), newPermExp, v.decider.getAnalysisInfo).asInstanceOf[NonQuantifiedChunk] + val takenChunk = Some(GeneralChunk.withPerm(ch, toTake, toTakeExp, v.decider.getAnalysisInfo, isExhale=true).asInstanceOf[NonQuantifiedChunk]) var newHeap = h - ch if (!v.decider.check(newChunk.perm === NoPerm, Verifier.config.checkTimeout())) { newHeap = newHeap + newChunk @@ -192,8 +192,8 @@ object chunkSupporter extends ChunkSupportRules { val constraintExp = permsExp.map(pe => ast.PermLtCmp(pe, ch.permExp.get)(pe.pos, pe.info, pe.errT)) v.decider.assume(PermLess(perms, ch.perm), Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp)), AssumptionType.Implicit) val newPermExp = permsExp.map(pe => ast.PermSub(ch.permExp.get, pe)(pe.pos, pe.info, pe.errT)) - val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, perms), newPermExp, v.decider.assumptionAnalyzer.getAnalysisInfo).asInstanceOf[NonQuantifiedChunk] - val takenChunk = GeneralChunk.withPerm(ch, perms, permsExp, v.decider.assumptionAnalyzer.getAnalysisInfo, isExhale=true).asInstanceOf[NonQuantifiedChunk] + val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, perms), newPermExp, v.decider.getAnalysisInfo).asInstanceOf[NonQuantifiedChunk] + val takenChunk = GeneralChunk.withPerm(ch, perms, permsExp, v.decider.getAnalysisInfo, isExhale=true).asInstanceOf[NonQuantifiedChunk] val newHeap = h - ch + newChunk assumeProperties(newChunk, newHeap) (Complete(), s, newHeap, Some(takenChunk)) @@ -253,7 +253,7 @@ object chunkSupporter extends ChunkSupportRules { val findRes = findChunk[NonQuantifiedChunk](h.values, id, args, v) findRes match { case Some(ch) if v.decider.check(IsPositive(ch.perm), Verifier.config.checkTimeout()) => - v.decider.assumptionAnalyzer.addPermissionAssertNode(ch, ch.permExp.map(pe => IsPositive(pe)(pe.pos, pe.info, pe.errT)), v.decider.assumptionAnalyzer.getFullSourceInfo) + v.decider.assumptionAnalyzer.addPermissionAssertNode(ch, ch.permExp.map(pe => IsPositive(pe)(pe.pos, pe.info, pe.errT)), v.decider.analysisSourceInfoStack.getFullSourceInfo) Q(s, ch.snap, v) case _ if v.decider.checkSmoke(true) => if (s.isInPackage) { diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index ed65f7cc9..d2931001b 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -176,10 +176,10 @@ object consumer extends ConsumptionRules { val s1 = s0.copy(h = s.h) /* s1 is s, but the retrying flag might be set */ val sepIdentifier = v1.symbExLog.openScope(new ConsumeRecord(a, s1, v.decider.pcs)) - v.decider.assumptionAnalyzer.addAnalysisSourceInfo(a) + v.decider.updateAnalysisSourceInfo(_.addAnalysisSourceInfo(a)) consumeTlc(s1, h0, a, returnSnap, pve, v1)((s2, h2, snap2, consumedChunks, v2) => { - v.decider.assumptionAnalyzer.popAnalysisSourceInfo() + v.decider.updateAnalysisSourceInfo(_.popAnalysisSourceInfo()) v2.symbExLog.closeScope(sepIdentifier) QS(s2, h2, snap2, consumedChunks, v2)}) })(Q) diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 5d55a32d0..9e7ceb540 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -93,9 +93,9 @@ object evaluator extends EvaluationRules { : VerificationResult = { val sepIdentifier = v.symbExLog.openScope(new EvaluateRecord(e, s, v.decider.pcs)) - v.decider.assumptionAnalyzer.addAnalysisSourceInfo(e) + v.decider.updateAnalysisSourceInfo(_.addAnalysisSourceInfo(e)) eval3(s, e, pve, v)((s1, t, eNew, v1) => { - v.decider.assumptionAnalyzer.popAnalysisSourceInfo() // TODO ake: can return multiple times + v.decider.updateAnalysisSourceInfo(_.popAnalysisSourceInfo()) // TODO ake: can return multiple times v1.symbExLog.closeScope(sepIdentifier) Q(s1, t, eNew, v1)}) } @@ -827,9 +827,9 @@ object evaluator extends EvaluationRules { val auxNonGlobalsExp = auxExps.map(_._2) val commentGlobal = "Nested auxiliary terms: globals (aux)" v1.decider.prover.comment(commentGlobal) - v1.decider.assumptionAnalyzer.setForcedSource(commentGlobal) + v1.decider.updateAnalysisSourceInfo(_.withForcedSource(commentGlobal)) v1.decider.assume(tAuxGlobal, Option.when(withExp)(DebugExp.createInstance(description=commentGlobal, children=auxGlobalsExp.get)), enforceAssumption = false, assumptionType=AssumptionType.Internal) - v1.decider.assumptionAnalyzer.unsetForcedSource() + v1.decider.updateAnalysisSourceInfo(_.withoutForcedSource()) val commentNonGlobals = "Nested auxiliary terms: non-globals (aux)" v1.decider.prover.comment(commentNonGlobals) v1.decider.assume(tAuxHeapIndep/*tAux*/, Option.when(withExp)(DebugExp.createInstance(description=commentNonGlobals, children=auxNonGlobalsExp.get)), enforceAssumption = false, assumptionType=AssumptionType.Internal) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 35fa4bc9e..162cfafcf 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -326,9 +326,9 @@ object executor extends ExecutionRules { (Q: (State, Verifier) => VerificationResult) : VerificationResult = { val sepIdentifier = v.symbExLog.openScope(new ExecuteRecord(stmt, s, v.decider.pcs)) - v.decider.assumptionAnalyzer.addAnalysisSourceInfo(StmtAnalysisSourceInfo(stmt)) + v.decider.updateAnalysisSourceInfo(_.addAnalysisSourceInfo(StmtAnalysisSourceInfo(stmt))) exec2(s, stmt, v)((s1, v1) => { - v.decider.assumptionAnalyzer.popAnalysisSourceInfo() + v.decider.updateAnalysisSourceInfo(_.popAnalysisSourceInfo()) v1.symbExLog.closeScope(sepIdentifier) Q(s1, v1)}) } @@ -401,7 +401,7 @@ object executor extends ExecutionRules { s2 } v2.decider.clearModel() - v2.decider.assumptionAnalyzer.setForcedSource(ExpAnalysisSourceInfo(fa)) + v2.decider.updateAnalysisSourceInfo(_.withForcedSource(ExpAnalysisSourceInfo(fa))) val result = quantifiedChunkSupporter.removePermissions( s2p, relevantChunks, @@ -416,7 +416,7 @@ object executor extends ExecutionRules { chunkOrderHeuristics, v2 ) - v2.decider.assumptionAnalyzer.unsetForcedSource() + v2.decider.updateAnalysisSourceInfo(_.withoutForcedSource()) result match { case (Complete(), s3, remainingChunks, consumedChunks) => // TODO ake: what to do with consumedChunks? val h3 = Heap(remainingChunks ++ otherChunks) @@ -424,10 +424,10 @@ object executor extends ExecutionRules { v1.decider.prover.comment("Definitional axioms for singleton-FVF's value") val debugExp = Option.when(withExp)(DebugExp.createInstance("Definitional axioms for singleton-FVF's value", isInternal_ = true)) v1.decider.assumeDefinition(smValueDef, debugExp, AssumptionType.Internal) - v1.decider.assumptionAnalyzer.setForcedSource(ExpAnalysisSourceInfo(fa)) + v1.decider.updateAnalysisSourceInfo(_.withForcedSource(ExpAnalysisSourceInfo(fa))) val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(Seq(`?r`), Option.when(withExp)(Seq(ast.LocalVarDecl("r", ast.Ref)(ass.pos, ass.info, ass.errT))), field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), sm, s.program, v1, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit), isExhale=false) - v1.decider.assumptionAnalyzer.unsetForcedSource() + v1.decider.updateAnalysisSourceInfo(_.withoutForcedSource()) if (s3.heapDependentTriggers.contains(field)) { val debugExp2 = Option.when(withExp)(DebugExp.createInstance(s"FieldTrigger(${eRcvrNew.toString}.${field.name})")) v1.decider.assume(FieldTrigger(field.name, sm, tRcvr), debugExp2, AssumptionType.Internal) @@ -447,15 +447,15 @@ object executor extends ExecutionRules { val resource = fa.res(s.program) val ve = pve dueTo InsufficientPermission(fa) val description = s"consume ${ass.pos}: $ass" - v2.decider.assumptionAnalyzer.setForcedSource(ExpAnalysisSourceInfo(fa)) + v2.decider.updateAnalysisSourceInfo(_.withForcedSource(ExpAnalysisSourceInfo(fa))) chunkSupporter.consume(s2, s2.h, resource, Seq(tRcvr), eRcvrNew.map(Seq(_)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), false, ve, v2, description)((s3, h3, _, consumedChunks, v3) => { - v3.decider.assumptionAnalyzer.setForcedSource(StmtAnalysisSourceInfo(ass)) + v3.decider.updateAnalysisSourceInfo(_.withForcedSource(StmtAnalysisSourceInfo(ass))) val (tSnap, _) = ssaifyRhs(tRhs, rhs, rhsNew, field.name, field.typ, v3, s3, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit)) val id = BasicChunkIdentifier(field.name) - v3.decider.assumptionAnalyzer.setForcedSource(ExpAnalysisSourceInfo(fa)) + v3.decider.updateAnalysisSourceInfo(_.withForcedSource(ExpAnalysisSourceInfo(fa))) val newChunk = BasicChunk.createDerivedChunk(Set.empty, FieldID, id, Seq(tRcvr), eRcvrNew.map(Seq(_)), tSnap, rhsNew, FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), - v3.decider.assumptionAnalyzer.getAnalysisInfo(annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit))) - v3.decider.assumptionAnalyzer.unsetForcedSource() + v3.decider.getAnalysisInfo(annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit))) + v3.decider.updateAnalysisSourceInfo(_.withoutForcedSource()) chunkSupporter.produce(s3, h3, newChunk, v3)((s4, h4, v4) => { val s5 = s4.copy(h = h4) val (debugHeapName, _) = v4.getDebugOldLabel(s5, fa.pos) @@ -487,7 +487,7 @@ object executor extends ExecutionRules { field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), p, pExp, sm, s.program, v, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit), isExhale=false) } else { val newChunk = BasicChunk(FieldID, BasicChunkIdentifier(field.name), Seq(tRcvr), Option.when(withExp)(Seq(x)), snap, snapExp, p, pExp, - v.decider.assumptionAnalyzer.getAnalysisInfo(annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit))) + v.decider.getAnalysisInfo(annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit))) newChunk } }) @@ -555,7 +555,7 @@ object executor extends ExecutionRules { case ast.MethodCall(methodName, _, _) if !Verifier.config.disableHavocHack407() && methodName.startsWith(hack407_method_name_prefix) => - val analysisInfo = v.decider.assumptionAnalyzer.getAnalysisInfo(annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit)) + val analysisInfo = v.decider.getAnalysisInfo(annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit)) val resourceName = methodName.stripPrefix(hack407_method_name_prefix) val member = s.program.collectFirst { case m: ast.Field if m.name == resourceName => m diff --git a/src/main/scala/rules/HavocSupporter.scala b/src/main/scala/rules/HavocSupporter.scala index 592f72e28..058d6d769 100644 --- a/src/main/scala/rules/HavocSupporter.scala +++ b/src/main/scala/rules/HavocSupporter.scala @@ -197,12 +197,12 @@ object havocSupporter extends SymbolicExecutionRules { val havockedSnap = v.decider.fresh("mwsf", sorts.MagicWandSnapFunction, Option.when(withExp)(PUnknown())) val cond = replacementCond(lhs, ch.args, condInfo) val magicWandSnapshot = MagicWandSnapshot(Ite(cond, havockedSnap, ch.snap.mwsf)) - NonQuantifiedChunk.withSnap(ch, magicWandSnapshot, None, v.decider.assumptionAnalyzer.getAnalysisInfo(assumptionType)) + NonQuantifiedChunk.withSnap(ch, magicWandSnapshot, None, v.decider.getAnalysisInfo(assumptionType)) case ch => val havockedSnap = freshSnap(ch.snap.sort, v) val cond = replacementCond(lhs, ch.args, condInfo) - NonQuantifiedChunk.withSnap(ch, Ite(cond, havockedSnap, ch.snap), None, v.decider.assumptionAnalyzer.getAnalysisInfo(assumptionType)) + NonQuantifiedChunk.withSnap(ch, Ite(cond, havockedSnap, ch.snap), None, v.decider.getAnalysisInfo(assumptionType)) } otherChunks ++ newChunks } @@ -286,7 +286,7 @@ object havocSupporter extends SymbolicExecutionRules { val debugExp = Option.when(withExp)(DebugExp.createInstance("havoc new axiom", isInternal_ = true)) v.decider.assume(newAxiom, debugExp, AssumptionType.Internal) - QuantifiedChunk.withSnapshotMap(ch, newSm, v.decider.assumptionAnalyzer.getAnalysisInfo(assumptionType)) + QuantifiedChunk.withSnapshotMap(ch, newSm, v.decider.getAnalysisInfo(assumptionType)) } newChunks ++ otherChunks } diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 2328212e8..be7b41515 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -97,7 +97,7 @@ object magicWandSupporter extends SymbolicExecutionRules { : VerificationResult = { evaluateWandArguments(s, wand, pve, v)((s1, ts, esNew, v1) => { val newChunk = MagicWandChunk(MagicWandIdentifier(wand, s.program), s1.g.values, ts, esNew, snap, FullPerm, - Option.when(withExp)(ast.FullPerm()(wand.pos, wand.info, wand.errT)), v.decider.assumptionAnalyzer.getAnalysisInfo(assumptionType)) + Option.when(withExp)(ast.FullPerm()(wand.pos, wand.info, wand.errT)), v.decider.getAnalysisInfo(assumptionType)) Q(s1, newChunk, v1) }) } @@ -421,6 +421,7 @@ object magicWandSupporter extends SymbolicExecutionRules { createWandChunkAndRecordResults(s1, freshSnap(sorts.Snap, v), freshSnap(sorts.Snap, v), v, assumptionType) } + val currentAnalysisSourceInfoStack = v.decider.analysisSourceInfoStack recordedBranches.foldLeft(tempResult)((prevRes, recordedState) => { prevRes && { val (state, branchConditions, branchConditionsExp, conservedPcs, magicWandChunk) = recordedState @@ -431,6 +432,7 @@ object magicWandSupporter extends SymbolicExecutionRules { // We execute the continuation Q in a new scope with all branch conditions and all conserved path conditions. executionFlowController.locally(s1, v)((s2, v1) => { + v1.decider.analysisSourceInfoStack = currentAnalysisSourceInfoStack val exp = viper.silicon.utils.ast.BigAnd(branchConditionsExp.map(_._1)) val expNew = Option.when(withExp)(viper.silicon.utils.ast.BigAnd(branchConditionsExp.map(_._2.get))) // Set the branch conditions diff --git a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala index fe4f884ca..0a8954936 100644 --- a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala +++ b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala @@ -365,8 +365,8 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { pSum = PermPlus(pSum, Ite(eq, ch.perm, NoPerm)) pSumExp = eqExp.map(eq => ast.PermAdd(pSumExp.get, ast.CondExp(eq, ch.permExp.get, ast.NoPerm()())(eq.pos, eq.info, eq.errT))()) - val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, pTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, pTakenExp.get)(pe.pos, pe.info, pe.errT)), v.decider.assumptionAnalyzer.getAnalysisInfo).asInstanceOf[NonQuantifiedChunk] - v.decider.assumptionAnalyzer.addPermissionExhaleNode(ch, pTakenExp, v.decider.assumptionAnalyzer.getFullSourceInfo) + val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, pTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, pTakenExp.get)(pe.pos, pe.info, pe.errT)), v.decider.getAnalysisInfo).asInstanceOf[NonQuantifiedChunk] + v.decider.assumptionAnalyzer.addPermissionExhaleNode(ch, pTakenExp, v.decider.analysisSourceInfoStack.getFullSourceInfo) pNeeded = PermMinus(pNeeded, pTaken) pNeededExp = permsExp.map(pe => ast.PermSub(pNeededExp.get, pTakenExp.get)(pe.pos, pe.info, pe.errT)) @@ -482,8 +482,8 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { newFr = newFr.recordPathSymbol(permTaken.applicable.asInstanceOf[Function]).recordConstraint(constraint) - v.decider.assumptionAnalyzer.addPermissionExhaleNode(ch, permTakenExp, v.decider.assumptionAnalyzer.getFullSourceInfo) - GeneralChunk.withPerm(ch, PermMinus(ch.perm, permTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, permTakenExp.get)(pe.pos, pe.info, pe.errT)), v.decider.assumptionAnalyzer.getAnalysisInfo).asInstanceOf[NonQuantifiedChunk] + v.decider.assumptionAnalyzer.addPermissionExhaleNode(ch, permTakenExp, v.decider.analysisSourceInfoStack.getFullSourceInfo) + GeneralChunk.withPerm(ch, PermMinus(ch.perm, permTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, permTakenExp.get)(pe.pos, pe.info, pe.errT)), v.decider.getAnalysisInfo).asInstanceOf[NonQuantifiedChunk] }) val totalTakenBounds = diff --git a/src/main/scala/rules/PredicateSupporter.scala b/src/main/scala/rules/PredicateSupporter.scala index ca0f27181..31a1f0c52 100644 --- a/src/main/scala/rules/PredicateSupporter.scala +++ b/src/main/scala/rules/PredicateSupporter.scala @@ -118,7 +118,7 @@ object predicateSupporter extends PredicateSupportRules { functionRecorder = s2.functionRecorder.recordFvfAndDomain(smDef)) Q(s3, v1) } else { - val ch = BasicChunk(PredicateID, BasicChunkIdentifier(predicate.name), tArgs, eArgs, snap.get.convert(sorts.Snap), None, tPerm, ePerm, v1.decider.assumptionAnalyzer.getAnalysisInfo(assumptionType)) + val ch = BasicChunk(PredicateID, BasicChunkIdentifier(predicate.name), tArgs, eArgs, snap.get.convert(sorts.Snap), None, tPerm, ePerm, v1.decider.getAnalysisInfo(assumptionType)) val s3 = s2.copy(g = s.g, smDomainNeeded = s.smDomainNeeded, permissionScalingFactor = s.permissionScalingFactor, diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index 0903cb969..2d0658673 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -155,10 +155,10 @@ object producer extends ProductionRules { val pve = pves.head - v.decider.assumptionAnalyzer.addAnalysisSourceInfo(a) + v.decider.updateAnalysisSourceInfo(_.addAnalysisSourceInfo(a)) if (as.tail.isEmpty) wrappedProduceTlc(s, sf, a, pve, v, assumptionType)((s1, v1) => { - v.decider.assumptionAnalyzer.popAnalysisSourceInfo() + v.decider.updateAnalysisSourceInfo(_.popAnalysisSourceInfo()) Q(s1, v1) }) else { @@ -172,7 +172,7 @@ object producer extends ProductionRules { */ wrappedProduceTlc(s, sf0, a, pve, v, assumptionType)((s1, v1) => { - v1.decider.assumptionAnalyzer.popAnalysisSourceInfo() + v1.decider.updateAnalysisSourceInfo(_.popAnalysisSourceInfo()) produceTlcs(s1, sf1, as.tail, pves.tail, v1, assumptionType)(Q) }) } catch { @@ -356,7 +356,7 @@ object producer extends ProductionRules { val (debugHeapName, debugLabel) = v3.getDebugOldLabel(s3, accPred.pos) val snapExp = Option.when(withExp)(ast.DebugLabelledOld(ast.FieldAccess(eRcvrNew.get, field)(), debugLabel)(accPred.pos, accPred.info, accPred.errT)) val ch = BasicChunk(FieldID, BasicChunkIdentifier(field.name), Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), snap, snapExp, gain, gainExp, - v1.decider.assumptionAnalyzer.getAnalysisInfo(assumptionType)) + v1.decider.getAnalysisInfo(assumptionType)) chunkSupporter.produce(s3, s3.h, ch, v3)((s4, h4, v4) => { val s5 = s4.copy(h = h4) val s6 = if (withExp) s5.copy(oldHeaps = s5.oldHeaps + (debugHeapName -> magicWandSupporter.getEvalHeap(s4))) else s5 @@ -384,7 +384,7 @@ object producer extends ProductionRules { } else { val snap1 = snap.convert(sorts.Snap) val ch = BasicChunk(PredicateID, BasicChunkIdentifier(predicate.name), tArgs, eArgsNew, snap1, None, gain, gainExp, - v1.decider.assumptionAnalyzer.getAnalysisInfo(assumptionType)) + v1.decider.getAnalysisInfo(assumptionType)) chunkSupporter.produce(s2, s2.h, ch, v2)((s3, h3, v3) => { if (Verifier.config.enablePredicateTriggersOnInhale() && s3.functionRecorder == NoopFunctionRecorder && !Verifier.config.disableFunctionUnfoldTrigger()) { diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 589d49f01..32bd70d87 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -440,7 +440,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optSingletonArguments.map(_.head), optSingletonArgumentsExp.map(_.head), hints, - v.decider.assumptionAnalyzer.getAnalysisInfo(assumptionType), + v.decider.getAnalysisInfo(assumptionType), isExhale) case predicate: ast.Predicate => @@ -457,7 +457,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optSingletonArguments, optSingletonArgumentsExp, hints, - v.decider.assumptionAnalyzer.getAnalysisInfo(assumptionType), + v.decider.getAnalysisInfo(assumptionType), isExhale) case wand: ast.MagicWand => @@ -474,7 +474,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optSingletonArguments, optSingletonArgumentsExp, hints, - v.decider.assumptionAnalyzer.getAnalysisInfo(assumptionType), + v.decider.getAnalysisInfo(assumptionType), isExhale) case other => @@ -732,7 +732,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { smDef: SnapshotMapDefinition, v: Verifier) : (PermMapDefinition, PmCache) = { - v.decider.assumptionAnalyzer.setForcedSource("summarizing heap") + v.decider.updateAnalysisSourceInfo(_.withForcedSource("summarizing heap")) v.decider.assumptionAnalyzer.addForcedDependencies(relevantChunks.toSet) val res = Verifier.config.mapCache(s.pmCache.get(resource, relevantChunks)) match { case Some(pmDef) => @@ -745,7 +745,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.assume(valueDef, Option.when(withExp)(DebugExp.createInstance("value definitions", isInternal_ = true)), enforceAssumption = false, assumptionType=AssumptionType.Internal) (pmDef, s.pmCache + ((resource, relevantChunks) -> pmDef)) } - v.decider.assumptionAnalyzer.unsetForcedSource() + v.decider.updateAnalysisSourceInfo(_.withoutForcedSource()) res } @@ -773,7 +773,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optSmDomainDefinitionCondition: Option[Term] = None, optQVarsInstantiations: Option[Seq[Term]] = None) : (SnapshotMapDefinition, SnapshotMapCache) = { - v.decider.assumptionAnalyzer.setForcedSource("summarizing heap") + v.decider.updateAnalysisSourceInfo(_.withForcedSource("summarizing heap")) v.decider.assumptionAnalyzer.addForcedDependencies(relevantChunks.toSet) def emitSnapshotMapDefinition(s: State, @@ -846,7 +846,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { emitSnapshotMapDefinition(s, smDef, v, optQVarsInstantiations) v.decider.assumptionAnalyzer.unsetForcedDependencies() - v.decider.assumptionAnalyzer.unsetForcedSource() + v.decider.updateAnalysisSourceInfo(_.withoutForcedSource()) (smDef, smCache) } @@ -858,7 +858,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optSmDomainDefinitionCondition: Option[Term] = None, optQVarsInstantiations: Option[Seq[Term]] = None) : (State, SnapshotMapDefinition, PermMapDefinition) = { - v.decider.assumptionAnalyzer.setForcedSource("summarizing heap") + v.decider.updateAnalysisSourceInfo(_.withForcedSource("summarizing heap")) v.decider.assumptionAnalyzer.addForcedDependencies(relevantChunks.toSet) val (smDef, smCache) = summarisingSnapshotMap( @@ -872,7 +872,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val s2 = s1.copy(pmCache = pmCache) v.decider.assumptionAnalyzer.unsetForcedDependencies() - v.decider.assumptionAnalyzer.unsetForcedSource() + v.decider.updateAnalysisSourceInfo(_.withoutForcedSource()) (s2, smDef, pmDef) } @@ -885,7 +885,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { relevantChunks: Seq[QuantifiedBasicChunk], v: Verifier) : (State, PermMapDefinition) = { - v.decider.assumptionAnalyzer.setForcedSource("summarizing heap") + v.decider.updateAnalysisSourceInfo(_.withForcedSource("summarizing heap")) v.decider.assumptionAnalyzer.addForcedDependencies(relevantChunks.toSet) val s1 = s val (pmDef, pmCache) = @@ -894,7 +894,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val s2 = s1.copy(pmCache = pmCache) v.decider.assumptionAnalyzer.unsetForcedDependencies() - v.decider.assumptionAnalyzer.unsetForcedSource() + v.decider.updateAnalysisSourceInfo(_.withoutForcedSource()) (s2, pmDef) } @@ -1003,10 +1003,10 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val commentGlobals = "Nested auxiliary terms: globals" v.decider.prover.comment(commentGlobals) - v.decider.assumptionAnalyzer.setForcedSource(commentGlobals) + v.decider.updateAnalysisSourceInfo(_.withForcedSource(commentGlobals)) v.decider.assume(auxGlobals, Option.when(withExp)(DebugExp.createInstance(description=commentGlobals, children=auxGlobalsExp.get)), enforceAssumption = false, assumptionType=AssumptionType.Internal) - v.decider.assumptionAnalyzer.unsetForcedSource() + v.decider.updateAnalysisSourceInfo(_.withoutForcedSource()) val commentNonGlobals = "Nested auxiliary terms: non-globals" v.decider.prover.comment(commentNonGlobals) @@ -1237,9 +1237,9 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val comment = "Nested auxiliary terms: globals" v.decider.prover.comment(comment) - v.decider.assumptionAnalyzer.setForcedSource(comment) + v.decider.updateAnalysisSourceInfo(_.withForcedSource(comment)) v.decider.assume(auxGlobals, Option.when(withExp)(DebugExp.createInstance(description=comment, children=auxGlobalsExp.get)), enforceAssumption = false, assumptionType=AssumptionType.Internal) - v.decider.assumptionAnalyzer.unsetForcedSource() + v.decider.updateAnalysisSourceInfo(_.withoutForcedSource()) val comment2 = "Nested auxiliary terms: non-globals" v.decider.prover.comment(comment2) @@ -1708,7 +1708,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.assume(permissionConstraint, permissionConstraintExp, permissionConstraintExp, AssumptionType.Internal) remainingChunks = - remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.assumptionAnalyzer.getAnalysisInfo(AssumptionType.Implicit)) + remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.getAnalysisInfo(AssumptionType.Implicit)) } else { v.decider.prover.comment(s"Chunk depleted?") val chunkDepleted = v.decider.check(depletedCheck, Verifier.config.splitTimeout()) @@ -1719,10 +1719,10 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { remainingChunks = remainingChunks :+ ithChunk } else { remainingChunks = - remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.assumptionAnalyzer.getAnalysisInfo(AssumptionType.Implicit)) + remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.getAnalysisInfo(AssumptionType.Implicit)) } }else{ - v.decider.assumptionAnalyzer.addPermissionExhaleNode(ithChunk, ithPTakenExp, v.decider.assumptionAnalyzer.getFullSourceInfo) + v.decider.assumptionAnalyzer.addPermissionExhaleNode(ithChunk, ithPTakenExp, v.decider.analysisSourceInfoStack.getFullSourceInfo) } } diff --git a/src/main/scala/rules/StateConsolidator.scala b/src/main/scala/rules/StateConsolidator.scala index f2f63422f..c347f92ad 100644 --- a/src/main/scala/rules/StateConsolidator.scala +++ b/src/main/scala/rules/StateConsolidator.scala @@ -61,7 +61,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol def consolidate(s: State, v: Verifier): State = { val comLog = new CommentRecord("state consolidation", s, v.decider.pcs) val sepIdentifier = v.symbExLog.openScope(comLog) - v.decider.assumptionAnalyzer.setForcedSource("state consolidation") + v.decider.updateAnalysisSourceInfo(_.withForcedSource("state consolidation")) v.decider.prover.comment("[state consolidation]") v.decider.prover.saturate(config.proverSaturationTimeouts.beforeIteration) @@ -122,7 +122,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol reserveHeaps = mergedHeaps.tail) val s2 = assumeUpperPermissionBoundForQPFields(s1, v) - v.decider.assumptionAnalyzer.unsetForcedSource() + v.decider.updateAnalysisSourceInfo(_.withoutForcedSource()) s2 } @@ -218,7 +218,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol case (BasicChunk(rid1, id1, args1, args1Exp, snap1, snap1Exp, perm1, perm1Exp), BasicChunk(_, _, _, _, snap2, _, perm2, perm2Exp)) => val (fr2, combinedSnap, snapEq) = combineSnapshots(fr1, snap1, snap2, perm1, perm2, qvars, v) val newExp = perm1Exp.map(p1 => ast.PermAdd(p1, perm2Exp.get)()) - Some(fr2, BasicChunk(rid1, id1, args1, args1Exp, combinedSnap, snap1Exp, PermPlus(perm1, perm2), newExp, v.decider.assumptionAnalyzer.getAnalysisInfo(AssumptionType.Internal)), snapEq) + Some(fr2, BasicChunk(rid1, id1, args1, args1Exp, combinedSnap, snap1Exp, PermPlus(perm1, perm2), newExp, v.decider.getAnalysisInfo(AssumptionType.Internal)), snapEq) case (l@QuantifiedFieldChunk(id1, fvf1, condition1, condition1Exp, perm1, perm1Exp, invs1, singletonRcvr1, singletonRcvr1Exp, hints1), r@QuantifiedFieldChunk(_, fvf2, _, _, perm2, perm2Exp, _, _, _, hints2)) => assert(l.quantifiedVars == Seq(`?r`)) @@ -228,14 +228,14 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol val permSum = PermPlus(perm1, perm2) val permSumExp = perm1Exp.map(p1 => ast.PermAdd(p1, perm2Exp.get)()) val bestHints = if (hints1.nonEmpty) hints1 else hints2 - Some(fr2, QuantifiedFieldChunk(id1, combinedSnap, condition1, condition1Exp, permSum, permSumExp, invs1, singletonRcvr1, singletonRcvr1Exp, bestHints, v.decider.assumptionAnalyzer.getAnalysisInfo(AssumptionType.Internal)), snapEq) + Some(fr2, QuantifiedFieldChunk(id1, combinedSnap, condition1, condition1Exp, permSum, permSumExp, invs1, singletonRcvr1, singletonRcvr1Exp, bestHints, v.decider.getAnalysisInfo(AssumptionType.Internal)), snapEq) case (l@QuantifiedPredicateChunk(id1, qVars1, qVars1Exp, psf1, _, _, perm1, perm1Exp, _, _, _, _), r@QuantifiedPredicateChunk(_, qVars2, qVars2Exp, psf2, condition2, condition2Exp, perm2, perm2Exp, invs2, singletonArgs2, singletonArgs2Exp, hints2)) => val (fr2, combinedSnap, snapEq) = quantifiedChunkSupporter.combinePredicateSnapshotMaps(fr1, id1.name, qVars2, psf1, psf2, l.perm.replace(qVars1, qVars2), r.perm, v) val permSum = PermPlus(perm1.replace(qVars1, qVars2), perm2) val permSumExp = perm1Exp.map(p1 => ast.PermAdd(p1.replace(qVars1Exp.get.zip(qVars2Exp.get).toMap), perm2Exp.get)()) - Some(fr2, QuantifiedPredicateChunk(id1, qVars2, qVars2Exp, combinedSnap, condition2, condition2Exp, permSum, permSumExp, invs2, singletonArgs2, singletonArgs2Exp, hints2, v.decider.assumptionAnalyzer.getAnalysisInfo(AssumptionType.Internal)), snapEq) + Some(fr2, QuantifiedPredicateChunk(id1, qVars2, qVars2Exp, combinedSnap, condition2, condition2Exp, permSum, permSumExp, invs2, singletonArgs2, singletonArgs2Exp, hints2, v.decider.getAnalysisInfo(AssumptionType.Internal)), snapEq) case _ => None } From 75c4f829e9e7d14d7e435ae93916444d4f7b0e67 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 10 Jun 2025 15:27:10 +0200 Subject: [PATCH 089/474] remove workaround and fix pipeline (test) --- .../assumptionAnalysis/AnalysisSourceInfo.scala | 10 ++++++++-- src/main/scala/rules/Brancher.scala | 15 +++++++++------ src/main/scala/rules/Consumer.scala | 7 ++++--- src/main/scala/rules/Evaluator.scala | 7 ++++--- src/main/scala/rules/Executor.scala | 5 +++-- src/main/scala/rules/Producer.scala | 10 +++++----- 6 files changed, 33 insertions(+), 21 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala index 26764de9e..ce6a8a4ce 100644 --- a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala +++ b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala @@ -99,8 +99,14 @@ case class AnalysisSourceInfoStack (sourceInfoes: List[AnalysisSourceInfo] = Lis AnalysisSourceInfoStack(ExpAnalysisSourceInfo(e) +: sourceInfoes, forcedMainSource) } - def popAnalysisSourceInfo(): AnalysisSourceInfoStack = { - AnalysisSourceInfoStack(sourceInfoes.tail, forcedMainSource) + def popAnalysisSourceInfo(analysisSourceInfo: AnalysisSourceInfo): AnalysisSourceInfoStack = { + var currSourceInfo = sourceInfoes + // popping just one source info might not be enough since infeasible branches might return without popping the source info + while(currSourceInfo.nonEmpty && !currSourceInfo.head.equals(analysisSourceInfo)) { + currSourceInfo = currSourceInfo.tail + } + if(currSourceInfo.isEmpty || !currSourceInfo.head.equals(analysisSourceInfo)) throw new RuntimeException("unexpected source info") + AnalysisSourceInfoStack(currSourceInfo.tail, forcedMainSource) } def withForcedSource(description: String): AnalysisSourceInfoStack = { diff --git a/src/main/scala/rules/Brancher.scala b/src/main/scala/rules/Brancher.scala index cd326fc34..3d55f8f20 100644 --- a/src/main/scala/rules/Brancher.scala +++ b/src/main/scala/rules/Brancher.scala @@ -60,7 +60,8 @@ object brancher extends BranchingRules { && s.quantifiedVariables.map(_._1).exists(condition.freeVariables.contains)) ) - v.decider.updateAnalysisSourceInfo(_.addAnalysisSourceInfo(ExpAnalysisSourceInfo(conditionExp._1))) + val sourceInfo = ExpAnalysisSourceInfo(conditionExp._1) + v.decider.updateAnalysisSourceInfo(_.addAnalysisSourceInfo(sourceInfo)) /* True if the then-branch is to be explored */ val executeThenBranch = ( skipPathFeasibilityCheck @@ -71,7 +72,7 @@ object brancher extends BranchingRules { !executeThenBranch /* Assumes that ast least one branch is feasible */ || skipPathFeasibilityCheck || !v.decider.check(condition, Verifier.config.checkTimeout())) - v.decider.updateAnalysisSourceInfo(_.popAnalysisSourceInfo()) + v.decider.updateAnalysisSourceInfo(_.popAnalysisSourceInfo(sourceInfo)) val parallelizeElseBranch = s.parallelizeBranches && executeThenBranch && executeElseBranch @@ -151,9 +152,10 @@ object brancher extends BranchingRules { v0.decider.analysisSourceInfoStack = currentAnalysisSourceInfoStack executionFlowController.locally(s, v0)((s1, v1) => { v1.decider.prover.comment(s"[else-branch: $cnt | $negatedCondition]") - v1.decider.updateAnalysisSourceInfo(_.addAnalysisSourceInfo(conditionExp._1)) + val sourceInfo = ExpAnalysisSourceInfo(conditionExp._1) + v1.decider.updateAnalysisSourceInfo(_.addAnalysisSourceInfo(sourceInfo)) v1.decider.setCurrentBranchCondition(negatedCondition, (negatedConditionExp, negatedConditionExpNew)) - v1.decider.updateAnalysisSourceInfo(_.popAnalysisSourceInfo()) + v1.decider.updateAnalysisSourceInfo(_.popAnalysisSourceInfo(sourceInfo)) var functionsOfElseBranchdDeciderBefore: Set[FunctionDecl] = null var nMacrosOfElseBranchDeciderBefore: Int = 0 @@ -204,9 +206,10 @@ object brancher extends BranchingRules { v.decider.analysisSourceInfoStack = currentAnalysisSourceInfoStack executionFlowController.locally(s, v)((s1, v1) => { v1.decider.prover.comment(s"[then-branch: $cnt | $condition]") - v1.decider.updateAnalysisSourceInfo(_.addAnalysisSourceInfo(conditionExp._1)) + val sourceInfo = ExpAnalysisSourceInfo(conditionExp._1) + v1.decider.updateAnalysisSourceInfo(_.addAnalysisSourceInfo(sourceInfo)) v1.decider.setCurrentBranchCondition(condition, conditionExp) - v1.decider.updateAnalysisSourceInfo(_.popAnalysisSourceInfo()) + v1.decider.updateAnalysisSourceInfo(_.popAnalysisSourceInfo(sourceInfo)) fThen(v1.stateConsolidator(s1).consolidateOptionally(s1, v1), v1) }) diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index d2931001b..d2b280bf7 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -7,7 +7,7 @@ package viper.silicon.rules import viper.silicon.Config.JoinMode -import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType, StringAnalysisSourceInfo} +import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType, ExpAnalysisSourceInfo, StringAnalysisSourceInfo} import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.VerificationResult import viper.silicon.interfaces.state.Chunk @@ -176,10 +176,11 @@ object consumer extends ConsumptionRules { val s1 = s0.copy(h = s.h) /* s1 is s, but the retrying flag might be set */ val sepIdentifier = v1.symbExLog.openScope(new ConsumeRecord(a, s1, v.decider.pcs)) - v.decider.updateAnalysisSourceInfo(_.addAnalysisSourceInfo(a)) + val sourceInfo = ExpAnalysisSourceInfo(a) + v.decider.updateAnalysisSourceInfo(_.addAnalysisSourceInfo(sourceInfo)) consumeTlc(s1, h0, a, returnSnap, pve, v1)((s2, h2, snap2, consumedChunks, v2) => { - v.decider.updateAnalysisSourceInfo(_.popAnalysisSourceInfo()) + v.decider.updateAnalysisSourceInfo(_.popAnalysisSourceInfo(sourceInfo)) v2.symbExLog.closeScope(sepIdentifier) QS(s2, h2, snap2, consumedChunks, v2)}) })(Q) diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 9e7ceb540..1ee19d5fb 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -7,7 +7,7 @@ package viper.silicon.rules import viper.silicon.Config.JoinMode -import viper.silicon.assumptionAnalysis.AssumptionType +import viper.silicon.assumptionAnalysis.{AssumptionType, ExpAnalysisSourceInfo} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.interfaces._ @@ -93,9 +93,10 @@ object evaluator extends EvaluationRules { : VerificationResult = { val sepIdentifier = v.symbExLog.openScope(new EvaluateRecord(e, s, v.decider.pcs)) - v.decider.updateAnalysisSourceInfo(_.addAnalysisSourceInfo(e)) + val sourceInfo = ExpAnalysisSourceInfo(e) + v.decider.updateAnalysisSourceInfo(_.addAnalysisSourceInfo(sourceInfo)) eval3(s, e, pve, v)((s1, t, eNew, v1) => { - v.decider.updateAnalysisSourceInfo(_.popAnalysisSourceInfo()) // TODO ake: can return multiple times + v1.decider.updateAnalysisSourceInfo(_.popAnalysisSourceInfo(sourceInfo)) v1.symbExLog.closeScope(sepIdentifier) Q(s1, t, eNew, v1)}) } diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 162cfafcf..4d61be575 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -326,9 +326,10 @@ object executor extends ExecutionRules { (Q: (State, Verifier) => VerificationResult) : VerificationResult = { val sepIdentifier = v.symbExLog.openScope(new ExecuteRecord(stmt, s, v.decider.pcs)) - v.decider.updateAnalysisSourceInfo(_.addAnalysisSourceInfo(StmtAnalysisSourceInfo(stmt))) + val sourceInfo = StmtAnalysisSourceInfo(stmt) + v.decider.updateAnalysisSourceInfo(_.addAnalysisSourceInfo(sourceInfo)) exec2(s, stmt, v)((s1, v1) => { - v.decider.updateAnalysisSourceInfo(_.popAnalysisSourceInfo()) + v.decider.updateAnalysisSourceInfo(_.popAnalysisSourceInfo(sourceInfo)) v1.symbExLog.closeScope(sepIdentifier) Q(s1, v1)}) } diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index 2d0658673..704b17102 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -7,7 +7,7 @@ package viper.silicon.rules import viper.silicon.Config.JoinMode -import viper.silicon.assumptionAnalysis.{AssumptionAnalyzer, AssumptionType} +import viper.silicon.assumptionAnalysis.{AssumptionAnalyzer, AssumptionType, ExpAnalysisSourceInfo} import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.{Unreachable, VerificationResult} @@ -154,11 +154,11 @@ object producer extends ProductionRules { val a = as.head.whenInhaling val pve = pves.head - - v.decider.updateAnalysisSourceInfo(_.addAnalysisSourceInfo(a)) + val sourceInfo = ExpAnalysisSourceInfo(a) + v.decider.updateAnalysisSourceInfo(_.addAnalysisSourceInfo(sourceInfo)) if (as.tail.isEmpty) wrappedProduceTlc(s, sf, a, pve, v, assumptionType)((s1, v1) => { - v.decider.updateAnalysisSourceInfo(_.popAnalysisSourceInfo()) + v.decider.updateAnalysisSourceInfo(_.popAnalysisSourceInfo(sourceInfo)) Q(s1, v1) }) else { @@ -172,7 +172,7 @@ object producer extends ProductionRules { */ wrappedProduceTlc(s, sf0, a, pve, v, assumptionType)((s1, v1) => { - v1.decider.updateAnalysisSourceInfo(_.popAnalysisSourceInfo()) + v1.decider.updateAnalysisSourceInfo(_.popAnalysisSourceInfo(sourceInfo)) produceTlcs(s1, sf1, as.tail, pves.tail, v1, assumptionType)(Q) }) } catch { From 9979a32a02e09ffda3b14bdfc4d07bd92e057e8b Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 10 Jun 2025 15:50:27 +0200 Subject: [PATCH 090/474] run pipeline with assumption analysis enabled --- src/main/scala/Config.scala | 4 ++-- src/main/scala/verifier/DefaultMainVerifier.scala | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/scala/Config.scala b/src/main/scala/Config.scala index efe4f8a28..d0b6d9d0e 100644 --- a/src/main/scala/Config.scala +++ b/src/main/scala/Config.scala @@ -820,13 +820,13 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { val enableDebugging: ScallopOption[Boolean] = opt[Boolean]("enableDebugging", descr = "Enable debugging mode", - default = Some(false), + default = Some(true), noshort = true ) val enableAssumptionAnalysis: ScallopOption[Boolean] = opt[Boolean]("enableAssumptionAnalysis", descr = "Enable assumption analysis mode", - default = Some(false), + default = Some(true), noshort = true ) diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index 3a48093ca..7fbeeba50 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -59,7 +59,7 @@ class DefaultMainVerifier(config: Config, Verifier.config = config - override val debugMode = config.enableDebugging() + override val debugMode: Boolean = config.enableDebugging() private val uniqueIdCounter = new Counter(1) def nextUniqueVerifierId(): String = f"${uniqueIdCounter.next()}%02d" @@ -316,14 +316,14 @@ class DefaultMainVerifier(config: Config, if(Verifier.config.enableAssumptionAnalysis()){ val assumptionAnalyzers = verificationResults.filter(_.assumptionAnalyzer.isInstanceOf[DefaultAssumptionAnalyzer]).map(_.assumptionAnalyzer) assumptionAnalyzers.foreach(_.assumptionGraph.addTransitiveEdges()) - assumptionAnalyzers foreach (_.exportGraph()) +// assumptionAnalyzers foreach (_.exportGraph()) logger debug s"assumption analyzers ${assumptionAnalyzers.mkString(", ")}" } - if (Verifier.config.enableDebugging()){ - val debugger = new SiliconDebugger(verificationResults, identifierFactory, reporter, FrontendStateCache.resolver, FrontendStateCache.pprogram, FrontendStateCache.translator, this) - debugger.startDebugger() - } +// if (Verifier.config.enableDebugging()){ +// val debugger = new SiliconDebugger(verificationResults, identifierFactory, reporter, FrontendStateCache.resolver, FrontendStateCache.pprogram, FrontendStateCache.translator, this) +// debugger.startDebugger() +// } verificationResults } From f3eaecb8d0c9df2f7d9e26bd92a8f9031f36d3f0 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 10 Jun 2025 17:06:37 +0200 Subject: [PATCH 091/474] refactor analysis source info stack --- .../AnalysisSourceInfo.scala | 41 ++++++++++--------- src/main/scala/decider/Decider.scala | 5 --- src/main/scala/rules/Brancher.scala | 18 ++++---- src/main/scala/rules/Consumer.scala | 4 +- src/main/scala/rules/Evaluator.scala | 8 ++-- src/main/scala/rules/Executor.scala | 20 ++++----- src/main/scala/rules/Producer.scala | 6 +-- .../scala/rules/QuantifiedChunkSupport.scala | 24 +++++------ src/main/scala/rules/StateConsolidator.scala | 4 +- 9 files changed, 64 insertions(+), 66 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala index ce6a8a4ce..5b496addd 100644 --- a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala +++ b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala @@ -77,47 +77,50 @@ case class CompositeAnalysisSourceInfo(coarseGrainedSource: AnalysisSourceInfo, } -case class AnalysisSourceInfoStack (sourceInfoes: List[AnalysisSourceInfo] = List.empty, - forcedMainSource: Option[AnalysisSourceInfo] = None){ +case class AnalysisSourceInfoStack() { + private var sourceInfos: List[AnalysisSourceInfo] = List.empty + private var forcedMainSource: Option[AnalysisSourceInfo] = None + def getAnalysisSourceInfos: List[AnalysisSourceInfo] = sourceInfos def getFullSourceInfo: AnalysisSourceInfo = { if(forcedMainSource.isDefined) return forcedMainSource.get - if(sourceInfoes.size <= 1){ - sourceInfoes.headOption.getOrElse(NoAnalysisSourceInfo()) + if(sourceInfos.size <= 1){ + sourceInfos.headOption.getOrElse(NoAnalysisSourceInfo()) } else{ - CompositeAnalysisSourceInfo(sourceInfoes.last, sourceInfoes.head) + CompositeAnalysisSourceInfo(sourceInfos.last, sourceInfos.head) } } - def addAnalysisSourceInfo(analysisSourceInfo: AnalysisSourceInfo): AnalysisSourceInfoStack = { - AnalysisSourceInfoStack(analysisSourceInfo +: sourceInfoes, forcedMainSource) + def addAnalysisSourceInfo(analysisSourceInfo: AnalysisSourceInfo): Unit = { + sourceInfos = analysisSourceInfo +: sourceInfos } - def addAnalysisSourceInfo(e: ast.Exp): AnalysisSourceInfoStack = { - AnalysisSourceInfoStack(ExpAnalysisSourceInfo(e) +: sourceInfoes, forcedMainSource) + def setAnalysisSourceInfo(analysisSourceInfo: List[AnalysisSourceInfo]): Unit = { + sourceInfos = analysisSourceInfo } - def popAnalysisSourceInfo(analysisSourceInfo: AnalysisSourceInfo): AnalysisSourceInfoStack = { - var currSourceInfo = sourceInfoes + def popAnalysisSourceInfo(analysisSourceInfo: AnalysisSourceInfo): Unit = { + var currSourceInfo = sourceInfos // popping just one source info might not be enough since infeasible branches might return without popping the source info while(currSourceInfo.nonEmpty && !currSourceInfo.head.equals(analysisSourceInfo)) { currSourceInfo = currSourceInfo.tail } - if(currSourceInfo.isEmpty || !currSourceInfo.head.equals(analysisSourceInfo)) throw new RuntimeException("unexpected source info") - AnalysisSourceInfoStack(currSourceInfo.tail, forcedMainSource) + if(currSourceInfo.isEmpty || !currSourceInfo.head.equals(analysisSourceInfo)) + throw new RuntimeException("unexpected source info") + sourceInfos = currSourceInfo.tail } - def withForcedSource(description: String): AnalysisSourceInfoStack = { - AnalysisSourceInfoStack(sourceInfoes, Some(StringAnalysisSourceInfo(description, getFullSourceInfo.getPosition))) + def setForcedSource(description: String): Unit = { + forcedMainSource = Some(StringAnalysisSourceInfo(description, getFullSourceInfo.getPosition)) } - def withForcedSource(source: AnalysisSourceInfo): AnalysisSourceInfoStack = { - AnalysisSourceInfoStack(sourceInfoes, Some(source)) + def setForcedSource(source: AnalysisSourceInfo): Unit = { + forcedMainSource = Some(source) } - def withoutForcedSource(): AnalysisSourceInfoStack = { - AnalysisSourceInfoStack(sourceInfoes, None) + def removeForcedSource(): Unit = { + forcedMainSource = None } } \ No newline at end of file diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index bb2ee9c52..09c5e935a 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -107,7 +107,6 @@ trait Decider { var analysisSourceInfoStack: AnalysisSourceInfoStack def initAssumptionAnalyzer(member: Member, preambleNodes: Iterable[AssumptionAnalysisNode]): Unit def removeAssumptionAnalyzer(): Unit - def updateAnalysisSourceInfo(f: AnalysisSourceInfoStack => AnalysisSourceInfoStack): Unit def getAnalysisInfo: AnalysisInfo def getAnalysisInfo(assumptionType: AssumptionType): AnalysisInfo } @@ -158,10 +157,6 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => prover.setAssumptionAnalyzer(assumptionAnalyzer) } - override def updateAnalysisSourceInfo(f: AnalysisSourceInfoStack => AnalysisSourceInfoStack): Unit = { - analysisSourceInfoStack = f(analysisSourceInfoStack) - } - def getAnalysisInfo: AnalysisInfo = getAnalysisInfo(AssumptionType.Implicit) def getAnalysisInfo(assumptionType: AssumptionType): AnalysisInfo = AnalysisInfo(assumptionAnalyzer, analysisSourceInfoStack.getFullSourceInfo, assumptionType) diff --git a/src/main/scala/rules/Brancher.scala b/src/main/scala/rules/Brancher.scala index 3d55f8f20..3746eec0d 100644 --- a/src/main/scala/rules/Brancher.scala +++ b/src/main/scala/rules/Brancher.scala @@ -61,7 +61,7 @@ object brancher extends BranchingRules { ) val sourceInfo = ExpAnalysisSourceInfo(conditionExp._1) - v.decider.updateAnalysisSourceInfo(_.addAnalysisSourceInfo(sourceInfo)) + v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo) /* True if the then-branch is to be explored */ val executeThenBranch = ( skipPathFeasibilityCheck @@ -72,7 +72,7 @@ object brancher extends BranchingRules { !executeThenBranch /* Assumes that ast least one branch is feasible */ || skipPathFeasibilityCheck || !v.decider.check(condition, Verifier.config.checkTimeout())) - v.decider.updateAnalysisSourceInfo(_.popAnalysisSourceInfo(sourceInfo)) + v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) val parallelizeElseBranch = s.parallelizeBranches && executeThenBranch && executeElseBranch @@ -102,7 +102,7 @@ object brancher extends BranchingRules { var macrosOfElseBranchDecider: Seq[MacroDecl] = null var pcsForElseBranch: PathConditionStack = null var noOfErrors = 0 - val currentAnalysisSourceInfoStack = v.decider.analysisSourceInfoStack + val currentAnalysisSourceInfos = v.decider.analysisSourceInfoStack.getAnalysisSourceInfos val elseBranchVerificationTask: Verifier => VerificationResult = if (executeElseBranch) { @@ -149,13 +149,13 @@ object brancher extends BranchingRules { } elseBranchVerifier = v0.uniqueId - v0.decider.analysisSourceInfoStack = currentAnalysisSourceInfoStack + v0.decider.analysisSourceInfoStack.setAnalysisSourceInfo(currentAnalysisSourceInfos) executionFlowController.locally(s, v0)((s1, v1) => { v1.decider.prover.comment(s"[else-branch: $cnt | $negatedCondition]") val sourceInfo = ExpAnalysisSourceInfo(conditionExp._1) - v1.decider.updateAnalysisSourceInfo(_.addAnalysisSourceInfo(sourceInfo)) + v1.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo) v1.decider.setCurrentBranchCondition(negatedCondition, (negatedConditionExp, negatedConditionExpNew)) - v1.decider.updateAnalysisSourceInfo(_.popAnalysisSourceInfo(sourceInfo)) + v1.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) var functionsOfElseBranchdDeciderBefore: Set[FunctionDecl] = null var nMacrosOfElseBranchDeciderBefore: Int = 0 @@ -203,13 +203,13 @@ object brancher extends BranchingRules { val res = { val thenRes = if (executeThenBranch) { v.symbExLog.markReachable(uidBranchPoint) - v.decider.analysisSourceInfoStack = currentAnalysisSourceInfoStack + v.decider.analysisSourceInfoStack.setAnalysisSourceInfo(currentAnalysisSourceInfos) executionFlowController.locally(s, v)((s1, v1) => { v1.decider.prover.comment(s"[then-branch: $cnt | $condition]") val sourceInfo = ExpAnalysisSourceInfo(conditionExp._1) - v1.decider.updateAnalysisSourceInfo(_.addAnalysisSourceInfo(sourceInfo)) + v1.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo) v1.decider.setCurrentBranchCondition(condition, conditionExp) - v1.decider.updateAnalysisSourceInfo(_.popAnalysisSourceInfo(sourceInfo)) + v1.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) fThen(v1.stateConsolidator(s1).consolidateOptionally(s1, v1), v1) }) diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index d2b280bf7..9e6be155c 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -177,10 +177,10 @@ object consumer extends ConsumptionRules { val sepIdentifier = v1.symbExLog.openScope(new ConsumeRecord(a, s1, v.decider.pcs)) val sourceInfo = ExpAnalysisSourceInfo(a) - v.decider.updateAnalysisSourceInfo(_.addAnalysisSourceInfo(sourceInfo)) + v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo) consumeTlc(s1, h0, a, returnSnap, pve, v1)((s2, h2, snap2, consumedChunks, v2) => { - v.decider.updateAnalysisSourceInfo(_.popAnalysisSourceInfo(sourceInfo)) + v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) v2.symbExLog.closeScope(sepIdentifier) QS(s2, h2, snap2, consumedChunks, v2)}) })(Q) diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 1ee19d5fb..2bc346be0 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -94,9 +94,9 @@ object evaluator extends EvaluationRules { val sepIdentifier = v.symbExLog.openScope(new EvaluateRecord(e, s, v.decider.pcs)) val sourceInfo = ExpAnalysisSourceInfo(e) - v.decider.updateAnalysisSourceInfo(_.addAnalysisSourceInfo(sourceInfo)) + v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo) eval3(s, e, pve, v)((s1, t, eNew, v1) => { - v1.decider.updateAnalysisSourceInfo(_.popAnalysisSourceInfo(sourceInfo)) + v1.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) v1.symbExLog.closeScope(sepIdentifier) Q(s1, t, eNew, v1)}) } @@ -828,9 +828,9 @@ object evaluator extends EvaluationRules { val auxNonGlobalsExp = auxExps.map(_._2) val commentGlobal = "Nested auxiliary terms: globals (aux)" v1.decider.prover.comment(commentGlobal) - v1.decider.updateAnalysisSourceInfo(_.withForcedSource(commentGlobal)) + v1.decider.analysisSourceInfoStack.setForcedSource(commentGlobal) v1.decider.assume(tAuxGlobal, Option.when(withExp)(DebugExp.createInstance(description=commentGlobal, children=auxGlobalsExp.get)), enforceAssumption = false, assumptionType=AssumptionType.Internal) - v1.decider.updateAnalysisSourceInfo(_.withoutForcedSource()) + v1.decider.analysisSourceInfoStack.removeForcedSource() val commentNonGlobals = "Nested auxiliary terms: non-globals (aux)" v1.decider.prover.comment(commentNonGlobals) v1.decider.assume(tAuxHeapIndep/*tAux*/, Option.when(withExp)(DebugExp.createInstance(description=commentNonGlobals, children=auxNonGlobalsExp.get)), enforceAssumption = false, assumptionType=AssumptionType.Internal) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 4d61be575..101a4dcc3 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -327,9 +327,9 @@ object executor extends ExecutionRules { : VerificationResult = { val sepIdentifier = v.symbExLog.openScope(new ExecuteRecord(stmt, s, v.decider.pcs)) val sourceInfo = StmtAnalysisSourceInfo(stmt) - v.decider.updateAnalysisSourceInfo(_.addAnalysisSourceInfo(sourceInfo)) + v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo) exec2(s, stmt, v)((s1, v1) => { - v.decider.updateAnalysisSourceInfo(_.popAnalysisSourceInfo(sourceInfo)) + v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) v1.symbExLog.closeScope(sepIdentifier) Q(s1, v1)}) } @@ -402,7 +402,7 @@ object executor extends ExecutionRules { s2 } v2.decider.clearModel() - v2.decider.updateAnalysisSourceInfo(_.withForcedSource(ExpAnalysisSourceInfo(fa))) + v2.decider.analysisSourceInfoStack.setForcedSource(ExpAnalysisSourceInfo(fa)) val result = quantifiedChunkSupporter.removePermissions( s2p, relevantChunks, @@ -417,7 +417,7 @@ object executor extends ExecutionRules { chunkOrderHeuristics, v2 ) - v2.decider.updateAnalysisSourceInfo(_.withoutForcedSource()) + v2.decider.analysisSourceInfoStack.removeForcedSource() result match { case (Complete(), s3, remainingChunks, consumedChunks) => // TODO ake: what to do with consumedChunks? val h3 = Heap(remainingChunks ++ otherChunks) @@ -425,10 +425,10 @@ object executor extends ExecutionRules { v1.decider.prover.comment("Definitional axioms for singleton-FVF's value") val debugExp = Option.when(withExp)(DebugExp.createInstance("Definitional axioms for singleton-FVF's value", isInternal_ = true)) v1.decider.assumeDefinition(smValueDef, debugExp, AssumptionType.Internal) - v1.decider.updateAnalysisSourceInfo(_.withForcedSource(ExpAnalysisSourceInfo(fa))) + v1.decider.analysisSourceInfoStack.setForcedSource(ExpAnalysisSourceInfo(fa)) val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(Seq(`?r`), Option.when(withExp)(Seq(ast.LocalVarDecl("r", ast.Ref)(ass.pos, ass.info, ass.errT))), field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), sm, s.program, v1, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit), isExhale=false) - v1.decider.updateAnalysisSourceInfo(_.withoutForcedSource()) + v1.decider.analysisSourceInfoStack.removeForcedSource() if (s3.heapDependentTriggers.contains(field)) { val debugExp2 = Option.when(withExp)(DebugExp.createInstance(s"FieldTrigger(${eRcvrNew.toString}.${field.name})")) v1.decider.assume(FieldTrigger(field.name, sm, tRcvr), debugExp2, AssumptionType.Internal) @@ -448,15 +448,15 @@ object executor extends ExecutionRules { val resource = fa.res(s.program) val ve = pve dueTo InsufficientPermission(fa) val description = s"consume ${ass.pos}: $ass" - v2.decider.updateAnalysisSourceInfo(_.withForcedSource(ExpAnalysisSourceInfo(fa))) + v2.decider.analysisSourceInfoStack.setForcedSource(ExpAnalysisSourceInfo(fa)) chunkSupporter.consume(s2, s2.h, resource, Seq(tRcvr), eRcvrNew.map(Seq(_)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), false, ve, v2, description)((s3, h3, _, consumedChunks, v3) => { - v3.decider.updateAnalysisSourceInfo(_.withForcedSource(StmtAnalysisSourceInfo(ass))) + v3.decider.analysisSourceInfoStack.setForcedSource(StmtAnalysisSourceInfo(ass)) val (tSnap, _) = ssaifyRhs(tRhs, rhs, rhsNew, field.name, field.typ, v3, s3, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit)) val id = BasicChunkIdentifier(field.name) - v3.decider.updateAnalysisSourceInfo(_.withForcedSource(ExpAnalysisSourceInfo(fa))) + v3.decider.analysisSourceInfoStack.setForcedSource(ExpAnalysisSourceInfo(fa)) val newChunk = BasicChunk.createDerivedChunk(Set.empty, FieldID, id, Seq(tRcvr), eRcvrNew.map(Seq(_)), tSnap, rhsNew, FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), v3.decider.getAnalysisInfo(annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit))) - v3.decider.updateAnalysisSourceInfo(_.withoutForcedSource()) + v3.decider.analysisSourceInfoStack.removeForcedSource() chunkSupporter.produce(s3, h3, newChunk, v3)((s4, h4, v4) => { val s5 = s4.copy(h = h4) val (debugHeapName, _) = v4.getDebugOldLabel(s5, fa.pos) diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index 704b17102..4ed258785 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -155,10 +155,10 @@ object producer extends ProductionRules { val pve = pves.head val sourceInfo = ExpAnalysisSourceInfo(a) - v.decider.updateAnalysisSourceInfo(_.addAnalysisSourceInfo(sourceInfo)) + v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo) if (as.tail.isEmpty) wrappedProduceTlc(s, sf, a, pve, v, assumptionType)((s1, v1) => { - v.decider.updateAnalysisSourceInfo(_.popAnalysisSourceInfo(sourceInfo)) + v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) Q(s1, v1) }) else { @@ -172,7 +172,7 @@ object producer extends ProductionRules { */ wrappedProduceTlc(s, sf0, a, pve, v, assumptionType)((s1, v1) => { - v1.decider.updateAnalysisSourceInfo(_.popAnalysisSourceInfo(sourceInfo)) + v1.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) produceTlcs(s1, sf1, as.tail, pves.tail, v1, assumptionType)(Q) }) } catch { diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 32bd70d87..1fd524152 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -732,7 +732,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { smDef: SnapshotMapDefinition, v: Verifier) : (PermMapDefinition, PmCache) = { - v.decider.updateAnalysisSourceInfo(_.withForcedSource("summarizing heap")) + v.decider.analysisSourceInfoStack.setForcedSource("summarizing heap") v.decider.assumptionAnalyzer.addForcedDependencies(relevantChunks.toSet) val res = Verifier.config.mapCache(s.pmCache.get(resource, relevantChunks)) match { case Some(pmDef) => @@ -745,7 +745,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.assume(valueDef, Option.when(withExp)(DebugExp.createInstance("value definitions", isInternal_ = true)), enforceAssumption = false, assumptionType=AssumptionType.Internal) (pmDef, s.pmCache + ((resource, relevantChunks) -> pmDef)) } - v.decider.updateAnalysisSourceInfo(_.withoutForcedSource()) + v.decider.analysisSourceInfoStack.removeForcedSource() res } @@ -773,7 +773,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optSmDomainDefinitionCondition: Option[Term] = None, optQVarsInstantiations: Option[Seq[Term]] = None) : (SnapshotMapDefinition, SnapshotMapCache) = { - v.decider.updateAnalysisSourceInfo(_.withForcedSource("summarizing heap")) + v.decider.analysisSourceInfoStack.setForcedSource("summarizing heap") v.decider.assumptionAnalyzer.addForcedDependencies(relevantChunks.toSet) def emitSnapshotMapDefinition(s: State, @@ -846,7 +846,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { emitSnapshotMapDefinition(s, smDef, v, optQVarsInstantiations) v.decider.assumptionAnalyzer.unsetForcedDependencies() - v.decider.updateAnalysisSourceInfo(_.withoutForcedSource()) + v.decider.analysisSourceInfoStack.removeForcedSource() (smDef, smCache) } @@ -858,7 +858,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optSmDomainDefinitionCondition: Option[Term] = None, optQVarsInstantiations: Option[Seq[Term]] = None) : (State, SnapshotMapDefinition, PermMapDefinition) = { - v.decider.updateAnalysisSourceInfo(_.withForcedSource("summarizing heap")) + v.decider.analysisSourceInfoStack.setForcedSource("summarizing heap") v.decider.assumptionAnalyzer.addForcedDependencies(relevantChunks.toSet) val (smDef, smCache) = summarisingSnapshotMap( @@ -872,7 +872,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val s2 = s1.copy(pmCache = pmCache) v.decider.assumptionAnalyzer.unsetForcedDependencies() - v.decider.updateAnalysisSourceInfo(_.withoutForcedSource()) + v.decider.analysisSourceInfoStack.removeForcedSource() (s2, smDef, pmDef) } @@ -885,7 +885,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { relevantChunks: Seq[QuantifiedBasicChunk], v: Verifier) : (State, PermMapDefinition) = { - v.decider.updateAnalysisSourceInfo(_.withForcedSource("summarizing heap")) + v.decider.analysisSourceInfoStack.setForcedSource("summarizing heap") v.decider.assumptionAnalyzer.addForcedDependencies(relevantChunks.toSet) val s1 = s val (pmDef, pmCache) = @@ -894,7 +894,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val s2 = s1.copy(pmCache = pmCache) v.decider.assumptionAnalyzer.unsetForcedDependencies() - v.decider.updateAnalysisSourceInfo(_.withoutForcedSource()) + v.decider.analysisSourceInfoStack.removeForcedSource() (s2, pmDef) } @@ -1003,10 +1003,10 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val commentGlobals = "Nested auxiliary terms: globals" v.decider.prover.comment(commentGlobals) - v.decider.updateAnalysisSourceInfo(_.withForcedSource(commentGlobals)) + v.decider.analysisSourceInfoStack.setForcedSource(commentGlobals) v.decider.assume(auxGlobals, Option.when(withExp)(DebugExp.createInstance(description=commentGlobals, children=auxGlobalsExp.get)), enforceAssumption = false, assumptionType=AssumptionType.Internal) - v.decider.updateAnalysisSourceInfo(_.withoutForcedSource()) + v.decider.analysisSourceInfoStack.removeForcedSource() val commentNonGlobals = "Nested auxiliary terms: non-globals" v.decider.prover.comment(commentNonGlobals) @@ -1237,9 +1237,9 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val comment = "Nested auxiliary terms: globals" v.decider.prover.comment(comment) - v.decider.updateAnalysisSourceInfo(_.withForcedSource(comment)) + v.decider.analysisSourceInfoStack.setForcedSource(comment) v.decider.assume(auxGlobals, Option.when(withExp)(DebugExp.createInstance(description=comment, children=auxGlobalsExp.get)), enforceAssumption = false, assumptionType=AssumptionType.Internal) - v.decider.updateAnalysisSourceInfo(_.withoutForcedSource()) + v.decider.analysisSourceInfoStack.removeForcedSource() val comment2 = "Nested auxiliary terms: non-globals" v.decider.prover.comment(comment2) diff --git a/src/main/scala/rules/StateConsolidator.scala b/src/main/scala/rules/StateConsolidator.scala index c347f92ad..e0f8b2363 100644 --- a/src/main/scala/rules/StateConsolidator.scala +++ b/src/main/scala/rules/StateConsolidator.scala @@ -61,7 +61,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol def consolidate(s: State, v: Verifier): State = { val comLog = new CommentRecord("state consolidation", s, v.decider.pcs) val sepIdentifier = v.symbExLog.openScope(comLog) - v.decider.updateAnalysisSourceInfo(_.withForcedSource("state consolidation")) + v.decider.analysisSourceInfoStack.setForcedSource("state consolidation") v.decider.prover.comment("[state consolidation]") v.decider.prover.saturate(config.proverSaturationTimeouts.beforeIteration) @@ -122,7 +122,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol reserveHeaps = mergedHeaps.tail) val s2 = assumeUpperPermissionBoundForQPFields(s1, v) - v.decider.updateAnalysisSourceInfo(_.withoutForcedSource()) + v.decider.analysisSourceInfoStack.removeForcedSource() s2 } From 34b36c356084a8bc0b4341154b2249f8c44801fb Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 10 Jun 2025 17:14:37 +0200 Subject: [PATCH 092/474] add config option & validation --- src/main/scala/Config.scala | 33 +++++++++++++++++++ .../scala/verifier/DefaultMainVerifier.scala | 8 ++--- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/src/main/scala/Config.scala b/src/main/scala/Config.scala index d0b6d9d0e..b4b3535bd 100644 --- a/src/main/scala/Config.scala +++ b/src/main/scala/Config.scala @@ -824,6 +824,12 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { noshort = true ) + val startDebuggerAutomatically: ScallopOption[Boolean] = opt[Boolean]("startDebuggerAutomatically", + descr = "Starts the debugging mode automatically after verification completes", + default = Some(false), + noshort = true + ) + val enableAssumptionAnalysis: ScallopOption[Boolean] = opt[Boolean]("enableAssumptionAnalysis", descr = "Enable assumption analysis mode", default = Some(true), @@ -877,6 +883,33 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { validateFileOpt(multisetAxiomatizationFile) validateFileOpt(sequenceAxiomatizationFile) + validateOpt(enableAssumptionAnalysis, parallelizeBranches) { + case (Some(false), _) => Right(()) + case (_, Some(false)) => Right(()) + case (Some(true), Some(true)) => + Left(s"Option ${enableAssumptionAnalysis.name} is not supported in combination with ${parallelizeBranches.name}") + case other => + sys.error(s"Unexpected combination: $other") + } + + validateOpt(enableAssumptionAnalysis, enableDebugging) { + case (Some(false), _) => Right(()) + case (Some(true), Some(true)) => Right(()) + case (Some(true), Some(false)) => + Left(s"Option ${enableAssumptionAnalysis.name} requires option ${enableDebugging.name}") + case other => + sys.error(s"Unexpected combination: $other") + } + + validateOpt(startDebuggerAutomatically, enableDebugging) { + case (Some(false), _) => Right(()) + case (Some(true), Some(true)) => Right(()) + case (Some(true), Some(false)) => + Left(s"Option ${startDebuggerAutomatically.name} requires option ${enableDebugging.name}") + case other => + sys.error(s"Unexpected combination: $other") + } + /* Finalise configuration */ verify() diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index 7fbeeba50..8499984a7 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -320,10 +320,10 @@ class DefaultMainVerifier(config: Config, logger debug s"assumption analyzers ${assumptionAnalyzers.mkString(", ")}" } -// if (Verifier.config.enableDebugging()){ -// val debugger = new SiliconDebugger(verificationResults, identifierFactory, reporter, FrontendStateCache.resolver, FrontendStateCache.pprogram, FrontendStateCache.translator, this) -// debugger.startDebugger() -// } + if (Verifier.config.startDebuggerAutomatically()){ + val debugger = new SiliconDebugger(verificationResults, identifierFactory, reporter, FrontendStateCache.resolver, FrontendStateCache.pprogram, FrontendStateCache.translator, this) + debugger.startDebugger() + } verificationResults } From 6bc405055b4ad5c07caea55d0cd532aba92d85c8 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 11 Jun 2025 09:59:49 +0200 Subject: [PATCH 093/474] disable analysis by default --- src/main/scala/Config.scala | 4 ++-- src/main/scala/verifier/DefaultMainVerifier.scala | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/scala/Config.scala b/src/main/scala/Config.scala index b4b3535bd..0b05d02ab 100644 --- a/src/main/scala/Config.scala +++ b/src/main/scala/Config.scala @@ -820,7 +820,7 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { val enableDebugging: ScallopOption[Boolean] = opt[Boolean]("enableDebugging", descr = "Enable debugging mode", - default = Some(true), + default = Some(false), noshort = true ) @@ -832,7 +832,7 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { val enableAssumptionAnalysis: ScallopOption[Boolean] = opt[Boolean]("enableAssumptionAnalysis", descr = "Enable assumption analysis mode", - default = Some(true), + default = Some(false), noshort = true ) diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index 8499984a7..06a1e1998 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -316,7 +316,7 @@ class DefaultMainVerifier(config: Config, if(Verifier.config.enableAssumptionAnalysis()){ val assumptionAnalyzers = verificationResults.filter(_.assumptionAnalyzer.isInstanceOf[DefaultAssumptionAnalyzer]).map(_.assumptionAnalyzer) assumptionAnalyzers.foreach(_.assumptionGraph.addTransitiveEdges()) -// assumptionAnalyzers foreach (_.exportGraph()) + assumptionAnalyzers foreach (_.exportGraph()) logger debug s"assumption analyzers ${assumptionAnalyzers.mkString(", ")}" } From 7d1ca3421dfebc1e1595eceacac6b577e1d68102 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 11 Jun 2025 10:00:20 +0200 Subject: [PATCH 094/474] add some test examples --- src/test/resources/andrea/gaussian.vpr | 5 ++-- src/test/resources/andrea/method-sum.vpr | 3 +++ .../andrea/quantified-permissions.vpr | 25 +++++++++++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 src/test/resources/andrea/quantified-permissions.vpr diff --git a/src/test/resources/andrea/gaussian.vpr b/src/test/resources/andrea/gaussian.vpr index b4d2b6769..b65f5246f 100644 --- a/src/test/resources/andrea/gaussian.vpr +++ b/src/test/resources/andrea/gaussian.vpr @@ -2,16 +2,17 @@ field f: Int method gaussianSimple(n: Int) returns (res: Int) requires 0 <= n - requires n <= 5 + requires @assumptionType("Implicit")(n <= 5) ensures res == n * (n + 1) / 2 { res := 0 var i: Int := 0 while(i <= n) - invariant i <= (n + 1) + invariant @assumptionType("Implicit")(i <= (n + 1)) invariant i <= 6 invariant res == (i - 1) * i / 2 { + @assumptionType("Explicit") res := res + i i := i + 1 } diff --git a/src/test/resources/andrea/method-sum.vpr b/src/test/resources/andrea/method-sum.vpr index 77054ebfe..8a5c1f5f0 100644 --- a/src/test/resources/andrea/method-sum.vpr +++ b/src/test/resources/andrea/method-sum.vpr @@ -15,6 +15,9 @@ method sumClient(x: Int, y: Int) assume y >= 0 assume x < y var n: Int := sum(x, y) + + // the following stmt reports dependency on n := sum(x, y) because (x < y && n == x + y && n > 100 ==> y != 0) + // although you could also prove it via (0 <= x && x < y ==> y != 0) var n2: Int := sum(x/y, y) n := n + n2 diff --git a/src/test/resources/andrea/quantified-permissions.vpr b/src/test/resources/andrea/quantified-permissions.vpr new file mode 100644 index 000000000..d7cb6de6a --- /dev/null +++ b/src/test/resources/andrea/quantified-permissions.vpr @@ -0,0 +1,25 @@ +field f: Int + +method foo(xs: Seq[Ref], ys: Seq[Ref]) { + assume |xs| > 5 && |ys| > 5 + inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) + inhale forall y: Ref :: y in ys ==> (acc(y.f) && y.f > 0) + + assert xs[0].f > 0 +} + +method foo2(xs: Seq[Ref], ys: Seq[Ref]) { + assume |xs| > 5 && |ys| > 5 + inhale forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0) + inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard) && y.f > 0) + + assert xs[0].f > 0 +} + +method foo3(xs: Seq[Ref], ys: Seq[Ref]) { + assume |xs| > 5 && |ys| > 5 + inhale forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0) + inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard)) + + assert xs[0].f > 0 +} \ No newline at end of file From 9d40056b79c06f69799a5f26770775654a640690 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 14 Jun 2025 11:13:56 +0200 Subject: [PATCH 095/474] qps: resolve impreciseness caused by heap summary --- .../AssumptionAnalysisGraph.scala | 21 ++--- .../AssumptionAnalyzer.scala | 86 ++++++++++++------- .../scala/rules/QuantifiedChunkSupport.scala | 42 +++++---- 3 files changed, 89 insertions(+), 60 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index 2b9160e48..60abeedde 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -77,10 +77,10 @@ trait AssumptionAnalysisGraph { val nodesPerSourceInfo = getNodesPerSourceInfo() nodesPerSourceInfo foreach {nodes => val asserts = nodes._2.filter(_.isInstanceOf[GeneralAssertionNode]) - val assumes = nodes._2.filter(_.isInstanceOf[GeneralAssumptionNode]) + val assumes = nodes._2.filter(n => !n.isClosed && n.isInstanceOf[GeneralAssumptionNode]) addTransitiveEdges(asserts, assumes) val checks = asserts.filter(_.isInstanceOf[SimpleCheckNode]) - val notChecks = nodes._2.filter(!_.isInstanceOf[SimpleCheckNode]) + val notChecks = nodes._2.filter(n => !n.isClosed && !n.isInstanceOf[SimpleCheckNode]) addTransitiveEdges(checks, notChecks) } } @@ -147,6 +147,7 @@ trait AssumptionAnalysisNode { val id: Int = AssumptionAnalysisGraphHelper.nextId() val sourceInfo: AnalysisSourceInfo val assumptionType: AssumptionType + val isClosed: Boolean override def toString: String = id.toString + " | " + getNodeString + " | " + sourceInfo.toString @@ -167,42 +168,42 @@ trait ChunkAnalysisInfo { def getChunk: Chunk = chunk } -case class SimpleAssumptionNode(assumption: ast.Exp, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Unknown) extends GeneralAssumptionNode { +case class SimpleAssumptionNode(assumption: ast.Exp, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Unknown, isClosed: Boolean) extends GeneralAssumptionNode { override def getNodeString: String ="assume " + assumption.toString } -case class StringAssumptionNode(description: String, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Unknown) extends GeneralAssumptionNode { +case class StringAssumptionNode(description: String, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Unknown, isClosed: Boolean) extends GeneralAssumptionNode { override def getNodeString: String = "assume " + description } -case class SimpleAssertionNode(assertion: ast.Exp, sourceInfo: AnalysisSourceInfo) extends GeneralAssertionNode { +case class SimpleAssertionNode(assertion: ast.Exp, sourceInfo: AnalysisSourceInfo, isClosed: Boolean) extends GeneralAssertionNode { val assumptionType: AssumptionType = Explicit override def getNodeString: String = "assert " + assertion.toString } -case class StringAssertionNode(description: String, sourceInfo: AnalysisSourceInfo) extends GeneralAssertionNode { +case class StringAssertionNode(description: String, sourceInfo: AnalysisSourceInfo, isClosed: Boolean) extends GeneralAssertionNode { val assumptionType: AssumptionType = Explicit override def getNodeString: String = "assert " + description } -case class SimpleCheckNode(t: Term, sourceInfo: AnalysisSourceInfo) extends GeneralAssertionNode { +case class SimpleCheckNode(t: Term, sourceInfo: AnalysisSourceInfo, isClosed: Boolean) extends GeneralAssertionNode { val assumptionType: AssumptionType = Internal override def getNodeString: String = "check " + t override def getNodeType: String = "Check" } -case class PermissionInhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Unknown) extends GeneralAssumptionNode with ChunkAnalysisInfo { +case class PermissionInhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Unknown, isClosed: Boolean) extends GeneralAssumptionNode with ChunkAnalysisInfo { override def getNodeString: String = "inhale " + chunk.getAnalysisInfo override def getNodeType: String = "Inhale" } -case class PermissionExhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo) extends GeneralAssertionNode with ChunkAnalysisInfo { +case class PermissionExhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, isClosed: Boolean) extends GeneralAssertionNode with ChunkAnalysisInfo { val assumptionType: AssumptionType = Explicit override def getNodeType: String = "Exhale" override def getNodeString: String = "exhale " + chunk.getAnalysisInfo } -case class PermissionAssertNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo) extends GeneralAssertionNode with ChunkAnalysisInfo { +case class PermissionAssertNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, isClosed: Boolean) extends GeneralAssertionNode with ChunkAnalysisInfo { val assumptionType: AssumptionType = Explicit override def getNodeString: String = "assert " + permAmount.getOrElse("") + " for " + chunk.getAnalysisInfo } diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index be8473c29..a83d8e93f 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -2,15 +2,24 @@ package viper.silicon.assumptionAnalysis import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType import viper.silicon.debugger.DebugExp +import viper.silicon.decider.Decider import viper.silicon.interfaces.state.Chunk -import viper.silicon.state.terms.{False, Term} +import viper.silicon.state.terms.{False, Ite, Term, True} import viper.silver.ast -import viper.silver.ast._ trait AssumptionAnalyzer { val assumptionGraph: AssumptionAnalysisGraph = new DefaultAssumptionAnalysisGraph() var forcedDependencies: List[Int] = List.empty + protected var enableCustomEdges_ = false + + def enableCustomEdges(): Unit = { + enableCustomEdges_ = true + } + + def disableCustomEdges(): Unit = { + enableCustomEdges_ = false + } def addPermissionInhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] def addPermissionAssertNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo): Option[Int] @@ -36,7 +45,7 @@ trait AssumptionAnalyzer { def addAssertFalseNode(isCheck: Boolean, sourceInfo: AnalysisSourceInfo): Option[Int] - def getMember: Option[Member] + def getMember: Option[ast.Member] def exportGraph(): Unit @@ -48,6 +57,8 @@ trait AssumptionAnalyzer { forcedDependencies = List.empty } + def createLabelledConditional(decider: Decider, sourceChunks: Iterable[Chunk], thenTerm: Term, elseTerm: Term): Term + } object AssumptionAnalyzer { @@ -55,28 +66,28 @@ object AssumptionAnalyzer { val enableAssumptionAnalysisAnnotationKey = "enableAssumptionAnalysis" val noAssumptionAnalyzerSingelton = new NoAssumptionAnalyzer() - private def extractAnnotationFromInfo(info: Info, annotationKey: String): Option[Seq[String]] = { - info.getAllInfos[AnnotationInfo] + private def extractAnnotationFromInfo(info: ast.Info, annotationKey: String): Option[Seq[String]] = { + info.getAllInfos[ast.AnnotationInfo] .filter(_.values.contains(annotationKey)) .map(_.values(annotationKey)).headOption } - def extractAssumptionTypeFromInfo(info: Info): Option[AssumptionType] = { + def extractAssumptionTypeFromInfo(info: ast.Info): Option[AssumptionType] = { val annotation = extractAnnotationFromInfo(info, assumptionTypeAnnotationKey) if(annotation.isDefined && annotation.get.nonEmpty) AssumptionType.fromString(annotation.get.head) else None } - def extractEnableAnalysisFromInfo(info: Info): Option[Boolean] = { + def extractEnableAnalysisFromInfo(info: ast.Info): Option[Boolean] = { val annotation = extractAnnotationFromInfo(info, enableAssumptionAnalysisAnnotationKey) if(annotation.isDefined && annotation.get.nonEmpty) annotation.get.head.toBooleanOption else None } - def createEnableAnalysisInfo(enableAnalysis: Boolean): AnnotationInfo = - AnnotationInfo(Map((enableAssumptionAnalysisAnnotationKey, Seq(enableAnalysis.toString)))) + def createEnableAnalysisInfo(enableAnalysis: Boolean): ast.AnnotationInfo = + ast.AnnotationInfo(Map((enableAssumptionAnalysisAnnotationKey, Seq(enableAnalysis.toString)))) - def createAnalysisAnnotationInfo(enableAnalysis: Boolean, assumptionType: AssumptionType): AnnotationInfo = - AnnotationInfo(Map( + def createAnalysisAnnotationInfo(enableAnalysis: Boolean, assumptionType: AssumptionType): ast.AnnotationInfo = + ast.AnnotationInfo(Map( (enableAssumptionAnalysisAnnotationKey, Seq(enableAnalysis.toString)), (assumptionTypeAnnotationKey, Seq(assumptionType.toString)) )) @@ -110,7 +121,7 @@ object AssumptionAnalyzer { def isAxiomLabel(label: String): Boolean = label.startsWith("axiom_") } -class DefaultAssumptionAnalyzer(member: Member) extends AssumptionAnalyzer { +class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { override def addNodes(nodes: Iterable[AssumptionAnalysisNode]): Unit = { assumptionGraph.addNodes(nodes) @@ -125,22 +136,22 @@ class DefaultAssumptionAnalyzer(member: Member) extends AssumptionAnalyzer { } override def addSingleAssumption(assumption: DebugExp, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { - val node = if(assumption.originalExp.isDefined) SimpleAssumptionNode(assumption.originalExp.get, analysisSourceInfo, AssumptionAnalyzer.extractAssumptionTypeFromInfo(assumption.originalExp.get.info).getOrElse(assumptionType)) - else StringAssumptionNode(assumption.description.getOrElse("unknown"), analysisSourceInfo, assumptionType) + val node = if(assumption.originalExp.isDefined) SimpleAssumptionNode(assumption.originalExp.get, analysisSourceInfo, AssumptionAnalyzer.extractAssumptionTypeFromInfo(assumption.originalExp.get.info).getOrElse(assumptionType), enableCustomEdges_) + else StringAssumptionNode(assumption.description.getOrElse("unknown"), analysisSourceInfo, assumptionType, enableCustomEdges_) addNode(node) Some(node.id) } override def addAssumption(assumption: ast.Exp, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { - val node = SimpleAssumptionNode(assumption, analysisSourceInfo, AssumptionAnalyzer.extractAssumptionTypeFromInfo(assumption.info).getOrElse(assumptionType)) + val node = SimpleAssumptionNode(assumption, analysisSourceInfo, AssumptionAnalyzer.extractAssumptionTypeFromInfo(assumption.info).getOrElse(assumptionType), enableCustomEdges_) addNode(node) Some(node.id) } override def addAssumption(assumption: String, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { - val node = StringAssumptionNode(assumption, analysisSourceInfo, assumptionType) + val node = StringAssumptionNode(assumption, analysisSourceInfo, assumptionType, enableCustomEdges_) addNode(node) Some(node.id) } @@ -148,19 +159,19 @@ class DefaultAssumptionAnalyzer(member: Member) extends AssumptionAnalyzer { override def addAssumptions(assumptions: Iterable[DebugExp], analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Seq[Int] = { val newNodes = assumptions.toSeq.map(a => if (a.originalExp.isDefined) SimpleAssumptionNode(a.originalExp.get, if(analysisSourceInfo.isInstanceOf[NoAnalysisSourceInfo]) ExpAnalysisSourceInfo(a.originalExp.get) else analysisSourceInfo, - AssumptionAnalyzer.extractAssumptionTypeFromInfo(a.originalExp.get.info).getOrElse(assumptionType)) - else StringAssumptionNode(a.description.getOrElse("unknown"), analysisSourceInfo, AssumptionType.Internal) + AssumptionAnalyzer.extractAssumptionTypeFromInfo(a.originalExp.get.info).getOrElse(assumptionType), enableCustomEdges_) + else StringAssumptionNode(a.description.getOrElse("unknown"), analysisSourceInfo, AssumptionType.Internal, enableCustomEdges_) ) newNodes foreach addNode newNodes.map(_.id) } override def createAssertOrCheckNode(term: Term, assertion: Either[String, ast.Exp], analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] = { - if(isCheck) return Some(SimpleCheckNode(term, analysisSourceInfo)) + if(isCheck) return Some(SimpleCheckNode(term, analysisSourceInfo, enableCustomEdges_)) Some(assertion match { - case Left(description) => StringAssertionNode(description, analysisSourceInfo) - case Right(exp) => SimpleAssertionNode(exp, analysisSourceInfo) + case Left(description) => StringAssertionNode(description, analysisSourceInfo, enableCustomEdges_) + case Right(exp) => SimpleAssertionNode(exp, analysisSourceInfo, enableCustomEdges_) }) } @@ -183,26 +194,26 @@ class DefaultAssumptionAnalyzer(member: Member) extends AssumptionAnalyzer { } override def addPermissionInhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { - val node = PermissionInhaleNode(chunk, permAmount, sourceInfo, assumptionType) + val node = PermissionInhaleNode(chunk, permAmount, sourceInfo, assumptionType, enableCustomEdges_) addNode(node) Some(node.id) } override def addPermissionAssertNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo): Option[Int] = { - val node = PermissionAssertNode(chunk, permAmount, sourceInfo) + val node = PermissionAssertNode(chunk, permAmount, sourceInfo, enableCustomEdges_) addNode(node) addPermissionDependencies(Set(chunk), Some(node.id)) Some(node.id) } override def addPermissionExhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo): Option[Int] = { - val node = PermissionExhaleNode(chunk, permAmount, sourceInfo) + val node = PermissionExhaleNode(chunk, permAmount, sourceInfo, enableCustomEdges_) addNode(node) addPermissionDependencies(Set(chunk), Some(node.id)) Some(node.id) } - override def addPermissionNode(chunk: Chunk, permAmount: Option[Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Explicit, isExhale: Boolean=false): Option[Int] = { + override def addPermissionNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Explicit, isExhale: Boolean=false): Option[Int] = { if(isExhale) addPermissionExhaleNode(chunk, permAmount, sourceInfo) else addPermissionInhaleNode(chunk, permAmount, sourceInfo, assumptionType) } @@ -227,20 +238,29 @@ class DefaultAssumptionAnalyzer(member: Member) extends AssumptionAnalyzer { addPermissionDependencies(oldChunks, newChunkId.headOption) } - override def getMember: Option[Member] = Some(member) + override def getMember: Option[ast.Member] = Some(member) override def exportGraph(): Unit = { val foldername: Option[String] = getMember map { - case Method(name, _, _, _, _, _) => name + case ast.Method(name, _, _, _, _, _) => name case ast.Function(name, _, _, _, _, _) => name - case Domain(name, _, _, _, _) => name - case contracted: Contracted => contracted.toString() - case location: Location => location.pos.toString - case member: ExtensionMember => member.pos.toString + case ast.Domain(name, _, _, _, _) => name + case contracted: ast.Contracted => contracted.toString() + case location: ast.Location => location.pos.toString + case member: ast.ExtensionMember => member.pos.toString } assumptionGraph.exportGraph("graphExports/" + foldername.getOrElse("latestExport")) } + override def createLabelledConditional(decider: Decider, sourceChunks: Iterable[Chunk], thenTerm: Term, elseTerm: Term): Term = { + val savedForcedDep = forcedDependencies + addForcedDependencies(sourceChunks.toSet) + val (label, labelExp) = decider.fresh(ast.LocalVar("analysisLabel", ast.Bool)()) + decider.assume(label === True, Some(DebugExp.createInstance(labelExp, labelExp)), AssumptionType.Internal) + forcedDependencies = savedForcedDep + Ite(label === True, thenTerm, elseTerm) + } + } class NoAssumptionAnalyzer extends AssumptionAnalyzer { @@ -270,7 +290,7 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { override def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNodeId: Chunk): Unit = {} - override def getMember: Option[Member] = None + override def getMember: Option[ast.Member] = None override def addSingleAssumption(assumption: DebugExp, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None @@ -278,4 +298,6 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { override def addAssumption(assumption: String, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None override def exportGraph(): Unit = {} + + override def createLabelledConditional(decider: Decider, sourceChunks: Iterable[Chunk], thenTerm: Term, elseTerm: Term): Term = thenTerm } \ No newline at end of file diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 1fd524152..8c77dc288 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -551,17 +551,18 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { Forall( codomainQVar, - Implies(effectiveCondition, BuiltinEquals(lookupSummary, lookupChunk)), + v.decider.assumptionAnalyzer.createLabelledConditional(v.decider, Set(chunk), Implies(effectiveCondition, BuiltinEquals(lookupSummary, lookupChunk)), True), if (Verifier.config.disableISCTriggers()) Nil else Seq(Trigger(lookupSummary), Trigger(lookupChunk)), s"qp.fvfValDef${v.counter(this).next()}", isGlobal = relevantQvars.isEmpty) }) val resourceAndValueDefinitions = if (s.heapDependentTriggers.contains(field)){ + val chunkTriggers = relevantChunks map (chunk => v.decider.assumptionAnalyzer.createLabelledConditional(v.decider, Set(chunk), FieldTrigger(field.name, chunk.snapshotMap, codomainQVar), True)) val resourceTriggerDefinition = Forall( codomainQVar, - And(relevantChunks map (chunk => FieldTrigger(field.name, chunk.snapshotMap, codomainQVar))), + And(chunkTriggers), Trigger(Lookup(field.name, sm, codomainQVar)), s"qp.fvfResTrgDef${v.counter(this).next()}", isGlobal = relevantQvars.isEmpty) @@ -638,9 +639,9 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { transformedOptSmDomainDefinitionCondition.getOrElse(True), /* Alternatively: qvarInDomainOfSummarisingSm */ IsPositive(chunk.perm).replace(snapToCodomainTermsSubstitution)) + val term = v.decider.assumptionAnalyzer.createLabelledConditional(v.decider, Set(chunk), Implies(effectiveCondition, And(snapshotNotUnit, BuiltinEquals(lookupSummary, lookupChunk))), True) Forall( - qvar, - Implies(effectiveCondition, And(snapshotNotUnit, BuiltinEquals(lookupSummary, lookupChunk))), + qvar, term, if (Verifier.config.disableISCTriggers()) Nil else Seq(Trigger(lookupSummary), Trigger(lookupChunk)), s"qp.psmValDef${v.counter(this).next()}", isGlobal = relevantQvars.isEmpty) @@ -651,10 +652,12 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { case r => r } val resourceAndValueDefinitions = if (s.heapDependentTriggers.contains(resourceIdentifier)){ + + val chunkTriggers = relevantChunks map (chunk => v.decider.assumptionAnalyzer.createLabelledConditional(v.decider, Set(chunk), ResourceTriggerFunction(resource, chunk.snapshotMap, Seq(qvar), s.program), True)) val resourceTriggerDefinition = Forall( qvar, - And(relevantChunks map (chunk => ResourceTriggerFunction(resource, chunk.snapshotMap, Seq(qvar), s.program))), + And(chunkTriggers), Trigger(ResourceLookup(resource, sm, Seq(qvar), s.program)), s"qp.psmResTrgDef${v.counter(this).next()}", isGlobal = relevantQvars.isEmpty) @@ -690,10 +693,11 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val permSummary = ResourcePermissionLookup(resource, pm, codomainQVars, s.program) + val chunkPerms = relevantChunks map (chunk => v.decider.assumptionAnalyzer.createLabelledConditional(v.decider, Set(chunk), chunk.perm, NoPerm)) val valueDefinitions = Forall( codomainQVars, - permSummary === BigPermSum(relevantChunks map (_.perm)), + permSummary === BigPermSum(chunkPerms), Trigger(permSummary), s"qp.resPrmSumDef${v.counter(this).next()}", isGlobal = true) @@ -707,12 +711,11 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { // TODO: Quantify over snapshot if resource is predicate. // Also check other places where a similar quantifier is constructed. + val chunkTriggerDefs = relevantChunks map (chunk => v.decider.assumptionAnalyzer.createLabelledConditional(v.decider, Set(chunk), ResourceTriggerFunction(resource, chunk.snapshotMap, codomainQVars, s.program), True)) val resourceTriggerDefinition = Forall( codomainQVars, - And(resourceTriggerFunction +: - relevantChunks.map(chunk => - ResourceTriggerFunction(resource, chunk.snapshotMap, codomainQVars, s.program))), + And(resourceTriggerFunction +: chunkTriggerDefs), Trigger(ResourcePermissionLookup(resource, pm, codomainQVars, s.program)), s"qp.resTrgDef${v.counter(this).next()}", isGlobal = true) @@ -733,8 +736,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v: Verifier) : (PermMapDefinition, PmCache) = { v.decider.analysisSourceInfoStack.setForcedSource("summarizing heap") - v.decider.assumptionAnalyzer.addForcedDependencies(relevantChunks.toSet) - val res = Verifier.config.mapCache(s.pmCache.get(resource, relevantChunks)) match { + v.decider.assumptionAnalyzer.enableCustomEdges() + val res = Verifier.config.mapCache(s.pmCache.get(resource, relevantChunks)) match { // TODO ake: do not get from cache when analysis is enabled case Some(pmDef) => v.decider.assume(pmDef.valueDefinitions, Option.when(withExp)(DebugExp.createInstance("value definitions", isInternal_ = true)), enforceAssumption = false, assumptionType=AssumptionType.Internal) (pmDef, s.pmCache) @@ -746,6 +749,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { (pmDef, s.pmCache + ((resource, relevantChunks) -> pmDef)) } v.decider.analysisSourceInfoStack.removeForcedSource() + v.decider.assumptionAnalyzer.disableCustomEdges() res } @@ -774,7 +778,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optQVarsInstantiations: Option[Seq[Term]] = None) : (SnapshotMapDefinition, SnapshotMapCache) = { v.decider.analysisSourceInfoStack.setForcedSource("summarizing heap") - v.decider.assumptionAnalyzer.addForcedDependencies(relevantChunks.toSet) + v.decider.assumptionAnalyzer.enableCustomEdges() def emitSnapshotMapDefinition(s: State, smDef: SnapshotMapDefinition, @@ -832,7 +836,9 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { quantifiedChunkSupporter.summarise( s, relevantChunks, codomainQVars, resource, optSmDomainDefinitionCondition, v) val smDef = SnapshotMapDefinition(resource, sm, valueDefs, optDomainDefinition.toSeq) - val totalPermissions = BigPermSum(relevantChunks.map(_.perm)) + + val chunkPerms = relevantChunks map (chunk => v.decider.assumptionAnalyzer.createLabelledConditional(v.decider, Set(chunk), chunk.perm, NoPerm)) + val totalPermissions = BigPermSum(chunkPerms) if (Verifier.config.disableValueMapCaching()) { (smDef, s.smCache) @@ -845,8 +851,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { } emitSnapshotMapDefinition(s, smDef, v, optQVarsInstantiations) - v.decider.assumptionAnalyzer.unsetForcedDependencies() v.decider.analysisSourceInfoStack.removeForcedSource() + v.decider.assumptionAnalyzer.disableCustomEdges() (smDef, smCache) } @@ -859,7 +865,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optQVarsInstantiations: Option[Seq[Term]] = None) : (State, SnapshotMapDefinition, PermMapDefinition) = { v.decider.analysisSourceInfoStack.setForcedSource("summarizing heap") - v.decider.assumptionAnalyzer.addForcedDependencies(relevantChunks.toSet) + v.decider.assumptionAnalyzer.enableCustomEdges() val (smDef, smCache) = summarisingSnapshotMap( s, resource, codomainQVars, relevantChunks, v, optSmDomainDefinitionCondition, optQVarsInstantiations) @@ -871,8 +877,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { s1, resource, codomainQVars, relevantChunks, smDef, v) val s2 = s1.copy(pmCache = pmCache) - v.decider.assumptionAnalyzer.unsetForcedDependencies() v.decider.analysisSourceInfoStack.removeForcedSource() + v.decider.assumptionAnalyzer.disableCustomEdges() (s2, smDef, pmDef) } @@ -886,15 +892,15 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v: Verifier) : (State, PermMapDefinition) = { v.decider.analysisSourceInfoStack.setForcedSource("summarizing heap") - v.decider.assumptionAnalyzer.addForcedDependencies(relevantChunks.toSet) + v.decider.assumptionAnalyzer.enableCustomEdges() val s1 = s val (pmDef, pmCache) = quantifiedChunkSupporter.summarisingPermissionMap( s1, resource, codomainQVars, relevantChunks, null, v) val s2 = s1.copy(pmCache = pmCache) - v.decider.assumptionAnalyzer.unsetForcedDependencies() v.decider.analysisSourceInfoStack.removeForcedSource() + v.decider.assumptionAnalyzer.disableCustomEdges() (s2, pmDef) } From c80656734d3ff1b2365fed125210d26d8f5f57ed Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 14 Jun 2025 12:33:15 +0200 Subject: [PATCH 096/474] field assign: resolve impreciseness --- .../assumptionAnalysis/AssumptionAnalyzer.scala | 11 +++++++++++ src/main/scala/rules/Executor.scala | 16 ++++++++-------- .../scala/rules/QuantifiedChunkSupport.scala | 4 ++-- src/main/scala/state/Chunks.scala | 1 + src/test/resources/andrea/impreciseness.vpr | 15 +++++++++++++++ 5 files changed, 37 insertions(+), 10 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index a83d8e93f..157a5577f 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -38,6 +38,7 @@ trait AssumptionAnalyzer { def createAssertOrCheckNode(term: Term, assertion: Either[String, ast.Exp], analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] def getChunkNodeIds(oldChunks: Set[Chunk]): Set[Int] + def addDependencyFromExhaleToInhale(newChunkNodeId: Option[Int]): Unit def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNodeId: Option[Int]): Unit def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNodeId: Chunk): Unit @@ -224,6 +225,15 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { .map(_.id).toSet } + override def addDependencyFromExhaleToInhale(newChunkNodeId: Option[Int]): Unit = { + if(newChunkNodeId.isEmpty) return + val newChunkNode = assumptionGraph.nodes.filter(_.id == newChunkNodeId.get).head + val exhaleNodes = assumptionGraph.nodes + .filter(c => c.isInstanceOf[PermissionExhaleNode] && c.isClosed && c.sourceInfo.equals(newChunkNode.sourceInfo)) + .map(_.id).toSet + assumptionGraph.addEdges(exhaleNodes, newChunkNodeId) + } + override def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNodeId: Option[Int]): Unit = { if(newChunkNodeId.isEmpty) return @@ -285,6 +295,7 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { override def getChunkNodeIds(oldChunks: Set[Chunk]): Set[Int] = Set.empty + override def addDependencyFromExhaleToInhale(newChunkNodeId: Option[Int]): Unit = {} override def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNode: Option[Int]): Unit = { } diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 101a4dcc3..5c1611e7b 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -402,7 +402,7 @@ object executor extends ExecutionRules { s2 } v2.decider.clearModel() - v2.decider.analysisSourceInfoStack.setForcedSource(ExpAnalysisSourceInfo(fa)) + v2.decider.assumptionAnalyzer.enableCustomEdges() // inhaling new chunks should not depend on any of the rhs assertions val result = quantifiedChunkSupporter.removePermissions( s2p, relevantChunks, @@ -417,7 +417,7 @@ object executor extends ExecutionRules { chunkOrderHeuristics, v2 ) - v2.decider.analysisSourceInfoStack.removeForcedSource() + v2.decider.assumptionAnalyzer.disableCustomEdges() result match { case (Complete(), s3, remainingChunks, consumedChunks) => // TODO ake: what to do with consumedChunks? val h3 = Heap(remainingChunks ++ otherChunks) @@ -427,7 +427,7 @@ object executor extends ExecutionRules { v1.decider.assumeDefinition(smValueDef, debugExp, AssumptionType.Internal) v1.decider.analysisSourceInfoStack.setForcedSource(ExpAnalysisSourceInfo(fa)) val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(Seq(`?r`), Option.when(withExp)(Seq(ast.LocalVarDecl("r", ast.Ref)(ass.pos, ass.info, ass.errT))), - field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), sm, s.program, v1, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit), isExhale=false) + field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), sm, s.program, v1, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Internal), isExhale=false) v1.decider.analysisSourceInfoStack.removeForcedSource() if (s3.heapDependentTriggers.contains(field)) { val debugExp2 = Option.when(withExp)(DebugExp.createInstance(s"FieldTrigger(${eRcvrNew.toString}.${field.name})")) @@ -448,15 +448,15 @@ object executor extends ExecutionRules { val resource = fa.res(s.program) val ve = pve dueTo InsufficientPermission(fa) val description = s"consume ${ass.pos}: $ass" - v2.decider.analysisSourceInfoStack.setForcedSource(ExpAnalysisSourceInfo(fa)) + v2.decider.assumptionAnalyzer.enableCustomEdges() // TODO ake: review implementation chunkSupporter.consume(s2, s2.h, resource, Seq(tRcvr), eRcvrNew.map(Seq(_)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), false, ve, v2, description)((s3, h3, _, consumedChunks, v3) => { - v3.decider.analysisSourceInfoStack.setForcedSource(StmtAnalysisSourceInfo(ass)) + v2.decider.assumptionAnalyzer.disableCustomEdges() val (tSnap, _) = ssaifyRhs(tRhs, rhs, rhsNew, field.name, field.typ, v3, s3, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit)) val id = BasicChunkIdentifier(field.name) - v3.decider.analysisSourceInfoStack.setForcedSource(ExpAnalysisSourceInfo(fa)) + v2.decider.assumptionAnalyzer.enableCustomEdges() // inhaling the new chunk should not depend on any rhs assertions, dependency to exhaling old chunk is added nevertheless val newChunk = BasicChunk.createDerivedChunk(Set.empty, FieldID, id, Seq(tRcvr), eRcvrNew.map(Seq(_)), tSnap, rhsNew, FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), - v3.decider.getAnalysisInfo(annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit))) - v3.decider.analysisSourceInfoStack.removeForcedSource() + v3.decider.getAnalysisInfo(annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Internal))) + v2.decider.assumptionAnalyzer.disableCustomEdges() chunkSupporter.produce(s3, h3, newChunk, v3)((s4, h4, v4) => { val s5 = s4.copy(h = h4) val (debugHeapName, _) = v4.getDebugOldLabel(s5, fa.pos) diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 8c77dc288..6c0d2adab 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -1714,7 +1714,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.assume(permissionConstraint, permissionConstraintExp, permissionConstraintExp, AssumptionType.Internal) remainingChunks = - remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.getAnalysisInfo(AssumptionType.Implicit)) + remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.getAnalysisInfo(AssumptionType.Internal)) // TODO ake: assumption Type? } else { v.decider.prover.comment(s"Chunk depleted?") val chunkDepleted = v.decider.check(depletedCheck, Verifier.config.splitTimeout()) @@ -1725,7 +1725,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { remainingChunks = remainingChunks :+ ithChunk } else { remainingChunks = - remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.getAnalysisInfo(AssumptionType.Implicit)) + remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.getAnalysisInfo(AssumptionType.Internal)) } }else{ v.decider.assumptionAnalyzer.addPermissionExhaleNode(ithChunk, ithPTakenExp, v.decider.analysisSourceInfoStack.getFullSourceInfo) diff --git a/src/main/scala/state/Chunks.scala b/src/main/scala/state/Chunks.scala index b84949ad3..7da8623e9 100644 --- a/src/main/scala/state/Chunks.scala +++ b/src/main/scala/state/Chunks.scala @@ -61,6 +61,7 @@ object BasicChunk { val newChunk = new BasicChunk(resourceID, id, args, argsExp, snap, snapExp, perm, permExp) val newNode = analysisInfo.assumptionAnalyzer.addPermissionNode(newChunk, permExp, analysisInfo.sourceInfo, analysisInfo.assumptionType, isExhale) analysisInfo.assumptionAnalyzer.addPermissionDependencies(oldChunks, newNode) + analysisInfo.assumptionAnalyzer.addDependencyFromExhaleToInhale(newNode) newChunk } } diff --git a/src/test/resources/andrea/impreciseness.vpr b/src/test/resources/andrea/impreciseness.vpr index ce7dea379..293f88c2a 100644 --- a/src/test/resources/andrea/impreciseness.vpr +++ b/src/test/resources/andrea/impreciseness.vpr @@ -14,3 +14,18 @@ method permTest(a: Ref, b: Ref, n: Int) assert a.f >= 0 // incorrectly depends on acc(b.f) } +method quantifiedPerm2Seqs2(xs: Seq[Ref], ys: Seq[Ref]) { + assume |xs| > 5 && |ys| > 3 + inhale forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0) + inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard) && y.f > 0) // incorrectly depends on inhale forall x in xs due to heap summary + + assert xs[0].f > 0 +} + +method quantifiedPerm2Seqs3(xs: Seq[Ref], ys: Seq[Ref]) { + assume |xs| > 5 && |ys| > 3 + inhale forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0) + inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard)) + + assert xs[0].f > 0 // incorrectly depends on inhale forall x in xs due to heap summary +} \ No newline at end of file From e71f7689a2637ce308a52b86cf86531ce4e44f31 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 16 Jun 2025 12:03:56 +0200 Subject: [PATCH 097/474] minor fixes --- src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index 60abeedde..54a6eb076 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -65,7 +65,7 @@ trait AssumptionAnalysisGraph { def addTransitiveEdges(source: AssumptionAnalysisNode, targets: Iterable[AssumptionAnalysisNode]): Unit = { val oldTargets = transitiveEdges.getOrElse(source.id, Set.empty) - val newTargets = targets filter(t => t.id > source.id) map(_.id) // we only want forward edges + val newTargets = targets map(_.id) if(newTargets.nonEmpty) transitiveEdges.update(source.id, oldTargets ++ newTargets) } From 11e6f4d585ad7c2774eadda48ce4379a9f14447a Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 16 Jun 2025 16:52:09 +0200 Subject: [PATCH 098/474] state consolidation: more precise --- .../NonQuantifiedPropertyInterpreter.scala | 3 +- .../resources/andrea/assistants-tests.vpr | 68 +++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 src/test/resources/andrea/assistants-tests.vpr diff --git a/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala b/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala index 37621ea33..ac7544af6 100644 --- a/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala +++ b/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala @@ -155,7 +155,8 @@ class NonQuantifiedPropertyInterpreter(heap: Iterable[Chunk], verifier: Verifier // check that only distinct tuples are handled // TODO: Is it possible to get this behavior without having to check every tuple? if (!info.pm.values.exists(chunk eq _)) { - Some(builder(chunk)) + val (resTerm, resExp) = builder(chunk) + Some((verifier.decider.assumptionAnalyzer.createLabelledConditional(verifier.decider, Set(chunk), resTerm, terms.True), resExp)) } else { None } diff --git a/src/test/resources/andrea/assistants-tests.vpr b/src/test/resources/andrea/assistants-tests.vpr new file mode 100644 index 000000000..5258e9d8f --- /dev/null +++ b/src/test/resources/andrea/assistants-tests.vpr @@ -0,0 +1,68 @@ +field f: Int + +method loopAssumption() +{ + var x: Int + assume x <= 100 + + while(x <= 10) + invariant x <= 100 + { + x := x * 11 + assume x <= 100 + } + + assert x <= 100 +} + +method alias1(a: Ref, b: Ref, c: Ref) + requires acc(a.f) && acc(b.f) +{ + var x: Int + if (c == a) { + x := c.f + } + if(c == b) { + x := c.f + assume a == c + assert x == a.f + assert false + } + + + assume a == c + assert x == a.f +} + +method alias2(a: Ref, b: Ref, c: Ref) + requires acc(a.f) && acc(b.f) + requires a != b +{ + var x: Int + if (c == a) { + x := c.f + } else { + if(c == b) { + x := c.f + } + } + + assume a == c + assert x == a.f +} + +method test3(a: Int, b: Int, c: Int) +{ +var x: Int := 4 + +assume a == 0 +assume b == 1 + + +if (c == a || c == b) { + x := c +} + +assume c == a +assert x == 0 +} From 7446250878b2afe4551ad4dadde2acb6f5779881 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 19 Jun 2025 17:30:00 +0200 Subject: [PATCH 099/474] add AssumptionAnalysisTests --- silver | 2 +- .../AssumptionAnalysisGraph.scala | 34 +++- .../DependencyAnalysisReporter.scala | 10 ++ .../scala/verifier/DefaultMainVerifier.scala | 6 +- .../dependencyAnalysisTests/snippets.json | 14 ++ .../test-templates.vpr | 61 +++++++ src/test/scala/AssumptionAnalysisTests.scala | 155 ++++++++++++++++++ 7 files changed, 274 insertions(+), 8 deletions(-) create mode 100644 src/main/scala/assumptionAnalysis/DependencyAnalysisReporter.scala create mode 100644 src/test/resources/dependencyAnalysisTests/snippets.json create mode 100644 src/test/resources/dependencyAnalysisTests/test-templates.vpr create mode 100644 src/test/scala/AssumptionAnalysisTests.scala diff --git a/silver b/silver index e8521cbb1..3aab5b86a 160000 --- a/silver +++ b/silver @@ -1 +1 @@ -Subproject commit e8521cbb1b4277a7ac8fc210d00211593f9cd6ed +Subproject commit 3aab5b86a491a67727c023197ae9216759c94226 diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index 54a6eb076..ea9873a3d 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -4,10 +4,12 @@ import viper.silicon.assumptionAnalysis.AssumptionType._ import viper.silicon.interfaces.state.Chunk import viper.silicon.state.terms.Term import viper.silver.ast +import viper.silver.ast.Position import java.io.{File, PrintWriter} import java.util.concurrent.atomic.AtomicInteger import scala.collection.mutable +import scala.collection.mutable.Seq object AssumptionAnalysisGraphHelper { @@ -29,6 +31,28 @@ trait AssumptionAnalysisGraph { def addEdges(sources: Iterable[Int], target: Int): Unit def addEdges(sources: Iterable[Int], targets: Iterable[Int]): Unit + def existsAnyDependency(sources: Set[Int], targets: Set[Int]): Boolean = { + var visited: Set[Int] = sources + var queue: List[Int] = sources.toList + while(queue.nonEmpty){ + val newVisits = edges.getOrElse(queue.head, Set()) + if(newVisits.intersect(targets).nonEmpty) + return true + visited = visited ++ Set(queue.head) + queue = queue.tail ++ (newVisits filter (!visited.contains(_))) + } + false + } + + def getNodesByProperties(nodeType: Option[String], assumptionType: Option[AssumptionType], sourceInfo: Option[String], position: Option[Position]): Seq[AssumptionAnalysisNode] = { + nodes filter (node => + nodeType.forall(node.getNodeType.equals) && + assumptionType.forall(node.assumptionType.equals) && + sourceInfo.forall(node.sourceInfo.toString.equals) && + position.forall(node.sourceInfo.getPosition.equals) + ) + } + def getExplicitAndAssertNodesOnly(): Seq[AssumptionAnalysisNode] = { nodes.filter(n => n.assumptionType.equals(AssumptionType.Explicit) || n.isInstanceOf[GeneralAssertionNode]) } @@ -85,11 +109,11 @@ trait AssumptionAnalysisGraph { } } - def exportGraph(fileName: String): Unit = { - val directory = new File(fileName) + def exportGraph(dirName: String): Unit = { + val directory = new File(dirName) directory.mkdir() - exportNodes(fileName) - exportEdges(fileName + "/edges.csv") + exportNodes(dirName + "/nodes.csv") + exportEdges(dirName + "/edges.csv") } def exportEdges(fileName: String): Unit = { @@ -106,7 +130,7 @@ trait AssumptionAnalysisGraph { val parts = Seq(node.id.toString, node.getNodeType, node.assumptionType.toString, node.getNodeString, node.sourceInfo.toString, node.sourceInfo.getStringForExport, node.sourceInfo.getFineGrainedSource.toString) parts.map(_.replace("#", "@")).mkString(sep) } - val writer = new PrintWriter(fileName + "/nodes.csv") + val writer = new PrintWriter(fileName) val headerParts = Seq("id", "node type", "assumption type", "node info", "source info", "position", "fine grained source") writer.println(headerParts.mkString(sep)) nodes foreach (n => writer.println(getNodeExportString(n).replace("\n", " "))) diff --git a/src/main/scala/assumptionAnalysis/DependencyAnalysisReporter.scala b/src/main/scala/assumptionAnalysis/DependencyAnalysisReporter.scala new file mode 100644 index 000000000..3e307925b --- /dev/null +++ b/src/main/scala/assumptionAnalysis/DependencyAnalysisReporter.scala @@ -0,0 +1,10 @@ +package viper.silicon.assumptionAnalysis + +import viper.silicon.assumptionAnalysis.AssumptionAnalysisGraph +import viper.silver.reporter.{Message, Reporter} + +case class DependencyAnalysisReporter(name: String = "dependencyAnalysis_reporter", path: String = "report.csv") extends Reporter { + var assumptionGraphs: List[AssumptionAnalysisGraph] = List.empty + override def report(msg: Message): Unit = {} + +} diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index 06a1e1998..c49da1a69 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -8,7 +8,7 @@ package viper.silicon.verifier import viper.silicon.Config.{ExhaleMode, JoinMode} import viper.silicon._ -import viper.silicon.assumptionAnalysis.DefaultAssumptionAnalyzer +import viper.silicon.assumptionAnalysis.{DefaultAssumptionAnalyzer, DependencyAnalysisReporter} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.SiliconDebugger import viper.silicon.decider.SMTLib2PreambleReader @@ -19,9 +19,9 @@ import viper.silicon.logger.{MemberSymbExLogger, SymbExLogger} import viper.silicon.reporting.{MultiRunRecorders, condenseToViperResult} import viper.silicon.state._ import viper.silicon.state.terms.{Decl, Sort, Term, sorts} +import viper.silicon.supporters._ import viper.silicon.supporters.functions.{DefaultFunctionVerificationUnitProvider, FunctionData} import viper.silicon.supporters.qps._ -import viper.silicon.supporters._ import viper.silicon.utils.Counter import viper.silver.ast import viper.silver.ast.utility.rewriter.Traverse @@ -317,6 +317,8 @@ class DefaultMainVerifier(config: Config, val assumptionAnalyzers = verificationResults.filter(_.assumptionAnalyzer.isInstanceOf[DefaultAssumptionAnalyzer]).map(_.assumptionAnalyzer) assumptionAnalyzers.foreach(_.assumptionGraph.addTransitiveEdges()) assumptionAnalyzers foreach (_.exportGraph()) + if(reporter.isInstanceOf[DependencyAnalysisReporter]) + reporter.asInstanceOf[DependencyAnalysisReporter].assumptionGraphs = assumptionAnalyzers.map(_.assumptionGraph) logger debug s"assumption analyzers ${assumptionAnalyzers.mkString(", ")}" } diff --git a/src/test/resources/dependencyAnalysisTests/snippets.json b/src/test/resources/dependencyAnalysisTests/snippets.json new file mode 100644 index 000000000..30b1459cc --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/snippets.json @@ -0,0 +1,14 @@ +{ + "addition": { + "initString": "var x: Int", + "assumptionString": "@dependency()\nassume x > 0", + "bodyString": "@dependency()\nx := x + x + 5", + "assertionString": "assert x > 0" + }, + "ref": { + "initString": "var x: Ref := new(f)", + "assumptionString": "assume x.f > 0", + "bodyString": "x.f := x.f + x.f + 5", + "assertionString": "assert x.f > 0" + } +} diff --git a/src/test/resources/dependencyAnalysisTests/test-templates.vpr b/src/test/resources/dependencyAnalysisTests/test-templates.vpr new file mode 100644 index 000000000..f88fc5f25 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/test-templates.vpr @@ -0,0 +1,61 @@ +field f: Int +field f2: Int +field bf: Bool +field r: Ref + + +method dummyTest() +{ + ##INIT## + + ##ASSUMPTIONS## + + ##BODY## + + @testAssertion() + ##ASSERTION## +} + + +method basic(a: Int) +{ + var b: Int + @obsolete() + assume a > 0 + + ##INIT## + + ##ASSUMPTIONS## + + @obsolete() + b := a * 2 + + ##BODY## + + @testAssertion() + ##ASSERTION## +} + +method basicBranch(a: Int) +{ + var b: Int + @obsolete() + assume a > 0 + + ##INIT## + + if(a >= 0){ + ##ASSUMPTIONS## + @obsolete() + b := a * 2 + }else{ + // infeasible + @obsolete() + b := a + 1 + } + + ##BODY## + + @testAssertion() + ##ASSERTION## +} \ No newline at end of file diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala new file mode 100644 index 000000000..00f6d2451 --- /dev/null +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -0,0 +1,155 @@ + +import org.scalatest.funsuite.AnyFunSuite +import viper.silicon.SiliconFrontend +import viper.silicon.assumptionAnalysis.{AssumptionAnalysisGraph, AssumptionAnalysisNode, DependencyAnalysisReporter} +import viper.silver.ast +import viper.silver.ast.utility.rewriter.StrategyInterface +import viper.silver.ast.utility.{DiskLoader, ViperStrategy} +import viper.silver.frontend.SilFrontend + +import java.io.File +import java.nio.file.Paths +import scala.util.{Failure, Success} + + +/** + * Annotations + * @dependency() -> for assumptions that should be reported as a dependency + * @obsolete() -> for assumptions that should NOT be reported as a dependency + * @testAssertion() -> the queried assertion + * + * assumptions/assertions that are not annotated are ignored + * + * !!! THERE CAN ONLY BE 1 TEST ASSERTION PER METHOD, + * but multiple dependency/obsolete annotations are allowed + * + */ +class AssumptionAnalysisTests extends AnyFunSuite { + + def testDirectories: Seq[String] = Seq("dependencyAnalysis") + + val GENERATE_TESTS = true + + val commandLineArguments: Seq[String] = + Seq("--timeout", "100" /* seconds */, "--enableDebugging", "--enableAssumptionAnalysis", "--z3Args", "proof=true unsat-core=true") + + test("first test"){ + val result = executeTest("dependencyAnalysisTests/", "test-templates_addition_generated", ViperStrategy.Slim({ + case a: ast.Inhale => a + }), frontend) + assert(result) + } + + if(GENERATE_TESTS) + generateTests("dependencyAnalysisTests/", "test-templates") + + def generateTests(filePrefix: String, + fileName: String): Unit = { + val path = getClass.getClassLoader.getResource(filePrefix + fileName + ".vpr") + val content: String = DiskLoader.loadContent(Paths.get(path.toURI)) match { + case Failure(exception) => throw exception + case Success(value) => value + } + + val jsonPath = getClass.getClassLoader.getResource(filePrefix + "snippets" + ".json") + val jsonContent: String = DiskLoader.loadContent(Paths.get(jsonPath.toURI)) match { + case Failure(exception) => throw exception + case Success(value) => value + } + val json = upickle.default.read[Map[String, Map[String, String]]](jsonContent) + + json foreach{case (testname, replacements) => generateSingleTestFile(filePrefix, fileName + "_" + testname, content, replacements)} + } + + def generateSingleTestFile(filePrefix: String, fileName: String, template: String, replacements: Map[String, String]): Unit = { + var newString = template + val initPlaceholder = "##INIT##" + val assumptionPlaceholder = "##ASSUMPTIONS##" + val bodyPlaceholder = "##BODY##" + val assertionPlaceholder = "##ASSERTION##" + + newString = newString.replaceAll(initPlaceholder, replacements("initString")) + newString = newString.replaceAll(assumptionPlaceholder, replacements("assumptionString")) + newString = newString.replaceAll(bodyPlaceholder, replacements("bodyString")) + newString = newString.replaceAll(assertionPlaceholder, replacements("assertionString")) + + // write generated file + val path2 = Paths.get("src/test/resources/" + filePrefix + fileName + "_generated" + ".vpr").toString + val pw = new java.io.PrintWriter(new File(path2)) + try pw.write(newString) finally pw.close() + } + + def frontend: SiliconFrontend = { + val reporter = DependencyAnalysisReporter() + val fe = new SiliconFrontend(reporter) + val backend = fe.createVerifier("") + backend.parseCommandLine(commandLineArguments ++ List("--ignoreFile", "dummy.sil")) + fe.init(backend) + fe.setVerifier(backend) + backend.start() + fe + } + + + + def executeTest(filePrefix: String, + fileName: String, + strategy: StrategyInterface[ast.Node], + frontend: SilFrontend) + : Boolean = { + + val program = tests.loadProgram(filePrefix, fileName, frontend) + val _ = frontend.verifier.verify(program) + + val assumptionGraphsReal = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].assumptionGraphs + + (assumptionGraphsReal forall checkDependencies) && (assumptionGraphsReal forall checkNonDependencies) + } + + def checkDependencies(assumptionGraph: AssumptionAnalysisGraph): Boolean = { + val assumptionNodes = extractTestAssumptionNodesFromGraph(assumptionGraph) + val assumptionsPerSource = assumptionNodes groupBy(_.sourceInfo.toString) + val assertionNodes = extractTestAssertionNodesFromGraph(assumptionGraph) + assumptionNodes.isEmpty || assertionNodes.isEmpty || + assumptionsPerSource.forall{case (_, assumptions) => + checkDependenciesForSingleSource(assumptionGraph, assumptions, assertionNodes) + } + } + + def checkNonDependencies(assumptionGraph: AssumptionAnalysisGraph): Boolean = { + val assumptionNodes = extractTestObsoleteAssumptionNodesFromGraph(assumptionGraph) + val assumptionsPerSource = assumptionNodes groupBy(_.sourceInfo.toString) + val assertionNodes = extractTestAssertionNodesFromGraph(assumptionGraph) + assumptionNodes.isEmpty || assertionNodes.isEmpty || + !assumptionsPerSource.exists{case (_, assumptions) => + checkDependenciesForSingleSource(assumptionGraph, assumptions, assertionNodes) + } + } + + def checkDependenciesForSingleSource(assumptionGraph: AssumptionAnalysisGraph, assumptions: Seq[AssumptionAnalysisNode], assertions: Seq[AssumptionAnalysisNode]): Boolean = { + assumptions exists (assumption => { + assertions exists (assertion => assumptionGraph.existsAnyDependency(Set(assumption.id), Set(assertion.id))) + }) + } + + def extractTestAssertionNodesFromGraph(graph: AssumptionAnalysisGraph): Seq[AssumptionAnalysisNode] = { + graph.nodes.filter(node => + node.getNodeType.equals("Assertion") && + node.sourceInfo.toString.contains("@testAssertion()") + ).toSeq + } + + def extractTestAssumptionNodesFromGraph(graph: AssumptionAnalysisGraph): Seq[AssumptionAnalysisNode] = { + graph.nodes.filter(node => + node.getNodeType.equals("Assumption") && + node.sourceInfo.toString.contains("@dependency()") + ).toSeq + } + + def extractTestObsoleteAssumptionNodesFromGraph(graph: AssumptionAnalysisGraph): Seq[AssumptionAnalysisNode] = { + graph.nodes.filter(node => + node.getNodeType.equals("Assumption") && + node.sourceInfo.toString.contains("@obsolete()") + ).toSeq + } +} From 0f392411c00fec4574e2b6ff81962ec6a31bd6c0 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 20 Jun 2025 15:28:11 +0200 Subject: [PATCH 100/474] fix AssumptionAnalysisTests and add test cases --- .../AssumptionAnalysisGraph.scala | 5 +- src/test/resources/andrea/quickTest.vpr | 41 ++++----- .../dependencyAnalysisTests/all/divBy0.vpr | 61 +++++++++++++ .../dependencyAnalysisTests/all/gaussian.vpr | 79 +++++++++++++++++ .../all/imprecision.vpr | 41 +++++++++ .../all/method-sum.vpr | 52 +++++++++++ .../all/predicates.vpr | 53 +++++++++++ .../all/presentation-examples.vpr | 27 ++++++ .../{ => generated}/snippets.json | 0 .../{ => generated}/test-templates.vpr | 0 src/test/scala/AssumptionAnalysisTests.scala | 87 ++++++++++++------- 11 files changed, 393 insertions(+), 53 deletions(-) create mode 100644 src/test/resources/dependencyAnalysisTests/all/divBy0.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/all/gaussian.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/all/imprecision.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/all/method-sum.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/all/predicates.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/all/presentation-examples.vpr rename src/test/resources/dependencyAnalysisTests/{ => generated}/snippets.json (100%) rename src/test/resources/dependencyAnalysisTests/{ => generated}/test-templates.vpr (100%) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index ea9873a3d..adc9f8a5d 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -35,10 +35,11 @@ trait AssumptionAnalysisGraph { var visited: Set[Int] = sources var queue: List[Int] = sources.toList while(queue.nonEmpty){ - val newVisits = edges.getOrElse(queue.head, Set()) + val curr = queue.head + val newVisits = edges.getOrElse(curr, Set()) ++ transitiveEdges.getOrElse(curr, Set()) if(newVisits.intersect(targets).nonEmpty) return true - visited = visited ++ Set(queue.head) + visited = visited ++ Set(curr) queue = queue.tail ++ (newVisits filter (!visited.contains(_))) } false diff --git a/src/test/resources/andrea/quickTest.vpr b/src/test/resources/andrea/quickTest.vpr index dd09d12b1..c5d2b649a 100644 --- a/src/test/resources/andrea/quickTest.vpr +++ b/src/test/resources/andrea/quickTest.vpr @@ -1,26 +1,27 @@ +field f: Int +field g: Int -function sum(a: Int, b: Int): Int - ensures result == a + b +method foo(x: Ref, y: Ref) -function rand(): Int - ensures result > 0 - -function func(a: Int, b: Int): Int - requires a > 0 - ensures result >= a && result >= b && result > 0 { - a>b? a : b -} + @dependency() + inhale acc(x.f) + @dependency() + inhale acc(y.f) + @obsolete() + inhale acc(y.g) -function useless(c: Bool): Int - requires c - ensures result == 5 + @dependency() + assume 0 < x.f + @obsolete() + assume x.f < 100 + @obsolete() + assume y.f < 100 + @obsolete() + assume 0 <= y.f + @dependency() + assume x.f < y.f -method funcClient(a: Int, b: Int) - requires a > 0 -{ - var res: Int - res := func(a, b) - assert res >= 0 - assert rand() + rand() > 0 + @testAssertion() + assert x.f / y.f <= 1 } \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/divBy0.vpr b/src/test/resources/dependencyAnalysisTests/all/divBy0.vpr new file mode 100644 index 000000000..ff8119d2b --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/all/divBy0.vpr @@ -0,0 +1,61 @@ +field f: Int + +method sum(x: Int, y: Int) returns(res: Int) + requires x >= 0 && y >= 0 + ensures res == x + y + ensures res > 100 +{ + assume x > 100 + res := x + y +} + +method divBy0() +{ + var x: Int + var y: Int + @dependency() + assume y > 0 + @testAssertion() + x := x/y +} + +method basic() +{ + var x: Int + var y: Int + @obsolete() + assume x > 0 + @dependency() + assume y > 0 + @dependency() + assume y < x + @dependency() + x := x/y + @testAssertion() + assert x >= 1 +} + + +method sumClient(x: Int, y: Int) +{ + @dependency() + assume x >= 0 + @dependency() + assume y >= 0 + @dependency() + assume x < y + var n: Int + @dependency() + n := sum(x, y) + + // the following stmt reports dependency on n := sum(x, y) because (x < y && n == x + y && n > 100 ==> y != 0) + // although you could also prove it via (0 <= x && x < y ==> y != 0) + var n2: Int + @dependency() + n2 := sum(x/y, y) + @dependency() + n := n + n2 + + @testAssertion() + assert n >= x + 2*y +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/gaussian.vpr b/src/test/resources/dependencyAnalysisTests/all/gaussian.vpr new file mode 100644 index 000000000..976a39e0b --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/all/gaussian.vpr @@ -0,0 +1,79 @@ +field f: Int + +method gaussianSimple(n: Int) returns (res: Int) + requires 0 <= n + requires @obsolete()(n <= 5) +{ + res := 0 + var i: Int := 0 + while(@dependency()(i <= n)) + invariant @dependency()(i <= (n + 1)) + invariant @obsolete()(i <= 6) + invariant @dependency()(res == (i - 1) * i / 2) + { + @dependency() + res := res + i + @dependency() + i := i + 1 + } + + @testAssertion() + assert res == n * (n + 1) / 2 +} + +method gaussianPerm(a: Ref, p: Perm) returns (res: Int) + requires @dependency()(none < p) && p < write + requires @dependency()(acc(a.f, p)) + requires 0 <= a.f + requires a.f <= 5 + ensures acc(a.f, p) +{ + res := 0 + var i: Int := 0 + while(@dependency()(i <= a.f)) + invariant @dependency()(acc(a.f, p)) + invariant 0 <= a.f && a.f <= 5 + invariant @dependency()(i <= (a.f + 1)) + invariant i <= 6 + invariant @dependency()(res == (i - 1) * i / 2) + { + @dependency() + res := res + i + @dependency() + i := i + 1 + } + + @testAssertion() + assert res == a.f * (a.f + 1) / 2 +} + +predicate gaussianEq(res: Int, n: Int){ + res == (n - 1) * n / 2 && n >= 0 +} + +method gaussianPred(n: Int) returns (res: Int) + requires 0 <= n + requires n <= 5 +{ + res := 0 + var i: Int := 0 + @dependency() + fold gaussianEq(res, i) + while(i <= n) + invariant i <= (n + 1) + invariant i <= 6 + invariant @dependency()(gaussianEq(res, i)) + { + @dependency() + unfold gaussianEq(res, i) + @dependency() + res := res + i + @dependency() + i := i + 1 + @dependency() + fold gaussianEq(res, i) + } + assert i == n+1 + @testAssertion() + assert gaussianEq(res, n+1) +} diff --git a/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr b/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr new file mode 100644 index 000000000..8489b7069 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr @@ -0,0 +1,41 @@ +field f: Int + +// test fails +method permTest(a: Ref, b: Ref, n: Int) + requires @dependency()(acc(a.f)) + requires @obsolete()(acc(b.f)) && @obsolete()(b.f > 0) +{ + //@dependency() TODO ake: soundness bug + assume n > 0 + @obsolete() + a.f := b.f + 2 + //@dependency() TODO ake: soundness bug + a.f := n + @testAssertion() + assert a.f >= 0 // incorrectly depends on acc(b.f) +} + + +method quantifiedPerm2Seqs2(xs: Seq[Ref], ys: Seq[Ref]) + requires @dependency()(|xs| > 5) + requires @obsolete()(|ys| > 3) +{ + @dependency() + inhale (forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0)) + inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard) && y.f > 0) // incorrectly depends on inhale forall x in xs due to heap summary + + @testAssertion() + assert xs[0].f > 0 +} + +method quantifiedPerm2Seqs3(xs: Seq[Ref], ys: Seq[Ref]) + requires @dependency()(|xs| > 5) + requires @obsolete()(|ys| > 3) +{ + @dependency() + inhale (forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0)) + inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard)) + + @testAssertion() + assert xs[0].f > 0 // incorrectly depends on inhale forall x in xs due to heap summary +} diff --git a/src/test/resources/dependencyAnalysisTests/all/method-sum.vpr b/src/test/resources/dependencyAnalysisTests/all/method-sum.vpr new file mode 100644 index 000000000..64d89e192 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/all/method-sum.vpr @@ -0,0 +1,52 @@ +field f: Int + +method sum(x: Int, y: Int) returns(res: Int) + requires x >= 0 && y >= 0 + ensures res == x + y + ensures res > 100 +{ + assume x > 100 + res := x + y +} + + +method sumClient(x: Int, y: Int) +{ + @dependency() + assume x >= 0 + @dependency() + assume y >= 0 + @dependency() + assume x < y + @dependency() + var n: Int := sum(x, y) + + // the following stmt reports dependency on n := sum(x, y) because (x < y && n == x + y && n > 100 ==> y != 0) + // although you could also prove it via (0 <= x && x < y ==> y != 0) + @dependency() + var n2: Int := sum(x/y, y) + @dependency() + n := n + n2 + + @testAssertion() + assert n >= x + 2*y +} + +method sumClient2(x: Int, y: Int) +{ + @obsolete() + assume x >= 0 + @dependency() + assume y >= 0 + @obsolete() + assume x < y + @obsolete() + var n: Int := sum(x, x) + @obsolete() + assert n >= 100 + + @dependency() + var n2: Int := sum(y, y) + @testAssertion() + assert n2 == 2*y +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/predicates.vpr b/src/test/resources/dependencyAnalysisTests/all/predicates.vpr new file mode 100644 index 000000000..7fb41c002 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/all/predicates.vpr @@ -0,0 +1,53 @@ + + +predicate greater0(n: Int) +{ + n > 0 +} + +predicate greater5(n: Int) +{ + n > 5 +} + +method foldP(n: Int) + requires @dependency()(n > 10) +{ + var x: Int := 1 + @testAssertion() + fold greater0(x + n) +} + +method unfoldP(n: Int) + requires @dependency()(greater0(n)) +{ + var x: Int := 1 + @dependency() + unfold greater0(n) + x := n + x + @testAssertion() + assert x > 1 +} + +method unfoldFoldP(a: Int, b: Int) + requires @dependency()(greater0(a) && greater5(b)) + ensures greater5(a + b) +{ + @dependency() + unfold greater0(a) + @dependency() + unfold greater5(b) + @testAssertion() + fold greater5(a + b) +} + +method callWithPredicate(a: Int, b: Int) + requires @dependency()(greater0(a) && greater5(b)) + ensures greater5(a + b) +{ + @dependency() + unfoldFoldP(a, b) + + @testAssertion() + assert greater5(a + b) +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/presentation-examples.vpr b/src/test/resources/dependencyAnalysisTests/all/presentation-examples.vpr new file mode 100644 index 000000000..c5d2b649a --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/all/presentation-examples.vpr @@ -0,0 +1,27 @@ +field f: Int +field g: Int + +method foo(x: Ref, y: Ref) + +{ + @dependency() + inhale acc(x.f) + @dependency() + inhale acc(y.f) + @obsolete() + inhale acc(y.g) + + @dependency() + assume 0 < x.f + @obsolete() + assume x.f < 100 + @obsolete() + assume y.f < 100 + @obsolete() + assume 0 <= y.f + @dependency() + assume x.f < y.f + + @testAssertion() + assert x.f / y.f <= 1 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/snippets.json b/src/test/resources/dependencyAnalysisTests/generated/snippets.json similarity index 100% rename from src/test/resources/dependencyAnalysisTests/snippets.json rename to src/test/resources/dependencyAnalysisTests/generated/snippets.json diff --git a/src/test/resources/dependencyAnalysisTests/test-templates.vpr b/src/test/resources/dependencyAnalysisTests/generated/test-templates.vpr similarity index 100% rename from src/test/resources/dependencyAnalysisTests/test-templates.vpr rename to src/test/resources/dependencyAnalysisTests/generated/test-templates.vpr diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index 00f6d2451..2cfc1487c 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -1,14 +1,14 @@ import org.scalatest.funsuite.AnyFunSuite import viper.silicon.SiliconFrontend -import viper.silicon.assumptionAnalysis.{AssumptionAnalysisGraph, AssumptionAnalysisNode, DependencyAnalysisReporter} -import viper.silver.ast -import viper.silver.ast.utility.rewriter.StrategyInterface -import viper.silver.ast.utility.{DiskLoader, ViperStrategy} +import viper.silicon.assumptionAnalysis.{AssumptionAnalysisGraph, AssumptionAnalysisNode, AssumptionType, DependencyAnalysisReporter} +import viper.silver.ast.utility.DiskLoader import viper.silver.frontend.SilFrontend +import viper.silver.verifier import java.io.File -import java.nio.file.Paths +import java.nio.file.{Files, Path, Paths} +import scala.jdk.CollectionConverters.IterableHasAsScala import scala.util.{Failure, Success} @@ -26,22 +26,37 @@ import scala.util.{Failure, Success} */ class AssumptionAnalysisTests extends AnyFunSuite { - def testDirectories: Seq[String] = Seq("dependencyAnalysis") + val GENERATE_TESTS = false + if(GENERATE_TESTS) + generateTests("dependencyAnalysisTests/", "test-templates") - val GENERATE_TESTS = true val commandLineArguments: Seq[String] = Seq("--timeout", "100" /* seconds */, "--enableDebugging", "--enableAssumptionAnalysis", "--z3Args", "proof=true unsat-core=true") - test("first test"){ - val result = executeTest("dependencyAnalysisTests/", "test-templates_addition_generated", ViperStrategy.Slim({ - case a: ast.Inhale => a - }), frontend) - assert(result) - } - if(GENERATE_TESTS) - generateTests("dependencyAnalysisTests/", "test-templates") + val testDirectories: Seq[String] = Seq("dependencyAnalysisTests/all") + testDirectories foreach createTests + +// test("dependencyAnalysisTests/all" + "/" + "imprecision"){ +// executeTest("dependencyAnalysisTests/all" + "/", "imprecision", frontend) +// } + + + def createTests(dirName: String): Unit = { + val path = getClass.getClassLoader.getResource(dirName) + val directoryStream = Files.newDirectoryStream(Paths.get(path.toURI)).asScala + val dirContent = directoryStream.toList + + for (filePath: Path <- dirContent.sorted + if Files.isReadable(filePath) + if !Files.isDirectory(filePath)){ + val fileName = filePath.getFileName.toString.replace(".vpr", "") + test(dirName + "/" + fileName){ + executeTest(dirName + "/", fileName, frontend) + } + } + } def generateTests(filePrefix: String, fileName: String): Unit = { @@ -94,35 +109,43 @@ class AssumptionAnalysisTests extends AnyFunSuite { def executeTest(filePrefix: String, fileName: String, - strategy: StrategyInterface[ast.Node], frontend: SilFrontend) - : Boolean = { + : Unit = { val program = tests.loadProgram(filePrefix, fileName, frontend) - val _ = frontend.verifier.verify(program) + val result = frontend.verifier.verify(program) + + assert(result match { + case verifier.Success => true + case verifier.Failure(_) => false + }, s"Verification failed for ${filePrefix + fileName + ".vpr"}") val assumptionGraphsReal = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].assumptionGraphs - (assumptionGraphsReal forall checkDependencies) && (assumptionGraphsReal forall checkNonDependencies) + // TODO ake: collect all errors and report them as one assertion + assumptionGraphsReal foreach checkDependencies + assumptionGraphsReal foreach checkNonDependencies } - def checkDependencies(assumptionGraph: AssumptionAnalysisGraph): Boolean = { + def checkDependencies(assumptionGraph: AssumptionAnalysisGraph): Unit = { val assumptionNodes = extractTestAssumptionNodesFromGraph(assumptionGraph) val assumptionsPerSource = assumptionNodes groupBy(_.sourceInfo.toString) val assertionNodes = extractTestAssertionNodesFromGraph(assumptionGraph) - assumptionNodes.isEmpty || assertionNodes.isEmpty || - assumptionsPerSource.forall{case (_, assumptions) => - checkDependenciesForSingleSource(assumptionGraph, assumptions, assertionNodes) - } + + assumptionsPerSource.foreach { case (sourceInfo, assumptions) => + val hasDeps = checkDependenciesForSingleSource(assumptionGraph, assumptions, assertionNodes) + assert(hasDeps, s"Missing dependency: $sourceInfo") + } } - def checkNonDependencies(assumptionGraph: AssumptionAnalysisGraph): Boolean = { + def checkNonDependencies(assumptionGraph: AssumptionAnalysisGraph): Unit = { val assumptionNodes = extractTestObsoleteAssumptionNodesFromGraph(assumptionGraph) val assumptionsPerSource = assumptionNodes groupBy(_.sourceInfo.toString) val assertionNodes = extractTestAssertionNodesFromGraph(assumptionGraph) - assumptionNodes.isEmpty || assertionNodes.isEmpty || - !assumptionsPerSource.exists{case (_, assumptions) => - checkDependenciesForSingleSource(assumptionGraph, assumptions, assertionNodes) + + assumptionsPerSource.foreach {case (sourceInfo, assumptions) => + val hasDependency = checkDependenciesForSingleSource(assumptionGraph, assumptions, assertionNodes) + assert(!hasDependency, s"Unexpected dependency: $sourceInfo") } } @@ -134,21 +157,23 @@ class AssumptionAnalysisTests extends AnyFunSuite { def extractTestAssertionNodesFromGraph(graph: AssumptionAnalysisGraph): Seq[AssumptionAnalysisNode] = { graph.nodes.filter(node => - node.getNodeType.equals("Assertion") && + (node.getNodeType.equals("Assertion") || node.getNodeType.equals("Exhale")) && node.sourceInfo.toString.contains("@testAssertion()") ).toSeq } def extractTestAssumptionNodesFromGraph(graph: AssumptionAnalysisGraph): Seq[AssumptionAnalysisNode] = { graph.nodes.filter(node => - node.getNodeType.equals("Assumption") && + (node.getNodeType.equals("Assumption") || node.getNodeType.equals("Inhale")) && + !node.assumptionType.equals(AssumptionType.Internal) && node.sourceInfo.toString.contains("@dependency()") ).toSeq } def extractTestObsoleteAssumptionNodesFromGraph(graph: AssumptionAnalysisGraph): Seq[AssumptionAnalysisNode] = { graph.nodes.filter(node => - node.getNodeType.equals("Assumption") && + (node.getNodeType.equals("Assumption") || node.getNodeType.equals("Inhale")) && + !node.assumptionType.equals(AssumptionType.Internal) && node.sourceInfo.toString.contains("@obsolete()") ).toSeq } From ad324c74bf16d3649ff11ad430378418bb3dfa10 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 21 Jun 2025 11:31:07 +0200 Subject: [PATCH 101/474] tests: check existence of nodes --- .../dependencyAnalysisTests/all/divBy0.vpr | 4 +- .../dependencyAnalysisTests/all/gaussian.vpr | 8 +-- .../all/predicates.vpr | 4 +- src/test/scala/AssumptionAnalysisTests.scala | 51 +++++++++++++++---- 4 files changed, 51 insertions(+), 16 deletions(-) diff --git a/src/test/resources/dependencyAnalysisTests/all/divBy0.vpr b/src/test/resources/dependencyAnalysisTests/all/divBy0.vpr index ff8119d2b..849fbd436 100644 --- a/src/test/resources/dependencyAnalysisTests/all/divBy0.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/divBy0.vpr @@ -13,7 +13,7 @@ method divBy0() { var x: Int var y: Int - @dependency() + @dependency("Explicit") assume y > 0 @testAssertion() x := x/y @@ -29,7 +29,7 @@ method basic() assume y > 0 @dependency() assume y < x - @dependency() + @dependency("Implicit") x := x/y @testAssertion() assert x >= 1 diff --git a/src/test/resources/dependencyAnalysisTests/all/gaussian.vpr b/src/test/resources/dependencyAnalysisTests/all/gaussian.vpr index 976a39e0b..a66a16723 100644 --- a/src/test/resources/dependencyAnalysisTests/all/gaussian.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/gaussian.vpr @@ -35,9 +35,9 @@ method gaussianPerm(a: Ref, p: Perm) returns (res: Int) invariant 0 <= a.f && a.f <= 5 invariant @dependency()(i <= (a.f + 1)) invariant i <= 6 - invariant @dependency()(res == (i - 1) * i / 2) + invariant @dependency("LoopInvariant")(res == (i - 1) * i / 2) { - @dependency() + @dependency("Implicit") res := res + i @dependency() i := i + 1 @@ -56,7 +56,9 @@ method gaussianPred(n: Int) returns (res: Int) requires n <= 5 { res := 0 - var i: Int := 0 + var i: Int + // @dependency() TODO ake + i := 0 @dependency() fold gaussianEq(res, i) while(i <= n) diff --git a/src/test/resources/dependencyAnalysisTests/all/predicates.vpr b/src/test/resources/dependencyAnalysisTests/all/predicates.vpr index 7fb41c002..dd2d4a06d 100644 --- a/src/test/resources/dependencyAnalysisTests/all/predicates.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/predicates.vpr @@ -30,7 +30,7 @@ method unfoldP(n: Int) } method unfoldFoldP(a: Int, b: Int) - requires @dependency()(greater0(a) && greater5(b)) + requires @dependency()(greater0(a)) && @dependency()(greater5(b)) ensures greater5(a + b) { @dependency() @@ -42,7 +42,7 @@ method unfoldFoldP(a: Int, b: Int) } method callWithPredicate(a: Int, b: Int) - requires @dependency()(greater0(a) && greater5(b)) + requires @dependency()(greater0(a)) && @dependency()(greater5(b)) ensures greater5(a + b) { @dependency() diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index 2cfc1487c..7a734d53a 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -1,8 +1,9 @@ import org.scalatest.funsuite.AnyFunSuite import viper.silicon.SiliconFrontend -import viper.silicon.assumptionAnalysis.{AssumptionAnalysisGraph, AssumptionAnalysisNode, AssumptionType, DependencyAnalysisReporter} -import viper.silver.ast.utility.DiskLoader +import viper.silicon.assumptionAnalysis.{AssumptionAnalysisGraph, AssumptionAnalysisNode, AssumptionType, DependencyAnalysisReporter, GeneralAssumptionNode} +import viper.silver.ast.{AnnotationInfo, Infoed, Node, Positioned, Program, Seqn} +import viper.silver.ast.utility.{DiskLoader, ViperStrategy} import viper.silver.frontend.SilFrontend import viper.silver.verifier @@ -26,6 +27,9 @@ import scala.util.{Failure, Success} */ class AssumptionAnalysisTests extends AnyFunSuite { + val obsoleteKeyword = "obsolete" + val dependencyKeyword = "dependency" + val GENERATE_TESTS = false if(GENERATE_TESTS) generateTests("dependencyAnalysisTests/", "test-templates") @@ -105,14 +109,12 @@ class AssumptionAnalysisTests extends AnyFunSuite { fe } - - def executeTest(filePrefix: String, fileName: String, frontend: SilFrontend) : Unit = { - val program = tests.loadProgram(filePrefix, fileName, frontend) + val program: Program = tests.loadProgram(filePrefix, fileName, frontend) val result = frontend.verifier.verify(program) assert(result match { @@ -121,12 +123,41 @@ class AssumptionAnalysisTests extends AnyFunSuite { }, s"Verification failed for ${filePrefix + fileName + ".vpr"}") val assumptionGraphsReal = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].assumptionGraphs + val stmtsWithAssumptionAnnotation: Set[Infoed] = extractAnnotatedAssumptionStmts(program) + val allAssumptionNodes = assumptionGraphsReal.flatMap(_.nodes.filter(_.isInstanceOf[GeneralAssumptionNode])) // TODO ake: collect all errors and report them as one assertion + stmtsWithAssumptionAnnotation foreach {n => checkNodeExists(allAssumptionNodes, n)} assumptionGraphsReal foreach checkDependencies assumptionGraphsReal foreach checkNonDependencies } + private def extractAnnotatedAssumptionStmts(program: Program): Set[Infoed] = { + var nodesWithAnnotation: Set[Infoed] = Set.empty + val newP: Program = ViperStrategy.Slim({ + case s: Seqn => s + case n: Infoed => + val annotationInfo = n.info.getUniqueInfo[AnnotationInfo] + .filter(ai => ai.values.contains(obsoleteKeyword) || ai.values.contains(dependencyKeyword)) + if (annotationInfo.isDefined) + nodesWithAnnotation += n + n + }).execute(program) + nodesWithAnnotation + } + + private def checkNodeExists(analysisNodes: List[AssumptionAnalysisNode], node: Infoed): Unit = { + val pos = node.asInstanceOf[Positioned].pos + val annotationInfo = node.info.getUniqueInfo[AnnotationInfo] + .map(ai => ai.values.getOrElse(obsoleteKeyword, ai.values.getOrElse(dependencyKeyword, List.empty))).getOrElse(List.empty) + val assumptionType = annotationInfo.map(AssumptionType.fromString).filter(_.isDefined).map(_.get) + val nodeExists = analysisNodes exists (analysisNode => { + analysisNode.sourceInfo.getPosition.equals(pos) && + assumptionType.forall(_.equals(analysisNode.assumptionType)) + }) + assert(nodeExists, s"Missing analysis node:\n${node.toString}\n$pos") + } + def checkDependencies(assumptionGraph: AssumptionAnalysisGraph): Unit = { val assumptionNodes = extractTestAssumptionNodesFromGraph(assumptionGraph) val assumptionsPerSource = assumptionNodes groupBy(_.sourceInfo.toString) @@ -163,18 +194,20 @@ class AssumptionAnalysisTests extends AnyFunSuite { } def extractTestAssumptionNodesFromGraph(graph: AssumptionAnalysisGraph): Seq[AssumptionAnalysisNode] = { - graph.nodes.filter(node => + graph.nodes.filter(node => { (node.getNodeType.equals("Assumption") || node.getNodeType.equals("Inhale")) && !node.assumptionType.equals(AssumptionType.Internal) && - node.sourceInfo.toString.contains("@dependency()") + node.sourceInfo.toString.contains("@" + dependencyKeyword + "()") + } ).toSeq } def extractTestObsoleteAssumptionNodesFromGraph(graph: AssumptionAnalysisGraph): Seq[AssumptionAnalysisNode] = { - graph.nodes.filter(node => + graph.nodes.filter(node => { (node.getNodeType.equals("Assumption") || node.getNodeType.equals("Inhale")) && !node.assumptionType.equals(AssumptionType.Internal) && - node.sourceInfo.toString.contains("@obsolete()") + node.sourceInfo.toString.contains("@" + obsoleteKeyword + "()") + } ).toSeq } } From 8f3835b07aff011c06f903cdf30425f5a94cf12c Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 21 Jun 2025 13:56:46 +0200 Subject: [PATCH 102/474] add many test cases --- .../dependencyAnalysisTests/all/aliasing.vpr | 33 +++++ .../dependencyAnalysisTests/all/branches.vpr | 120 ++++++++++++++++++ .../all/infeasible.vpr | 49 +++++++ .../dependencyAnalysisTests/todo/list.vpr | 37 ++++++ .../dependencyAnalysisTests/todo/lseg.vpr | 30 +++++ .../todo/permissions.vpr | 65 ++++++++++ .../todo/quantified-perm.vpr | 115 +++++++++++++++++ .../unitTests/branches.vpr | 73 +++++++++++ .../unitTests/loops.vpr | 78 ++++++++++++ .../unitTests/magicWands.vpr | 47 +++++++ .../unitTests/method-call.vpr | 73 +++++++++++ .../unitTests/permissions.vpr | 84 ++++++++++++ .../{all => unitTests}/predicates.vpr | 7 +- .../unitTests/quantifiedPermissions.vpr | 45 +++++++ .../unitTests/transitivity.vpr | 13 ++ src/test/scala/AssumptionAnalysisTests.scala | 24 +++- 16 files changed, 883 insertions(+), 10 deletions(-) create mode 100644 src/test/resources/dependencyAnalysisTests/all/aliasing.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/all/branches.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/all/infeasible.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/todo/list.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/todo/lseg.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/todo/permissions.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/todo/quantified-perm.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/unitTests/branches.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/unitTests/loops.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/unitTests/method-call.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr rename src/test/resources/dependencyAnalysisTests/{all => unitTests}/predicates.vpr (91%) create mode 100644 src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/unitTests/transitivity.vpr diff --git a/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr b/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr new file mode 100644 index 000000000..80241b814 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr @@ -0,0 +1,33 @@ +field f: Int + + +method maybeAlias(a: Ref, b: Ref, c: Bool, n: Int) + requires @dependency()(acc(a.f, 1/2)) + requires @dependency()(acc(b.f, 1/2)) + requires c ==> a == b // TODO ake: @dependency() + requires a.f > 0 && n > 0 && b.f >= 0 + requires a.f < 100 + requires !c ==> a.f < b.f +{ + if(c){ + @testAssertion() + a.f := n + 1 + } +} + +method aliasing(x: Ref, n: Int) + requires @dependency()(acc(x.f)) + requires @obsolete()(n > 0) + requires @obsolete()(x.f > n) +{ + @dependency() + x.f := n + 1 + + var y: Ref + // @dependency() // TODO ake: missing node + y := x + + @testAssertion() + assert y.f > n +} + diff --git a/src/test/resources/dependencyAnalysisTests/all/branches.vpr b/src/test/resources/dependencyAnalysisTests/all/branches.vpr new file mode 100644 index 000000000..99ec89f50 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/all/branches.vpr @@ -0,0 +1,120 @@ +method branches1(a: Int, b: Int) +{ + var n:Int, c: Bool + + @dependency() + assume 0 < a + @dependency() + assume b > 4 + + @obsolete() + assume a < 100 + @obsolete() + assume b < 50 + @obsolete() + assume c ==> a > 5 + + if(c){ + @dependency() + n := a + 1 + }else{ + @dependency() + n := b + 1 + } + + @testAssertion() + assert n > 1 +} + +method branches2(a: Int, b: Int) +{ + var n:Int, c: Bool + + @dependency() + assume 0 < a + @obsolete() + assume a < 100 + @obsolete() + assume a < b + @obsolete() + assume b < 50 + + var x: Int + if(a >= n){ + @obsolete() + x := a + b + }else{ + @dependency() + x := n + 1 + @testAssertion() + assert x > 1 + } +} + +method branches3(a: Int, b: Int) +{ + var n:Int, c: Bool + + @obsolete() + assume 0 < a + @obsolete() + assume a < 100 + @dependency() + assume 0 < b + @obsolete() + assume b < 50 + + if(c){ + n := a + 1 + }else{ + n := b + 1 + } + + var x: Int + if(a >= n){ + @dependency() + x := a + b + }else{ + @dependency() + x := n + 1 + } + + @testAssertion() + assert x > n +} + + +method nestedBranches1(a: Int, b: Int) +{ + var n:Int, c: Bool + + @obsolete() + assume 0 < a + @obsolete() + assume a < 100 + @dependency() + assume 0 < b + @obsolete() + assume b < 50 + @obsolete() + assume c ==> a > 5 + + if(c){ + if(a > b){ + @dependency() + n := a - b + }else{ + @dependency() + n := a + b + } + @dependency() + n := n - 1 + }else{ + @dependency() + n := a + b + } + + @testAssertion() + assert n <= a + b + assert c ==> n < a + b +} diff --git a/src/test/resources/dependencyAnalysisTests/all/infeasible.vpr b/src/test/resources/dependencyAnalysisTests/all/infeasible.vpr new file mode 100644 index 000000000..a5eeb6444 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/all/infeasible.vpr @@ -0,0 +1,49 @@ +field f: Int + +method infeasibleBranch1(a: Int, b: Int) + requires @dependency()(a > 0) +{ + if(a < 0){ + @testAssertion() + assert false + } +} + +method infeasibleBranch2(a: Int, b: Int) + requires @dependency()(a > 0) +{ + var res: Int + if(@dependency()(a < 0)){ + @obsolete() + assume res < 0 + }else{ + @dependency() + assume res > 0 + } + @testAssertion() + assert res > 0 +} + + +method infeasibleBranchPerm(a: Ref, b: Ref) + requires @dependency()(acc(a.f, 1/2)) + requires @dependency()(a.f > 0) +{ + if(a.f < 0){ + @testAssertion() + a.f := a.f + 1 + } +} + + +method infeasibleBranchNoPerm(a: Int, b: Ref) + requires a > 0 //@dependency()(a > 0) +{ + var res: Int + if(a < 0){ + //@obsolete() TODO ake: nodes missing + res := b.f + 1 + //@testAssertion() + assert false + } +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/todo/list.vpr b/src/test/resources/dependencyAnalysisTests/todo/list.vpr new file mode 100644 index 000000000..c763ab30f --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/todo/list.vpr @@ -0,0 +1,37 @@ +field elem: Int +field next: Ref + +predicate list(this: Ref) { + acc(this.elem) && acc(this.next) && + (this.next != null ==> list(this.next)) +} + +function listLength(l:Ref) : Int + requires list(l) + ensures result > 0 +{ + unfolding list(l) in l.next == null ? 1 : 1 + listLength(l.next) +} + +method appendList(this: Ref, e: Int) + requires list(this) + requires 0 <= e && e < 100 + ensures list(this) +{ + unfold list(this) + assume 0 <= this.elem && this.elem < 100 + + if (this.next == null) { + var n: Ref + + n := new(elem, next) + n.elem := e + n.next := null + this.next := n + + fold list(n) + } else { + appendList(this.next, e) + } + fold list(this) +} diff --git a/src/test/resources/dependencyAnalysisTests/todo/lseg.vpr b/src/test/resources/dependencyAnalysisTests/todo/lseg.vpr new file mode 100644 index 000000000..b696523f7 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/todo/lseg.vpr @@ -0,0 +1,30 @@ +field elem: Int +field next: Ref + +predicate lseg(this: Ref, last: Ref) { + this != last ==> + acc(this.elem) && acc(this.next) && + this.next != null && + lseg(this.next, last) +} + +function values(this: Ref, last: Ref): Seq[Int] + requires lseg(this, last) +{ + unfolding lseg(this, last) in + this == last + ? Seq[Int]() + : Seq(this.elem) ++ values(this.next, last) +} + +method removeFirst(this: Ref, last: Ref) returns (first: Int, rest: Ref) + requires lseg(this, last) + requires this != last + ensures lseg(rest, last) + ensures values(rest, last) == old(values(this, last)[1..]) +{ + unfold lseg(this, last) + + first := this.elem + rest := this.next +} diff --git a/src/test/resources/dependencyAnalysisTests/todo/permissions.vpr b/src/test/resources/dependencyAnalysisTests/todo/permissions.vpr new file mode 100644 index 000000000..2acea8a72 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/todo/permissions.vpr @@ -0,0 +1,65 @@ +field f: Int + +function id(n: Int): Int + ensures result == n + +method foo(a: Ref) + requires @assumptionType("Internal")(acc(a.f, 1/2)) && a.f >= 0 + ensures acc(a.f, 1/2) + +method call(x: Ref) + requires acc(x.f) + ensures acc(x.f) +{ + assume x.f > 0 + foo(x) + assert x.f >= 0 +} + +method permAmount(x: Ref, y: Ref, p: Perm) + requires p > none + requires acc(x.f) && acc(y.f, p) + ensures acc(x.f) && acc(y.f, p) +{ + x.f := 5 + assume p > 1/2 + assume y.f == 1 + x.f := y.f + 1 + foo(x) + assert x.f == y.f + 1 +} + +method transitivity(a: Ref, n: Int) + requires n > 0 + requires acc(a.f) && a.f > 0 +{ + var res: Int + res := a.f / n + assert res >= 0 +} + +method maybeAlias(a: Ref, b: Ref, c: Bool, n: Int) + requires acc(a.f, 1/2) && acc(b.f, 1/2) + requires c ==> a == b + requires a.f > 0 && n > 0 && b.f >= 0 + requires a.f < 100 + requires !c ==> a.f < b.f +{ + if(c){ + a.f := n + 1 + } + assert a.f >= 0 + assert !c ==> a.f <= 100 +} + +method aliasing(x: Ref, n: Int) + requires acc(x.f) && x.f < n +{ + var y: Ref := x + y.f := n + 1 + assert x.f >= n + + assume y.f < n + assert false + +} diff --git a/src/test/resources/dependencyAnalysisTests/todo/quantified-perm.vpr b/src/test/resources/dependencyAnalysisTests/todo/quantified-perm.vpr new file mode 100644 index 000000000..f40687468 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/todo/quantified-perm.vpr @@ -0,0 +1,115 @@ +field f: Int +field first : Ref +field second : Ref + +method quantifiedPerm(xs: Seq[Ref]) { + assume |xs| > 5 + inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) + + assert xs[2].f > 0 + var a : Ref := xs[0] + a.f := 0 + assert xs[0].f >= 0 + assert xs[0].f == 0 + assert xs[2].f >= 0 + a.f := -1 + assert xs[2] != a ==> xs[2].f > 0 +} + +method quantifiedWritePerm(nodes: Set[Ref], x: Ref) + requires forall n:Ref :: { n.first } n in nodes ==> + acc(n.first) && + (n.first != null ==> n.first in nodes) + requires forall n:Ref :: { n.second } n in nodes ==> + acc(n.second) && + (n.second != null ==> n.second in nodes) + requires x in nodes +{ + var y : Ref + if(x.second != null) { + y := x.second // permissions covered by preconditions + y.second := y + assert x.second.second == x.second + } +} + +method quantifiedSum(nodes: Set[Ref], x: Ref) + requires forall n:Ref :: { n.first } n in nodes ==> + acc(n.first) && + (n.first != null ==> n.first in nodes) + requires forall n:Ref :: { n.f } n in nodes ==> + acc(n.f) && 0 <= n.f && n.f <= 100 + requires x in nodes +{ + var a: Int := x.f + if(x.first != null) { + a := a + x.first.f + } + assert a >= 0 +} + + +method quantifiedPerm2Seqs(xs: Seq[Ref], ys: Seq[Ref]) { + assume |xs| > 5 && |ys| > 3 + inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) + inhale forall y: Ref :: y in ys ==> (acc(y.f) && y.f > 0) + + assert xs[0].f > 0 +} + +method quantifiedPerm2Seqs2(xs: Seq[Ref], ys: Seq[Ref]) { + assume |xs| > 5 && |ys| > 3 + inhale forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0) + inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard) && y.f > 0) + + assert xs[0].f > 0 +} + +method quantifiedPerm2Seqs3(xs: Seq[Ref], ys: Seq[Ref]) { + assume |xs| > 5 && |ys| > 3 + inhale forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0) + inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard)) + + assert xs[0].f > 0 +} + +method quantifiedExhalePartially(xs: Seq[Ref], ys: Seq[Ref]) { + assume |xs| > 5 + inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) + + xs[0].f := 10 + exhale forall x: Ref :: x in xs ==> acc(x.f, 1/2) + assert xs[1].f > 0 +} + +method quantifiedExhaleFully(xs: Seq[Ref], ys: Seq[Ref]) { + assume |xs| > 5 + inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) + + xs[0].f := 10 + exhale forall x: Ref :: x in xs ==> acc(x.f) +} + +method quantifiedPermBig(xs: Seq[Ref], ys: Seq[Ref], r: Ref) { + assume |xs| > 5 && |ys| > 5 + inhale acc(r.f, 1/2) + inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) + inhale forall y: Ref :: y in ys ==> (acc(y.f, 1/2) && y.f > 0) + + assert xs[0] != ys[0] + assert r != xs[0] + assert xs[2].f >= 0 + xs[0].f := xs[1].f + xs[2].f + xs[1].f := ys[0].f + + inhale ys[0] == r + + ys[0].f := xs[0].f + r.f := r.f + xs[0].f + assert r.f == 2*xs[0].f + + xs[3].f := r.f + + assert forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) + assert forall y: Ref :: y in ys ==> (acc(y.f, 1/2) && y.f > 0) + } \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/branches.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/branches.vpr new file mode 100644 index 000000000..e03f71342 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/unitTests/branches.vpr @@ -0,0 +1,73 @@ + +method branch1(){ + var x: Int, y: Int + + if(@dependency()(x > 0)){ + @dependency() + y := x + 1 + }else{ + @dependency() + y := -x + 1 + } + + @testAssertion() + assert y > 0 +} + +method branch2(){ + var x: Int, y: Int + + if(@obsolete()(x > 0)){ + @dependency() + assume y > 0 + }else{ + @dependency() + assume y >= 10 + } + + @testAssertion() + assert y > 0 +} + +method branch3(){ + var x: Int, y: Int + + if(@dependency()(x > 0)){ + @dependency() + y := x + 1 + }else{ + @obsolete() + assume y >= 10 + } + + @testAssertion() + assert x > 0 ==> y > 0 +} + +method branch4(){ + var x: Int, y: Int + + if(@dependency()(x > 0)){ + @obsolete() + y := x + 1 + }else{ + @dependency() + y := 10 - x + @testAssertion() + assert y > 0 + } +} + +method branch5(){ + var x: Int, y: Int + @dependency() + assume y > 0 + + if(@obsolete()(x > 0)){ + @obsolete() + y := x + 1 + }else{ + @testAssertion() + assert y >= 0 + } +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/loops.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/loops.vpr new file mode 100644 index 000000000..aaa7b042b --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/unitTests/loops.vpr @@ -0,0 +1,78 @@ + +method loop1(){ + var i: Int + var res: Int + res := 0 + i := 10 + while(i > 0) + invariant @obsolete()(i <= 10) + invariant @obsolete()(i >= 0) + invariant @dependency()(res >= 0) + { + @dependency() + res := res + i + i := i - 1 + } + + @testAssertion() + assert res >= 0 +} + +method loop2(){ + var i: Int + var res: Int + res := 0 + i := 10 + while(@dependency()(i > 0)) + invariant @obsolete()(i <= 10) + invariant @obsolete()(i >= 0) + invariant @obsolete()(res >= 0) + { + @obsolete() + res := res + i + @dependency() + i := i - 1 + @testAssertion() + assert i >= 0 + } +} + +method loop3(){ + var i: Int + var res: Int + res := 0 + i := 10 + while(@dependency()(i > 0)) + invariant @obsolete()(i <= 10) + invariant @obsolete()(i >= 0) + invariant @dependency()(res >= 0) + { + @dependency() + res := res + i + @obsolete() + i := i - 1 + @testAssertion() + assert res > 0 + } +} + +method loop4(){ + var i: Int + var res: Int + res := 0 + i := 10 + while(@obsolete()(i > 0)) + invariant @obsolete()(i <= 10) + invariant @obsolete()(i >= 0) + invariant @dependency()(res >= 0) + { + res := res - 1 + @obsolete() + i := i - 1 + @dependency() + assume res > 0 + } + + @testAssertion() + assert res >= 0 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr new file mode 100644 index 000000000..27472a1aa --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr @@ -0,0 +1,47 @@ +field next : Ref +field val : Int + +predicate list(start : Ref) +{ + acc(start.val) && acc(start.next) && + (start.next != null && list(start.next)) +} + + +method basicApply() +{ + var x: Int + var y: Int + @dependency() + inhale x > 0 + @dependency() + inhale x > 0 --* y > 0 + @dependency() + apply x > 0 --* y > 0 + @testAssertion() + assert y > 0 +} + + +method basicPackage(l: Ref) + requires @dependency()(list(l)) + ensures list(l) + { + @dependency() + unfold list(l) + var tmp : Ref + @dependency() + tmp := l.next + + @dependency() + package list(tmp) --* list(l) + { + fold list(l) + } + + @dependency() + apply list(tmp) --* list(l) + + @testAssertion() + assert list(l) +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/method-call.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/method-call.vpr new file mode 100644 index 000000000..78f27f7a7 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/unitTests/method-call.vpr @@ -0,0 +1,73 @@ +field f: Int + +method sum(x: Int, y: Int) returns(res: Int) + requires x >= 0 && y >= 0 + ensures res == x + y +{ + res := x + y +} + +method call1(){ + var x: Int, y: Int + @dependency() + assume x > 10 + @dependency() + assume y > 0 + + @testAssertion() + x := sum(x, y) +} + +method call2(){ + var x: Int, y: Int, z: Int + @dependency() + assume x > 10 + @dependency() + assume y > 0 + + @dependency() + z := sum(x, y) + + @testAssertion() + assert z == x + y +} + +method call3(x: Int, y: Int) +{ + @dependency() + assume x > 0 + @dependency() + assume y > 0 + + var n: Int, n2: Int + @dependency() + n := sum(x, y) + @dependency() + n2 := sum(x, y) + @dependency() + n := n + n2 + + @testAssertion() + assert n == 2*x + 2*y +} + + +method call4(x: Int, y: Int, z: Int) +{ + @dependency() + assume x > 0 + @dependency() + assume y > 0 + + @obsolete() + assume z > 0 + + var n: Int, n2: Int + @dependency() + n := sum(x, y) + @obsolete() + n2 := sum(x, z) + + @testAssertion() + assert n == x + y +} diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr new file mode 100644 index 000000000..13b0c14a9 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr @@ -0,0 +1,84 @@ +field f: Int + + +method perm1(){ + var x: Ref + @dependency() + x := new(f) + @testAssertion() + inhale x.f > 0 +} + +method perm2(){ + var x: Ref + @dependency() + inhale acc(x.f, 1/2) + @testAssertion() + inhale x.f > 0 +} + +method perm3(){ + var x: Ref + @dependency() + inhale acc(x.f) + @testAssertion() + x.f := x.f + 1 +} + +method perm4(){ + var x: Ref + @dependency() + inhale acc(x.f) + @dependency() + inhale x.f > 0 + @dependency() + x.f := x.f + 1 + @testAssertion() + assert x.f > 1 +} + +method perm5(){ + var x: Ref + @dependency() + inhale acc(x.f) + x.f := x.f + 1 + @testAssertion() + exhale acc(x.f) +} + +method perm6(){ + var x: Ref + @dependency() + inhale acc(x.f) + @dependency() + inhale x.f > 0 + @dependency() + x.f := x.f + 1 + // @obsolete() // TODO ake + exhale acc(x.f, 1/2) + @testAssertion() + assert x.f > 1 +} + +method permAmount1(x: Ref, p: Perm) + requires p > none + requires @dependency()(acc(x.f, p)) +{ + @dependency() + assume p > 1/2 + @testAssertion() + exhale acc(x.f, 1/2) +} + +method permAmount2(x: Ref, p: Perm) + requires p > none + requires @dependency()(acc(x.f, p)) +{ + @dependency() + inhale x.f > 0 + @dependency() + assume p > 1/2 + exhale acc(x.f, 1/2) + @testAssertion() + assert x.f > 0 +} diff --git a/src/test/resources/dependencyAnalysisTests/all/predicates.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/predicates.vpr similarity index 91% rename from src/test/resources/dependencyAnalysisTests/all/predicates.vpr rename to src/test/resources/dependencyAnalysisTests/unitTests/predicates.vpr index dd2d4a06d..5a7bdc4a9 100644 --- a/src/test/resources/dependencyAnalysisTests/all/predicates.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/predicates.vpr @@ -1,12 +1,10 @@ -predicate greater0(n: Int) -{ +predicate greater0(n: Int){ n > 0 } -predicate greater5(n: Int) -{ +predicate greater5(n: Int){ n > 5 } @@ -24,6 +22,7 @@ method unfoldP(n: Int) var x: Int := 1 @dependency() unfold greater0(n) + @dependency() x := n + x @testAssertion() assert x > 1 diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr new file mode 100644 index 000000000..2da344bd5 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr @@ -0,0 +1,45 @@ +field f: Int + +method quantifiedPerm1(xs: Seq[Ref]) { + assume @dependency()(|xs| > 5) + inhale @dependency()(forall x: Ref :: x in xs ==> acc(x.f)) + + @testAssertion() + xs[0].f := 10 +} + +method quantifiedPerm2(xs: Seq[Ref]) { + assume @dependency()(|xs| > 5) + inhale @dependency()(forall x: Ref :: x in xs ==> acc(x.f)) + + @testAssertion() + xs[0].f := xs[1].f + xs[4].f +} + +method quantifiedPerm3(xs: Seq[Ref], y: Ref) { + assume @dependency()(|xs| > 5) + inhale @dependency()(forall x: Ref :: x in xs ==> acc(x.f)) + inhale @dependency()(acc(y.f, wildcard)) + + @testAssertion() + assert xs[0] != y +} + +method quantifiedExhalePartially(xs: Seq[Ref]) { + var res: Int + assume @dependency()(|xs| > 5) + // inhale @dependency()(forall x: Ref :: x in xs ==> acc(x.f)) + inhale (forall x: Ref :: x in xs ==> acc(x.f)) // TODO ake: missing dependency + + exhale forall x: Ref :: x in xs ==> acc(x.f, 1/2) + @testAssertion() + res := xs[1].f + 1 +} + +method quantifiedExhaleFully(xs: Seq[Ref]) { + assume @obsolete()(|xs| > 5) + inhale @dependency()(forall x: Ref :: x in xs ==> acc(x.f)) + + @testAssertion() + exhale forall x: Ref :: x in xs ==> acc(x.f) +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/transitivity.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/transitivity.vpr new file mode 100644 index 000000000..08a9b1786 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/unitTests/transitivity.vpr @@ -0,0 +1,13 @@ +field f: Int + +method transitivity(a: Ref, n: Int) + requires @dependency()(n > 0) + requires @dependency()(acc(a.f)) + requires @dependency()(a.f > 0) +{ + var res: Int + @dependency() + res := a.f / n + @testAssertion() + assert res >= 0 +} \ No newline at end of file diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index 7a734d53a..fefe76b01 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -2,7 +2,7 @@ import org.scalatest.funsuite.AnyFunSuite import viper.silicon.SiliconFrontend import viper.silicon.assumptionAnalysis.{AssumptionAnalysisGraph, AssumptionAnalysisNode, AssumptionType, DependencyAnalysisReporter, GeneralAssumptionNode} -import viper.silver.ast.{AnnotationInfo, Infoed, Node, Positioned, Program, Seqn} +import viper.silver.ast.{AnnotationInfo, HasLineColumn, Infoed, NoPosition, Node, Position, Positioned, Program, Seqn, VirtualPosition} import viper.silver.ast.utility.{DiskLoader, ViperStrategy} import viper.silver.frontend.SilFrontend import viper.silver.verifier @@ -39,7 +39,10 @@ class AssumptionAnalysisTests extends AnyFunSuite { Seq("--timeout", "100" /* seconds */, "--enableDebugging", "--enableAssumptionAnalysis", "--z3Args", "proof=true unsat-core=true") - val testDirectories: Seq[String] = Seq("dependencyAnalysisTests/all") + val testDirectories: Seq[String] = Seq( + "dependencyAnalysisTests/unitTests", + "dependencyAnalysisTests/all", + ) testDirectories foreach createTests // test("dependencyAnalysisTests/all" + "/" + "imprecision"){ @@ -127,6 +130,8 @@ class AssumptionAnalysisTests extends AnyFunSuite { val allAssumptionNodes = assumptionGraphsReal.flatMap(_.nodes.filter(_.isInstanceOf[GeneralAssumptionNode])) // TODO ake: collect all errors and report them as one assertion + // TODO ake: warning if testAssertion is missing + // TODO ake: obsolete should result in warnings only stmtsWithAssumptionAnnotation foreach {n => checkNodeExists(allAssumptionNodes, n)} assumptionGraphsReal foreach checkDependencies assumptionGraphsReal foreach checkNonDependencies @@ -147,20 +152,27 @@ class AssumptionAnalysisTests extends AnyFunSuite { } private def checkNodeExists(analysisNodes: List[AssumptionAnalysisNode], node: Infoed): Unit = { - val pos = node.asInstanceOf[Positioned].pos + val pos = extractSourceLine(node.asInstanceOf[Positioned].pos) val annotationInfo = node.info.getUniqueInfo[AnnotationInfo] .map(ai => ai.values.getOrElse(obsoleteKeyword, ai.values.getOrElse(dependencyKeyword, List.empty))).getOrElse(List.empty) val assumptionType = annotationInfo.map(AssumptionType.fromString).filter(_.isDefined).map(_.get) val nodeExists = analysisNodes exists (analysisNode => { - analysisNode.sourceInfo.getPosition.equals(pos) && + extractSourceLine(analysisNode.sourceInfo.getPosition) == pos && assumptionType.forall(_.equals(analysisNode.assumptionType)) }) assert(nodeExists, s"Missing analysis node:\n${node.toString}\n$pos") } + def extractSourceLine(pos: Position): Int = { + pos match { + case column: HasLineColumn => column.line + case _ => -1 + } + } + def checkDependencies(assumptionGraph: AssumptionAnalysisGraph): Unit = { val assumptionNodes = extractTestAssumptionNodesFromGraph(assumptionGraph) - val assumptionsPerSource = assumptionNodes groupBy(_.sourceInfo.toString) + val assumptionsPerSource = assumptionNodes groupBy(n => extractSourceLine(n.sourceInfo.getPosition)) val assertionNodes = extractTestAssertionNodesFromGraph(assumptionGraph) assumptionsPerSource.foreach { case (sourceInfo, assumptions) => @@ -171,7 +183,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { def checkNonDependencies(assumptionGraph: AssumptionAnalysisGraph): Unit = { val assumptionNodes = extractTestObsoleteAssumptionNodesFromGraph(assumptionGraph) - val assumptionsPerSource = assumptionNodes groupBy(_.sourceInfo.toString) + val assumptionsPerSource = assumptionNodes groupBy(n => extractSourceLine(n.sourceInfo.getPosition)) val assertionNodes = extractTestAssertionNodesFromGraph(assumptionGraph) assumptionsPerSource.foreach {case (sourceInfo, assumptions) => From c42594321ff96b75ddd8c6ee7229c921479f8852 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 23 Jun 2025 18:35:04 +0200 Subject: [PATCH 103/474] fix simple assignments --- src/main/scala/rules/Executor.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 5c1611e7b..22b0cd7a4 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -759,7 +759,7 @@ object executor extends ExecutionRules { private def ssaifyRhs(rhs: Term, rhsExp: ast.Exp, rhsExpNew: Option[ast.Exp], name: String, typ: ast.Type, v: Verifier, s : State, assumptionType: AssumptionType): (Term, Option[ast.Exp]) = { rhs match { - case _: Var | _: Literal => + case _: Var | _: Literal if !Verifier.config.enableAssumptionAnalysis() => (rhs, rhsExpNew) case _ => From 49e76c167fee2e7bc14e86ea3e877a4128422842 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 23 Jun 2025 18:35:17 +0200 Subject: [PATCH 104/474] update test --- .../dependencyAnalysisTests/all/aliasing.vpr | 4 +-- .../dependencyAnalysisTests/all/gaussian.vpr | 2 +- .../all/imprecision.vpr | 27 ++++++++++++++----- .../all/infeasible.vpr | 2 +- .../unitTests/magicWands.vpr | 2 +- .../unitTests/{transitivity.vpr => misc.vpr} | 10 +++---- .../unitTests/permissions.vpr | 1 + .../unitTests/quantifiedPermissions.vpr | 2 +- 8 files changed, 32 insertions(+), 18 deletions(-) rename src/test/resources/dependencyAnalysisTests/unitTests/{transitivity.vpr => misc.vpr} (56%) diff --git a/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr b/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr index 80241b814..d15809b83 100644 --- a/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr @@ -4,7 +4,7 @@ field f: Int method maybeAlias(a: Ref, b: Ref, c: Bool, n: Int) requires @dependency()(acc(a.f, 1/2)) requires @dependency()(acc(b.f, 1/2)) - requires c ==> a == b // TODO ake: @dependency() + requires @dependency()(c ==> a == b) // TODO ake requires a.f > 0 && n > 0 && b.f >= 0 requires a.f < 100 requires !c ==> a.f < b.f @@ -24,7 +24,7 @@ method aliasing(x: Ref, n: Int) x.f := n + 1 var y: Ref - // @dependency() // TODO ake: missing node + @dependency() y := x @testAssertion() diff --git a/src/test/resources/dependencyAnalysisTests/all/gaussian.vpr b/src/test/resources/dependencyAnalysisTests/all/gaussian.vpr index a66a16723..d60058439 100644 --- a/src/test/resources/dependencyAnalysisTests/all/gaussian.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/gaussian.vpr @@ -57,7 +57,7 @@ method gaussianPred(n: Int) returns (res: Int) { res := 0 var i: Int - // @dependency() TODO ake + @dependency() i := 0 @dependency() fold gaussianEq(res, i) diff --git a/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr b/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr index 8489b7069..2951b8552 100644 --- a/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr @@ -5,37 +5,52 @@ method permTest(a: Ref, b: Ref, n: Int) requires @dependency()(acc(a.f)) requires @obsolete()(acc(b.f)) && @obsolete()(b.f > 0) { - //@dependency() TODO ake: soundness bug + @dependency() assume n > 0 @obsolete() a.f := b.f + 2 - //@dependency() TODO ake: soundness bug + @dependency() a.f := n @testAssertion() assert a.f >= 0 // incorrectly depends on acc(b.f) } -method quantifiedPerm2Seqs2(xs: Seq[Ref], ys: Seq[Ref]) +method quantifiedPerm2(xs: Seq[Ref], ys: Seq[Ref]) requires @dependency()(|xs| > 5) requires @obsolete()(|ys| > 3) { @dependency() inhale (forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0)) - inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard) && y.f > 0) // incorrectly depends on inhale forall x in xs due to heap summary + //@obsolete() TODO ake + inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard) && y.f > 0) @testAssertion() assert xs[0].f > 0 } -method quantifiedPerm2Seqs3(xs: Seq[Ref], ys: Seq[Ref]) +method quantifiedPerm3(xs: Seq[Ref], ys: Seq[Ref]) requires @dependency()(|xs| > 5) requires @obsolete()(|ys| > 3) { @dependency() inhale (forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0)) + @obsolete() inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard)) @testAssertion() - assert xs[0].f > 0 // incorrectly depends on inhale forall x in xs due to heap summary + assert xs[0].f > 0 +} + +method quantifiedPerm4(xs: Seq[Ref], ys: Seq[Ref]) + requires @dependency()(|xs| > 5) + requires @obsolete()(|ys| > 3) +{ + @dependency() + inhale (forall x: Ref :: x in xs ==> acc(x.f) && x.f > 0) + @obsolete() + inhale forall y: Ref :: y in ys ==> acc(y.f) + + @testAssertion() + assert xs[0].f > 0 } diff --git a/src/test/resources/dependencyAnalysisTests/all/infeasible.vpr b/src/test/resources/dependencyAnalysisTests/all/infeasible.vpr index a5eeb6444..0187b933d 100644 --- a/src/test/resources/dependencyAnalysisTests/all/infeasible.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/infeasible.vpr @@ -41,7 +41,7 @@ method infeasibleBranchNoPerm(a: Int, b: Ref) { var res: Int if(a < 0){ - //@obsolete() TODO ake: nodes missing + //@obsolete() TODO ake: nodes missing (infeasible path) res := b.f + 1 //@testAssertion() assert false diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr index 27472a1aa..a1199444c 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr @@ -36,7 +36,7 @@ method basicPackage(l: Ref) @dependency() package list(tmp) --* list(l) { - fold list(l) + fold list(l) // TODO ake } @dependency() diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/transitivity.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/misc.vpr similarity index 56% rename from src/test/resources/dependencyAnalysisTests/unitTests/transitivity.vpr rename to src/test/resources/dependencyAnalysisTests/unitTests/misc.vpr index 08a9b1786..936c2a0af 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/transitivity.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/misc.vpr @@ -1,13 +1,11 @@ field f: Int -method transitivity(a: Ref, n: Int) +method divBy0(a: Ref, n: Int) requires @dependency()(n > 0) requires @dependency()(acc(a.f)) - requires @dependency()(a.f > 0) + requires @obsolete()(a.f > 0) { var res: Int - @dependency() - res := a.f / n @testAssertion() - assert res >= 0 -} \ No newline at end of file + res := a.f / n +} diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr index 13b0c14a9..ad1ba7b1b 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr @@ -41,6 +41,7 @@ method perm5(){ var x: Ref @dependency() inhale acc(x.f) + @obsolete() x.f := x.f + 1 @testAssertion() exhale acc(x.f) diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr index 2da344bd5..9a76ca835 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr @@ -28,7 +28,7 @@ method quantifiedPerm3(xs: Seq[Ref], y: Ref) { method quantifiedExhalePartially(xs: Seq[Ref]) { var res: Int assume @dependency()(|xs| > 5) - // inhale @dependency()(forall x: Ref :: x in xs ==> acc(x.f)) + //inhale @dependency()(forall x: Ref :: x in xs ==> acc(x.f)) inhale (forall x: Ref :: x in xs ==> acc(x.f)) // TODO ake: missing dependency exhale forall x: Ref :: x in xs ==> acc(x.f, 1/2) From 0f22c2339d7c3ae21ab019389535100ac9d64f25 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 23 Jun 2025 18:40:34 +0200 Subject: [PATCH 105/474] pcs: resolve unsoundness through adding labels --- .../assumptionAnalysis/AnalysisInfo.scala | 9 +- .../AssumptionAnalysisGraph.scala | 17 ++- .../AssumptionAnalyzer.scala | 132 +++++++++++++---- src/main/scala/decider/Decider.scala | 56 +++++--- src/main/scala/decider/PathConditions.scala | 39 +++-- .../scala/interfaces/decider/Prover.scala | 2 +- .../NonQuantifiedPropertyInterpreter.scala | 2 +- src/main/scala/rules/Consumer.scala | 13 +- src/main/scala/rules/Evaluator.scala | 22 ++- src/main/scala/rules/Executor.scala | 2 +- src/main/scala/rules/Joiner.scala | 15 +- src/main/scala/rules/MagicWandSupporter.scala | 2 + .../scala/rules/QuantifiedChunkSupport.scala | 14 +- src/main/scala/rules/StateConsolidator.scala | 4 +- src/main/scala/state/State.scala | 9 +- .../functions/FunctionVerificationUnit.scala | 7 +- .../resources/andrea/assistants-tests.vpr | 16 +++ src/test/resources/andrea/quickTest.vpr | 136 +++++++++++++++--- .../dependencyAnalysisTests/all/aliasing.vpr | 2 +- .../all/imprecision.vpr | 2 +- .../unitTests/quantifiedPermissions.vpr | 4 +- 21 files changed, 378 insertions(+), 127 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AnalysisInfo.scala b/src/main/scala/assumptionAnalysis/AnalysisInfo.scala index 1bb6fcf97..7d973d8c7 100644 --- a/src/main/scala/assumptionAnalysis/AnalysisInfo.scala +++ b/src/main/scala/assumptionAnalysis/AnalysisInfo.scala @@ -7,14 +7,9 @@ object AssumptionType extends Enumeration { def fromString(s: String): Option[Value] = values.find(_.toString == s) } import viper.silicon.assumptionAnalysis.AssumptionType._ +import viper.silicon.decider.Decider -case class AnalysisInfo(assumptionAnalyzer: AssumptionAnalyzer, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType) { - - def withAssumptionType(at: AssumptionType): AnalysisInfo = AnalysisInfo(assumptionAnalyzer, sourceInfo, at) - def withSourceInfo(si: AnalysisSourceInfo): AnalysisInfo = AnalysisInfo(assumptionAnalyzer, si, assumptionType) +case class AnalysisInfo(decider: Decider, assumptionAnalyzer: AssumptionAnalyzer, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType) { } -class NoAnalysisInfo extends AnalysisInfo(AssumptionAnalyzer.noAssumptionAnalyzerSingelton, NoAnalysisSourceInfo(), AssumptionType.Unknown) { - -} diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index adc9f8a5d..a258cb211 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -181,6 +181,8 @@ trait AssumptionAnalysisNode { } trait GeneralAssumptionNode extends AssumptionAnalysisNode { + val term: Term + def getTerm: Term = term override def getNodeType: String = "Assumption" } trait GeneralAssertionNode extends AssumptionAnalysisNode { @@ -193,11 +195,11 @@ trait ChunkAnalysisInfo { def getChunk: Chunk = chunk } -case class SimpleAssumptionNode(assumption: ast.Exp, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Unknown, isClosed: Boolean) extends GeneralAssumptionNode { +case class SimpleAssumptionNode(assumption: ast.Exp, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Unknown, isClosed: Boolean) extends GeneralAssumptionNode { override def getNodeString: String ="assume " + assumption.toString } -case class StringAssumptionNode(description: String, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Unknown, isClosed: Boolean) extends GeneralAssumptionNode { +case class StringAssumptionNode(description: String, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Unknown, isClosed: Boolean) extends GeneralAssumptionNode { override def getNodeString: String = "assume " + description } @@ -217,7 +219,7 @@ case class SimpleCheckNode(t: Term, sourceInfo: AnalysisSourceInfo, isClosed: Bo override def getNodeType: String = "Check" } -case class PermissionInhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Unknown, isClosed: Boolean) extends GeneralAssumptionNode with ChunkAnalysisInfo { +case class PermissionInhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Unknown, isClosed: Boolean) extends GeneralAssumptionNode with ChunkAnalysisInfo { override def getNodeString: String = "inhale " + chunk.getAnalysisInfo override def getNodeType: String = "Inhale" } @@ -233,3 +235,12 @@ case class PermissionAssertNode(chunk: Chunk, permAmount: Option[ast.Exp], sourc override def getNodeString: String = "assert " + permAmount.getOrElse("") + " for " + chunk.getAnalysisInfo } +case class LabelNode(term: Term) extends GeneralAssumptionNode { + val sourceInfo: AnalysisSourceInfo = NoAnalysisSourceInfo() + val assumptionType: AssumptionType = AssumptionType.Internal + val isClosed: Boolean = true + val description: String = term.toString + override def getNodeType: String = "Assumption" // TODO ake: change to Label once supported + override def getNodeString: String = "assume " + description +} + diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 157a5577f..ea5915910 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -4,7 +4,7 @@ import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType import viper.silicon.debugger.DebugExp import viper.silicon.decider.Decider import viper.silicon.interfaces.state.Chunk -import viper.silicon.state.terms.{False, Ite, Term, True} +import viper.silicon.state.terms.{False, Implies, Ite, NoPerm, Term, True} import viper.silver.ast @@ -32,12 +32,13 @@ trait AssumptionAnalyzer { def addSingleAssumption(assumption: DebugExp, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] def addAssumptions(assumptions: Iterable[DebugExp], analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Seq[Int] - def addAssumption(assumption: ast.Exp, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] - def addAssumption(assumption: String, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] + def addAssumption(assumption: ast.Exp, term: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] + def addAssumption(assumption: String, term: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] def createAssertOrCheckNode(term: Term, assertion: Either[String, ast.Exp], analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] def getChunkNodeIds(oldChunks: Set[Chunk]): Set[Int] + def getNodeIds(terms: Set[Term]): Set[Int] def addDependencyFromExhaleToInhale(newChunkNodeId: Option[Int]): Unit def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNodeId: Option[Int]): Unit def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNodeId: Chunk): Unit @@ -50,15 +51,27 @@ trait AssumptionAnalyzer { def exportGraph(): Unit - def addForcedDependencies(chunks: Set[Chunk]): Unit = { - forcedDependencies = forcedDependencies ++ getChunkNodeIds(chunks).toList + def addForcedDependencies(ids: Set[Int]): Unit = { + forcedDependencies = forcedDependencies ++ ids + } + + def addForcedChunkDependencies(chunks: Set[Chunk]): Unit = { + addForcedDependencies(getChunkNodeIds(chunks)) } def unsetForcedDependencies(): Unit = { forcedDependencies = List.empty } - def createLabelledConditional(decider: Decider, sourceChunks: Iterable[Chunk], thenTerm: Term, elseTerm: Term): Term + def wrapWithLabel(labelNode: LabelNode, term: Term): Term = term + def wrapPermissionWithLabel(labelNode: LabelNode, term: Term): Term = term + def createAndAssumeLabelNode(decider: Decider, sourceNodeIds: Iterable[Int]): LabelNode = LabelNode(True) + def createLabelledConditionalChunks(decider: Decider, sourceChunks: Iterable[Chunk], thenTerm: Term, elseTerm: Term): Term = thenTerm + def createLabelledConditional(decider: Decider, sourceTerms: Iterable[Term], term: Term): Term = term + def createLabelledConditional(decider: Decider, sourceTerms: Iterable[Term], terms: Seq[Term]): Seq[Term] = terms + def reassumeLabels(decider: Decider): Unit = {} + + def createLabelledConditional(decider: Decider, sourceNodeId: Int, term: Term): Term = term } @@ -123,6 +136,7 @@ object AssumptionAnalyzer { } class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { + protected var originalLabelNodes: List[LabelNode] = List.empty override def addNodes(nodes: Iterable[AssumptionAnalysisNode]): Unit = { assumptionGraph.addNodes(nodes) @@ -137,31 +151,32 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { } override def addSingleAssumption(assumption: DebugExp, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { - val node = if(assumption.originalExp.isDefined) SimpleAssumptionNode(assumption.originalExp.get, analysisSourceInfo, AssumptionAnalyzer.extractAssumptionTypeFromInfo(assumption.originalExp.get.info).getOrElse(assumptionType), enableCustomEdges_) - else StringAssumptionNode(assumption.description.getOrElse("unknown"), analysisSourceInfo, assumptionType, enableCustomEdges_) + val node = + if(assumption.originalExp.isDefined) SimpleAssumptionNode(assumption.originalExp.get, assumption.term.getOrElse(True) /* TODO ake */, analysisSourceInfo, AssumptionAnalyzer.extractAssumptionTypeFromInfo(assumption.originalExp.get.info).getOrElse(assumptionType), enableCustomEdges_) + else StringAssumptionNode(assumption.description.getOrElse("unknown"), assumption.term.getOrElse(True) /* TODO ake */, analysisSourceInfo, assumptionType, enableCustomEdges_) addNode(node) Some(node.id) } - override def addAssumption(assumption: ast.Exp, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { - val node = SimpleAssumptionNode(assumption, analysisSourceInfo, AssumptionAnalyzer.extractAssumptionTypeFromInfo(assumption.info).getOrElse(assumptionType), enableCustomEdges_) + override def addAssumption(assumption: ast.Exp, term: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { + val node = SimpleAssumptionNode(assumption, term, analysisSourceInfo, AssumptionAnalyzer.extractAssumptionTypeFromInfo(assumption.info).getOrElse(assumptionType), enableCustomEdges_) addNode(node) Some(node.id) } - override def addAssumption(assumption: String, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { - val node = StringAssumptionNode(assumption, analysisSourceInfo, assumptionType, enableCustomEdges_) + override def addAssumption(assumption: String, term: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { + val node = StringAssumptionNode(assumption, term, analysisSourceInfo, assumptionType, enableCustomEdges_) addNode(node) Some(node.id) } override def addAssumptions(assumptions: Iterable[DebugExp], analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Seq[Int] = { val newNodes = assumptions.toSeq.map(a => - if (a.originalExp.isDefined) SimpleAssumptionNode(a.originalExp.get, if(analysisSourceInfo.isInstanceOf[NoAnalysisSourceInfo]) ExpAnalysisSourceInfo(a.originalExp.get) else analysisSourceInfo, + if (a.originalExp.isDefined) SimpleAssumptionNode(a.originalExp.get, a.term.getOrElse(True) /* TODO ake */, if(analysisSourceInfo.isInstanceOf[NoAnalysisSourceInfo]) ExpAnalysisSourceInfo(a.originalExp.get) else analysisSourceInfo, AssumptionAnalyzer.extractAssumptionTypeFromInfo(a.originalExp.get.info).getOrElse(assumptionType), enableCustomEdges_) - else StringAssumptionNode(a.description.getOrElse("unknown"), analysisSourceInfo, AssumptionType.Internal, enableCustomEdges_) + else StringAssumptionNode(a.description.getOrElse("unknown"), a.term.getOrElse(True) /* TODO ake */, analysisSourceInfo, AssumptionType.Internal, enableCustomEdges_) ) newNodes foreach addNode newNodes.map(_.id) @@ -195,7 +210,7 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { } override def addPermissionInhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { - val node = PermissionInhaleNode(chunk, permAmount, sourceInfo, assumptionType, enableCustomEdges_) + val node = PermissionInhaleNode(chunk, permAmount, True /* TODO ake */, sourceInfo, assumptionType, enableCustomEdges_) addNode(node) Some(node.id) } @@ -225,6 +240,12 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { .map(_.id).toSet } + override def getNodeIds(terms: Set[Term]): Set[Int] = { + assumptionGraph.nodes + .filter(t => t.isInstanceOf[GeneralAssumptionNode] && terms.contains(t.asInstanceOf[GeneralAssumptionNode].getTerm)) + .map(_.id).toSet + } + override def addDependencyFromExhaleToInhale(newChunkNodeId: Option[Int]): Unit = { if(newChunkNodeId.isEmpty) return val newChunkNode = assumptionGraph.nodes.filter(_.id == newChunkNodeId.get).head @@ -262,15 +283,75 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { assumptionGraph.exportGraph("graphExports/" + foldername.getOrElse("latestExport")) } - override def createLabelledConditional(decider: Decider, sourceChunks: Iterable[Chunk], thenTerm: Term, elseTerm: Term): Term = { - val savedForcedDep = forcedDependencies - addForcedDependencies(sourceChunks.toSet) - val (label, labelExp) = decider.fresh(ast.LocalVar("analysisLabel", ast.Bool)()) - decider.assume(label === True, Some(DebugExp.createInstance(labelExp, labelExp)), AssumptionType.Internal) - forcedDependencies = savedForcedDep - Ite(label === True, thenTerm, elseTerm) + override def createLabelledConditionalChunks(decider: Decider, sourceChunks: Iterable[Chunk], thenTerm: Term, elseTerm: Term): Term = { + val sourceNodeIds = getChunkNodeIds(sourceChunks.toSet) + val labelNode = createAndAssumeLabelNode(decider, sourceNodeIds) + Ite(labelNode.term, thenTerm, elseTerm) + } + + private def createLabelNode(decider: Decider, sourceNodeIds: Iterable[Int]): LabelNode = { + val (label, _) = decider.fresh(ast.LocalVar("analysisLabel", ast.Bool)()) + val labelNode = LabelNode(label) + addNode(labelNode) + assumptionGraph.addEdges(sourceNodeIds, labelNode.id) + originalLabelNodes = originalLabelNodes :+ labelNode + labelNode + } + + override def createAndAssumeLabelNode(decider: Decider, sourceNodeIds: Iterable[Int]): LabelNode = { + val labelNode = createLabelNode(decider, sourceNodeIds) + val smtLabel = AssumptionAnalyzer.createAssumptionLabel(Some(labelNode.id)) + decider.assumeLabel(labelNode.term, smtLabel) + labelNode + } + + override def createLabelledConditional(decider: Decider, sourceTerms: Iterable[Term], term: Term): Term = { + if(term.equals(True)) return term + val sourceNodeIds = getNodeIds(sourceTerms.toSet) // TODO ake: maybe we have node id as argument + if(sourceNodeIds.isEmpty) { + term + } else { + val labelNode = createAndAssumeLabelNode(decider, sourceNodeIds) + Implies(labelNode.term, term) + } } + override def createLabelledConditional(decider: Decider, sourceNodeId: Int, term: Term): Term = { + val labelNode = createAndAssumeLabelNode(decider, Set(sourceNodeId)) + Implies(labelNode.term, term) + } + + override def createLabelledConditional(decider: Decider, sourceTerms: Iterable[Term], terms: Seq[Term]): Seq[Term] = { + if(!(terms exists (t => !t.equals(True)))) return terms + val sourceNodeIds = getNodeIds(sourceTerms.toSet) // TODO ake: maybe we have node id as argument + if(sourceNodeIds.isEmpty) { + terms + } else { + val labelNode = createAndAssumeLabelNode(decider, sourceNodeIds) + terms map (t => Implies(labelNode.term, t)) + } + } + + private def reassumeLabel(decider: Decider, oldLabelNode: LabelNode): Unit = { + val newLabelNode = LabelNode(oldLabelNode.term) + // do not add to originalLabelNodes! + addNode(newLabelNode) + assumptionGraph.addEdges(Set(oldLabelNode.id), newLabelNode.id) + val smtLabel = AssumptionAnalyzer.createAssumptionLabel(Some(newLabelNode.id)) + decider.assumeLabel(oldLabelNode.term, smtLabel) + } + + override def reassumeLabels(decider: Decider): Unit = { // TODO ake: work with scopes! + originalLabelNodes foreach (reassumeLabel(decider, _)) // assumes label + } + + override def wrapWithLabel(labelNode: LabelNode, term: Term): Term = { + Implies(labelNode.term, term) + } + + override def wrapPermissionWithLabel(labelNode: LabelNode, term: Term): Term = { + Ite(labelNode.term, term, NoPerm) + } } class NoAssumptionAnalyzer extends AssumptionAnalyzer { @@ -294,6 +375,7 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { override def getChunkNodeIds(oldChunks: Set[Chunk]): Set[Int] = Set.empty + override def getNodeIds(terms: Set[Term]): Set[Int] = Set.empty override def addDependencyFromExhaleToInhale(newChunkNodeId: Option[Int]): Unit = {} override def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNode: Option[Int]): Unit = { @@ -305,10 +387,8 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { override def addSingleAssumption(assumption: DebugExp, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None - override def addAssumption(assumption: ast.Exp, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None + override def addAssumption(assumption: ast.Exp, term: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None - override def addAssumption(assumption: String, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None + override def addAssumption(assumption: String, term: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None override def exportGraph(): Unit = {} - - override def createLabelledConditional(decider: Decider, sourceChunks: Iterable[Chunk], thenTerm: Term, elseTerm: Term): Term = thenTerm } \ No newline at end of file diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 09c5e935a..c17ea32d4 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -66,6 +66,7 @@ trait Decider { def assume(terms: Seq[Term], debugExps: Option[Seq[DebugExp]], assumptionType: AssumptionType): Unit def assumeDefinition(t: Term, debugExp: Option[DebugExp], assumptionType: AssumptionType): Unit def assume(terms: Iterable[Term], debugExp: Option[DebugExp], enforceAssumption: Boolean, assumptionType: AssumptionType): Unit + def assumeLabel(term: Term, assumptionLabel: String): Unit def check(t: Term, timeout: Int): Boolean @@ -137,6 +138,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 + private var _isReassumeAnalysisLabelsRequired: Boolean = false var assumptionAnalyzer: AssumptionAnalyzer = new NoAssumptionAnalyzer() var analysisSourceInfoStack: AnalysisSourceInfoStack = AnalysisSourceInfoStack() @@ -159,7 +161,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => def getAnalysisInfo: AnalysisInfo = getAnalysisInfo(AssumptionType.Implicit) - def getAnalysisInfo(assumptionType: AssumptionType): AnalysisInfo = AnalysisInfo(assumptionAnalyzer, analysisSourceInfoStack.getFullSourceInfo, assumptionType) + def getAnalysisInfo(assumptionType: AssumptionType): AnalysisInfo = AnalysisInfo(this, assumptionAnalyzer, analysisSourceInfoStack.getFullSourceInfo, assumptionType) def functionDecls: Set[FunctionDecl] = _declaredFreshFunctions def macroDecls: Vector[MacroDecl] = _declaredFreshMacros @@ -175,6 +177,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => pathConditions = other while (prover.pushPopScopeDepth > 1){ prover.pop() + _isReassumeAnalysisLabelsRequired = true } // TODO: Change interface to make the cast unnecessary? val layeredStack = other.asInstanceOf[LayeredPathConditionStack] @@ -276,6 +279,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => //val commentRecord = new CommentRecord("pop", null, null) //val sepIdentifier = symbExLog.openScope(commentRecord) _prover.pop() + _isReassumeAnalysisLabelsRequired = true pathConditions.popScope() //symbExLog.closeScope(sepIdentifier) } @@ -333,8 +337,8 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => filteredAssumptions foreach (a => addDebugExp(a._2.get.withTerm(a._1))) } - val filteredAssumptionsWithLabels = filteredAssumptions map{case (t, de) => - val assumptionId: Option[Int] = if(de.isDefined) assumptionAnalyzer.addSingleAssumption(de.get, analysisSourceInfo, assumptionType) else None + val filteredAssumptionsWithLabels = filteredAssumptions map{case (t, _) => + val assumptionId: Option[Int] = assumptionAnalyzer.addAssumption(t.toString /* TODO ake */, t, analysisSourceInfo, assumptionType) (t, AssumptionAnalyzer.createAssumptionLabel(assumptionId)) } @@ -344,11 +348,12 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } def assume(assumptions: Seq[Term], debugExps: Option[Seq[DebugExp]], assumptionType: AssumptionType): Unit = { - val assumptionIds = if(debugExps.isDefined) assumptionAnalyzer.addAssumptions(debugExps.get, analysisSourceInfoStack.getFullSourceInfo, assumptionType) else Seq.empty - val assumptionsWithLabels = - if(assumptions.size == assumptionIds.size) assumptions.zip(assumptionIds).map{case (t, id) => (t, AssumptionAnalyzer.createAssumptionLabel(Some(id)))} - else assumptions map (t => (t, "")) + val assumptionsWithLabels = assumptions map (t => { + val assumptionId = assumptionAnalyzer.addAssumption(t.toString /* TODO ake */, t, analysisSourceInfoStack.getFullSourceInfo, assumptionType) + (t, AssumptionAnalyzer.createAssumptionLabel(assumptionId)) + }) + assumeWithoutSmokeChecks(InsertionOrderedSet(assumptionsWithLabels)) if (debugMode) { debugExps.get foreach (e => addDebugExp(e)) @@ -358,11 +363,13 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => // TODO ake: review this def assume(assumptions: Iterable[Term], debugExps: Option[Iterable[DebugExp]], description: String, enforceAssumption: Boolean, assumptionType: AssumptionType): Unit = { val debugExp = Option.when(debugExps.isDefined)(DebugExp.createInstance(description, InsertionOrderedSet(debugExps.get))) - val assumptionIds = if(debugExps.isDefined) assumptionAnalyzer.addAssumptions(debugExps.get, analysisSourceInfoStack.getFullSourceInfo, assumptionType) else Seq.empty - val assumptionsWithLabels = - if(assumptions.size == assumptionIds.size) assumptions.zip(assumptionIds).map{case (t, id) => (t, AssumptionAnalyzer.createAssumptionLabel(Some(id)))} - else assumptions map (t => (t, "")) + // TODO ake: put after filtering + val assumptionsWithLabels = assumptions map (t => { + val assumptionIds = assumptionAnalyzer.addAssumption(t.toString, t, analysisSourceInfoStack.getFullSourceInfo, assumptionType) + (t, AssumptionAnalyzer.createAssumptionLabel(assumptionIds)) + }) + val filteredTerms = if (enforceAssumption) assumptionsWithLabels @@ -387,13 +394,12 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => if (debugMode) { addDebugExp(debugExp.get.withTerm(And(filteredTerms))) - - val assumptionId: Option[Int] = if(debugExp.isDefined) assumptionAnalyzer.addSingleAssumption(debugExp.get.withTerm(And(filteredTerms)), analysisSourceInfoStack.getFullSourceInfo, assumptionType) else None - val termsWithLabel = filteredTerms.zipWithIndex.iterator.map {case (t, idx) => (t, AssumptionAnalyzer.createAssumptionLabel(assumptionId, idx))}.toSeq - assumeWithoutSmokeChecks(InsertionOrderedSet(termsWithLabel)) - }else{ - assumeWithoutSmokeChecks(InsertionOrderedSet(filteredTerms.map ((_, "")))) } + val termsWithLabel = filteredTerms map (t => { + val assumptionId = assumptionAnalyzer.addAssumption(t.toString, t, analysisSourceInfoStack.getFullSourceInfo, assumptionType) + (t, AssumptionAnalyzer.createAssumptionLabel(assumptionId)) + }) + assumeWithoutSmokeChecks(InsertionOrderedSet(termsWithLabel)) } def debuggerAssume(terms: Iterable[Term], de: DebugExp) = { @@ -425,6 +431,11 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => None } + def assumeLabel(term: Term, assumptionLabel: String): Unit = { + // do not add to pathConditions! + prover.assume(term, assumptionLabel) + } + /* Asserting facts */ def checkSmoke(isAssert: Boolean = false): Boolean = { @@ -432,6 +443,12 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => val nodeId = assumptionAnalyzer.addAssertFalseNode(!isAssert, analysisSourceInfoStack.getFullSourceInfo) AssumptionAnalyzer.createAssertionLabel(nodeId) }else{ "" } + + if(_isReassumeAnalysisLabelsRequired) { + assumptionAnalyzer.reassumeLabels(this) + _isReassumeAnalysisLabelsRequired = false + } + val timeout = if (isAssert) Verifier.config.assertTimeout.toOption else Verifier.config.checkTimeout.toOption prover.check(timeout, label) == Unsat } @@ -499,6 +516,11 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => val assertRecord = new ProverAssertRecord(t, timeout) val sepIdentifier = symbExLog.openScope(assertRecord) + if(_isReassumeAnalysisLabelsRequired) { + assumptionAnalyzer.reassumeLabels(this) + _isReassumeAnalysisLabelsRequired = false + } + val result = prover.assert(t, timeout, label) symbExLog.whenEnabled { diff --git a/src/main/scala/decider/PathConditions.scala b/src/main/scala/decider/PathConditions.scala index de38c28cd..3c048bf77 100644 --- a/src/main/scala/decider/PathConditions.scala +++ b/src/main/scala/decider/PathConditions.scala @@ -37,6 +37,7 @@ trait RecordedPathConditions { def contains(assumption: Term): Boolean def conditionalized: Seq[Term] + def conditionalizedWithAnalysis: (Seq[Term], Seq[Term]) def conditionalizedExp: Seq[DebugExp] @@ -45,7 +46,8 @@ trait RecordedPathConditions { triggers: Seq[Trigger], name: String, isGlobal: Boolean, - ignore: Term /* TODO: Hack, implement properly */) + ignore: Term /* TODO: Hack, implement properly */, + v: Verifier) : (Seq[Term], Seq[Quantification]) def quantifiedExp(quantifier: Quantifier, @@ -261,9 +263,15 @@ private trait LayeredPathConditionStackLike { layers exists (_.contains(assumption)) protected def conditionalized(layers: Stack[PathConditionStackLayer]): Seq[Term] = { + conditionalizedWithAnalysis(layers)._1 + } + + // TODO ake: precision + protected def conditionalizedWithAnalysis(layers: Stack[PathConditionStackLayer]): (Seq[Term], Seq[Term]) = { var unconditionalTerms = Vector.empty[Term] var conditionalTerms = Vector.empty[Term] var implicationLHS: Term = True + var originalTerms = Vector.empty[Term] for (layer <- layers.reverseIterator) { unconditionalTerms ++= layer.globalAssumptions @@ -271,14 +279,15 @@ private trait LayeredPathConditionStackLike { layer.branchCondition match { case Some(condition) => implicationLHS = And(implicationLHS, condition) + originalTerms = originalTerms :+ condition case None => } - conditionalTerms :+= - Implies(implicationLHS, And(layer.nonGlobalAssumptions)) + conditionalTerms :+= Implies(implicationLHS, And(layer.nonGlobalAssumptions)) + originalTerms = originalTerms ++ layer.nonGlobalAssumptions ++ layer.globalAssumptions } - unconditionalTerms ++ conditionalTerms + (unconditionalTerms ++ conditionalTerms, originalTerms) } protected def conditionalizedExp(layers: Stack[PathConditionStackLayer]): Seq[DebugExp] = { @@ -312,13 +321,15 @@ private trait LayeredPathConditionStackLike { unconditionalTerms ++ conditionalTerms } + // TODO ake: add edges protected def quantified(layers: Stack[PathConditionStackLayer], quantifier: Quantifier, qvars: Seq[Var], triggers: Seq[Trigger], name: String, isGlobal: Boolean, - ignore: Term) + ignore: Term, + v: Verifier) : (Seq[Term], Seq[Quantification]) = { var globals = Vector.empty[Term] @@ -327,8 +338,8 @@ private trait LayeredPathConditionStackLike { val ignores = ignore.topLevelConjuncts for (layer <- layers) { - val actualBranchCondition = layer.branchCondition.getOrElse(True) - val relevantNonGlobals = layer.nonGlobalAssumptions -- ignores + val actualBranchCondition = layer.branchCondition.map(a => v.decider.assumptionAnalyzer.createLabelledConditional(v.decider, Set(a), a)).getOrElse(True) + val relevantNonGlobals = (layer.nonGlobalAssumptions -- ignores).map(a => v.decider.assumptionAnalyzer.createLabelledConditional(v.decider, Set(a), a)) val (trueNonGlobals, additionalGlobals) = if (!actualBranchCondition.existsDefined{ case t if qvars.contains(t) => }) { // The branch condition is independent of all quantified variables // Any assumptions that are also independent of all quantified variables can be treated as global assumptions. @@ -338,7 +349,7 @@ private trait LayeredPathConditionStackLike { (relevantNonGlobals, Seq()) } - globals ++= layer.globalAssumptions ++ additionalGlobals + globals ++= layer.globalAssumptions.map(a => v.decider.assumptionAnalyzer.createLabelledConditional(v.decider, Set(a), a)) ++ additionalGlobals nonGlobals :+= Quantification( @@ -406,6 +417,7 @@ private class DefaultRecordedPathConditions(from: Stack[PathConditionStackLayer] def contains(assumption: Term): Boolean = contains(from, assumption) val conditionalized: Seq[Term] = conditionalized(from) + val conditionalizedWithAnalysis: (Seq[Term], Seq[Term]) = conditionalizedWithAnalysis(from) lazy val conditionalizedExp: Seq[DebugExp] = conditionalizedExp(from) def definitionsOnly(): RecordedPathConditions = { @@ -417,10 +429,11 @@ private class DefaultRecordedPathConditions(from: Stack[PathConditionStackLayer] triggers: Seq[Trigger], name: String, isGlobal: Boolean, - ignore: Term) + ignore: Term, + v: Verifier) : (Seq[Term], Seq[Quantification]) = { - quantified(from, quantifier, qvars, triggers, name, isGlobal, ignore) + quantified(from, quantifier, qvars, triggers, name, isGlobal, ignore, v) } def quantifiedExp(quantifier: Quantifier, @@ -574,6 +587,7 @@ private[decider] class LayeredPathConditionStack def contains(assumption: Term): Boolean = allAssumptions.contains(assumption) def conditionalized: Seq[Term] = conditionalized(layers) + override def conditionalizedWithAnalysis: (Seq[Term], Seq[Term]) = conditionalizedWithAnalysis(layers) def conditionalizedExp: Seq[DebugExp] = conditionalizedExp(layers) @@ -582,10 +596,11 @@ private[decider] class LayeredPathConditionStack triggers: Seq[Trigger], name: String, isGlobal: Boolean, - ignore: Term) + ignore: Term, + v: Verifier) : (Seq[Term], Seq[Quantification]) = { - quantified(layers, quantifier, qvars, triggers, name, isGlobal, ignore) + quantified(layers, quantifier, qvars, triggers, name, isGlobal, ignore, v) } def quantifiedExp(quantifier: Quantifier, diff --git a/src/main/scala/interfaces/decider/Prover.scala b/src/main/scala/interfaces/decider/Prover.scala index 3e71050ad..28e1d41a3 100644 --- a/src/main/scala/interfaces/decider/Prover.scala +++ b/src/main/scala/interfaces/decider/Prover.scala @@ -41,7 +41,7 @@ trait ProverLike { if (debugMode) { preambleAssumptions :+= new DebugAxiom(description, axioms.map(_._1)) axioms.foreach(axiom => { - val id = if(axiom._2.isAnalysisEnabled) preambleAssumptionAnalyzer.addAssumption(axiom._2.toString, axiom._2, AssumptionType.Axiom) else None + val id = if(axiom._2.isAnalysisEnabled) preambleAssumptionAnalyzer.addAssumption(axiom._2.toString, axiom._1, axiom._2, AssumptionType.Axiom) else None assume(axiom._1, AssumptionAnalyzer.createAxiomLabel(id)) }) }else{ diff --git a/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala b/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala index ac7544af6..6a675905c 100644 --- a/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala +++ b/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala @@ -156,7 +156,7 @@ class NonQuantifiedPropertyInterpreter(heap: Iterable[Chunk], verifier: Verifier // TODO: Is it possible to get this behavior without having to check every tuple? if (!info.pm.values.exists(chunk eq _)) { val (resTerm, resExp) = builder(chunk) - Some((verifier.decider.assumptionAnalyzer.createLabelledConditional(verifier.decider, Set(chunk), resTerm, terms.True), resExp)) + Some((verifier.decider.assumptionAnalyzer.createLabelledConditionalChunks(verifier.decider, Set(chunk), resTerm, terms.True), resExp)) } else { None } diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index 9e6be155c..68e3c0f91 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -570,15 +570,20 @@ object consumer extends ConsumptionRules { case Seq(entry) => // One branch is dead (entry.s, entry.data) case Seq(entry1, entry2) => // Both branches are alive + // TODO ake: precision? + val branchConditions1 = entry1.pathConditions.branchConditions + val labelledBranchConditions1 = v.decider.assumptionAnalyzer.createLabelledConditional(v.decider, branchConditions1, And(branchConditions1)) + val branchConditions2 = entry2.pathConditions.branchConditions + val labelledBranchConditions2 = v.decider.assumptionAnalyzer.createLabelledConditional(v.decider, branchConditions2, And(branchConditions2)) val mergedData = ( State.mergeHeap( - entry1.data._1, And(entry1.pathConditions.branchConditions), Option.when(withExp)(BigAnd(entry1.pathConditions.branchConditionExps.map(_._2.get))), - entry2.data._1, And(entry2.pathConditions.branchConditions), Option.when(withExp)(BigAnd(entry2.pathConditions.branchConditionExps.map(_._2.get))), - AnalysisInfo(v.decider.assumptionAnalyzer, StringAnalysisSourceInfo("conditional join", e0.pos), AssumptionType.Implicit) // TODO ake + entry1.data._1, labelledBranchConditions1, Option.when(withExp)(BigAnd(entry1.pathConditions.branchConditionExps.map(_._2.get))), + entry2.data._1, labelledBranchConditions2, Option.when(withExp)(BigAnd(entry2.pathConditions.branchConditionExps.map(_._2.get))), + AnalysisInfo(v.decider, v.decider.assumptionAnalyzer, StringAnalysisSourceInfo("conditional join", e0.pos), AssumptionType.Implicit) // TODO ake ), // Assume that entry1.pcs is inverse of entry2.pcs (entry1.data._2, entry2.data._2) match { - case (Some(t1), Some(t2)) if returnSnap => Some(Ite(And(entry1.pathConditions.branchConditions), t1, t2)) + case (Some(t1), Some(t2)) if returnSnap => Some(Ite(labelledBranchConditions1, t1, t2)) case (None, None) if !returnSnap => None case (_, _) => sys.error(s"Unexpected join data entries: $entries") }, diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 2bc346be0..f6343d59d 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -1354,10 +1354,16 @@ object evaluator extends EvaluationRules { var finalState = s3 val es2AndTriggerResult = evals(s3, es2, _ => pve, v2)((s4, ts2, es2New, v3) => { evalTriggers(s4, optTriggers.getOrElse(Nil), pve, v3)((s5, tTriggers, _) => { // TODO: v4 isn't forward - problem? + val pcs = v3.decider.pcs.after(preMark) val (auxGlobals, auxNonGlobalQuants) = - v3.decider.pcs.after(preMark).quantified(quant, tVars, tTriggers, s"$name-aux", isGlobal = false, bc) + pcs.quantified(quant, tVars, tTriggers, s"$name-aux", isGlobal = false, bc, v3) +// val auxGlobals = v3.decider.assumptionAnalyzer.createLabelledConditional(v3.decider, pcs.branchConditions ++ pcs.assumptions, auxGlobalsTmp) +// val auxNonGlobalQuants: Seq[Quantification] = auxNonGlobalQuantsTmp.map(q => { +// val newBody = v3.decider.assumptionAnalyzer.createLabelledConditional(v3.decider, pcs.branchConditions ++ pcs.assumptions, q.body) +// q.copy(body=newBody) +// }) val auxExps = - Option.when(withExp)(v3.decider.pcs.after(preMark).quantifiedExp(quant, varPairs map (_._2.get), tVars, optTriggers.getOrElse(Nil), tTriggers, s"$name-aux", isGlobal = false, bc)) + Option.when(withExp)(pcs.quantifiedExp(quant, varPairs map (_._2.get), tVars, optTriggers.getOrElse(Nil), tTriggers, s"$name-aux", isGlobal = false, bc)) val additionalPossibleTriggers: Map[ast.Exp, Term] = if (s.recordPossibleTriggers) s5.possibleTriggers else Map() es2AndTriggerTerms = Some((ts2, es2New, tTriggers, (auxGlobals, auxNonGlobalQuants), auxExps, additionalPossibleTriggers)) @@ -1654,7 +1660,7 @@ object evaluator extends EvaluationRules { val r = evals(s, remainingTriggerExpressions, _ => pve, v)((_, remainingTriggerTerms, _, v1) => { optRemainingTriggerTerms = Some(remainingTriggerTerms) - pcDelta = v1.decider.pcs.after(preMark).assumptions //decider.π -- πPre + pcDelta = v1.decider.pcs.after(preMark).assumptions map (t => v1.decider.assumptionAnalyzer.createLabelledConditional(v.decider, Set(t), t)) //decider.π -- πPre pcDeltaExp = v1.decider.pcs.after(preMark).assumptionExps Success()}) @@ -1700,10 +1706,14 @@ object evaluator extends EvaluationRules { val joinTerm = App(joinSymbol, joinFunctionArgs) val joinExp = joinFunctionArgsExp.map(jfa => ast.FuncApp(joinFunctionName, jfa)(ast.NoPosition, ast.NoInfo, joinType, ast.NoTrafos)) - val joinDefEqs: Seq[(Term, Option[ast.Exp], Option[ast.Exp])] = entries map (entry => - (Implies(And(entry.pathConditions.branchConditions), BuiltinEquals(joinTerm, entry.data._1)), + val joinDefEqs: Seq[(Term, Option[ast.Exp], Option[ast.Exp])] = entries map (entry =>{ + // TODO ake: precision? + val branchConditions = entry.pathConditions.branchConditions + val labelledBranchCondition = v.decider.assumptionAnalyzer.createLabelledConditional(v.decider, branchConditions, And(branchConditions)) + (Implies(labelledBranchCondition, BuiltinEquals(joinTerm, entry.data._1)), Option.when(withExp)(ast.Implies(BigAnd(entry.pathConditions.branchConditionExps.map(bc => bc._1)), ast.EqCmp(joinExp.get, entry.data._2.get)())()), - Option.when(withExp)(ast.Implies(BigAnd(entry.pathConditions.branchConditionExps.map(bc => bc._2.get)), ast.EqCmp(joinExp.get, entry.data._2.get)())()))) + Option.when(withExp)(ast.Implies(BigAnd(entry.pathConditions.branchConditionExps.map(bc => bc._2.get)), ast.EqCmp(joinExp.get, entry.data._2.get)())())) + }) var sJoined = entries.tail.foldLeft(entries.head.s)((sAcc, entry) => sAcc.merge(entry.s)) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 22b0cd7a4..a5d74448f 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -280,7 +280,7 @@ object executor extends ExecutionRules { intermediateResult combine executionFlowController.locally(s2, v1)((s3, v2) => { v2.decider.declareAndRecordAsFreshFunctions(ff1 -- v2.decider.freshFunctions) /* [BRANCH-PARALLELISATION] */ // TODO ake: pcs.assumptionExps without exps do not have a source, but setting the source here will result in all invariants having the same source - v2.decider.assume(pcs.assumptions, Some(pcs.assumptionExps), "Loop invariant", enforceAssumption=false, assumptionType=AssumptionType.LoopInvariant) + v2.decider.assume(pcs.assumptions map (t => v.decider.assumptionAnalyzer.createLabelledConditional(v2.decider, Set(t), t)), Some(pcs.assumptionExps), "Loop invariant", enforceAssumption=false, assumptionType=AssumptionType.LoopInvariant) v2.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) if (v2.decider.checkSmoke()) Success() diff --git a/src/main/scala/rules/Joiner.scala b/src/main/scala/rules/Joiner.scala index 7d322772b..7e0bdd9cc 100644 --- a/src/main/scala/rules/Joiner.scala +++ b/src/main/scala/rules/Joiner.scala @@ -13,7 +13,7 @@ import viper.silicon.decider.RecordedPathConditions import viper.silicon.interfaces.{Success, VerificationResult} import viper.silicon.logger.records.structural.JoiningRecord import viper.silicon.state.State -import viper.silicon.state.terms.{And, Or, Term} +import viper.silicon.state.terms.{And, Or, Term, True} import viper.silicon.utils.ast.{BigAnd, BigOr} import viper.silicon.verifier.Verifier import viper.silver.ast @@ -26,12 +26,12 @@ case class JoinDataEntry[D](s: State, data: D, pathConditions: RecordedPathCondi // we can directly merge JoinDataEntries to obtain new States, // and the join data entries themselves provide information about the path conditions to State.merge. def pathConditionAwareMerge(other: JoinDataEntry[D], v: Verifier): State = { - val res = State.merge(this.s, this.pathConditions, other.s, other.pathConditions, AnalysisInfo(v.decider.assumptionAnalyzer, StringAnalysisSourceInfo("merge", NoPosition), AssumptionType.Implicit)) + val res = State.merge(this.s, this.pathConditions, other.s, other.pathConditions, AnalysisInfo(v.decider, v.decider.assumptionAnalyzer, StringAnalysisSourceInfo("merge", NoPosition), AssumptionType.Implicit)) v.stateConsolidator(s).consolidate(res, v) } def pathConditionAwareMergeWithoutConsolidation(other: JoinDataEntry[D], v: Verifier): State = { - State.merge(this.s, this.pathConditions, other.s, other.pathConditions, AnalysisInfo(v.decider.assumptionAnalyzer, StringAnalysisSourceInfo("merge", NoPosition), AssumptionType.Implicit)) + State.merge(this.s, this.pathConditions, other.s, other.pathConditions, AnalysisInfo(v.decider, v.decider.assumptionAnalyzer, StringAnalysisSourceInfo("merge", NoPosition), AssumptionType.Implicit)) } } @@ -101,12 +101,15 @@ object joiner extends JoiningRules { var feasibleBranchesExpNew: Option[List[ast.Exp]] = Option.when(withExp)(Nil) entries foreach (entry => { - val pcs = entry.pathConditions.conditionalized + val (pcs, pcsDependencies) = entry.pathConditions.conditionalizedWithAnalysis val pcsExp = Option.when(withExp)(entry.pathConditions.conditionalizedExp) val comment = "Joined path conditions" v.decider.prover.comment(comment) - v.decider.assume(pcs, pcsExp, comment, enforceAssumption = false, assumptionType=AssumptionType.Internal) - feasibleBranches = And(entry.pathConditions.branchConditions) :: feasibleBranches + val pcsLabelled = v.decider.assumptionAnalyzer.createLabelledConditional(v.decider, pcsDependencies, pcs) + v.decider.assume(pcsLabelled, pcsExp, comment, enforceAssumption = false, assumptionType=AssumptionType.Internal) + val branchConditions = entry.pathConditions.branchConditions + val branchConditionsLabelled = v.decider.assumptionAnalyzer.createLabelledConditional(v.decider, branchConditions, And(branchConditions)) + feasibleBranches = branchConditionsLabelled :: feasibleBranches feasibleBranchesExp = feasibleBranchesExp.map(fbe => BigAnd(entry.pathConditions.branchConditionExps.map(_._1)) :: fbe) feasibleBranchesExpNew = feasibleBranchesExpNew.map(fbe => BigAnd(entry.pathConditions.branchConditionExps.map(_._2.get)) :: fbe) }) diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index be7b41515..06fbf97e7 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -332,6 +332,7 @@ object magicWandSupporter extends SymbolicExecutionRules { val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(formalVars, formalVarExps, wand, args, Option.when(withExp)(bodyVars), FullPerm, Option.when(withExp)(ast.FullPerm()()), sm, s.program, v4, assumptionType, isExhale=false) val conservedPcs = s5.conservedPcs.head :+ v4.decider.pcs.after(preMark).definitionsOnly + // TODO ake: conditionalizedWithAnalysis? appendToResults(s5, ch, v4.decider.pcs.after(preMark), (conservedPcs.flatMap(_.conditionalized), Option.when(withExp)(conservedPcs.flatMap(_.conditionalizedExp))), v4) Success() }) @@ -339,6 +340,7 @@ object magicWandSupporter extends SymbolicExecutionRules { this.createChunk(s4, wand, wandSnapshot, pve, v3, assumptionType)((s5, ch, v4) => { val conservedPcs = s5.conservedPcs.head :+ v4.decider.pcs.after(preMark).definitionsOnly // Partition path conditions into a set which include the freshSnapRoot and those which do not + // TODO conditionalizedWithAnalysis? val (pcsWithFreshSnapRoot, pcsWithoutFreshSnapRoot) = conservedPcs.flatMap(pcs => pcs.conditionalized).partition(_.contains(freshSnapRoot)) val pcsWithoutExp = Option.when(withExp)(filterDebugExpsWithoutSnapshot(conservedPcs.flatMap(pcs => pcs.conditionalizedExp), freshSnapRoot)) // For all path conditions which include the freshSnapRoot, add those as part of the definition of the MWSF in the same forall quantifier diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 6c0d2adab..cdcce39f7 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -551,14 +551,14 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { Forall( codomainQVar, - v.decider.assumptionAnalyzer.createLabelledConditional(v.decider, Set(chunk), Implies(effectiveCondition, BuiltinEquals(lookupSummary, lookupChunk)), True), + v.decider.assumptionAnalyzer.createLabelledConditionalChunks(v.decider, Set(chunk), Implies(effectiveCondition, BuiltinEquals(lookupSummary, lookupChunk)), True), if (Verifier.config.disableISCTriggers()) Nil else Seq(Trigger(lookupSummary), Trigger(lookupChunk)), s"qp.fvfValDef${v.counter(this).next()}", isGlobal = relevantQvars.isEmpty) }) val resourceAndValueDefinitions = if (s.heapDependentTriggers.contains(field)){ - val chunkTriggers = relevantChunks map (chunk => v.decider.assumptionAnalyzer.createLabelledConditional(v.decider, Set(chunk), FieldTrigger(field.name, chunk.snapshotMap, codomainQVar), True)) + val chunkTriggers = relevantChunks map (chunk => v.decider.assumptionAnalyzer.createLabelledConditionalChunks(v.decider, Set(chunk), FieldTrigger(field.name, chunk.snapshotMap, codomainQVar), True)) val resourceTriggerDefinition = Forall( codomainQVar, @@ -639,7 +639,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { transformedOptSmDomainDefinitionCondition.getOrElse(True), /* Alternatively: qvarInDomainOfSummarisingSm */ IsPositive(chunk.perm).replace(snapToCodomainTermsSubstitution)) - val term = v.decider.assumptionAnalyzer.createLabelledConditional(v.decider, Set(chunk), Implies(effectiveCondition, And(snapshotNotUnit, BuiltinEquals(lookupSummary, lookupChunk))), True) + val term = v.decider.assumptionAnalyzer.createLabelledConditionalChunks(v.decider, Set(chunk), Implies(effectiveCondition, And(snapshotNotUnit, BuiltinEquals(lookupSummary, lookupChunk))), True) Forall( qvar, term, if (Verifier.config.disableISCTriggers()) Nil else Seq(Trigger(lookupSummary), Trigger(lookupChunk)), @@ -653,7 +653,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { } val resourceAndValueDefinitions = if (s.heapDependentTriggers.contains(resourceIdentifier)){ - val chunkTriggers = relevantChunks map (chunk => v.decider.assumptionAnalyzer.createLabelledConditional(v.decider, Set(chunk), ResourceTriggerFunction(resource, chunk.snapshotMap, Seq(qvar), s.program), True)) + val chunkTriggers = relevantChunks map (chunk => v.decider.assumptionAnalyzer.createLabelledConditionalChunks(v.decider, Set(chunk), ResourceTriggerFunction(resource, chunk.snapshotMap, Seq(qvar), s.program), True)) val resourceTriggerDefinition = Forall( qvar, @@ -693,7 +693,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val permSummary = ResourcePermissionLookup(resource, pm, codomainQVars, s.program) - val chunkPerms = relevantChunks map (chunk => v.decider.assumptionAnalyzer.createLabelledConditional(v.decider, Set(chunk), chunk.perm, NoPerm)) + val chunkPerms = relevantChunks map (chunk => v.decider.assumptionAnalyzer.createLabelledConditionalChunks(v.decider, Set(chunk), chunk.perm, NoPerm)) val valueDefinitions = Forall( codomainQVars, @@ -711,7 +711,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { // TODO: Quantify over snapshot if resource is predicate. // Also check other places where a similar quantifier is constructed. - val chunkTriggerDefs = relevantChunks map (chunk => v.decider.assumptionAnalyzer.createLabelledConditional(v.decider, Set(chunk), ResourceTriggerFunction(resource, chunk.snapshotMap, codomainQVars, s.program), True)) + val chunkTriggerDefs = relevantChunks map (chunk => v.decider.assumptionAnalyzer.createLabelledConditionalChunks(v.decider, Set(chunk), ResourceTriggerFunction(resource, chunk.snapshotMap, codomainQVars, s.program), True)) val resourceTriggerDefinition = Forall( codomainQVars, @@ -837,7 +837,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { s, relevantChunks, codomainQVars, resource, optSmDomainDefinitionCondition, v) val smDef = SnapshotMapDefinition(resource, sm, valueDefs, optDomainDefinition.toSeq) - val chunkPerms = relevantChunks map (chunk => v.decider.assumptionAnalyzer.createLabelledConditional(v.decider, Set(chunk), chunk.perm, NoPerm)) + val chunkPerms = relevantChunks map (chunk => v.decider.assumptionAnalyzer.createLabelledConditionalChunks(v.decider, Set(chunk), chunk.perm, NoPerm)) val totalPermissions = BigPermSum(chunkPerms) if (Verifier.config.disableValueMapCaching()) { diff --git a/src/main/scala/rules/StateConsolidator.scala b/src/main/scala/rules/StateConsolidator.scala index e0f8b2363..1954dfccd 100644 --- a/src/main/scala/rules/StateConsolidator.scala +++ b/src/main/scala/rules/StateConsolidator.scala @@ -83,7 +83,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol val (_functionRecorder, _mergedChunks, _newChunks, snapEqs) = singleMerge(functionRecorder, destChunks, newChunks, s.functionRecorderQuantifiedVariables().map(_._1), v) - v.decider.assumptionAnalyzer.addForcedDependencies(_newChunks.toSet) + v.decider.assumptionAnalyzer.addForcedChunkDependencies(_newChunks.toSet) snapEqs foreach (t => v.decider.assume(t, Option.when(withExp)(DebugExp.createInstance("Snapshot Equations", isInternal_ = true)), AssumptionType.Internal)) v.decider.assumptionAnalyzer.unsetForcedDependencies() @@ -103,7 +103,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol mergedChunks.filter(_.isInstanceOf[BasicChunk]) foreach { case ch: BasicChunk => val resource = Resources.resourceDescriptions(ch.resourceID) val pathCond = interpreter.buildPathConditionsForChunk(ch, resource.instanceProperties(s.mayAssumeUpperBounds)) - v.decider.assumptionAnalyzer.addForcedDependencies(Set(ch)) + v.decider.assumptionAnalyzer.addForcedChunkDependencies(Set(ch)) pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), AssumptionType.Internal)) v.decider.assumptionAnalyzer.unsetForcedDependencies() } diff --git a/src/main/scala/state/State.scala b/src/main/scala/state/State.scala index 6b8b0fe6a..e8a144c7e 100644 --- a/src/main/scala/state/State.scala +++ b/src/main/scala/state/State.scala @@ -16,9 +16,8 @@ import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.decider.RecordedPathConditions import viper.silicon.interfaces.state.GeneralChunk import viper.silicon.state.State.OldHeaps -import viper.silicon.state.terms.{Term, Var} +import viper.silicon.state.terms.{And, Ite, Term, True, Var} import viper.silicon.interfaces.state.Chunk -import viper.silicon.state.terms.{And, Ite} import viper.silicon.supporters.PredicateData import viper.silicon.supporters.functions.{FunctionData, FunctionRecorder, NoopFunctionRecorder} import viper.silicon.utils.ast.BigAnd @@ -370,11 +369,11 @@ object State { val smDomainNeeded3 = smDomainNeeded1 || smDomainNeeded2 - val conditions1 = And(pc1.branchConditions) + val conditions1 = analysisInfo.assumptionAnalyzer.createLabelledConditional(analysisInfo.decider, pc1.branchConditions, And(pc1.branchConditions)) val withExp = Verifier.config.enableDebugging() val conditions1Exp = if (withExp) Some(BigAnd(pc1.branchConditionExps.map(_._2.get))) else None - val conditions2 = And(pc2.branchConditions) - val conditions2Exp = if (withExp) Some(BigAnd(pc2.branchConditionExps.map(_._2.get))) else None + val conditions2 = analysisInfo.assumptionAnalyzer.createLabelledConditional(analysisInfo.decider, pc2.branchConditions, And(pc2.branchConditions)) + val conditions2Exp = if (withExp) Some(BigAnd(pc2.branchConditionExps.map(_._2.get))) else None val mergeStore = (g1: Store, g2: Store) => { Store(mergeMaps(g1.values, (conditions1, conditions1Exp), g2.values, (conditions2, conditions2Exp)) diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index cd245a3ec..0c1ff1b7e 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -226,8 +226,8 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver val preMark = decider.setPathConditionMark() produces(s0, toSf(`?s`), pres, ContractNotWellformed, v, AssumptionType.Internal)((s1, _) => { val relevantPathConditionStack = decider.pcs.after(preMark) - phase1Data :+= Phase1Data(s1, relevantPathConditionStack.branchConditions, relevantPathConditionStack.branchConditionExps, - relevantPathConditionStack.assumptions, Option.when(evaluator.withExp)(relevantPathConditionStack.assumptionExps)) + phase1Data :+= Phase1Data(s1, relevantPathConditionStack.branchConditions map (t => v.decider.assumptionAnalyzer.createLabelledConditional(v.decider, Set(t), t)), relevantPathConditionStack.branchConditionExps, + relevantPathConditionStack.assumptions map (t => v.decider.assumptionAnalyzer.createLabelledConditional(v.decider, Set(t), t)), Option.when(evaluator.withExp)(relevantPathConditionStack.assumptionExps)) // The postcondition must be produced with a fresh snapshot (different from `?s`) because // the postcondition's snapshot structure is most likely different than that of the // precondition @@ -260,7 +260,8 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver case (fatalResult: FatalResult, _) => fatalResult case (intermediateResult, Phase1Data(sPre, bcsPre, bcsPreExp, pcsPre, pcsPreExp)) => intermediateResult && executionFlowController.locally(sPre, v)((s1, _) => { - decider.setCurrentBranchCondition(And(bcsPre), (BigAnd(bcsPreExp.map(_._1)), Option.when(wExp)(BigAnd(bcsPreExp.map(_._2.get))))) + val labelledBcsPre = v.decider.assumptionAnalyzer.createLabelledConditional(v.decider,bcsPre, And(bcsPre)) + decider.setCurrentBranchCondition(labelledBcsPre, (BigAnd(bcsPreExp.map(_._1)), Option.when(wExp)(BigAnd(bcsPreExp.map(_._2.get))))) // TODO ake: pcsPreExp are missing position infos sometimes (e.g. Snapshots) decider.assume(pcsPre, pcsPreExp, s"precondition of ${function.name}", enforceAssumption=false, assumptionType=annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit)) v.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) diff --git a/src/test/resources/andrea/assistants-tests.vpr b/src/test/resources/andrea/assistants-tests.vpr index 5258e9d8f..dbaf2735e 100644 --- a/src/test/resources/andrea/assistants-tests.vpr +++ b/src/test/resources/andrea/assistants-tests.vpr @@ -66,3 +66,19 @@ if (c == a || c == b) { assume c == a assert x == 0 } + + +method stupidSMT(){ + var a: Int + + + if (exists x: Int, y: Int, z: Int :: x > 0 && y > 0 && z > 0 && x*x*x + y*y*y == z*z*z) { + // complicated condition that is equivalent to false, but can’t be proven by solver + // effectively unreachable code + assume a == 0 // assumption 1 + } else { + assume a == 0 // assumption 2 + } + + assert a == 0 +} \ No newline at end of file diff --git a/src/test/resources/andrea/quickTest.vpr b/src/test/resources/andrea/quickTest.vpr index c5d2b649a..bab0fa2fc 100644 --- a/src/test/resources/andrea/quickTest.vpr +++ b/src/test/resources/andrea/quickTest.vpr @@ -1,27 +1,119 @@ field f: Int -field g: Int -method foo(x: Ref, y: Ref) +method loopAssumption() +{ + var x: Int + assume x <= 100 + + while(x <= 10) + invariant x <= 100 + { + x := x * 11 + assume x <= 100 + } + + assert x <= 100 +} + +method alias1(a: Ref, b: Ref, c: Ref) + requires acc(a.f) && acc(b.f) +{ + var x: Int + if (c == a) { + x := c.f + 1 + } + if(c == b) { + x := c.f + 1 + assume a == c + assert x == a.f + 1 + assert false + } + + + assume a == c + assert x == a.f + 1 +} +method alias2(a: Ref, b: Ref, c: Ref) + requires acc(a.f) && acc(b.f) + requires a != b { - @dependency() - inhale acc(x.f) - @dependency() - inhale acc(y.f) - @obsolete() - inhale acc(y.g) - - @dependency() - assume 0 < x.f - @obsolete() - assume x.f < 100 - @obsolete() - assume y.f < 100 - @obsolete() - assume 0 <= y.f - @dependency() - assume x.f < y.f - - @testAssertion() - assert x.f / y.f <= 1 + var x: Int + if (c == a) { + x := c.f + } else { + if(c == b) { + x := c.f + } + } + + assume a == c + assert x == a.f +} + +method test3(a: Int, b: Int, c: Int) +{ +var x: Int := 4 + +assume a == 0 +assume b == 1 + + +if (c == a || c == b) { + x := c + 1 +} + +assume c == a +assert x == 1 +} + + +method stupidSMT(){ + var a: Int + + + if (exists x: Int, y: Int, z: Int :: x > 0 && y > 0 && z > 0 && x*x*x + y*y*y == z*z*z) { + // complicated condition that is equivalent to false, but can’t be proven by solver + // effectively unreachable code + assume a == 0 // assumption 1 + } else { + assume a == 0 // assumption 2 + } + + assert a == 0 +} + +method joinerTest(){ + var a: Int + var n: Int + assume n >= 0 + var b: Bool + + + if (b) { + a := n + 1 + } else { + a := 1 + n + } + + assert a > 0 +} + +method quantifiedPerm2Seqs3(xs: Seq[Ref], ys: Seq[Ref]) { + assume |xs| > 5 && |ys| > 3 + inhale forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0) + inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard)) + + assert xs[0].f > 0 // incorrectly depends on inhale forall x in xs due to heap summary + // still not precise due to wildcart +} + + +method quantifiedPerm2Seqs3W(xs: Seq[Ref], ys: Seq[Ref]) { + assume |xs| > 5 && |ys| > 3 + inhale forall x: Ref :: x in xs ==> (acc(x.f, 1/2) && x.f > 0) + inhale forall y: Ref :: y in ys ==> (acc(y.f, 1/2)) + + assert xs[0].f > 0 // incorrectly depended on inhale forall x in xs due to heap summary + // is precise now } \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr b/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr index d15809b83..e665e0cb0 100644 --- a/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr @@ -4,7 +4,7 @@ field f: Int method maybeAlias(a: Ref, b: Ref, c: Bool, n: Int) requires @dependency()(acc(a.f, 1/2)) requires @dependency()(acc(b.f, 1/2)) - requires @dependency()(c ==> a == b) // TODO ake + requires c ==> a == b // @dependency()(c ==> a == b) // TODO ake requires a.f > 0 && n > 0 && b.f >= 0 requires a.f < 100 requires !c ==> a.f < b.f diff --git a/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr b/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr index 2951b8552..46a583e27 100644 --- a/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr @@ -35,7 +35,7 @@ method quantifiedPerm3(xs: Seq[Ref], ys: Seq[Ref]) { @dependency() inhale (forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0)) - @obsolete() + // @obsolete() inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard)) @testAssertion() diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr index 9a76ca835..ee55a42a6 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr @@ -28,8 +28,8 @@ method quantifiedPerm3(xs: Seq[Ref], y: Ref) { method quantifiedExhalePartially(xs: Seq[Ref]) { var res: Int assume @dependency()(|xs| > 5) - //inhale @dependency()(forall x: Ref :: x in xs ==> acc(x.f)) - inhale (forall x: Ref :: x in xs ==> acc(x.f)) // TODO ake: missing dependency + inhale @dependency()(forall x: Ref :: x in xs ==> acc(x.f)) + //inhale (forall x: Ref :: x in xs ==> acc(x.f)) // TODO ake: missing dependency exhale forall x: Ref :: x in xs ==> acc(x.f, 1/2) @testAssertion() From 665a0a92c02292245a98f7803f667b4061fc9e2d Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 23 Jun 2025 19:06:35 +0200 Subject: [PATCH 106/474] disable analysis source info by default --- src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala index 5b496addd..1a9be3857 100644 --- a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala +++ b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala @@ -1,5 +1,6 @@ package viper.silicon.assumptionAnalysis +import viper.silicon.verifier.Verifier import viper.silver.ast import viper.silver.ast.{HasLineColumn, NoPosition, Position, VirtualPosition} @@ -84,6 +85,7 @@ case class AnalysisSourceInfoStack() { def getAnalysisSourceInfos: List[AnalysisSourceInfo] = sourceInfos def getFullSourceInfo: AnalysisSourceInfo = { + if(!Verifier.config.enableAssumptionAnalysis()) return NoAnalysisSourceInfo() if(forcedMainSource.isDefined) return forcedMainSource.get if(sourceInfos.size <= 1){ @@ -94,14 +96,18 @@ case class AnalysisSourceInfoStack() { } def addAnalysisSourceInfo(analysisSourceInfo: AnalysisSourceInfo): Unit = { + if(!Verifier.config.enableAssumptionAnalysis()) return sourceInfos = analysisSourceInfo +: sourceInfos } def setAnalysisSourceInfo(analysisSourceInfo: List[AnalysisSourceInfo]): Unit = { + if(!Verifier.config.enableAssumptionAnalysis()) return sourceInfos = analysisSourceInfo } def popAnalysisSourceInfo(analysisSourceInfo: AnalysisSourceInfo): Unit = { + if(!Verifier.config.enableAssumptionAnalysis()) return + var currSourceInfo = sourceInfos // popping just one source info might not be enough since infeasible branches might return without popping the source info while(currSourceInfo.nonEmpty && !currSourceInfo.head.equals(analysisSourceInfo)) { From bae0c3608c712809a4f972644f93bbc3c376e8b2 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 23 Jun 2025 19:59:24 +0200 Subject: [PATCH 107/474] fix some tests --- .../scala/rules/QuantifiedChunkSupport.scala | 4 +- src/test/resources/andrea/quickTest.vpr | 125 ++---------------- .../dependencyAnalysisTests/all/aliasing.vpr | 4 +- .../all/imprecision.vpr | 6 +- .../unitTests/quantifiedPermissions.vpr | 10 +- src/test/scala/AssumptionAnalysisTests.scala | 40 +++--- 6 files changed, 48 insertions(+), 141 deletions(-) diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index cdcce39f7..1329dff4d 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -1058,8 +1058,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.prover.comment(comment) val definitionalAxiomMark = v.decider.setPathConditionMark() v.decider.assume(inv.definitionalAxioms.map(a => FunctionPreconditionTransformer.transform(a, s.program)), - Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, assumptionType=AssumptionType.Internal) - v.decider.assume(inv.definitionalAxioms, Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, assumptionType=AssumptionType.Internal) + Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, assumptionType=assumptionType) // TODO ake: assumptionType? + v.decider.assume(inv.definitionalAxioms, Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, assumptionType=assumptionType) val conservedPcs = if (s.recordPcs) (s.conservedPcs.head :+ v.decider.pcs.after(definitionalAxiomMark)) +: s.conservedPcs.tail else s.conservedPcs diff --git a/src/test/resources/andrea/quickTest.vpr b/src/test/resources/andrea/quickTest.vpr index bab0fa2fc..004e2e6d6 100644 --- a/src/test/resources/andrea/quickTest.vpr +++ b/src/test/resources/andrea/quickTest.vpr @@ -1,119 +1,12 @@ field f: Int -method loopAssumption() -{ - var x: Int - assume x <= 100 - - while(x <= 10) - invariant x <= 100 - { - x := x * 11 - assume x <= 100 - } - - assert x <= 100 -} - -method alias1(a: Ref, b: Ref, c: Ref) - requires acc(a.f) && acc(b.f) -{ - var x: Int - if (c == a) { - x := c.f + 1 - } - if(c == b) { - x := c.f + 1 - assume a == c - assert x == a.f + 1 - assert false - } - - - assume a == c - assert x == a.f + 1 -} - -method alias2(a: Ref, b: Ref, c: Ref) - requires acc(a.f) && acc(b.f) - requires a != b -{ - var x: Int - if (c == a) { - x := c.f - } else { - if(c == b) { - x := c.f - } - } - - assume a == c - assert x == a.f -} - -method test3(a: Int, b: Int, c: Int) -{ -var x: Int := 4 - -assume a == 0 -assume b == 1 - - -if (c == a || c == b) { - x := c + 1 -} - -assume c == a -assert x == 1 -} - - -method stupidSMT(){ - var a: Int - - - if (exists x: Int, y: Int, z: Int :: x > 0 && y > 0 && z > 0 && x*x*x + y*y*y == z*z*z) { - // complicated condition that is equivalent to false, but can’t be proven by solver - // effectively unreachable code - assume a == 0 // assumption 1 - } else { - assume a == 0 // assumption 2 - } - - assert a == 0 -} - -method joinerTest(){ - var a: Int - var n: Int - assume n >= 0 - var b: Bool - - - if (b) { - a := n + 1 - } else { - a := 1 + n - } - - assert a > 0 -} - -method quantifiedPerm2Seqs3(xs: Seq[Ref], ys: Seq[Ref]) { - assume |xs| > 5 && |ys| > 3 - inhale forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0) - inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard)) - - assert xs[0].f > 0 // incorrectly depends on inhale forall x in xs due to heap summary - // still not precise due to wildcart -} - - -method quantifiedPerm2Seqs3W(xs: Seq[Ref], ys: Seq[Ref]) { - assume |xs| > 5 && |ys| > 3 - inhale forall x: Ref :: x in xs ==> (acc(x.f, 1/2) && x.f > 0) - inhale forall y: Ref :: y in ys ==> (acc(y.f, 1/2)) - - assert xs[0].f > 0 // incorrectly depended on inhale forall x in xs due to heap summary - // is precise now +method quantifiedExhalePartially(xs: Seq[Ref]) { + var res: Int + assume @dependency()(|xs| > 5) + //inhale @dependency()(forall x: Ref :: x in xs ==> acc(x.f)) + inhale (forall x: Ref :: x in xs ==> acc(x.f)) // TODO ake: missing dependency + + exhale forall x: Ref :: x in xs ==> acc(x.f, 1/2) + @testAssertion() + res := xs[1].f + 1 } \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr b/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr index e665e0cb0..64f788a65 100644 --- a/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr @@ -4,12 +4,12 @@ field f: Int method maybeAlias(a: Ref, b: Ref, c: Bool, n: Int) requires @dependency()(acc(a.f, 1/2)) requires @dependency()(acc(b.f, 1/2)) - requires c ==> a == b // @dependency()(c ==> a == b) // TODO ake + requires @dependency()(c ==> a == b) requires a.f > 0 && n > 0 && b.f >= 0 requires a.f < 100 requires !c ==> a.f < b.f { - if(c){ + if(@dependency()(c)){ @testAssertion() a.f := n + 1 } diff --git a/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr b/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr index 46a583e27..366d20410 100644 --- a/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr @@ -12,7 +12,7 @@ method permTest(a: Ref, b: Ref, n: Int) @dependency() a.f := n @testAssertion() - assert a.f >= 0 // incorrectly depends on acc(b.f) + assert a.f >= 0 } @@ -22,7 +22,7 @@ method quantifiedPerm2(xs: Seq[Ref], ys: Seq[Ref]) { @dependency() inhale (forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0)) - //@obsolete() TODO ake + @obsolete() inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard) && y.f > 0) @testAssertion() @@ -35,7 +35,7 @@ method quantifiedPerm3(xs: Seq[Ref], ys: Seq[Ref]) { @dependency() inhale (forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0)) - // @obsolete() + @obsolete() inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard)) @testAssertion() diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr index ee55a42a6..76ef761ed 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr @@ -25,11 +25,19 @@ method quantifiedPerm3(xs: Seq[Ref], y: Ref) { assert xs[0] != y } +method quantifiedExhalePartiallyTest(xs: Seq[Ref]) { + var res: Int + assume |xs| > 5 + inhale @dependency()(forall x: Ref :: x in xs ==> acc(x.f)) + + @testAssertion() + exhale forall x: Ref :: x in xs ==> acc(x.f, 1/2) +} + method quantifiedExhalePartially(xs: Seq[Ref]) { var res: Int assume @dependency()(|xs| > 5) inhale @dependency()(forall x: Ref :: x in xs ==> acc(x.f)) - //inhale (forall x: Ref :: x in xs ==> acc(x.f)) // TODO ake: missing dependency exhale forall x: Ref :: x in xs ==> acc(x.f, 1/2) @testAssertion() diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index fefe76b01..b4ee7d363 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -27,6 +27,8 @@ import scala.util.{Failure, Success} */ class AssumptionAnalysisTests extends AnyFunSuite { + val CHECK_PRECISION = false + val obsoleteKeyword = "obsolete" val dependencyKeyword = "dependency" @@ -129,12 +131,16 @@ class AssumptionAnalysisTests extends AnyFunSuite { val stmtsWithAssumptionAnnotation: Set[Infoed] = extractAnnotatedAssumptionStmts(program) val allAssumptionNodes = assumptionGraphsReal.flatMap(_.nodes.filter(_.isInstanceOf[GeneralAssumptionNode])) - // TODO ake: collect all errors and report them as one assertion // TODO ake: warning if testAssertion is missing - // TODO ake: obsolete should result in warnings only - stmtsWithAssumptionAnnotation foreach {n => checkNodeExists(allAssumptionNodes, n)} - assumptionGraphsReal foreach checkDependencies - assumptionGraphsReal foreach checkNonDependencies + var errorMsgs = stmtsWithAssumptionAnnotation.map(checkNodeExists(allAssumptionNodes, _)).filter(_.isDefined).map(_.get).toSeq + errorMsgs ++= assumptionGraphsReal flatMap checkDependencies + val warnMsgs = assumptionGraphsReal flatMap checkNonDependencies + if(CHECK_PRECISION) + errorMsgs ++= warnMsgs + else if(warnMsgs.nonEmpty) println(warnMsgs.mkString("\n")) // TODO ake: warning + + val check = errorMsgs.isEmpty + assert(check, "\n" + errorMsgs.mkString("\n")) } private def extractAnnotatedAssumptionStmts(program: Program): Set[Infoed] = { @@ -151,7 +157,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { nodesWithAnnotation } - private def checkNodeExists(analysisNodes: List[AssumptionAnalysisNode], node: Infoed): Unit = { + private def checkNodeExists(analysisNodes: List[AssumptionAnalysisNode], node: Infoed): Option[String] = { val pos = extractSourceLine(node.asInstanceOf[Positioned].pos) val annotationInfo = node.info.getUniqueInfo[AnnotationInfo] .map(ai => ai.values.getOrElse(obsoleteKeyword, ai.values.getOrElse(dependencyKeyword, List.empty))).getOrElse(List.empty) @@ -160,7 +166,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { extractSourceLine(analysisNode.sourceInfo.getPosition) == pos && assumptionType.forall(_.equals(analysisNode.assumptionType)) }) - assert(nodeExists, s"Missing analysis node:\n${node.toString}\n$pos") + Option.when(!nodeExists)(s"Missing analysis node:\n${node.toString}\n$pos") } def extractSourceLine(pos: Position): Int = { @@ -170,26 +176,26 @@ class AssumptionAnalysisTests extends AnyFunSuite { } } - def checkDependencies(assumptionGraph: AssumptionAnalysisGraph): Unit = { + def checkDependencies(assumptionGraph: AssumptionAnalysisGraph): Seq[String] = { val assumptionNodes = extractTestAssumptionNodesFromGraph(assumptionGraph) val assumptionsPerSource = assumptionNodes groupBy(n => extractSourceLine(n.sourceInfo.getPosition)) val assertionNodes = extractTestAssertionNodesFromGraph(assumptionGraph) - assumptionsPerSource.foreach { case (sourceInfo, assumptions) => + assumptionsPerSource.map({ case (_, assumptions) => val hasDeps = checkDependenciesForSingleSource(assumptionGraph, assumptions, assertionNodes) - assert(hasDeps, s"Missing dependency: $sourceInfo") - } + Option.when(!hasDeps)(s"Missing dependency: ${assumptions.head.sourceInfo.toString}") + }).filter(_.isDefined).map(_.get).toSeq } - def checkNonDependencies(assumptionGraph: AssumptionAnalysisGraph): Unit = { + def checkNonDependencies(assumptionGraph: AssumptionAnalysisGraph): Seq[String] = { val assumptionNodes = extractTestObsoleteAssumptionNodesFromGraph(assumptionGraph) val assumptionsPerSource = assumptionNodes groupBy(n => extractSourceLine(n.sourceInfo.getPosition)) val assertionNodes = extractTestAssertionNodesFromGraph(assumptionGraph) - assumptionsPerSource.foreach {case (sourceInfo, assumptions) => + assumptionsPerSource.map({case (_, assumptions) => val hasDependency = checkDependenciesForSingleSource(assumptionGraph, assumptions, assertionNodes) - assert(!hasDependency, s"Unexpected dependency: $sourceInfo") - } + Option.when(hasDependency)(s"Unexpected dependency: ${assumptions.head.sourceInfo.toString}") + }).filter(_.isDefined).map(_.get).toSeq } def checkDependenciesForSingleSource(assumptionGraph: AssumptionAnalysisGraph, assumptions: Seq[AssumptionAnalysisNode], assertions: Seq[AssumptionAnalysisNode]): Boolean = { @@ -200,7 +206,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { def extractTestAssertionNodesFromGraph(graph: AssumptionAnalysisGraph): Seq[AssumptionAnalysisNode] = { graph.nodes.filter(node => - (node.getNodeType.equals("Assertion") || node.getNodeType.equals("Exhale")) && + (node.getNodeType.equals("Assertion") || node.getNodeType.equals("Exhale") || node.getNodeType.equals("Check")) && node.sourceInfo.toString.contains("@testAssertion()") ).toSeq } @@ -208,7 +214,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { def extractTestAssumptionNodesFromGraph(graph: AssumptionAnalysisGraph): Seq[AssumptionAnalysisNode] = { graph.nodes.filter(node => { (node.getNodeType.equals("Assumption") || node.getNodeType.equals("Inhale")) && - !node.assumptionType.equals(AssumptionType.Internal) && + !node.assumptionType.equals(AssumptionType.Internal) && // TODO ake: really? node.sourceInfo.toString.contains("@" + dependencyKeyword + "()") } ).toSeq From a826bbd3a3fe4978f5eb7fefcc1cdf467b125bb0 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 23 Jun 2025 20:27:43 +0200 Subject: [PATCH 108/474] update test --- src/test/resources/dependencyAnalysisTests/all/infeasible.vpr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/resources/dependencyAnalysisTests/all/infeasible.vpr b/src/test/resources/dependencyAnalysisTests/all/infeasible.vpr index 0187b933d..3dab3dba9 100644 --- a/src/test/resources/dependencyAnalysisTests/all/infeasible.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/infeasible.vpr @@ -3,7 +3,7 @@ field f: Int method infeasibleBranch1(a: Int, b: Int) requires @dependency()(a > 0) { - if(a < 0){ + if(@dependency()(a < 0)){ @testAssertion() assert false } @@ -29,7 +29,7 @@ method infeasibleBranchPerm(a: Ref, b: Ref) requires @dependency()(acc(a.f, 1/2)) requires @dependency()(a.f > 0) { - if(a.f < 0){ + if(@dependency()(a.f < 0)){ @testAssertion() a.f := a.f + 1 } From 5c42d7d2aa428373670b6595ba629fd3505439df Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 24 Jun 2025 10:14:25 +0200 Subject: [PATCH 109/474] remove coupling between assumption analyzer and prover --- src/main/scala/Config.scala | 16 ++++----- src/main/scala/decider/Decider.scala | 14 ++++---- src/main/scala/decider/PathConditions.scala | 5 ++- src/main/scala/decider/ProverStdIO.scala | 33 +++++++++---------- src/main/scala/decider/Z3ProverAPI.scala | 7 ++-- .../scala/interfaces/decider/Prover.scala | 12 ++++--- 6 files changed, 43 insertions(+), 44 deletions(-) diff --git a/src/main/scala/Config.scala b/src/main/scala/Config.scala index 0b05d02ab..4a0d64ad8 100644 --- a/src/main/scala/Config.scala +++ b/src/main/scala/Config.scala @@ -892,14 +892,14 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { sys.error(s"Unexpected combination: $other") } - validateOpt(enableAssumptionAnalysis, enableDebugging) { - case (Some(false), _) => Right(()) - case (Some(true), Some(true)) => Right(()) - case (Some(true), Some(false)) => - Left(s"Option ${enableAssumptionAnalysis.name} requires option ${enableDebugging.name}") - case other => - sys.error(s"Unexpected combination: $other") - } +// validateOpt(enableAssumptionAnalysis, enableDebugging) { +// case (Some(false), _) => Right(()) +// case (Some(true), Some(true)) => Right(()) +// case (Some(true), Some(false)) => +// Left(s"Option ${enableAssumptionAnalysis.name} requires option ${enableDebugging.name}") +// case other => +// sys.error(s"Unexpected combination: $other") +// } validateOpt(startDebuggerAutomatically, enableDebugging) { case (Some(false), _) => Right(()) diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index c17ea32d4..d367a570a 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -148,7 +148,6 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => if(isAnalysisEnabled) { assumptionAnalyzer = new DefaultAssumptionAnalyzer(member) assumptionAnalyzer.addNodes(preambleNodes) - prover.setAssumptionAnalyzer(assumptionAnalyzer) }else{ removeAssumptionAnalyzer() } @@ -156,7 +155,6 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => override def removeAssumptionAnalyzer(): Unit = { assumptionAnalyzer = new NoAssumptionAnalyzer - prover.setAssumptionAnalyzer(assumptionAnalyzer) } def getAnalysisInfo: AnalysisInfo = getAnalysisInfo(AssumptionType.Implicit) @@ -182,7 +180,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => // TODO: Change interface to make the cast unnecessary? val layeredStack = other.asInstanceOf[LayeredPathConditionStack] layeredStack.layers.reverse.foreach(l => { - l.assumptions foreach prover.assume + l.assumptions foreach prover.assume // TODO ake: labels? prover.push(timeout = Verifier.config.pushTimeout.toOption) }) } @@ -450,7 +448,10 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } val timeout = if (isAssert) Verifier.config.assertTimeout.toOption else Verifier.config.checkTimeout.toOption - prover.check(timeout, label) == Unsat + val result = prover.check(timeout, label) == Unsat + if(result) + assumptionAnalyzer.processUnsatCoreAndAddDependencies(prover.getLastUnsatCore, label) + result } def check(t: Term, timeout: Int): Boolean = { @@ -492,8 +493,6 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => if(result || !isCheck) assertNode foreach assumptionAnalyzer.addNode - - symbExLog.closeScope(sepIdentifier) result } @@ -523,6 +522,9 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => val result = prover.assert(t, timeout, label) + if(result) + assumptionAnalyzer.processUnsatCoreAndAddDependencies(prover.getLastUnsatCore, label) + symbExLog.whenEnabled { assertRecord.statistics = Some(symbExLog.deltaStatistics(prover.statistics())) } diff --git a/src/main/scala/decider/PathConditions.scala b/src/main/scala/decider/PathConditions.scala index 3c048bf77..37721ea18 100644 --- a/src/main/scala/decider/PathConditions.scala +++ b/src/main/scala/decider/PathConditions.scala @@ -37,7 +37,7 @@ trait RecordedPathConditions { def contains(assumption: Term): Boolean def conditionalized: Seq[Term] - def conditionalizedWithAnalysis: (Seq[Term], Seq[Term]) + def conditionalizedWithAnalysis: (Seq[Term], Seq[Term]) // TODO ake: remove and integrated labels into conditionalized def conditionalizedExp: Seq[DebugExp] @@ -320,8 +320,7 @@ private trait LayeredPathConditionStackLike { unconditionalTerms ++ conditionalTerms } - - // TODO ake: add edges + protected def quantified(layers: Stack[PathConditionStackLayer], quantifier: Quantifier, qvars: Seq[Var], diff --git a/src/main/scala/decider/ProverStdIO.scala b/src/main/scala/decider/ProverStdIO.scala index 97378dc81..d068cf347 100644 --- a/src/main/scala/decider/ProverStdIO.scala +++ b/src/main/scala/decider/ProverStdIO.scala @@ -7,7 +7,6 @@ package viper.silicon.decider import com.typesafe.scalalogging.LazyLogging -import viper.silicon.assumptionAnalysis.{AssumptionAnalyzer, NoAssumptionAnalyzer} import viper.silicon.common.config.Version import viper.silicon.interfaces.decider._ import viper.silicon.reporting.{ExternalToolError, ProverInteractionFailed} @@ -40,11 +39,11 @@ abstract class ProverStdIO(uniqueId: String, protected var allDecls: Seq[Decl] = Seq() protected var allEmits: Seq[String] = Seq() protected var proverLabelId: Int = 0 - var assumptionAnalyzer: AssumptionAnalyzer = new NoAssumptionAnalyzer() var proverPath: Path = _ var lastReasonUnknown : String = _ var lastModel : String = _ + protected var lastUnsatCore_ : String = _ def exeEnvironmentalVariable: String def dependencies: Seq[SilDefaultDependency] @@ -53,10 +52,6 @@ abstract class ProverStdIO(uniqueId: String, protected def setTimeout(timeout: Option[Int]): Unit protected def getProverPath: Path - override def setAssumptionAnalyzer(assumptionAnalyzer: AssumptionAnalyzer): Unit = { - this.assumptionAnalyzer = assumptionAnalyzer - } - @inline private def readLineFromInput(): String = { val line = input.readLine() @@ -244,14 +239,16 @@ abstract class ProverStdIO(uniqueId: String, // problems.foreach(p => quantificationLogger.println(s" $p")) // } // }) - if(label.isEmpty){ - assume(term, "prover_" + proverLabelId) - proverLabelId += 1 - }else{ - assume(termConverter.convert(term), label) - } + val finalLabel = if(label.isEmpty) nextProverLabel() else label + assume(termConverter.convert(term), finalLabel) } +def nextProverLabel(): String = { + val label = "prover_" + proverLabelId + proverLabelId += 1 + label +} + def assume(term: String, label: String): Unit = { // bookkeeper.assumptionCounter += 1 @@ -302,8 +299,7 @@ abstract class ProverStdIO(uniqueId: String, retrieveAndSaveModel() retrieveReasonUnknown() }else if(Verifier.config.enableAssumptionAnalysis()){ - val unsatCore = extractUnsatCore() - assumptionAnalyzer.processUnsatCoreAndAddDependencies(unsatCore, label) + lastUnsatCore_ = extractUnsatCore() } pop() @@ -318,6 +314,8 @@ abstract class ProverStdIO(uniqueId: String, unsatCore } + def getLastUnsatCore: String = lastUnsatCore_ + def saturate(data: Option[Config.ProverStateSaturationTimeout]): Unit = { data match { case Some(Config.ProverStateSaturationTimeout(timeout, comment)) => saturate(timeout, comment) @@ -394,10 +392,9 @@ abstract class ProverStdIO(uniqueId: String, case "unknown" => Unknown } - if(result == Unsat && Verifier.config.enableAssumptionAnalysis()){ - val unsatCore = extractUnsatCore() - assumptionAnalyzer.processUnsatCoreAndAddDependencies(unsatCore, label) - } + if(result == Unsat && Verifier.config.enableAssumptionAnalysis()) + lastUnsatCore_ = extractUnsatCore() + result } diff --git a/src/main/scala/decider/Z3ProverAPI.scala b/src/main/scala/decider/Z3ProverAPI.scala index fab12009c..ce2e0edaa 100644 --- a/src/main/scala/decider/Z3ProverAPI.scala +++ b/src/main/scala/decider/Z3ProverAPI.scala @@ -9,7 +9,6 @@ package viper.silicon.decider import com.microsoft.z3._ import com.microsoft.z3.enumerations.Z3_param_kind import com.typesafe.scalalogging.LazyLogging -import viper.silicon.assumptionAnalysis.AssumptionAnalyzer import viper.silicon.common.config.Version import viper.silicon.interfaces.decider._ import viper.silicon.reporting.{ExternalToolError, ProverInteractionFailed} @@ -406,6 +405,8 @@ class Z3ProverAPI(uniqueId: String, } } + def getLastUnsatCore: String = "" // TODO ake + def endPreamblePhase(): Unit = { if (!preamblePhaseOver) { preamblePhaseOver = true @@ -562,8 +563,4 @@ class Z3ProverAPI(uniqueId: String, lazy val randomizeSeedsOptions: Seq[String] = { Seq(Z3ProverAPI.randomizeSeedsSetting) } - - override def setAssumptionAnalyzer(assumptionAnalyzer: AssumptionAnalyzer): Unit = { - // TODO ake - } } diff --git a/src/main/scala/interfaces/decider/Prover.scala b/src/main/scala/interfaces/decider/Prover.scala index 28e1d41a3..eb5e1a51c 100644 --- a/src/main/scala/interfaces/decider/Prover.scala +++ b/src/main/scala/interfaces/decider/Prover.scala @@ -37,17 +37,22 @@ trait ProverLike { preambleAssumptions :+= new DebugAxiom(description, terms) terms foreach assume } + + // TODO ake: review def assumeAxiomsWithAnalysisInfo(axioms: InsertionOrderedSet[(Term, AnalysisSourceInfo)], description: String): Unit = { - if (debugMode) { + if (debugMode) preambleAssumptions :+= new DebugAxiom(description, axioms.map(_._1)) + + if(Verifier.config.enableAssumptionAnalysis()){ axioms.foreach(axiom => { val id = if(axiom._2.isAnalysisEnabled) preambleAssumptionAnalyzer.addAssumption(axiom._2.toString, axiom._1, axiom._2, AssumptionType.Axiom) else None assume(axiom._1, AssumptionAnalyzer.createAxiomLabel(id)) }) - }else{ + } else{ axioms.foreach(t => assume(t._1)) } } + def getPreambleAnalysisNodes: Iterable[AssumptionAnalysisNode] = preambleAssumptionAnalyzer.getNodes def setOption(name: String, value: String): String def assume(term: Term): Unit @@ -62,6 +67,7 @@ trait Prover extends ProverLike with StatefulComponent { def start(userArgsString: Option[String]): Unit def assert(goal: Term, timeout: Option[Int] = None, label: String = ""): Boolean def check(timeout: Option[Int] = None, label: String = ""): Result + def getLastUnsatCore: String def fresh(id: String, argSorts: Seq[Sort], resultSort: Sort): Function def statistics(): Map[String, String] def hasModel(): Boolean @@ -85,6 +91,4 @@ trait Prover extends ProverLike with StatefulComponent { def getAllDecls(): Seq[Decl] def getAllEmits(): Seq[String] - - def setAssumptionAnalyzer(assumptionAnalyzer: AssumptionAnalyzer): Unit } From 8f7bcc930132c3d270367dacc801a0ccd57f08c7 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 24 Jun 2025 10:53:46 +0200 Subject: [PATCH 110/474] refactoring --- .../scala/assumptionAnalysis/AssumptionAnalyzer.scala | 7 +++++++ .../resources/NonQuantifiedPropertyInterpreter.scala | 2 +- src/main/scala/rules/Executor.scala | 3 ++- src/main/scala/rules/QuantifiedChunkSupport.scala | 10 +++++----- .../scala/supporters/BuiltinDomainsContributor.scala | 1 - src/test/scala/AssumptionAnalysisTests.scala | 2 +- 6 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index ea5915910..c5525bf0c 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -67,6 +67,7 @@ trait AssumptionAnalyzer { def wrapPermissionWithLabel(labelNode: LabelNode, term: Term): Term = term def createAndAssumeLabelNode(decider: Decider, sourceNodeIds: Iterable[Int]): LabelNode = LabelNode(True) def createLabelledConditionalChunks(decider: Decider, sourceChunks: Iterable[Chunk], thenTerm: Term, elseTerm: Term): Term = thenTerm + def createLabelledConditionalChunks(decider: Decider, sourceChunks: Iterable[Chunk], thenTerm: Term): Term = thenTerm def createLabelledConditional(decider: Decider, sourceTerms: Iterable[Term], term: Term): Term = term def createLabelledConditional(decider: Decider, sourceTerms: Iterable[Term], terms: Seq[Term]): Seq[Term] = terms def reassumeLabels(decider: Decider): Unit = {} @@ -289,6 +290,12 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { Ite(labelNode.term, thenTerm, elseTerm) } + override def createLabelledConditionalChunks(decider: Decider, sourceChunks: Iterable[Chunk], thenTerm: Term): Term = { + val sourceNodeIds = getChunkNodeIds(sourceChunks.toSet) + val labelNode = createAndAssumeLabelNode(decider, sourceNodeIds) + Implies(labelNode.term, thenTerm) + } + private def createLabelNode(decider: Decider, sourceNodeIds: Iterable[Int]): LabelNode = { val (label, _) = decider.fresh(ast.LocalVar("analysisLabel", ast.Bool)()) val labelNode = LabelNode(label) diff --git a/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala b/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala index 6a675905c..3f8b21520 100644 --- a/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala +++ b/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala @@ -156,7 +156,7 @@ class NonQuantifiedPropertyInterpreter(heap: Iterable[Chunk], verifier: Verifier // TODO: Is it possible to get this behavior without having to check every tuple? if (!info.pm.values.exists(chunk eq _)) { val (resTerm, resExp) = builder(chunk) - Some((verifier.decider.assumptionAnalyzer.createLabelledConditionalChunks(verifier.decider, Set(chunk), resTerm, terms.True), resExp)) + Some((verifier.decider.assumptionAnalyzer.createLabelledConditionalChunks(verifier.decider, Set(chunk), resTerm), resExp)) } else { None } diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index a5d74448f..6ff53f264 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -588,7 +588,8 @@ object executor extends ExecutionRules { val pveCallTransformed = pveCall.withReasonNodeTransformed(reasonTransformer) val methodAnnotatedAssumptionType = AssumptionAnalyzer.extractAssumptionTypeFromInfo(meth.info) - val finalAssumptionType = annotatedAssumptionTypeOpt.getOrElse(methodAnnotatedAssumptionType.getOrElse(AssumptionType.Explicit)) + val defaultAssumptionType = if(meth.body.isDefined) AssumptionType.Implicit else AssumptionType.Explicit + val finalAssumptionType = annotatedAssumptionTypeOpt.getOrElse(methodAnnotatedAssumptionType.getOrElse(defaultAssumptionType)) val mcLog = new MethodCallRecord(call, s, v.decider.pcs) val sepIdentifier = v.symbExLog.openScope(mcLog) diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 1329dff4d..845d12ba1 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -551,14 +551,14 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { Forall( codomainQVar, - v.decider.assumptionAnalyzer.createLabelledConditionalChunks(v.decider, Set(chunk), Implies(effectiveCondition, BuiltinEquals(lookupSummary, lookupChunk)), True), + v.decider.assumptionAnalyzer.createLabelledConditionalChunks(v.decider, Set(chunk), Implies(effectiveCondition, BuiltinEquals(lookupSummary, lookupChunk))), if (Verifier.config.disableISCTriggers()) Nil else Seq(Trigger(lookupSummary), Trigger(lookupChunk)), s"qp.fvfValDef${v.counter(this).next()}", isGlobal = relevantQvars.isEmpty) }) val resourceAndValueDefinitions = if (s.heapDependentTriggers.contains(field)){ - val chunkTriggers = relevantChunks map (chunk => v.decider.assumptionAnalyzer.createLabelledConditionalChunks(v.decider, Set(chunk), FieldTrigger(field.name, chunk.snapshotMap, codomainQVar), True)) + val chunkTriggers = relevantChunks map (chunk => v.decider.assumptionAnalyzer.createLabelledConditionalChunks(v.decider, Set(chunk), FieldTrigger(field.name, chunk.snapshotMap, codomainQVar))) val resourceTriggerDefinition = Forall( codomainQVar, @@ -639,7 +639,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { transformedOptSmDomainDefinitionCondition.getOrElse(True), /* Alternatively: qvarInDomainOfSummarisingSm */ IsPositive(chunk.perm).replace(snapToCodomainTermsSubstitution)) - val term = v.decider.assumptionAnalyzer.createLabelledConditionalChunks(v.decider, Set(chunk), Implies(effectiveCondition, And(snapshotNotUnit, BuiltinEquals(lookupSummary, lookupChunk))), True) + val term = v.decider.assumptionAnalyzer.createLabelledConditionalChunks(v.decider, Set(chunk), Implies(effectiveCondition, And(snapshotNotUnit, BuiltinEquals(lookupSummary, lookupChunk)))) Forall( qvar, term, if (Verifier.config.disableISCTriggers()) Nil else Seq(Trigger(lookupSummary), Trigger(lookupChunk)), @@ -653,7 +653,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { } val resourceAndValueDefinitions = if (s.heapDependentTriggers.contains(resourceIdentifier)){ - val chunkTriggers = relevantChunks map (chunk => v.decider.assumptionAnalyzer.createLabelledConditionalChunks(v.decider, Set(chunk), ResourceTriggerFunction(resource, chunk.snapshotMap, Seq(qvar), s.program), True)) + val chunkTriggers = relevantChunks map (chunk => v.decider.assumptionAnalyzer.createLabelledConditionalChunks(v.decider, Set(chunk), ResourceTriggerFunction(resource, chunk.snapshotMap, Seq(qvar), s.program))) val resourceTriggerDefinition = Forall( qvar, @@ -711,7 +711,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { // TODO: Quantify over snapshot if resource is predicate. // Also check other places where a similar quantifier is constructed. - val chunkTriggerDefs = relevantChunks map (chunk => v.decider.assumptionAnalyzer.createLabelledConditionalChunks(v.decider, Set(chunk), ResourceTriggerFunction(resource, chunk.snapshotMap, codomainQVars, s.program), True)) + val chunkTriggerDefs = relevantChunks map (chunk => v.decider.assumptionAnalyzer.createLabelledConditionalChunks(v.decider, Set(chunk), ResourceTriggerFunction(resource, chunk.snapshotMap, codomainQVars, s.program))) val resourceTriggerDefinition = Forall( codomainQVars, diff --git a/src/main/scala/supporters/BuiltinDomainsContributor.scala b/src/main/scala/supporters/BuiltinDomainsContributor.scala index 8820a81ac..b6ef014d2 100644 --- a/src/main/scala/supporters/BuiltinDomainsContributor.scala +++ b/src/main/scala/supporters/BuiltinDomainsContributor.scala @@ -16,7 +16,6 @@ import viper.silicon.interfaces.PreambleContributor import viper.silicon.interfaces.decider.ProverLike import viper.silicon.state.DefaultSymbolConverter import viper.silicon.state.terms._ -import viper.silver.ast.NoPosition abstract class BuiltinDomainsContributor extends PreambleContributor[Sort, DomainFun, Term] { type BuiltinDomainType <: ast.GenericType diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index b4ee7d363..10d56c79d 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -38,7 +38,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { val commandLineArguments: Seq[String] = - Seq("--timeout", "100" /* seconds */, "--enableDebugging", "--enableAssumptionAnalysis", "--z3Args", "proof=true unsat-core=true") + Seq("--timeout", "100" /* seconds */, "--enableAssumptionAnalysis", "--z3Args", "proof=true unsat-core=true") val testDirectories: Seq[String] = Seq( From 61ad80c005e1506c8a40b534d260af8b1f679d06 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 24 Jun 2025 11:13:01 +0200 Subject: [PATCH 111/474] remove AssumptionType Unknown --- src/main/scala/assumptionAnalysis/AnalysisInfo.scala | 2 +- .../scala/assumptionAnalysis/AssumptionAnalysisGraph.scala | 6 +++--- src/main/scala/rules/Evaluator.scala | 2 +- src/main/scala/rules/MoreCompleteExhaleSupporter.scala | 2 +- src/test/scala/AssumptionAnalysisTests.scala | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AnalysisInfo.scala b/src/main/scala/assumptionAnalysis/AnalysisInfo.scala index 7d973d8c7..02c2eace2 100644 --- a/src/main/scala/assumptionAnalysis/AnalysisInfo.scala +++ b/src/main/scala/assumptionAnalysis/AnalysisInfo.scala @@ -2,7 +2,7 @@ package viper.silicon.assumptionAnalysis object AssumptionType extends Enumeration { type AssumptionType = Value - val Explicit, LoopInvariant, PathCondition, Rewrite, Implicit, Internal, Unknown, Axiom = Value + val Explicit, LoopInvariant, PathCondition, Rewrite, Implicit, Internal, Axiom = Value def fromString(s: String): Option[Value] = values.find(_.toString == s) } diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index a258cb211..da6aa597e 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -195,11 +195,11 @@ trait ChunkAnalysisInfo { def getChunk: Chunk = chunk } -case class SimpleAssumptionNode(assumption: ast.Exp, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Unknown, isClosed: Boolean) extends GeneralAssumptionNode { +case class SimpleAssumptionNode(assumption: ast.Exp, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean) extends GeneralAssumptionNode { override def getNodeString: String ="assume " + assumption.toString } -case class StringAssumptionNode(description: String, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Unknown, isClosed: Boolean) extends GeneralAssumptionNode { +case class StringAssumptionNode(description: String, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean) extends GeneralAssumptionNode { override def getNodeString: String = "assume " + description } @@ -219,7 +219,7 @@ case class SimpleCheckNode(t: Term, sourceInfo: AnalysisSourceInfo, isClosed: Bo override def getNodeType: String = "Check" } -case class PermissionInhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = Unknown, isClosed: Boolean) extends GeneralAssumptionNode with ChunkAnalysisInfo { +case class PermissionInhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean) extends GeneralAssumptionNode with ChunkAnalysisInfo { override def getNodeString: String = "inhale " + chunk.getAnalysisInfo override def getNodeType: String = "Inhale" } diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index f6343d59d..0bedb6f2c 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -847,7 +847,7 @@ object evaluator extends EvaluationRules { val exp = ast.Forall(eQuant.variables, eTriggers, body)(sourceQuant.pos, sourceQuant.info, sourceQuant.errT) DebugExp.createInstance(exp, expNew) }) - v1.decider.assume(Quantification(Forall, tVars, FunctionPreconditionTransformer.transform(tBody, s1.program), tTriggers, name, quantWeight), debugExp, AssumptionType.Unknown) + v1.decider.assume(Quantification(Forall, tVars, FunctionPreconditionTransformer.transform(tBody, s1.program), tTriggers, name, quantWeight), debugExp, AssumptionType.Internal) } val tQuant = Quantification(qantOp, tVars, tBody, tTriggers, name, quantWeight) diff --git a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala index 0a8954936..3bab009d3 100644 --- a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala +++ b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala @@ -549,7 +549,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { relevantChunks foreach (chunk => { val instantiatedPermSum = permissionSum.replace(freeReceiver, chunk.args.head) val exp = permissionSumExp.map(pse => ast.PermLeCmp(replaceVarsInExp(pse, Seq(freeReceiverExp.name), Seq(chunk.argsExp.get.head)), ast.FullPerm()())()) - v.decider.assume(PermAtMost(instantiatedPermSum, FullPerm), exp, exp, AssumptionType.Unknown) + v.decider.assume(PermAtMost(instantiatedPermSum, FullPerm), exp, exp, AssumptionType.Internal) }) } } diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index 10d56c79d..bee7a6ed7 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -27,7 +27,7 @@ import scala.util.{Failure, Success} */ class AssumptionAnalysisTests extends AnyFunSuite { - val CHECK_PRECISION = false + val CHECK_PRECISION = true val obsoleteKeyword = "obsolete" val dependencyKeyword = "dependency" From 39f4cee45f4d642263eeada9ee2e9733486c30d2 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 24 Jun 2025 12:51:45 +0200 Subject: [PATCH 112/474] add bigger tests --- src/main/scala/rules/ChunkSupporter.scala | 4 +- .../dependencyAnalysisTests/all/list.vpr | 107 ++++++++++++++++ .../all/quantified-perm.vpr | 45 +++++++ .../dependencyAnalysisTests/todo/list.vpr | 37 ------ .../todo/permissions.vpr | 65 ---------- .../todo/quantified-perm.vpr | 115 ------------------ .../unitTests/quantifiedPermissions.vpr | 36 ++++++ 7 files changed, 190 insertions(+), 219 deletions(-) create mode 100644 src/test/resources/dependencyAnalysisTests/all/list.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/all/quantified-perm.vpr delete mode 100644 src/test/resources/dependencyAnalysisTests/todo/list.vpr delete mode 100644 src/test/resources/dependencyAnalysisTests/todo/permissions.vpr delete mode 100644 src/test/resources/dependencyAnalysisTests/todo/quantified-perm.vpr diff --git a/src/main/scala/rules/ChunkSupporter.scala b/src/main/scala/rules/ChunkSupporter.scala index 87b033644..e5bde56f2 100644 --- a/src/main/scala/rules/ChunkSupporter.scala +++ b/src/main/scala/rules/ChunkSupporter.scala @@ -178,7 +178,7 @@ object chunkSupporter extends ChunkSupportRules { val toTake = PermMin(ch.perm, perms) val toTakeExp = permsExp.map(pe => buildMinExp(Seq(ch.permExp.get, pe), ast.Perm)) val newPermExp = permsExp.map(pe => ast.PermSub(ch.permExp.get, toTakeExp.get)(pe.pos, pe.info, pe.errT)) - val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, toTake), newPermExp, v.decider.getAnalysisInfo).asInstanceOf[NonQuantifiedChunk] + val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, toTake), newPermExp, v.decider.getAnalysisInfo(AssumptionType.Internal)).asInstanceOf[NonQuantifiedChunk] val takenChunk = Some(GeneralChunk.withPerm(ch, toTake, toTakeExp, v.decider.getAnalysisInfo, isExhale=true).asInstanceOf[NonQuantifiedChunk]) var newHeap = h - ch if (!v.decider.check(newChunk.perm === NoPerm, Verifier.config.checkTimeout())) { @@ -192,7 +192,7 @@ object chunkSupporter extends ChunkSupportRules { val constraintExp = permsExp.map(pe => ast.PermLtCmp(pe, ch.permExp.get)(pe.pos, pe.info, pe.errT)) v.decider.assume(PermLess(perms, ch.perm), Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp)), AssumptionType.Implicit) val newPermExp = permsExp.map(pe => ast.PermSub(ch.permExp.get, pe)(pe.pos, pe.info, pe.errT)) - val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, perms), newPermExp, v.decider.getAnalysisInfo).asInstanceOf[NonQuantifiedChunk] + val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, perms), newPermExp, v.decider.getAnalysisInfo(AssumptionType.Internal)).asInstanceOf[NonQuantifiedChunk] val takenChunk = GeneralChunk.withPerm(ch, perms, permsExp, v.decider.getAnalysisInfo, isExhale=true).asInstanceOf[NonQuantifiedChunk] val newHeap = h - ch + newChunk assumeProperties(newChunk, newHeap) diff --git a/src/test/resources/dependencyAnalysisTests/all/list.vpr b/src/test/resources/dependencyAnalysisTests/all/list.vpr new file mode 100644 index 000000000..c8853c3c5 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/all/list.vpr @@ -0,0 +1,107 @@ +field elem: Int +field next: Ref + +predicate list(this: Ref) { + acc(this.elem) && acc(this.next) && + (this.next != null ==> list(this.next)) +} + +function listLength(l:Ref) : Int + requires list(l) + ensures result > 0 +{ + unfolding list(l) in l.next == null ? 1 : 1 + listLength(l.next) +} + +method appendList1(this: Ref, e: Int) + requires @obsolete()(list(this)) + requires @obsolete()(0 <= e && e < 100) + ensures list(this) +{ + @obsolete() + unfold list(this) + @obsolete() + assume 0 <= this.elem && this.elem < 100 + + if (@obsolete()(this.next == null)) { + var n: Ref + + @dependency() + n := new(elem, next) + @obsolete() + n.elem := e + @dependency() + n.next := null + @obsolete() + this.next := n + @testAssertion() + fold list(n) + } else { + appendList1(this.next, e) + } + + fold list(this) +} + +method appendList2(this: Ref, e: Int) + requires @dependency()(list(this)) + requires @dependency()(0 <= e && e < 100) + ensures list(this) +{ + @dependency() + unfold list(this) + @obsolete() + assume 0 <= this.elem && this.elem < 100 + + if (@dependency()(this.next == null)) { + var n: Ref + + @obsolete() + n := new(elem, next) + @obsolete() + n.elem := e + @obsolete() + n.next := null + @obsolete() + this.next := n + @obsolete() + fold list(n) + } else { + @testAssertion() + appendList2(this.next, e) + } + + fold list(this) +} + +method appendListFull(this: Ref, e: Int) + requires @dependency()(list(this)) + requires @dependency()(0 <= e && e < 100) + ensures list(this) +{ + @dependency() + unfold list(this) + @obsolete() + assume 0 <= this.elem && this.elem < 100 + + if (@dependency()(this.next == null)) { + var n: Ref + + @dependency() + n := new(elem, next) + @obsolete() + n.elem := e + @dependency() + n.next := null + @dependency() + this.next := n + @dependency() + fold list(n) + } else { + @dependency() + appendListFull(this.next, e) + } + + @testAssertion() + fold list(this) +} diff --git a/src/test/resources/dependencyAnalysisTests/all/quantified-perm.vpr b/src/test/resources/dependencyAnalysisTests/all/quantified-perm.vpr new file mode 100644 index 000000000..3e03293fd --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/all/quantified-perm.vpr @@ -0,0 +1,45 @@ +field f: Int +field first : Ref +field second : Ref + + + +method quantifiedWritePerm(nodes: Set[Ref], x: Ref) + requires @obsolete()(forall n:Ref :: { n.first } n in nodes ==> + acc(n.first) && + (n.first != null ==> n.first in nodes)) + requires @dependency()(forall n:Ref :: { n.second } n in nodes ==> + acc(n.second) && + (n.second != null ==> n.second in nodes)) + requires @dependency()(x in nodes) +{ + var y : Ref + if(@dependency()(x.second != null)) { + @dependency() + y := x.second // permissions covered by preconditions + @dependency() + y.second := y + @testAssertion() + assert x.second.second == x.second + } +} + +method quantifiedSum(nodes: Set[Ref], x: Ref) + requires @dependency()(forall n:Ref :: { n.first } n in nodes ==> + acc(n.first) && + (n.first != null ==> n.first in nodes)) + requires @dependency()(forall n:Ref :: { n.f } n in nodes ==> + acc(n.f) && 0 <= n.f && n.f <= 100) + requires @dependency()(x in nodes) +{ + var a: Int + @dependency() + a := x.f + if(@dependency()(x.first != null)) { + @dependency() + a := a + x.first.f + } + @testAssertion() + assert a >= 0 +} + diff --git a/src/test/resources/dependencyAnalysisTests/todo/list.vpr b/src/test/resources/dependencyAnalysisTests/todo/list.vpr deleted file mode 100644 index c763ab30f..000000000 --- a/src/test/resources/dependencyAnalysisTests/todo/list.vpr +++ /dev/null @@ -1,37 +0,0 @@ -field elem: Int -field next: Ref - -predicate list(this: Ref) { - acc(this.elem) && acc(this.next) && - (this.next != null ==> list(this.next)) -} - -function listLength(l:Ref) : Int - requires list(l) - ensures result > 0 -{ - unfolding list(l) in l.next == null ? 1 : 1 + listLength(l.next) -} - -method appendList(this: Ref, e: Int) - requires list(this) - requires 0 <= e && e < 100 - ensures list(this) -{ - unfold list(this) - assume 0 <= this.elem && this.elem < 100 - - if (this.next == null) { - var n: Ref - - n := new(elem, next) - n.elem := e - n.next := null - this.next := n - - fold list(n) - } else { - appendList(this.next, e) - } - fold list(this) -} diff --git a/src/test/resources/dependencyAnalysisTests/todo/permissions.vpr b/src/test/resources/dependencyAnalysisTests/todo/permissions.vpr deleted file mode 100644 index 2acea8a72..000000000 --- a/src/test/resources/dependencyAnalysisTests/todo/permissions.vpr +++ /dev/null @@ -1,65 +0,0 @@ -field f: Int - -function id(n: Int): Int - ensures result == n - -method foo(a: Ref) - requires @assumptionType("Internal")(acc(a.f, 1/2)) && a.f >= 0 - ensures acc(a.f, 1/2) - -method call(x: Ref) - requires acc(x.f) - ensures acc(x.f) -{ - assume x.f > 0 - foo(x) - assert x.f >= 0 -} - -method permAmount(x: Ref, y: Ref, p: Perm) - requires p > none - requires acc(x.f) && acc(y.f, p) - ensures acc(x.f) && acc(y.f, p) -{ - x.f := 5 - assume p > 1/2 - assume y.f == 1 - x.f := y.f + 1 - foo(x) - assert x.f == y.f + 1 -} - -method transitivity(a: Ref, n: Int) - requires n > 0 - requires acc(a.f) && a.f > 0 -{ - var res: Int - res := a.f / n - assert res >= 0 -} - -method maybeAlias(a: Ref, b: Ref, c: Bool, n: Int) - requires acc(a.f, 1/2) && acc(b.f, 1/2) - requires c ==> a == b - requires a.f > 0 && n > 0 && b.f >= 0 - requires a.f < 100 - requires !c ==> a.f < b.f -{ - if(c){ - a.f := n + 1 - } - assert a.f >= 0 - assert !c ==> a.f <= 100 -} - -method aliasing(x: Ref, n: Int) - requires acc(x.f) && x.f < n -{ - var y: Ref := x - y.f := n + 1 - assert x.f >= n - - assume y.f < n - assert false - -} diff --git a/src/test/resources/dependencyAnalysisTests/todo/quantified-perm.vpr b/src/test/resources/dependencyAnalysisTests/todo/quantified-perm.vpr deleted file mode 100644 index f40687468..000000000 --- a/src/test/resources/dependencyAnalysisTests/todo/quantified-perm.vpr +++ /dev/null @@ -1,115 +0,0 @@ -field f: Int -field first : Ref -field second : Ref - -method quantifiedPerm(xs: Seq[Ref]) { - assume |xs| > 5 - inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) - - assert xs[2].f > 0 - var a : Ref := xs[0] - a.f := 0 - assert xs[0].f >= 0 - assert xs[0].f == 0 - assert xs[2].f >= 0 - a.f := -1 - assert xs[2] != a ==> xs[2].f > 0 -} - -method quantifiedWritePerm(nodes: Set[Ref], x: Ref) - requires forall n:Ref :: { n.first } n in nodes ==> - acc(n.first) && - (n.first != null ==> n.first in nodes) - requires forall n:Ref :: { n.second } n in nodes ==> - acc(n.second) && - (n.second != null ==> n.second in nodes) - requires x in nodes -{ - var y : Ref - if(x.second != null) { - y := x.second // permissions covered by preconditions - y.second := y - assert x.second.second == x.second - } -} - -method quantifiedSum(nodes: Set[Ref], x: Ref) - requires forall n:Ref :: { n.first } n in nodes ==> - acc(n.first) && - (n.first != null ==> n.first in nodes) - requires forall n:Ref :: { n.f } n in nodes ==> - acc(n.f) && 0 <= n.f && n.f <= 100 - requires x in nodes -{ - var a: Int := x.f - if(x.first != null) { - a := a + x.first.f - } - assert a >= 0 -} - - -method quantifiedPerm2Seqs(xs: Seq[Ref], ys: Seq[Ref]) { - assume |xs| > 5 && |ys| > 3 - inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) - inhale forall y: Ref :: y in ys ==> (acc(y.f) && y.f > 0) - - assert xs[0].f > 0 -} - -method quantifiedPerm2Seqs2(xs: Seq[Ref], ys: Seq[Ref]) { - assume |xs| > 5 && |ys| > 3 - inhale forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0) - inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard) && y.f > 0) - - assert xs[0].f > 0 -} - -method quantifiedPerm2Seqs3(xs: Seq[Ref], ys: Seq[Ref]) { - assume |xs| > 5 && |ys| > 3 - inhale forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0) - inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard)) - - assert xs[0].f > 0 -} - -method quantifiedExhalePartially(xs: Seq[Ref], ys: Seq[Ref]) { - assume |xs| > 5 - inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) - - xs[0].f := 10 - exhale forall x: Ref :: x in xs ==> acc(x.f, 1/2) - assert xs[1].f > 0 -} - -method quantifiedExhaleFully(xs: Seq[Ref], ys: Seq[Ref]) { - assume |xs| > 5 - inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) - - xs[0].f := 10 - exhale forall x: Ref :: x in xs ==> acc(x.f) -} - -method quantifiedPermBig(xs: Seq[Ref], ys: Seq[Ref], r: Ref) { - assume |xs| > 5 && |ys| > 5 - inhale acc(r.f, 1/2) - inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) - inhale forall y: Ref :: y in ys ==> (acc(y.f, 1/2) && y.f > 0) - - assert xs[0] != ys[0] - assert r != xs[0] - assert xs[2].f >= 0 - xs[0].f := xs[1].f + xs[2].f - xs[1].f := ys[0].f - - inhale ys[0] == r - - ys[0].f := xs[0].f - r.f := r.f + xs[0].f - assert r.f == 2*xs[0].f - - xs[3].f := r.f - - assert forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) - assert forall y: Ref :: y in ys ==> (acc(y.f, 1/2) && y.f > 0) - } \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr index 76ef761ed..69d38251d 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr @@ -50,4 +50,40 @@ method quantifiedExhaleFully(xs: Seq[Ref]) { @testAssertion() exhale forall x: Ref :: x in xs ==> acc(x.f) +} + +method quantifiedPermWrite1(xs: Seq[Ref]) { + @dependency() + assume |xs| > 5 + @dependency() + inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) + + @testAssertion() + xs[0].f := 0 +} + +method quantifiedPermWrite2(xs: Seq[Ref]) { + @dependency() + assume |xs| > 5 + @dependency() + inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) + + @dependency() + xs[0].f := 0 + + @testAssertion() + assert xs[0].f == 0 +} + +method quantifiedPermWrite3(xs: Seq[Ref]) { + @dependency() + assume |xs| > 5 + @dependency() + inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) + + @obsolete() + xs[0].f := 0 + + @testAssertion() + assert xs[0] != xs[1] ==> xs[1].f > 0 } \ No newline at end of file From 745782aacbb45a6cc9dddcf0cb0004ebe0783d04 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 24 Jun 2025 15:43:25 +0200 Subject: [PATCH 113/474] add tests from viper tutorial --- src/main/scala/rules/Brancher.scala | 4 +- .../dependencyAnalysisTests/all/list.vpr | 6 +- .../dependencyAnalysisTests/all/sum-loops.vpr | 76 +++++++++++++++++ .../dependencyAnalysisTests/all/tuples.vpr | 32 ++++++++ .../dependencyAnalysisTests/all/wands.vpr | 82 +++++++++++++++++++ .../dependencyAnalysisTests/todo/lseg.vpr | 30 ------- .../unitTests/permissions.vpr | 10 +++ 7 files changed, 205 insertions(+), 35 deletions(-) create mode 100644 src/test/resources/dependencyAnalysisTests/all/sum-loops.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/all/tuples.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/all/wands.vpr delete mode 100644 src/test/resources/dependencyAnalysisTests/todo/lseg.vpr diff --git a/src/main/scala/rules/Brancher.scala b/src/main/scala/rules/Brancher.scala index 3746eec0d..7bd463cb9 100644 --- a/src/main/scala/rules/Brancher.scala +++ b/src/main/scala/rules/Brancher.scala @@ -54,8 +54,8 @@ object brancher extends BranchingRules { * (2) the branch condition contains a quantified variable */ val skipPathFeasibilityCheck = ( - Verifier.config.enableAssumptionAnalysis() - || fromShortCircuitingAnd + // Verifier.config.enableAssumptionAnalysis() || TODO ake: review infeasibility check + fromShortCircuitingAnd || ( s.quantifiedVariables.nonEmpty && s.quantifiedVariables.map(_._1).exists(condition.freeVariables.contains)) ) diff --git a/src/test/resources/dependencyAnalysisTests/all/list.vpr b/src/test/resources/dependencyAnalysisTests/all/list.vpr index c8853c3c5..4b375c797 100644 --- a/src/test/resources/dependencyAnalysisTests/all/list.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/list.vpr @@ -23,7 +23,7 @@ method appendList1(this: Ref, e: Int) @obsolete() assume 0 <= this.elem && this.elem < 100 - if (@obsolete()(this.next == null)) { + if (this.next == null) { // TODO ake: node does not exist? var n: Ref @dependency() @@ -53,7 +53,7 @@ method appendList2(this: Ref, e: Int) @obsolete() assume 0 <= this.elem && this.elem < 100 - if (@dependency()(this.next == null)) { + if (this.next == null) { // TODO ake: node does not exist var n: Ref @obsolete() @@ -84,7 +84,7 @@ method appendListFull(this: Ref, e: Int) @obsolete() assume 0 <= this.elem && this.elem < 100 - if (@dependency()(this.next == null)) { + if (this.next == null) { var n: Ref @dependency() diff --git a/src/test/resources/dependencyAnalysisTests/all/sum-loops.vpr b/src/test/resources/dependencyAnalysisTests/all/sum-loops.vpr new file mode 100644 index 000000000..114faca4b --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/all/sum-loops.vpr @@ -0,0 +1,76 @@ +field f: Int + +method sum(n: Int) returns (res: Int) + requires @dependency()(0 <= n) + ensures res == n * (n + 1) / 2 +{ + @dependency() + res := 0 + var i: Int + @dependency() + i := 0; + while(@dependency()(i <= n)) + invariant @dependency()(i <= (n + 1)) + invariant @dependency()(res == (i - 1) * i / 2) + { + @dependency() + res := res + i + @dependency() + i := i + 1 + } + + @testAssertion() + assert res == n * (n + 1) / 2 +} + + +method sumPerm1(n: Int, res: Ref) + requires @dependency()(acc(res.f)) + requires @dependency()(0 <= n) + ensures acc(res.f) + ensures res.f == n * (n + 1) / 2 +{ + @dependency() + res.f := 0 + var i: Int + @dependency() + i := 0; + while(@dependency()(i <= n)) + invariant @dependency()(acc(res.f)) + invariant @dependency()(i <= (n + 1)) + invariant @dependency()(res.f == (i - 1) * i / 2) + { + @dependency() + res.f := res.f + i + @dependency() + i := i + 1 + } + + @testAssertion() + assert res.f == n * (n + 1) / 2 +} + +method sumPerm2(n: Int, res: Ref) + requires @dependency()(acc(res.f)) + requires @obsolete()(0 <= n) + ensures acc(res.f) +{ + @obsolete() + res.f := 0 + var i: Int + @obsolete() + i := 0; + while(@obsolete()(i <= n)) + invariant @dependency()(acc(res.f)) + invariant @obsolete()(i <= (n + 1)) + invariant @obsolete()(res.f == (i - 1) * i / 2) + { + @obsolete() + res.f := res.f + i + @obsolete() + i := i + 1 + } + + @testAssertion() // only check permission flow + res.f := 5 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/tuples.vpr b/src/test/resources/dependencyAnalysisTests/all/tuples.vpr new file mode 100644 index 000000000..5bb81dbad --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/all/tuples.vpr @@ -0,0 +1,32 @@ +field left: Int +field right: Int + +predicate tuple(this: Ref) { + acc(this.left) && acc(this.right) +} + +method setTuple(this: Ref, l: Int, r: Int) + requires @dependency()(tuple(this)) + ensures tuple(this) +{ + @dependency() + unfold tuple(this) + @obsolete() + this.left := l + @obsolete() + this.right := r + @testAssertion() + fold tuple(this) +} + +method addTuple(this: Ref) returns (sum: Int) + requires @dependency()(acc(tuple(this), 1/2)) + ensures acc(tuple(this), 1/2) +{ + @dependency() + unfold acc(tuple(this), 1/2) + @obsolete() + sum := this.left + this.right + @testAssertion() + fold acc(tuple(this), 1/2) +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/wands.vpr b/src/test/resources/dependencyAnalysisTests/all/wands.vpr new file mode 100644 index 000000000..8c9d258c8 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/all/wands.vpr @@ -0,0 +1,82 @@ +field next : Ref +field val : Int + +predicate list(start : Ref) +{ + acc(start.val) && acc(start.next) && + (start.next != null ==> list(start.next)) +} + +function elems(start: Ref) : Seq[Int] + requires list(start) +{ + unfolding list(start) in ( + (start.next == null ? Seq(start.val) : + Seq(start.val) ++ elems(start.next) )) +} + +method appendit_wand1(l1 : Ref, l2: Ref) + requires @dependency()(list(l1)) + requires @dependency()(list(l2)) + requires l2 != null + ensures list(l1) + { + @dependency() + unfold list(l1) + if(l1.next == null) { // easy case + @dependency() + l1.next := l2 + @testAssertion() + fold list(l1) + } else { + @obsolete() + assume false + } + } + +// TODO ake +// method appendit_wand2(l1 : Ref, l2: Ref) +// requires @dependency()(list(l1)) +// requires @dependency()(list(l2)) +// requires l2 != null +// ensures list(l1) +// { +// @dependency() +// unfold list(l1) +// if(l1.next == null) { // easy case +// @obsolete() +// l1.next := l2 +// @obsolete() +// fold list(l1) +// } else { +// var tmp : Ref := l1.next +// var index : Int := 1 + +// package list(tmp) --* list(l1) && elems(l1) == old(elems(l1)[..index]) ++ old[lhs](elems(tmp)) +// { +// fold list(l1) +// } + +// while(unfolding list(tmp) in tmp.next != null) +// invariant index >= 0 +// invariant list(tmp) && elems(tmp) == old(elems(l1))[index..] +// invariant list(tmp) --* list(l1) && elems(l1) == old(elems(l1)[..index]) ++ old[lhs](elems(tmp)) +// { +// unfold list(tmp) +// var prev : Ref := tmp +// tmp := tmp.next +// index := index + 1 + +// package list(tmp) --* list(l1) && elems(l1) == old(elems(l1)[..index]) ++ old[lhs](elems(tmp)) +// { +// fold list(prev) +// apply list(prev) --* list(l1) && elems(l1) == old(elems(l1)[..index-1]) ++ old[lhs](elems(prev)) +// } +// } +// unfold list(tmp) +// tmp.next := l2 +// fold list(tmp) +// apply list(tmp) --* list(l1) && elems(l1) == old(elems(l1)[..index]) ++ old[lhs](elems(tmp)) + +// } +// } \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/todo/lseg.vpr b/src/test/resources/dependencyAnalysisTests/todo/lseg.vpr deleted file mode 100644 index b696523f7..000000000 --- a/src/test/resources/dependencyAnalysisTests/todo/lseg.vpr +++ /dev/null @@ -1,30 +0,0 @@ -field elem: Int -field next: Ref - -predicate lseg(this: Ref, last: Ref) { - this != last ==> - acc(this.elem) && acc(this.next) && - this.next != null && - lseg(this.next, last) -} - -function values(this: Ref, last: Ref): Seq[Int] - requires lseg(this, last) -{ - unfolding lseg(this, last) in - this == last - ? Seq[Int]() - : Seq(this.elem) ++ values(this.next, last) -} - -method removeFirst(this: Ref, last: Ref) returns (first: Int, rest: Ref) - requires lseg(this, last) - requires this != last - ensures lseg(rest, last) - ensures values(rest, last) == old(values(this, last)[1..]) -{ - unfold lseg(this, last) - - first := this.elem - rest := this.next -} diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr index ad1ba7b1b..02dd91a5f 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr @@ -83,3 +83,13 @@ method permAmount2(x: Ref, p: Perm) @testAssertion() assert x.f > 0 } + + +method noAlias(a: Ref, b: Ref, c: Ref) + requires @dependency()(acc(a.f)) + requires @dependency()(acc(b.f, 1/2)) + requires @obsolete()(acc(c.f, 1/2)) +{ + @testAssertion() + assert a != b +} \ No newline at end of file From b167c14bdf323649331e078aa1ae92b26ddfd60e Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 26 Jun 2025 08:23:39 +0200 Subject: [PATCH 114/474] add tests for meeting --- .../dependencyAnalysisTests/new/meeting.vpr | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 src/test/resources/dependencyAnalysisTests/new/meeting.vpr diff --git a/src/test/resources/dependencyAnalysisTests/new/meeting.vpr b/src/test/resources/dependencyAnalysisTests/new/meeting.vpr new file mode 100644 index 000000000..f6f6b86d3 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/new/meeting.vpr @@ -0,0 +1,41 @@ +field f: Int + + +method infeasible1(x: Ref) + requires @dependency()(acc(x.f, 1/2)) // missing dependency iff infeasible branches are skipped + requires @dependency()(x.f > 0) // missing dependency iff infeasible branches are skipped +{ + if(@dependency()(x.f < 0)){ // missing dependency iff infeasible branches are skipped + @testAssertion() + x.f := x.f + 1 // // missing node iff infeasible branches are skipped + } +} + + +method infeasible2(x: Ref) + requires @obsolete()(x != null ==> acc(x.f)) // unexpected dependency iff infeasible branches are always executed +{ + if(@obsolete()(x == null)){ // unexpected dependency iff infeasible branches are always executed + var a: Int + @dependency() + a := 0 + @testAssertion() + assert a >= 0 + } +} + +method infeasible3(x: Ref) +{ + if(@obsolete()(x != null)){ // unexpected dependency iff infeasible branches are always executed + @obsolete() + inhale acc(x.f) + } + + if(@obsolete()(x == null)){ // unexpected dependency iff infeasible branches are always executed + var a: Int + @dependency() + a := 0 + @testAssertion() + assert a >= 0 + } +} From fda1fd09be9997eff65eff1250a5577e62801144 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 26 Jun 2025 11:03:50 +0200 Subject: [PATCH 115/474] review assumption types (e.g. well-formedness checks) --- src/main/scala/rules/Evaluator.scala | 16 ++++++++-------- src/main/scala/rules/Executor.scala | 8 ++++---- src/main/scala/rules/HavocSupporter.scala | 4 ++-- src/main/scala/rules/MagicWandSupporter.scala | 10 +++++----- src/main/scala/rules/PredicateSupporter.scala | 2 +- src/main/scala/rules/Producer.scala | 4 ++-- .../scala/rules/QuantifiedChunkSupport.scala | 4 ++-- src/main/scala/supporters/MethodSupporter.scala | 2 +- .../supporters/PredicateVerificationUnit.scala | 2 +- .../scala/supporters/SnapshotSupporter.scala | 13 ++++++++----- .../functions/FunctionVerificationUnit.scala | 4 ++-- 11 files changed, 36 insertions(+), 33 deletions(-) diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 0bedb6f2c..674ac5394 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -943,7 +943,7 @@ object evaluator extends EvaluationRules { val preExp = Option.when(withExp)({ DebugExp.createInstance(Some(s"precondition of ${func.name}(${eArgsNew.get.mkString(", ")}) holds"), None, None, InsertionOrderedSet.empty) }) - v3.decider.assume(preFApp, preExp, AssumptionType.Internal) + v3.decider.assume(preFApp, preExp, AssumptionType.Explicit) // TODO ake: assumption Type? val funcAnn = func.info.getUniqueInfo[AnnotationInfo] val tFApp = funcAnn match { case Some(a) if a.values.contains("opaque") => @@ -1086,21 +1086,21 @@ object evaluator extends EvaluationRules { case false => val failure = createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, idxLtLength, idxLtLengthExp) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { - v1.decider.assume(idxLtLength, idxLtLengthExp, idxLtLengthExpNew, AssumptionType.Internal) + v1.decider.assume(idxLtLength, idxLtLengthExp, idxLtLengthExpNew, AssumptionType.Explicit) // TODO ake: assumption type failure combine Q(s1, SeqAt(t0, t1), eNew, v1) } else failure} case false => val failure1 = createFailure(pve dueTo SeqIndexNegative(e0, e1), v1, s1, idxGe0, idxGe0ExpNew) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { - v1.decider.assume(idxGe0, idxGe0Exp, idxGe0ExpNew, AssumptionType.Internal) + v1.decider.assume(idxGe0, idxGe0Exp, idxGe0ExpNew, AssumptionType.Explicit) v1.decider.assert(idxLtLength, idxLtLengthExp) { case true => failure1 combine Q(s1, SeqAt(t0, t1), eNew, v1) case false => val failure2 = failure1 combine createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, idxLtLength, idxLtLengthExpNew) if (v1.reportFurtherErrors()) { - v1.decider.assume(idxLtLength, idxLtLengthExp, idxLtLengthExpNew, AssumptionType.Internal) + v1.decider.assume(idxLtLength, idxLtLengthExp, idxLtLengthExpNew, AssumptionType.Explicit) failure2 combine Q(s1, SeqAt(t0, t1), eNew, v1) } else failure2} } else failure1}}}) @@ -1143,14 +1143,14 @@ object evaluator extends EvaluationRules { case false => val failure1 = createFailure(pve dueTo SeqIndexNegative(e0, e1), v1, s1, idxGe0, idxGe0ExpNew) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { - v1.decider.assume(idxGe0, idxGe0Exp, idxGe0ExpNew, AssumptionType.Internal) + v1.decider.assume(idxGe0, idxGe0Exp, idxGe0ExpNew, AssumptionType.Explicit) v1.decider.assert(idxLtLength, idxLtLengthExp) { case true => failure1 combine Q(s1, SeqUpdate(t0, t1, t2), eNew, v1) case false => val failure2 = failure1 combine createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, idxLtLength, idxLtLengthExpNew) if (v1.reportFurtherErrors()) { - v1.decider.assume(idxLtLength, idxLtLengthExp, idxLtLengthExpNew, AssumptionType.Internal) + v1.decider.assume(idxLtLength, idxLtLengthExp, idxLtLengthExpNew, AssumptionType.Explicit) failure2 combine Q(s1, SeqUpdate(t0, t1, t2), eNew, v1) } else failure2} } else failure1}}}) @@ -1269,7 +1269,7 @@ object evaluator extends EvaluationRules { case false => val failure1 = createFailure(pve dueTo MapKeyNotContained(base, key), v1, s1, SetIn(keyT, MapDomain(baseT)), assertExpNew) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { - v1.decider.assume(SetIn(keyT, MapDomain(baseT)), assertExp, assertExpNew, AssumptionType.Internal) + v1.decider.assume(SetIn(keyT, MapDomain(baseT)), assertExp, assertExpNew, AssumptionType.Explicit) failure1 combine Q(s1, MapLookup(baseT, keyT), eNew, v1) } else { failure1 @@ -1529,7 +1529,7 @@ object evaluator extends EvaluationRules { case false => val failure = createFailure(pve dueTo DivisionByZero(eDivisor), v, s, tDivisor !== tZero, notZeroExpNew) if (s.retryLevel == 0 && v.reportFurtherErrors()) { - v.decider.assume(tDivisor !== tZero, notZeroExp, notZeroExpNew, AssumptionType.Internal) + v.decider.assume(tDivisor !== tZero, notZeroExp, notZeroExpNew, AssumptionType.Explicit) failure combine Q(s, t, v) } else failure } diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 6ff53f264..dc64e3a61 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -424,10 +424,10 @@ object executor extends ExecutionRules { val (sm, smValueDef) = quantifiedChunkSupporter.singletonSnapshotMap(s3, field, Seq(tRcvr), tRhs, v2) v1.decider.prover.comment("Definitional axioms for singleton-FVF's value") val debugExp = Option.when(withExp)(DebugExp.createInstance("Definitional axioms for singleton-FVF's value", isInternal_ = true)) - v1.decider.assumeDefinition(smValueDef, debugExp, AssumptionType.Internal) + v1.decider.assumeDefinition(smValueDef, debugExp, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Internal)) v1.decider.analysisSourceInfoStack.setForcedSource(ExpAnalysisSourceInfo(fa)) val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(Seq(`?r`), Option.when(withExp)(Seq(ast.LocalVarDecl("r", ast.Ref)(ass.pos, ass.info, ass.errT))), - field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), sm, s.program, v1, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Internal), isExhale=false) + field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), sm, s.program, v1, AssumptionType.Internal, isExhale=false) v1.decider.analysisSourceInfoStack.removeForcedSource() if (s3.heapDependentTriggers.contains(field)) { val debugExp2 = Option.when(withExp)(DebugExp.createInstance(s"FieldTrigger(${eRcvrNew.toString}.${field.name})")) @@ -455,7 +455,7 @@ object executor extends ExecutionRules { val id = BasicChunkIdentifier(field.name) v2.decider.assumptionAnalyzer.enableCustomEdges() // inhaling the new chunk should not depend on any rhs assertions, dependency to exhaling old chunk is added nevertheless val newChunk = BasicChunk.createDerivedChunk(Set.empty, FieldID, id, Seq(tRcvr), eRcvrNew.map(Seq(_)), tSnap, rhsNew, FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), - v3.decider.getAnalysisInfo(annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Internal))) + v3.decider.getAnalysisInfo(AssumptionType.Internal)) v2.decider.assumptionAnalyzer.disableCustomEdges() chunkSupporter.produce(s3, h3, newChunk, v3)((s4, h4, v4) => { val s5 = s4.copy(h = h4) @@ -483,7 +483,7 @@ object executor extends ExecutionRules { val (sm, smValueDef) = quantifiedChunkSupporter.singletonSnapshotMap(s, field, Seq(tRcvr), snap, v) v.decider.prover.comment("Definitional axioms for singleton-FVF's value") val debugExp = Option.when(withExp)(DebugExp.createInstance("Definitional axioms for singleton-FVF's value", isInternal_ = true)) - v.decider.assumeDefinition(smValueDef, debugExp, AssumptionType.Internal) + v.decider.assumeDefinition(smValueDef, debugExp, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit)) quantifiedChunkSupporter.createSingletonQuantifiedChunk(Seq(`?r`), Option.when(withExp)(Seq(ast.LocalVarDecl("r", ast.Ref)(stmt.pos, stmt.info, stmt.errT))), field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), p, pExp, sm, s.program, v, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit), isExhale=false) } else { diff --git a/src/main/scala/rules/HavocSupporter.scala b/src/main/scala/rules/HavocSupporter.scala index 058d6d769..94764fa21 100644 --- a/src/main/scala/rules/HavocSupporter.scala +++ b/src/main/scala/rules/HavocSupporter.scala @@ -151,7 +151,7 @@ object havocSupporter extends SymbolicExecutionRules { ) val comment = "Definitional axioms for havocall inverse functions" v.decider.prover.comment(comment) - v.decider.assume(inverseFunctions.definitionalAxioms, Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, assumptionType=AssumptionType.Internal) + v.decider.assume(inverseFunctions.definitionalAxioms, Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, assumptionType=assumptionType) // Call the havoc helper function, which returns a new set of chunks, some of // which may be havocked. Since we are executing a Havocall statement, we wrap @@ -284,7 +284,7 @@ object havocSupporter extends SymbolicExecutionRules { v.decider.prover.comment("axiomatized snapshot map after havoc") val debugExp = Option.when(withExp)(DebugExp.createInstance("havoc new axiom", isInternal_ = true)) - v.decider.assume(newAxiom, debugExp, AssumptionType.Internal) + v.decider.assume(newAxiom, debugExp, assumptionType) QuantifiedChunk.withSnapshotMap(ch, newSm, v.decider.getAnalysisInfo(assumptionType)) } diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 06fbf97e7..d522c422c 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -115,14 +115,14 @@ object magicWandSupporter extends SymbolicExecutionRules { * @param v Verifier instance * @return Fresh instance of [[viper.silicon.state.terms.MagicWandSnapshot]] */ - def createMagicWandSnapshot(abstractLhs: Var, rhsSnapshot: Term, v: Verifier): MagicWandSnapshot = { + def createMagicWandSnapshot(abstractLhs: Var, rhsSnapshot: Term, v: Verifier, assumptionType: AssumptionType): MagicWandSnapshot = { val mwsf = v.decider.fresh("mwsf", sorts.MagicWandSnapFunction, Option.when(withExp)(PUnknown())) val magicWandSnapshot = MagicWandSnapshot(mwsf) v.decider.assumeDefinition(Forall( abstractLhs, MWSFLookup(mwsf, abstractLhs) === rhsSnapshot, Trigger(MWSFLookup(mwsf, abstractLhs)) - ), Option.when(withExp)(DebugExp.createInstance("Magic wand snapshot definition", isInternal_ = true)), AssumptionType.Internal) + ), Option.when(withExp)(DebugExp.createInstance("Magic wand snapshot definition", isInternal_ = true)), assumptionType) magicWandSnapshot } @@ -185,7 +185,7 @@ object magicWandSupporter extends SymbolicExecutionRules { case (Some(ch1: QuantifiedBasicChunk), Some(ch2: QuantifiedBasicChunk)) => ch1.snapshotMap === ch2.snapshotMap case _ => True } - v.decider.assume(tEq, Option.when(withExp)(DebugExp.createInstance("Snapshots", isInternal_ = true)), AssumptionType.Internal) + v.decider.assume(tEq, Option.when(withExp)(DebugExp.createInstance("Snapshots", isInternal_ = true)), AssumptionType.Explicit) /* In the future it might be worth to recheck whether the permissions needed, in the case of * success being an instance of Incomplete, are zero. @@ -315,7 +315,7 @@ object magicWandSupporter extends SymbolicExecutionRules { val preMark = v3.decider.setPathConditionMark() v3.decider.prover.comment(s"Create MagicWandSnapFunction for wand $wand") - val wandSnapshot = this.createMagicWandSnapshot(freshSnapRoot, snapRhs, v3) + val wandSnapshot = this.createMagicWandSnapshot(freshSnapRoot, snapRhs, v3, assumptionType) // If the wand is used as a quantified resource anywhere in the program if (s4.qpMagicWands.contains(MagicWandIdentifier(wand, s.program))) { @@ -487,7 +487,7 @@ object magicWandSupporter extends SymbolicExecutionRules { case SortWrapper(snapshot: MagicWandSnapshot, _) => snapshot.applyToMWSF(snapLhs.get) // Fallback solution for quantified magic wands case predicateLookup: PredicateLookup => - v2.decider.assume(snapLhs.get === First(snapWand.get), Option.when(withExp)(DebugExp.createInstance("Magic wand snapshot", isInternal_ = true)), AssumptionType.Internal) + v2.decider.assume(snapLhs.get === First(snapWand.get), Option.when(withExp)(DebugExp.createInstance("Magic wand snapshot", isInternal_ = true)), assumptionType) Second(predicateLookup) case _ => snapWand.get } diff --git a/src/main/scala/rules/PredicateSupporter.scala b/src/main/scala/rules/PredicateSupporter.scala index 31a1f0c52..ea5ccb9d6 100644 --- a/src/main/scala/rules/PredicateSupporter.scala +++ b/src/main/scala/rules/PredicateSupporter.scala @@ -90,7 +90,7 @@ object predicateSupporter extends PredicateSupportRules { quantifiedChunkSupporter.singletonSnapshotMap(s2, predicate, tArgs, predSnap, v1) v1.decider.prover.comment("Definitional axioms for singleton-SM's value") val debugExp = Option.when(withExp)(DebugExp.createInstance("Definitional axioms for singleton-SM's value", isInternal_ = true)) - v1.decider.assumeDefinition(smValueDef, debugExp, AssumptionType.Internal) + v1.decider.assumeDefinition(smValueDef, debugExp, assumptionType) val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk( formalArgs, Option.when(withExp)(predicate.formalArgs), predicate, tArgs, eArgs, tPerm, ePerm, sm, s.program, v1, assumptionType, isExhale=false) diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index 4ed258785..34faa2fb3 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -164,7 +164,7 @@ object producer extends ProductionRules { else { try { val (sf0, sf1) = - v.snapshotSupporter.createSnapshotPair(s, sf, a, viper.silicon.utils.ast.BigAnd(as.tail), v) + v.snapshotSupporter.createSnapshotPair(s, sf, a, viper.silicon.utils.ast.BigAnd(as.tail), v, assumptionType) /* TODO: Refactor createSnapshotPair s.t. it can be used with Seq[Exp], * then remove use of BigAnd; for one it is not efficient since * the tail of the (decreasing list parameter as) is BigAnd-ed @@ -405,7 +405,7 @@ object producer extends ProductionRules { v1.decider.prover.comment("Definitional axioms for singleton-SM's value") val definitionalAxiomMark = v1.decider.setPathConditionMark() val debugExp = Option.when(withExp)(DebugExp.createInstance("Definitional axioms for singleton-SM's value", isInternal_ = true)) - v1.decider.assumeDefinition(smValueDef, debugExp, AssumptionType.Internal) + v1.decider.assumeDefinition(smValueDef, debugExp, assumptionType) val conservedPcs = if (s1.recordPcs) (s1.conservedPcs.head :+ v1.decider.pcs.after(definitionalAxiomMark)) +: s1.conservedPcs.tail else s1.conservedPcs diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 845d12ba1..616d88b53 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -1082,7 +1082,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { triggers = effectiveTriggers, qidPrefix = qid ) - v.decider.assume(pcsForChunk, pcsForChunkExp, pcsForChunkExp, AssumptionType.Internal) + v.decider.assume(pcsForChunk, pcsForChunkExp, pcsForChunkExp, assumptionType) }) val (fr1, h1) = v.stateConsolidator(s).merge(s.functionRecorder, s, s.h, Heap(Seq(ch)), v) @@ -1146,7 +1146,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val comment = "Definitional axioms for singleton-SM's value" v.decider.prover.comment(comment) val definitionalAxiomMark = v.decider.setPathConditionMark() - v.decider.assumeDefinition(smValueDef, Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), AssumptionType.Internal) + v.decider.assumeDefinition(smValueDef, Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), assumptionType) val conservedPcs = if (s.recordPcs) (s.conservedPcs.head :+ v.decider.pcs.after(definitionalAxiomMark)) +: s.conservedPcs.tail else s.conservedPcs diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index 1e4b47a86..2e24d39e1 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -108,7 +108,7 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif val s4 = s3.copy(h = Heap()) val impLog = new WellformednessCheckRecord(posts, s, v.decider.pcs) val sepIdentifier = symbExLog.openScope(impLog) - produces(s4, freshSnap, posts, ContractNotWellformed, v3, AssumptionType.Internal)((_, _) => { + produces(s4, freshSnap, posts, ContractNotWellformed, v3, AssumptionType.Explicit)((_, _) => { symbExLog.closeScope(sepIdentifier) Success()})}) && { diff --git a/src/main/scala/supporters/PredicateVerificationUnit.scala b/src/main/scala/supporters/PredicateVerificationUnit.scala index 7549bacde..096e11383 100644 --- a/src/main/scala/supporters/PredicateVerificationUnit.scala +++ b/src/main/scala/supporters/PredicateVerificationUnit.scala @@ -93,7 +93,7 @@ trait DefaultPredicateVerificationUnitProvider extends VerifierComponent { v: Ve oldHeaps = OldHeaps()) val err = PredicateNotWellformed(predicate) - val assumptionType = AssumptionAnalyzer.extractAssumptionTypeFromInfo(predicate.info).getOrElse(AssumptionType.Internal) + val assumptionType = AssumptionAnalyzer.extractAssumptionTypeFromInfo(predicate.info).getOrElse(AssumptionType.Explicit) val result = predicate.body match { case None => diff --git a/src/main/scala/supporters/SnapshotSupporter.scala b/src/main/scala/supporters/SnapshotSupporter.scala index eecb1c69c..22bb1dba7 100644 --- a/src/main/scala/supporters/SnapshotSupporter.scala +++ b/src/main/scala/supporters/SnapshotSupporter.scala @@ -7,6 +7,7 @@ package viper.silicon.supporters import viper.silicon.assumptionAnalysis.AssumptionType +import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType import viper.silicon.debugger.DebugExp import viper.silicon.state.terms.{Combine, First, Second, Sort, Term, Unit, sorts} import viper.silicon.state.{MagicWandIdentifier, State, SymbolConverter} @@ -27,7 +28,8 @@ trait SnapshotSupporter { sf: (Sort, Verifier) => Term, a0: ast.Exp, a1: ast.Exp, - v: Verifier) + v: Verifier, + assumptionType: AssumptionType) : ((Sort, Verifier) => Term, (Sort, Verifier) => Term) } @@ -118,10 +120,11 @@ class DefaultSnapshotSupporter(symbolConverter: SymbolConverter) extends Snapsho sf: (Sort, Verifier) => Term, a0: ast.Exp, a1: ast.Exp, - v: Verifier) + v: Verifier, + assumptionType: AssumptionType) : ((Sort, Verifier) => Term, (Sort, Verifier) => Term) = { - val (snap0, snap1) = createSnapshotPair(s, sf(sorts.Snap, v), a0, a1, v) + val (snap0, snap1) = createSnapshotPair(s, sf(sorts.Snap, v), a0, a1, v, assumptionType) val sf0 = toSf(snap0) val sf1 = toSf(snap1) @@ -129,7 +132,7 @@ class DefaultSnapshotSupporter(symbolConverter: SymbolConverter) extends Snapsho (sf0, sf1) } - private def createSnapshotPair(@unused s: State, snap: Term, @unused a0: ast.Exp, @unused a1: ast.Exp, v: Verifier): (Term, Term) = { + private def createSnapshotPair(@unused s: State, snap: Term, @unused a0: ast.Exp, @unused a1: ast.Exp, v: Verifier, assumptionType: AssumptionType): (Term, Term) = { /* [2015-11-17 Malte] If both fresh snapshot terms and first/second datatypes * are used, then the overall test suite verifies in 2min 10sec, whereas * it takes 2min 20sec when only first/second datatypes are used. Might be @@ -161,7 +164,7 @@ class DefaultSnapshotSupporter(symbolConverter: SymbolConverter) extends Snapsho (snap0, snap1, snap === Combine(snap0, snap1)) } - v.decider.assume(snapshotEq, Option.when(Verifier.config.enableDebugging())(DebugExp.createInstance("Snapshot", isInternal_ = true)), AssumptionType.Internal) + v.decider.assume(snapshotEq, Option.when(Verifier.config.enableDebugging())(DebugExp.createInstance("Snapshot", isInternal_ = true)), assumptionType) (snap0, snap1) } diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index 0c1ff1b7e..7dcd14108 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -224,14 +224,14 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver val result = executionFlowController.locally(s, v)((s0, _) => { val preMark = decider.setPathConditionMark() - produces(s0, toSf(`?s`), pres, ContractNotWellformed, v, AssumptionType.Internal)((s1, _) => { + produces(s0, toSf(`?s`), pres, ContractNotWellformed, v, AssumptionType.Explicit)((s1, _) => { val relevantPathConditionStack = decider.pcs.after(preMark) phase1Data :+= Phase1Data(s1, relevantPathConditionStack.branchConditions map (t => v.decider.assumptionAnalyzer.createLabelledConditional(v.decider, Set(t), t)), relevantPathConditionStack.branchConditionExps, relevantPathConditionStack.assumptions map (t => v.decider.assumptionAnalyzer.createLabelledConditional(v.decider, Set(t), t)), Option.when(evaluator.withExp)(relevantPathConditionStack.assumptionExps)) // The postcondition must be produced with a fresh snapshot (different from `?s`) because // the postcondition's snapshot structure is most likely different than that of the // precondition - produces(s1, freshSnap, posts, ContractNotWellformed, v, AssumptionType.Internal)((s2, _) => { + produces(s1, freshSnap, posts, ContractNotWellformed, v, AssumptionType.Explicit)((s2, _) => { recorders :+= s2.functionRecorder Success()})})}) From 18e85158ce1281329c5231e4987d6f16e3f0ad9a Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 26 Jun 2025 11:30:27 +0200 Subject: [PATCH 116/474] refactor tests --- .../dependencyAnalysisTests/all/aliasing.vpr | 4 +-- .../dependencyAnalysisTests/all/branches.vpr | 28 +++++++++---------- .../dependencyAnalysisTests/all/divBy0.vpr | 2 +- .../dependencyAnalysisTests/all/gaussian.vpr | 4 +-- .../all/imprecision.vpr | 16 +++++------ .../all/infeasible.vpr | 4 +-- .../dependencyAnalysisTests/all/list.vpr | 28 +++++++++---------- .../all/method-sum.vpr | 8 +++--- .../all/presentation-examples.vpr | 8 +++--- .../all/quantified-perm.vpr | 2 +- .../dependencyAnalysisTests/all/sum-loops.vpr | 16 +++++------ .../dependencyAnalysisTests/all/tuples.vpr | 6 ++-- .../dependencyAnalysisTests/all/wands.vpr | 6 ++-- .../generated/test-templates.vpr | 10 +++---- .../dependencyAnalysisTests/new/meeting.vpr | 10 +++---- .../unitTests/branches.vpr | 10 +++---- .../unitTests/loops.vpr | 26 ++++++++--------- .../unitTests/method-call.vpr | 4 +-- .../unitTests/misc.vpr | 2 +- .../unitTests/permissions.vpr | 6 ++-- .../unitTests/quantifiedPermissions.vpr | 4 +-- src/test/scala/AssumptionAnalysisTests.scala | 15 ++++++---- 22 files changed, 111 insertions(+), 108 deletions(-) diff --git a/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr b/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr index 64f788a65..3dc798447 100644 --- a/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr @@ -17,8 +17,8 @@ method maybeAlias(a: Ref, b: Ref, c: Bool, n: Int) method aliasing(x: Ref, n: Int) requires @dependency()(acc(x.f)) - requires @obsolete()(n > 0) - requires @obsolete()(x.f > n) + requires @irrelevant()(n > 0) + requires @irrelevant()(x.f > n) { @dependency() x.f := n + 1 diff --git a/src/test/resources/dependencyAnalysisTests/all/branches.vpr b/src/test/resources/dependencyAnalysisTests/all/branches.vpr index 99ec89f50..bc0818fe1 100644 --- a/src/test/resources/dependencyAnalysisTests/all/branches.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/branches.vpr @@ -7,11 +7,11 @@ method branches1(a: Int, b: Int) @dependency() assume b > 4 - @obsolete() + @irrelevant() assume a < 100 - @obsolete() + @irrelevant() assume b < 50 - @obsolete() + @irrelevant() assume c ==> a > 5 if(c){ @@ -32,16 +32,16 @@ method branches2(a: Int, b: Int) @dependency() assume 0 < a - @obsolete() + @irrelevant() assume a < 100 - @obsolete() + @irrelevant() assume a < b - @obsolete() + @irrelevant() assume b < 50 var x: Int if(a >= n){ - @obsolete() + @irrelevant() x := a + b }else{ @dependency() @@ -55,13 +55,13 @@ method branches3(a: Int, b: Int) { var n:Int, c: Bool - @obsolete() + @irrelevant() assume 0 < a - @obsolete() + @irrelevant() assume a < 100 @dependency() assume 0 < b - @obsolete() + @irrelevant() assume b < 50 if(c){ @@ -88,15 +88,15 @@ method nestedBranches1(a: Int, b: Int) { var n:Int, c: Bool - @obsolete() + @irrelevant() assume 0 < a - @obsolete() + @irrelevant() assume a < 100 @dependency() assume 0 < b - @obsolete() + @irrelevant() assume b < 50 - @obsolete() + @irrelevant() assume c ==> a > 5 if(c){ diff --git a/src/test/resources/dependencyAnalysisTests/all/divBy0.vpr b/src/test/resources/dependencyAnalysisTests/all/divBy0.vpr index 849fbd436..5fdfe2ab5 100644 --- a/src/test/resources/dependencyAnalysisTests/all/divBy0.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/divBy0.vpr @@ -23,7 +23,7 @@ method basic() { var x: Int var y: Int - @obsolete() + @irrelevant() assume x > 0 @dependency() assume y > 0 diff --git a/src/test/resources/dependencyAnalysisTests/all/gaussian.vpr b/src/test/resources/dependencyAnalysisTests/all/gaussian.vpr index d60058439..f4499684e 100644 --- a/src/test/resources/dependencyAnalysisTests/all/gaussian.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/gaussian.vpr @@ -2,13 +2,13 @@ field f: Int method gaussianSimple(n: Int) returns (res: Int) requires 0 <= n - requires @obsolete()(n <= 5) + requires @irrelevant()(n <= 5) { res := 0 var i: Int := 0 while(@dependency()(i <= n)) invariant @dependency()(i <= (n + 1)) - invariant @obsolete()(i <= 6) + invariant @irrelevant()(i <= 6) invariant @dependency()(res == (i - 1) * i / 2) { @dependency() diff --git a/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr b/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr index 366d20410..61bee32fe 100644 --- a/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr @@ -3,11 +3,11 @@ field f: Int // test fails method permTest(a: Ref, b: Ref, n: Int) requires @dependency()(acc(a.f)) - requires @obsolete()(acc(b.f)) && @obsolete()(b.f > 0) + requires @irrelevant()(acc(b.f)) && @irrelevant()(b.f > 0) { @dependency() assume n > 0 - @obsolete() + @irrelevant() a.f := b.f + 2 @dependency() a.f := n @@ -18,11 +18,11 @@ method permTest(a: Ref, b: Ref, n: Int) method quantifiedPerm2(xs: Seq[Ref], ys: Seq[Ref]) requires @dependency()(|xs| > 5) - requires @obsolete()(|ys| > 3) + requires @irrelevant()(|ys| > 3) { @dependency() inhale (forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0)) - @obsolete() + @irrelevant() inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard) && y.f > 0) @testAssertion() @@ -31,11 +31,11 @@ method quantifiedPerm2(xs: Seq[Ref], ys: Seq[Ref]) method quantifiedPerm3(xs: Seq[Ref], ys: Seq[Ref]) requires @dependency()(|xs| > 5) - requires @obsolete()(|ys| > 3) + requires @irrelevant()(|ys| > 3) { @dependency() inhale (forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0)) - @obsolete() + @irrelevant() inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard)) @testAssertion() @@ -44,11 +44,11 @@ method quantifiedPerm3(xs: Seq[Ref], ys: Seq[Ref]) method quantifiedPerm4(xs: Seq[Ref], ys: Seq[Ref]) requires @dependency()(|xs| > 5) - requires @obsolete()(|ys| > 3) + requires @irrelevant()(|ys| > 3) { @dependency() inhale (forall x: Ref :: x in xs ==> acc(x.f) && x.f > 0) - @obsolete() + @irrelevant() inhale forall y: Ref :: y in ys ==> acc(y.f) @testAssertion() diff --git a/src/test/resources/dependencyAnalysisTests/all/infeasible.vpr b/src/test/resources/dependencyAnalysisTests/all/infeasible.vpr index 3dab3dba9..e5b6eb718 100644 --- a/src/test/resources/dependencyAnalysisTests/all/infeasible.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/infeasible.vpr @@ -14,7 +14,7 @@ method infeasibleBranch2(a: Int, b: Int) { var res: Int if(@dependency()(a < 0)){ - @obsolete() + @irrelevant() assume res < 0 }else{ @dependency() @@ -41,7 +41,7 @@ method infeasibleBranchNoPerm(a: Int, b: Ref) { var res: Int if(a < 0){ - //@obsolete() TODO ake: nodes missing (infeasible path) + //@irrelevant() TODO ake: nodes missing (infeasible path) res := b.f + 1 //@testAssertion() assert false diff --git a/src/test/resources/dependencyAnalysisTests/all/list.vpr b/src/test/resources/dependencyAnalysisTests/all/list.vpr index 4b375c797..70578c9e1 100644 --- a/src/test/resources/dependencyAnalysisTests/all/list.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/list.vpr @@ -14,13 +14,13 @@ function listLength(l:Ref) : Int } method appendList1(this: Ref, e: Int) - requires @obsolete()(list(this)) - requires @obsolete()(0 <= e && e < 100) + requires @irrelevant()(list(this)) + requires @irrelevant()(0 <= e && e < 100) ensures list(this) { - @obsolete() + @irrelevant() unfold list(this) - @obsolete() + @irrelevant() assume 0 <= this.elem && this.elem < 100 if (this.next == null) { // TODO ake: node does not exist? @@ -28,11 +28,11 @@ method appendList1(this: Ref, e: Int) @dependency() n := new(elem, next) - @obsolete() + @irrelevant() n.elem := e @dependency() n.next := null - @obsolete() + @irrelevant() this.next := n @testAssertion() fold list(n) @@ -50,21 +50,21 @@ method appendList2(this: Ref, e: Int) { @dependency() unfold list(this) - @obsolete() + @irrelevant() assume 0 <= this.elem && this.elem < 100 if (this.next == null) { // TODO ake: node does not exist var n: Ref - @obsolete() + @irrelevant() n := new(elem, next) - @obsolete() + @irrelevant() n.elem := e - @obsolete() + @irrelevant() n.next := null - @obsolete() + @irrelevant() this.next := n - @obsolete() + @irrelevant() fold list(n) } else { @testAssertion() @@ -81,7 +81,7 @@ method appendListFull(this: Ref, e: Int) { @dependency() unfold list(this) - @obsolete() + @irrelevant() assume 0 <= this.elem && this.elem < 100 if (this.next == null) { @@ -89,7 +89,7 @@ method appendListFull(this: Ref, e: Int) @dependency() n := new(elem, next) - @obsolete() + @irrelevant() n.elem := e @dependency() n.next := null diff --git a/src/test/resources/dependencyAnalysisTests/all/method-sum.vpr b/src/test/resources/dependencyAnalysisTests/all/method-sum.vpr index 64d89e192..9185796d1 100644 --- a/src/test/resources/dependencyAnalysisTests/all/method-sum.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/method-sum.vpr @@ -34,15 +34,15 @@ method sumClient(x: Int, y: Int) method sumClient2(x: Int, y: Int) { - @obsolete() + @irrelevant() assume x >= 0 @dependency() assume y >= 0 - @obsolete() + @irrelevant() assume x < y - @obsolete() + @irrelevant() var n: Int := sum(x, x) - @obsolete() + @irrelevant() assert n >= 100 @dependency() diff --git a/src/test/resources/dependencyAnalysisTests/all/presentation-examples.vpr b/src/test/resources/dependencyAnalysisTests/all/presentation-examples.vpr index c5d2b649a..8cdaba977 100644 --- a/src/test/resources/dependencyAnalysisTests/all/presentation-examples.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/presentation-examples.vpr @@ -8,16 +8,16 @@ method foo(x: Ref, y: Ref) inhale acc(x.f) @dependency() inhale acc(y.f) - @obsolete() + @irrelevant() inhale acc(y.g) @dependency() assume 0 < x.f - @obsolete() + @irrelevant() assume x.f < 100 - @obsolete() + @irrelevant() assume y.f < 100 - @obsolete() + @irrelevant() assume 0 <= y.f @dependency() assume x.f < y.f diff --git a/src/test/resources/dependencyAnalysisTests/all/quantified-perm.vpr b/src/test/resources/dependencyAnalysisTests/all/quantified-perm.vpr index 3e03293fd..625570444 100644 --- a/src/test/resources/dependencyAnalysisTests/all/quantified-perm.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/quantified-perm.vpr @@ -5,7 +5,7 @@ field second : Ref method quantifiedWritePerm(nodes: Set[Ref], x: Ref) - requires @obsolete()(forall n:Ref :: { n.first } n in nodes ==> + requires @irrelevant()(forall n:Ref :: { n.first } n in nodes ==> acc(n.first) && (n.first != null ==> n.first in nodes)) requires @dependency()(forall n:Ref :: { n.second } n in nodes ==> diff --git a/src/test/resources/dependencyAnalysisTests/all/sum-loops.vpr b/src/test/resources/dependencyAnalysisTests/all/sum-loops.vpr index 114faca4b..809bc691e 100644 --- a/src/test/resources/dependencyAnalysisTests/all/sum-loops.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/sum-loops.vpr @@ -52,22 +52,22 @@ method sumPerm1(n: Int, res: Ref) method sumPerm2(n: Int, res: Ref) requires @dependency()(acc(res.f)) - requires @obsolete()(0 <= n) + requires @irrelevant()(0 <= n) ensures acc(res.f) { - @obsolete() + @irrelevant() res.f := 0 var i: Int - @obsolete() + @irrelevant() i := 0; - while(@obsolete()(i <= n)) + while(@irrelevant()(i <= n)) invariant @dependency()(acc(res.f)) - invariant @obsolete()(i <= (n + 1)) - invariant @obsolete()(res.f == (i - 1) * i / 2) + invariant @irrelevant()(i <= (n + 1)) + invariant @irrelevant()(res.f == (i - 1) * i / 2) { - @obsolete() + @irrelevant() res.f := res.f + i - @obsolete() + @irrelevant() i := i + 1 } diff --git a/src/test/resources/dependencyAnalysisTests/all/tuples.vpr b/src/test/resources/dependencyAnalysisTests/all/tuples.vpr index 5bb81dbad..441262f77 100644 --- a/src/test/resources/dependencyAnalysisTests/all/tuples.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/tuples.vpr @@ -11,9 +11,9 @@ method setTuple(this: Ref, l: Int, r: Int) { @dependency() unfold tuple(this) - @obsolete() + @irrelevant() this.left := l - @obsolete() + @irrelevant() this.right := r @testAssertion() fold tuple(this) @@ -25,7 +25,7 @@ method addTuple(this: Ref) returns (sum: Int) { @dependency() unfold acc(tuple(this), 1/2) - @obsolete() + @irrelevant() sum := this.left + this.right @testAssertion() fold acc(tuple(this), 1/2) diff --git a/src/test/resources/dependencyAnalysisTests/all/wands.vpr b/src/test/resources/dependencyAnalysisTests/all/wands.vpr index 8c9d258c8..46efcca63 100644 --- a/src/test/resources/dependencyAnalysisTests/all/wands.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/wands.vpr @@ -29,7 +29,7 @@ method appendit_wand1(l1 : Ref, l2: Ref) @testAssertion() fold list(l1) } else { - @obsolete() + @irrelevant() assume false } } @@ -44,9 +44,9 @@ method appendit_wand1(l1 : Ref, l2: Ref) // @dependency() // unfold list(l1) // if(l1.next == null) { // easy case -// @obsolete() +// @irrelevant() // l1.next := l2 -// @obsolete() +// @irrelevant() // fold list(l1) // } else { // var tmp : Ref := l1.next diff --git a/src/test/resources/dependencyAnalysisTests/generated/test-templates.vpr b/src/test/resources/dependencyAnalysisTests/generated/test-templates.vpr index f88fc5f25..04c8265be 100644 --- a/src/test/resources/dependencyAnalysisTests/generated/test-templates.vpr +++ b/src/test/resources/dependencyAnalysisTests/generated/test-templates.vpr @@ -20,14 +20,14 @@ method dummyTest() method basic(a: Int) { var b: Int - @obsolete() + @irrelevant() assume a > 0 ##INIT## ##ASSUMPTIONS## - @obsolete() + @irrelevant() b := a * 2 ##BODY## @@ -39,18 +39,18 @@ method basic(a: Int) method basicBranch(a: Int) { var b: Int - @obsolete() + @irrelevant() assume a > 0 ##INIT## if(a >= 0){ ##ASSUMPTIONS## - @obsolete() + @irrelevant() b := a * 2 }else{ // infeasible - @obsolete() + @irrelevant() b := a + 1 } diff --git a/src/test/resources/dependencyAnalysisTests/new/meeting.vpr b/src/test/resources/dependencyAnalysisTests/new/meeting.vpr index f6f6b86d3..33d4b0094 100644 --- a/src/test/resources/dependencyAnalysisTests/new/meeting.vpr +++ b/src/test/resources/dependencyAnalysisTests/new/meeting.vpr @@ -13,9 +13,9 @@ method infeasible1(x: Ref) method infeasible2(x: Ref) - requires @obsolete()(x != null ==> acc(x.f)) // unexpected dependency iff infeasible branches are always executed + requires @irrelevant()(x != null ==> acc(x.f)) // unexpected dependency iff infeasible branches are always executed { - if(@obsolete()(x == null)){ // unexpected dependency iff infeasible branches are always executed + if(@irrelevant()(x == null)){ // unexpected dependency iff infeasible branches are always executed var a: Int @dependency() a := 0 @@ -26,12 +26,12 @@ method infeasible2(x: Ref) method infeasible3(x: Ref) { - if(@obsolete()(x != null)){ // unexpected dependency iff infeasible branches are always executed - @obsolete() + if(@irrelevant()(x != null)){ // unexpected dependency iff infeasible branches are always executed + @irrelevant() inhale acc(x.f) } - if(@obsolete()(x == null)){ // unexpected dependency iff infeasible branches are always executed + if(@irrelevant()(x == null)){ // unexpected dependency iff infeasible branches are always executed var a: Int @dependency() a := 0 diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/branches.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/branches.vpr index e03f71342..e92ca3175 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/branches.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/branches.vpr @@ -17,7 +17,7 @@ method branch1(){ method branch2(){ var x: Int, y: Int - if(@obsolete()(x > 0)){ + if(@irrelevant()(x > 0)){ @dependency() assume y > 0 }else{ @@ -36,7 +36,7 @@ method branch3(){ @dependency() y := x + 1 }else{ - @obsolete() + @irrelevant() assume y >= 10 } @@ -48,7 +48,7 @@ method branch4(){ var x: Int, y: Int if(@dependency()(x > 0)){ - @obsolete() + @irrelevant() y := x + 1 }else{ @dependency() @@ -63,8 +63,8 @@ method branch5(){ @dependency() assume y > 0 - if(@obsolete()(x > 0)){ - @obsolete() + if(@irrelevant()(x > 0)){ + @irrelevant() y := x + 1 }else{ @testAssertion() diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/loops.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/loops.vpr index aaa7b042b..f164c36c5 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/loops.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/loops.vpr @@ -5,8 +5,8 @@ method loop1(){ res := 0 i := 10 while(i > 0) - invariant @obsolete()(i <= 10) - invariant @obsolete()(i >= 0) + invariant @irrelevant()(i <= 10) + invariant @irrelevant()(i >= 0) invariant @dependency()(res >= 0) { @dependency() @@ -24,11 +24,11 @@ method loop2(){ res := 0 i := 10 while(@dependency()(i > 0)) - invariant @obsolete()(i <= 10) - invariant @obsolete()(i >= 0) - invariant @obsolete()(res >= 0) + invariant @irrelevant()(i <= 10) + invariant @irrelevant()(i >= 0) + invariant @irrelevant()(res >= 0) { - @obsolete() + @irrelevant() res := res + i @dependency() i := i - 1 @@ -43,13 +43,13 @@ method loop3(){ res := 0 i := 10 while(@dependency()(i > 0)) - invariant @obsolete()(i <= 10) - invariant @obsolete()(i >= 0) + invariant @irrelevant()(i <= 10) + invariant @irrelevant()(i >= 0) invariant @dependency()(res >= 0) { @dependency() res := res + i - @obsolete() + @irrelevant() i := i - 1 @testAssertion() assert res > 0 @@ -61,13 +61,13 @@ method loop4(){ var res: Int res := 0 i := 10 - while(@obsolete()(i > 0)) - invariant @obsolete()(i <= 10) - invariant @obsolete()(i >= 0) + while(@irrelevant()(i > 0)) + invariant @irrelevant()(i <= 10) + invariant @irrelevant()(i >= 0) invariant @dependency()(res >= 0) { res := res - 1 - @obsolete() + @irrelevant() i := i - 1 @dependency() assume res > 0 diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/method-call.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/method-call.vpr index 78f27f7a7..97f59110b 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/method-call.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/method-call.vpr @@ -59,13 +59,13 @@ method call4(x: Int, y: Int, z: Int) @dependency() assume y > 0 - @obsolete() + @irrelevant() assume z > 0 var n: Int, n2: Int @dependency() n := sum(x, y) - @obsolete() + @irrelevant() n2 := sum(x, z) @testAssertion() diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/misc.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/misc.vpr index 936c2a0af..e39e1ca87 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/misc.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/misc.vpr @@ -3,7 +3,7 @@ field f: Int method divBy0(a: Ref, n: Int) requires @dependency()(n > 0) requires @dependency()(acc(a.f)) - requires @obsolete()(a.f > 0) + requires @irrelevant()(a.f > 0) { var res: Int @testAssertion() diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr index 02dd91a5f..79a8e74c9 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr @@ -41,7 +41,7 @@ method perm5(){ var x: Ref @dependency() inhale acc(x.f) - @obsolete() + @irrelevant() x.f := x.f + 1 @testAssertion() exhale acc(x.f) @@ -55,7 +55,7 @@ method perm6(){ inhale x.f > 0 @dependency() x.f := x.f + 1 - // @obsolete() // TODO ake + // @irrelevant() // TODO ake exhale acc(x.f, 1/2) @testAssertion() assert x.f > 1 @@ -88,7 +88,7 @@ method permAmount2(x: Ref, p: Perm) method noAlias(a: Ref, b: Ref, c: Ref) requires @dependency()(acc(a.f)) requires @dependency()(acc(b.f, 1/2)) - requires @obsolete()(acc(c.f, 1/2)) + requires @irrelevant()(acc(c.f, 1/2)) { @testAssertion() assert a != b diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr index 69d38251d..2eecc2134 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr @@ -45,7 +45,7 @@ method quantifiedExhalePartially(xs: Seq[Ref]) { } method quantifiedExhaleFully(xs: Seq[Ref]) { - assume @obsolete()(|xs| > 5) + assume @irrelevant()(|xs| > 5) inhale @dependency()(forall x: Ref :: x in xs ==> acc(x.f)) @testAssertion() @@ -81,7 +81,7 @@ method quantifiedPermWrite3(xs: Seq[Ref]) { @dependency() inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) - @obsolete() + @irrelevant() xs[0].f := 0 @testAssertion() diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index bee7a6ed7..58122e8c1 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -29,8 +29,9 @@ class AssumptionAnalysisTests extends AnyFunSuite { val CHECK_PRECISION = true - val obsoleteKeyword = "obsolete" + val obsoleteKeyword = "irrelevant" val dependencyKeyword = "dependency" + val testAssertionKeyword = "testAssertion" val GENERATE_TESTS = false if(GENERATE_TESTS) @@ -128,11 +129,13 @@ class AssumptionAnalysisTests extends AnyFunSuite { }, s"Verification failed for ${filePrefix + fileName + ".vpr"}") val assumptionGraphsReal = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].assumptionGraphs - val stmtsWithAssumptionAnnotation: Set[Infoed] = extractAnnotatedAssumptionStmts(program) + val stmtsWithAssumptionAnnotation: Set[Infoed] = extractAnnotatedStmts(program, { annotationInfo => annotationInfo.values.contains(obsoleteKeyword) || annotationInfo.values.contains(dependencyKeyword)}) + val stmtsWithAssertionAnnotation: Set[Infoed] = extractAnnotatedStmts(program, { annotationInfo => annotationInfo.values.contains(testAssertionKeyword)}) val allAssumptionNodes = assumptionGraphsReal.flatMap(_.nodes.filter(_.isInstanceOf[GeneralAssumptionNode])) - // TODO ake: warning if testAssertion is missing var errorMsgs = stmtsWithAssumptionAnnotation.map(checkNodeExists(allAssumptionNodes, _)).filter(_.isDefined).map(_.get).toSeq + if(stmtsWithAssertionAnnotation.isEmpty) + errorMsgs ++= "Missing test assertion." errorMsgs ++= assumptionGraphsReal flatMap checkDependencies val warnMsgs = assumptionGraphsReal flatMap checkNonDependencies if(CHECK_PRECISION) @@ -143,13 +146,13 @@ class AssumptionAnalysisTests extends AnyFunSuite { assert(check, "\n" + errorMsgs.mkString("\n")) } - private def extractAnnotatedAssumptionStmts(program: Program): Set[Infoed] = { + private def extractAnnotatedStmts(program: Program, annotationFiler: (AnnotationInfo => Boolean)): Set[Infoed] = { var nodesWithAnnotation: Set[Infoed] = Set.empty val newP: Program = ViperStrategy.Slim({ case s: Seqn => s case n: Infoed => val annotationInfo = n.info.getUniqueInfo[AnnotationInfo] - .filter(ai => ai.values.contains(obsoleteKeyword) || ai.values.contains(dependencyKeyword)) + .filter(annotationFiler) if (annotationInfo.isDefined) nodesWithAnnotation += n n @@ -207,7 +210,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { def extractTestAssertionNodesFromGraph(graph: AssumptionAnalysisGraph): Seq[AssumptionAnalysisNode] = { graph.nodes.filter(node => (node.getNodeType.equals("Assertion") || node.getNodeType.equals("Exhale") || node.getNodeType.equals("Check")) && - node.sourceInfo.toString.contains("@testAssertion()") + node.sourceInfo.toString.contains("@" + testAssertionKeyword + "()") ).toSeq } From 75d118b03fd4c138f76092badf2cfdb73084065d Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 26 Jun 2025 12:13:37 +0200 Subject: [PATCH 117/474] add check for existence of testAssertion --- .../AssumptionAnalyzer.scala | 1 - .../DependencyAnalysisReporter.scala | 2 +- src/main/scala/rules/Executor.scala | 2 +- .../scala/verifier/DefaultMainVerifier.scala | 2 +- .../unitTests/misc.vpr | 9 +++++ src/test/scala/AssumptionAnalysisTests.scala | 35 +++++++++++-------- 6 files changed, 33 insertions(+), 18 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index c5525bf0c..f3321849d 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -200,7 +200,6 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { override def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit = { val assumptionLabels = dep.replace("(", "").replace(")", "").split(" ") - if(assumptionLabels.size < 2) return val assumptionIds = assumptionLabels.filter(AssumptionAnalyzer.isAssumptionLabel).map(AssumptionAnalyzer.getIdFromLabel) val assertionIdsFromUnsatCore = assumptionLabels.filter(AssumptionAnalyzer.isAssertionLabel).map(AssumptionAnalyzer.getIdFromLabel) val assertionIdFromLabel = AssumptionAnalyzer.getIdFromLabel(assertionLabel) diff --git a/src/main/scala/assumptionAnalysis/DependencyAnalysisReporter.scala b/src/main/scala/assumptionAnalysis/DependencyAnalysisReporter.scala index 3e307925b..dcfe41dcf 100644 --- a/src/main/scala/assumptionAnalysis/DependencyAnalysisReporter.scala +++ b/src/main/scala/assumptionAnalysis/DependencyAnalysisReporter.scala @@ -4,7 +4,7 @@ import viper.silicon.assumptionAnalysis.AssumptionAnalysisGraph import viper.silver.reporter.{Message, Reporter} case class DependencyAnalysisReporter(name: String = "dependencyAnalysis_reporter", path: String = "report.csv") extends Reporter { - var assumptionGraphs: List[AssumptionAnalysisGraph] = List.empty + var assumptionAnalyzers: List[AssumptionAnalyzer] = List.empty override def report(msg: Message): Unit = {} } diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index dc64e3a61..96dcbcb87 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -501,7 +501,7 @@ object executor extends ExecutionRules { case inhale @ ast.Inhale(a) => a match { - case _: ast.FalseLit => + case _: ast.FalseLit if !Verifier.config.enableAssumptionAnalysis() => /* We're done */ Success() case _ => diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index c49da1a69..cde54b8e9 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -318,7 +318,7 @@ class DefaultMainVerifier(config: Config, assumptionAnalyzers.foreach(_.assumptionGraph.addTransitiveEdges()) assumptionAnalyzers foreach (_.exportGraph()) if(reporter.isInstanceOf[DependencyAnalysisReporter]) - reporter.asInstanceOf[DependencyAnalysisReporter].assumptionGraphs = assumptionAnalyzers.map(_.assumptionGraph) + reporter.asInstanceOf[DependencyAnalysisReporter].assumptionAnalyzers = assumptionAnalyzers logger debug s"assumption analyzers ${assumptionAnalyzers.mkString(", ")}" } diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/misc.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/misc.vpr index e39e1ca87..0eeb61124 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/misc.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/misc.vpr @@ -9,3 +9,12 @@ method divBy0(a: Ref, n: Int) @testAssertion() res := a.f / n } + +method assumeFalse() +{ + var a: Int + @dependency() + assume false + @testAssertion() + assert a == 2 +} \ No newline at end of file diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index 58122e8c1..4f9322436 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -1,9 +1,9 @@ import org.scalatest.funsuite.AnyFunSuite import viper.silicon.SiliconFrontend -import viper.silicon.assumptionAnalysis.{AssumptionAnalysisGraph, AssumptionAnalysisNode, AssumptionType, DependencyAnalysisReporter, GeneralAssumptionNode} -import viper.silver.ast.{AnnotationInfo, HasLineColumn, Infoed, NoPosition, Node, Position, Positioned, Program, Seqn, VirtualPosition} +import viper.silicon.assumptionAnalysis._ import viper.silver.ast.utility.{DiskLoader, ViperStrategy} +import viper.silver.ast._ import viper.silver.frontend.SilFrontend import viper.silver.verifier @@ -122,22 +122,20 @@ class AssumptionAnalysisTests extends AnyFunSuite { val program: Program = tests.loadProgram(filePrefix, fileName, frontend) val result = frontend.verifier.verify(program) + result match { + case verifier.Failure(failure) => assert(false, s"Verification failed for ${filePrefix + fileName + ".vpr"}: ${failure.mkString(",")}") + case _ => + } - assert(result match { - case verifier.Success => true - case verifier.Failure(_) => false - }, s"Verification failed for ${filePrefix + fileName + ".vpr"}") - - val assumptionGraphsReal = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].assumptionGraphs + val assumptionAnalyzers = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].assumptionAnalyzers + val assumptionGraphs = assumptionAnalyzers map (_.assumptionGraph) val stmtsWithAssumptionAnnotation: Set[Infoed] = extractAnnotatedStmts(program, { annotationInfo => annotationInfo.values.contains(obsoleteKeyword) || annotationInfo.values.contains(dependencyKeyword)}) - val stmtsWithAssertionAnnotation: Set[Infoed] = extractAnnotatedStmts(program, { annotationInfo => annotationInfo.values.contains(testAssertionKeyword)}) - val allAssumptionNodes = assumptionGraphsReal.flatMap(_.nodes.filter(_.isInstanceOf[GeneralAssumptionNode])) + val allAssumptionNodes = assumptionGraphs.flatMap(_.nodes.filter(_.isInstanceOf[GeneralAssumptionNode])) var errorMsgs = stmtsWithAssumptionAnnotation.map(checkNodeExists(allAssumptionNodes, _)).filter(_.isDefined).map(_.get).toSeq - if(stmtsWithAssertionAnnotation.isEmpty) - errorMsgs ++= "Missing test assertion." - errorMsgs ++= assumptionGraphsReal flatMap checkDependencies - val warnMsgs = assumptionGraphsReal flatMap checkNonDependencies + errorMsgs ++= assumptionAnalyzers flatMap checkTestAssertionNodeExists + errorMsgs ++= assumptionGraphs flatMap checkDependencies + val warnMsgs = assumptionGraphs flatMap checkNonDependencies if(CHECK_PRECISION) errorMsgs ++= warnMsgs else if(warnMsgs.nonEmpty) println(warnMsgs.mkString("\n")) // TODO ake: warning @@ -179,6 +177,15 @@ class AssumptionAnalysisTests extends AnyFunSuite { } } + private def checkTestAssertionNodeExists(assumptionAnalyzer: AssumptionAnalyzer): Seq[String] = { + val assumptionNodes = extractTestAssumptionNodesFromGraph(assumptionAnalyzer.assumptionGraph) ++ extractTestObsoleteAssumptionNodesFromGraph(assumptionAnalyzer.assumptionGraph) + val assertionNodes = extractTestAssertionNodesFromGraph(assumptionAnalyzer.assumptionGraph) + if(assumptionNodes.nonEmpty && assertionNodes.isEmpty) + Seq(s"Missing testAssertion for member: ${assumptionAnalyzer.getMember.map(_.name).getOrElse("unknown")}") + else + Seq.empty + } + def checkDependencies(assumptionGraph: AssumptionAnalysisGraph): Seq[String] = { val assumptionNodes = extractTestAssumptionNodesFromGraph(assumptionGraph) val assumptionsPerSource = assumptionNodes groupBy(n => extractSourceLine(n.sourceInfo.getPosition)) From 99abe80276abf596c2b6a612253d868c54de878d Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 28 Jun 2025 09:34:49 +0200 Subject: [PATCH 118/474] fix pipeline --- src/main/scala/Config.scala | 20 +++++++++++-------- .../AssumptionAnalyzer.scala | 5 ++++- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/main/scala/Config.scala b/src/main/scala/Config.scala index 4a0d64ad8..75d7d47dd 100644 --- a/src/main/scala/Config.scala +++ b/src/main/scala/Config.scala @@ -836,6 +836,12 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { noshort = true ) + val assumptionAnalysisExportPath: ScallopOption[String] = opt[String]("assumptionAnalysisExportPath", + descr = "Path to the directory where the assumption analysis graphs should be exported to", + default = None, + noshort = true + ) + /* Option validation (trailing file argument is validated by parent class) */ validateOpt(prover) { @@ -892,14 +898,12 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { sys.error(s"Unexpected combination: $other") } -// validateOpt(enableAssumptionAnalysis, enableDebugging) { -// case (Some(false), _) => Right(()) -// case (Some(true), Some(true)) => Right(()) -// case (Some(true), Some(false)) => -// Left(s"Option ${enableAssumptionAnalysis.name} requires option ${enableDebugging.name}") -// case other => -// sys.error(s"Unexpected combination: $other") -// } + validateOpt(assumptionAnalysisExportPath, enableAssumptionAnalysis) { + case (None, _) => Right(()) + case (Some(_), Some(true)) => Right(()) + case (Some(_), Some(false)) => + Left(s"Option ${assumptionAnalysisExportPath.name} requires option ${enableAssumptionAnalysis.name}") + } validateOpt(startDebuggerAutomatically, enableDebugging) { case (Some(false), _) => Right(()) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index f3321849d..438c84265 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -5,6 +5,7 @@ import viper.silicon.debugger.DebugExp import viper.silicon.decider.Decider import viper.silicon.interfaces.state.Chunk import viper.silicon.state.terms.{False, Implies, Ite, NoPerm, Term, True} +import viper.silicon.verifier.Verifier import viper.silver.ast @@ -272,6 +273,8 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { override def getMember: Option[ast.Member] = Some(member) override def exportGraph(): Unit = { + if(Verifier.config.assumptionAnalysisExportPath.isEmpty) return + val foldername: Option[String] = getMember map { case ast.Method(name, _, _, _, _, _) => name case ast.Function(name, _, _, _, _, _) => name @@ -280,7 +283,7 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { case location: ast.Location => location.pos.toString case member: ast.ExtensionMember => member.pos.toString } - assumptionGraph.exportGraph("graphExports/" + foldername.getOrElse("latestExport")) + assumptionGraph.exportGraph(Verifier.config.assumptionAnalysisExportPath() + "/" + foldername.getOrElse("latestExport")) } override def createLabelledConditionalChunks(decider: Decider, sourceChunks: Iterable[Chunk], thenTerm: Term, elseTerm: Term): Term = { From bcf912363dd912afa01eb51472a11100cc37aec7 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 28 Jun 2025 09:52:59 +0200 Subject: [PATCH 119/474] ignore failing tests for now --- .../resources/dependencyAnalysisTests/all/imprecision.vpr | 4 ++-- src/test/scala/AssumptionAnalysisTests.scala | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr b/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr index 61bee32fe..410b6c953 100644 --- a/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr @@ -22,7 +22,7 @@ method quantifiedPerm2(xs: Seq[Ref], ys: Seq[Ref]) { @dependency() inhale (forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0)) - @irrelevant() + //@irrelevant() // TODO ake inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard) && y.f > 0) @testAssertion() @@ -35,7 +35,7 @@ method quantifiedPerm3(xs: Seq[Ref], ys: Seq[Ref]) { @dependency() inhale (forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0)) - @irrelevant() + // @irrelevant() // TODO ake inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard)) @testAssertion() diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index 4f9322436..dd3b7bf7f 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -28,6 +28,7 @@ import scala.util.{Failure, Success} class AssumptionAnalysisTests extends AnyFunSuite { val CHECK_PRECISION = true + val ignores: Seq[String] = Seq("infeasible") val obsoleteKeyword = "irrelevant" val dependencyKeyword = "dependency" @@ -62,9 +63,10 @@ class AssumptionAnalysisTests extends AnyFunSuite { if Files.isReadable(filePath) if !Files.isDirectory(filePath)){ val fileName = filePath.getFileName.toString.replace(".vpr", "") - test(dirName + "/" + fileName){ - executeTest(dirName + "/", fileName, frontend) - } + if(!ignores.contains(fileName)) + test(dirName + "/" + fileName){ + executeTest(dirName + "/", fileName, frontend) + } } } From a2aa03fc99ba858152517b69dd4a27c2bcbaa29c Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 28 Jun 2025 14:52:08 +0200 Subject: [PATCH 120/474] fix assumption type for method and function postconditions --- src/main/scala/rules/Evaluator.scala | 6 ++++-- src/main/scala/rules/Executor.scala | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 674ac5394..fad57827d 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -7,7 +7,7 @@ package viper.silicon.rules import viper.silicon.Config.JoinMode -import viper.silicon.assumptionAnalysis.{AssumptionType, ExpAnalysisSourceInfo} +import viper.silicon.assumptionAnalysis.{AssumptionAnalyzer, AssumptionType, ExpAnalysisSourceInfo} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.interfaces._ @@ -864,6 +864,8 @@ object evaluator extends EvaluationRules { case fapp @ ast.FuncApp(funcName, eArgs) => val func = s.program.findFunction(funcName) + val funcAssumptionType = if(func.body.isDefined || !AssumptionAnalyzer.extractEnableAnalysisFromInfo(func.info).getOrElse(true)) AssumptionType.Implicit else AssumptionType.Explicit + val assumptionType = AssumptionAnalyzer.extractAssumptionTypeFromInfo(fapp.info).getOrElse(funcAssumptionType) evals2(s, eArgs, Nil, _ => pve, v)((s1, tArgs, eArgsNew, v1) => { // bookkeeper.functionApplications += 1 val joinFunctionArgs = tArgs //++ c2a.quantifiedVariables.filterNot(tArgs.contains) @@ -943,7 +945,7 @@ object evaluator extends EvaluationRules { val preExp = Option.when(withExp)({ DebugExp.createInstance(Some(s"precondition of ${func.name}(${eArgsNew.get.mkString(", ")}) holds"), None, None, InsertionOrderedSet.empty) }) - v3.decider.assume(preFApp, preExp, AssumptionType.Explicit) // TODO ake: assumption Type? + v3.decider.assume(preFApp, preExp, assumptionType) val funcAnn = func.info.getUniqueInfo[AnnotationInfo] val tFApp = funcAnn match { case Some(a) if a.values.contains("opaque") => diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 96dcbcb87..3ad4409c2 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -588,7 +588,7 @@ object executor extends ExecutionRules { val pveCallTransformed = pveCall.withReasonNodeTransformed(reasonTransformer) val methodAnnotatedAssumptionType = AssumptionAnalyzer.extractAssumptionTypeFromInfo(meth.info) - val defaultAssumptionType = if(meth.body.isDefined) AssumptionType.Implicit else AssumptionType.Explicit + val defaultAssumptionType = if(meth.body.isDefined || !AssumptionAnalyzer.extractEnableAnalysisFromInfo(meth.info).getOrElse(true)) AssumptionType.Implicit else AssumptionType.Explicit val finalAssumptionType = annotatedAssumptionTypeOpt.getOrElse(methodAnnotatedAssumptionType.getOrElse(defaultAssumptionType)) val mcLog = new MethodCallRecord(call, s, v.decider.pcs) From fcdfa92ae36e708abfca3ac85a010486f2d68611 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 28 Jun 2025 14:52:44 +0200 Subject: [PATCH 121/474] fix analysis source info string --- src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala index 1a9be3857..4d07aa443 100644 --- a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala +++ b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala @@ -2,7 +2,7 @@ package viper.silicon.assumptionAnalysis import viper.silicon.verifier.Verifier import viper.silver.ast -import viper.silver.ast.{HasLineColumn, NoPosition, Position, VirtualPosition} +import viper.silver.ast._ abstract class AnalysisSourceInfo { @@ -11,6 +11,7 @@ abstract class AnalysisSourceInfo { def getStringForExport: String = { getPosition match { case NoPosition => "???" + case filePos: AbstractSourcePosition => filePos.file.getFileName.toString + " @ line " + filePos.line case column: HasLineColumn => "line " + column.line.toString case VirtualPosition(identifier) => "label " + identifier } From a2c87545625cd17067b661389afeafeede2dd4a7 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 28 Jun 2025 16:26:47 +0200 Subject: [PATCH 122/474] PoC: prettier source info strings for gobra --- src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala index 4d07aa443..fcca9a491 100644 --- a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala +++ b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala @@ -31,7 +31,8 @@ case class NoAnalysisSourceInfo() extends AnalysisSourceInfo { } case class ExpAnalysisSourceInfo(source: ast.Exp) extends AnalysisSourceInfo { - override def toString: String = source.toString + " (" + super.toString + ")" + override def toString: String = (if(source.info.getSourceString.isEmpty) source.toString else source.info.getSourceString) + + " (" + super.toString + ")" override def getPosition: Position = source.pos @@ -47,7 +48,8 @@ case class ExpAnalysisSourceInfo(source: ast.Exp) extends AnalysisSourceInfo { } case class StmtAnalysisSourceInfo(source: ast.Stmt) extends AnalysisSourceInfo { - override def toString: String = source.toString + " (" + super.toString + ")" + override def toString: String = (if(source.info.getSourceString.isEmpty) source.toString() else source.info.getSourceString) + + " (" + super.toString + ")" override def getPosition: Position = source.pos override def equals(obj: Any): Boolean = { From f9d04f7ead3f0c2e36750decb807bae84b71260a Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 28 Jun 2025 16:27:39 +0200 Subject: [PATCH 123/474] update submodules --- silver | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/silver b/silver index 3aab5b86a..77fc2f87d 160000 --- a/silver +++ b/silver @@ -1 +1 @@ -Subproject commit 3aab5b86a491a67727c023197ae9216759c94226 +Subproject commit 77fc2f87d52f1068b098b189a55b2ff1c5927066 From 1c6d73bdc2f84159ca2e14b05683a8c6191866ec Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 30 Jun 2025 11:57:10 +0200 Subject: [PATCH 124/474] add terms to assertion nodes --- .../assumptionAnalysis/AssumptionAnalysisGraph.scala | 9 ++++----- .../scala/assumptionAnalysis/AssumptionAnalyzer.scala | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index da6aa597e..b5f068a30 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -154,9 +154,8 @@ class DefaultAssumptionAnalysisGraph extends AssumptionAnalysisGraph { override def addEdges(source: Int, targets: Iterable[Int]): Unit = { val oldTargets = edges.getOrElse(source, Set.empty) - val newTargets = targets filter(t => t > source) // only forward edges - if(newTargets.nonEmpty) - edges.update(source, oldTargets ++ newTargets) + if(targets.nonEmpty) + edges.update(source, oldTargets ++ targets) } override def addEdges(sources: Iterable[Int], target: Int): Unit = { @@ -203,12 +202,12 @@ case class StringAssumptionNode(description: String, term: Term, sourceInfo: Ana override def getNodeString: String = "assume " + description } -case class SimpleAssertionNode(assertion: ast.Exp, sourceInfo: AnalysisSourceInfo, isClosed: Boolean) extends GeneralAssertionNode { +case class SimpleAssertionNode(assertion: ast.Exp, term: Term, sourceInfo: AnalysisSourceInfo, isClosed: Boolean) extends GeneralAssertionNode { val assumptionType: AssumptionType = Explicit override def getNodeString: String = "assert " + assertion.toString } -case class StringAssertionNode(description: String, sourceInfo: AnalysisSourceInfo, isClosed: Boolean) extends GeneralAssertionNode { +case class StringAssertionNode(description: String, term: Term, sourceInfo: AnalysisSourceInfo, isClosed: Boolean) extends GeneralAssertionNode { val assumptionType: AssumptionType = Explicit override def getNodeString: String = "assert " + description } diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 438c84265..ef0ea487e 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -188,8 +188,8 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { if(isCheck) return Some(SimpleCheckNode(term, analysisSourceInfo, enableCustomEdges_)) Some(assertion match { - case Left(description) => StringAssertionNode(description, analysisSourceInfo, enableCustomEdges_) - case Right(exp) => SimpleAssertionNode(exp, analysisSourceInfo, enableCustomEdges_) + case Left(description) => StringAssertionNode(description, term, analysisSourceInfo, enableCustomEdges_) + case Right(exp) => SimpleAssertionNode(exp, term, analysisSourceInfo, enableCustomEdges_) }) } From 21d05b186260da85c6be62274cd6578edbb0799d Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 30 Jun 2025 11:58:42 +0200 Subject: [PATCH 125/474] add labels to chunk permissions --- .../AssumptionAnalyzer.scala | 24 +++------ src/main/scala/decider/Decider.scala | 39 ++++++++++++++- src/main/scala/interfaces/state/Chunks.scala | 49 ++++++++----------- .../scala/rules/QuantifiedChunkSupport.scala | 4 +- src/main/scala/state/Chunks.scala | 32 ++++++------ 5 files changed, 83 insertions(+), 65 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index ef0ea487e..38c97b3c8 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -43,6 +43,7 @@ trait AssumptionAnalyzer { def addDependencyFromExhaleToInhale(newChunkNodeId: Option[Int]): Unit def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNodeId: Option[Int]): Unit def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNodeId: Chunk): Unit + def addDependency(source: Int, dest: Int): Unit def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit @@ -64,10 +65,7 @@ trait AssumptionAnalyzer { forcedDependencies = List.empty } - def wrapWithLabel(labelNode: LabelNode, term: Term): Term = term - def wrapPermissionWithLabel(labelNode: LabelNode, term: Term): Term = term def createAndAssumeLabelNode(decider: Decider, sourceNodeIds: Iterable[Int]): LabelNode = LabelNode(True) - def createLabelledConditionalChunks(decider: Decider, sourceChunks: Iterable[Chunk], thenTerm: Term, elseTerm: Term): Term = thenTerm def createLabelledConditionalChunks(decider: Decider, sourceChunks: Iterable[Chunk], thenTerm: Term): Term = thenTerm def createLabelledConditional(decider: Decider, sourceTerms: Iterable[Term], term: Term): Term = term def createLabelledConditional(decider: Decider, sourceTerms: Iterable[Term], terms: Seq[Term]): Seq[Term] = terms @@ -270,6 +268,10 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { addPermissionDependencies(oldChunks, newChunkId.headOption) } + override def addDependency(source: Int, dest: Int): Unit = { + assumptionGraph.addEdges(source, Set(dest)) + } + override def getMember: Option[ast.Member] = Some(member) override def exportGraph(): Unit = { @@ -286,12 +288,6 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { assumptionGraph.exportGraph(Verifier.config.assumptionAnalysisExportPath() + "/" + foldername.getOrElse("latestExport")) } - override def createLabelledConditionalChunks(decider: Decider, sourceChunks: Iterable[Chunk], thenTerm: Term, elseTerm: Term): Term = { - val sourceNodeIds = getChunkNodeIds(sourceChunks.toSet) - val labelNode = createAndAssumeLabelNode(decider, sourceNodeIds) - Ite(labelNode.term, thenTerm, elseTerm) - } - override def createLabelledConditionalChunks(decider: Decider, sourceChunks: Iterable[Chunk], thenTerm: Term): Term = { val sourceNodeIds = getChunkNodeIds(sourceChunks.toSet) val labelNode = createAndAssumeLabelNode(decider, sourceNodeIds) @@ -353,14 +349,6 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { override def reassumeLabels(decider: Decider): Unit = { // TODO ake: work with scopes! originalLabelNodes foreach (reassumeLabel(decider, _)) // assumes label } - - override def wrapWithLabel(labelNode: LabelNode, term: Term): Term = { - Implies(labelNode.term, term) - } - - override def wrapPermissionWithLabel(labelNode: LabelNode, term: Term): Term = { - Ite(labelNode.term, term, NoPerm) - } } class NoAssumptionAnalyzer extends AssumptionAnalyzer { @@ -391,7 +379,7 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { } override def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNodeId: Chunk): Unit = {} - + def addDependency(source: Int, dest: Int): Unit = {} override def getMember: Option[ast.Member] = None override def addSingleAssumption(assumption: DebugExp, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index d367a570a..b98aec50e 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -14,6 +14,7 @@ import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.interfaces._ import viper.silicon.interfaces.decider._ +import viper.silicon.interfaces.state.GeneralChunk import viper.silicon.logger.records.data.{DeciderAssertRecord, DeciderAssumeRecord, ProverAssertRecord} import viper.silicon.state._ import viper.silicon.state.terms.{Term, _} @@ -60,6 +61,9 @@ trait Decider { def startDebugSubExp(): Unit + def registerChunk[CH <: GeneralChunk](buildChunk: (Term => CH), perm: Term, analysisInfo: AnalysisInfo, isExhale: Boolean): CH + def registerDerivedChunk[CH <: GeneralChunk](sourceChunk: CH, buildChunk: (Term => CH), perm: Term, analysisInfo: AnalysisInfo, isExhale: Boolean, createLabel: Boolean=true): CH + def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp], assumptionType: AssumptionType): Unit def assume(t: Term, debugExp: Option[DebugExp], assumptionType: AssumptionType): Unit def assume(assumptions: Iterable[Term], debugExps: Option[Iterable[DebugExp]], description: String, enforceAssumption: Boolean, assumptionType: AssumptionType): Unit @@ -303,6 +307,39 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } } + def registerChunk[CH <: GeneralChunk](buildChunk: (Term => CH), perm: Term, analysisInfo: AnalysisInfo, isExhale: Boolean): CH = { + + val (chunk, labelNodeId) = if(isExhale) { + (buildChunk(perm), None) + }else { + val labelNode = assumptionAnalyzer.createAndAssumeLabelNode(this, Set()) + val finalPerm = Ite(labelNode.term, perm, NoPerm) + (buildChunk(finalPerm), Some(labelNode.id)) + } + + val chunkNode = assumptionAnalyzer.addPermissionNode(chunk, chunk.permExp, analysisInfo.sourceInfo, analysisInfo.assumptionType, isExhale) + if(chunkNode.isDefined && labelNodeId.isDefined) + assumptionAnalyzer.addDependency(chunkNode.get, labelNodeId.get) + chunk + } + + def registerDerivedChunk[CH <: GeneralChunk](sourceChunk: CH, buildChunk: (Term => CH), perm: Term, analysisInfo: AnalysisInfo, isExhale: Boolean, createLabel: Boolean=true): CH = { + val (newChunk, labelNodeId) = if(!createLabel) { + (buildChunk(perm), None) + }else { + val labelNode = assumptionAnalyzer.createAndAssumeLabelNode(this, Set()) + val finalPerm = Ite(labelNode.term, perm, NoPerm) + (buildChunk(finalPerm), Some(labelNode.id)) + } + + val newChunkNode = assumptionAnalyzer.addPermissionNode(newChunk, newChunk.permExp, analysisInfo.sourceInfo, analysisInfo.assumptionType, isExhale) + assumptionAnalyzer.addPermissionDependencies(Set(sourceChunk), newChunkNode) + if(newChunkNode.isDefined && labelNodeId.isDefined) + assumptionAnalyzer.addDependency(newChunkNode.get, labelNodeId.get) + newChunk + + } + def addDebugExp(e: DebugExp): Unit = { if (debugMode) { pathConditions.addDebugExp(e) @@ -438,7 +475,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => def checkSmoke(isAssert: Boolean = false): Boolean = { val label = if(Verifier.config.enableAssumptionAnalysis()){ - val nodeId = assumptionAnalyzer.addAssertFalseNode(!isAssert, analysisSourceInfoStack.getFullSourceInfo) + val nodeId = assumptionAnalyzer.addAssertFalseNode(!isAssert, analysisSourceInfoStack.getFullSourceInfo) // TODO ake: add node only if it can be verified AssumptionAnalyzer.createAssertionLabel(nodeId) }else{ "" } diff --git a/src/main/scala/interfaces/state/Chunks.scala b/src/main/scala/interfaces/state/Chunks.scala index e6dbdd84c..35993cbd8 100644 --- a/src/main/scala/interfaces/state/Chunks.scala +++ b/src/main/scala/interfaces/state/Chunks.scala @@ -32,36 +32,31 @@ trait GeneralChunk extends Chunk { object GeneralChunk { def applyCondition(chunk: GeneralChunk, newCond: Term, newCondExp: Option[ast.Exp], analysisInfo: AnalysisInfo): GeneralChunk = { - val newChunk = chunk.applyCondition(newCond, newCondExp) - val newNodeId = analysisInfo.assumptionAnalyzer.addPermissionInhaleNode(newChunk, newChunk.permExp, analysisInfo.sourceInfo, analysisInfo.assumptionType) - analysisInfo.assumptionAnalyzer.addPermissionDependencies(Set(chunk), newNodeId) - newChunk + analysisInfo.decider.registerDerivedChunk[GeneralChunk](chunk, {_ => + chunk.applyCondition(newCond, newCondExp)}, + chunk.perm, analysisInfo, isExhale=false, createLabel=false) } def permMinus(chunk: GeneralChunk, newPerm: Term, newPermExp: Option[ast.Exp], analysisInfo: AnalysisInfo, isExhale: Boolean=false): GeneralChunk = { - val newChunk = chunk.permMinus(newPerm, newPermExp) - val newNodeId = analysisInfo.assumptionAnalyzer.addPermissionNode(newChunk, - Option.when(chunk.permExp.isDefined && newPermExp.isDefined)(ast.PermSub(chunk.permExp.get, newPermExp.get)(newPermExp.get.pos, newPermExp.get.info)), - analysisInfo.sourceInfo, analysisInfo.assumptionType, isExhale) - analysisInfo.assumptionAnalyzer.addPermissionDependencies(Set(chunk), newNodeId) - val exhaledChunk = chunk.withPerm(newPerm, newPermExp) - val exhaledNodeId = analysisInfo.assumptionAnalyzer.addPermissionNode(exhaledChunk, newPermExp, analysisInfo.sourceInfo, AssumptionType.Implicit, isExhale=true) - analysisInfo.assumptionAnalyzer.addPermissionDependencies(Set(chunk), exhaledNodeId) + val newChunk = analysisInfo.decider.registerDerivedChunk[GeneralChunk](chunk, {finalPerm => + chunk.permMinus(finalPerm, newPermExp)}, + newPerm, analysisInfo, isExhale=false, createLabel=false) + val exhaledChunk = analysisInfo.decider.registerDerivedChunk[GeneralChunk](chunk, {finalPerm => + chunk.withPerm(finalPerm, newPermExp)}, + newPerm, analysisInfo, isExhale=true, createLabel=false) newChunk } def permPlus(chunk: GeneralChunk, newPerm: Term, newPermExp: Option[ast.Exp], analysisInfo: AnalysisInfo, isExhale: Boolean=false): GeneralChunk = { - val newChunk = chunk.permPlus(newPerm, newPermExp) - val newNodeId = analysisInfo.assumptionAnalyzer.addPermissionNode(newChunk, newPermExp, analysisInfo.sourceInfo, analysisInfo.assumptionType, isExhale) - analysisInfo.assumptionAnalyzer.addPermissionDependencies(Set(chunk), newNodeId) - newChunk + analysisInfo.decider.registerDerivedChunk[GeneralChunk](chunk, {finalPerm => + chunk.permPlus(finalPerm, newPermExp)}, + newPerm, analysisInfo, isExhale, createLabel=true) } def withPerm(chunk: GeneralChunk, newPerm: Term, newPermExp: Option[ast.Exp], analysisInfo: AnalysisInfo, isExhale: Boolean=false): GeneralChunk = { - val newChunk = chunk.withPerm(newPerm, newPermExp) - val newNodeId = analysisInfo.assumptionAnalyzer.addPermissionNode(newChunk, newPermExp, analysisInfo.sourceInfo, analysisInfo.assumptionType, isExhale) - analysisInfo.assumptionAnalyzer.addPermissionDependencies(Set(chunk), newNodeId) - newChunk + analysisInfo.decider.registerDerivedChunk[GeneralChunk](chunk, {finalPerm => + chunk.withPerm(finalPerm, newPermExp)}, + newPerm, analysisInfo, isExhale, createLabel=true) } } @@ -79,10 +74,9 @@ trait NonQuantifiedChunk extends GeneralChunk { object NonQuantifiedChunk { def withSnap(chunk: NonQuantifiedChunk, snap: Term, snapExp: Option[ast.Exp], analysisInfo: AnalysisInfo): NonQuantifiedChunk = { - val newChunk = chunk.withSnap(snap, snapExp) - val newNodeId = analysisInfo.assumptionAnalyzer.addPermissionInhaleNode(newChunk, newChunk.permExp, analysisInfo.sourceInfo, analysisInfo.assumptionType) - analysisInfo.assumptionAnalyzer.addPermissionDependencies(Set(chunk), newNodeId) - newChunk + analysisInfo.decider.registerDerivedChunk[NonQuantifiedChunk](chunk, {_ => + chunk.withSnap(snap, snapExp)}, + chunk.perm, analysisInfo, isExhale=false, createLabel=false) } } @@ -100,9 +94,8 @@ trait QuantifiedChunk extends GeneralChunk { object QuantifiedChunk { def withSnapshotMap(chunk: QuantifiedChunk, snap: Term, analysisInfo: AnalysisInfo): QuantifiedChunk = { - val newChunk = chunk.withSnapshotMap(snap) - val newNodeId = analysisInfo.assumptionAnalyzer.addPermissionInhaleNode(newChunk, newChunk.permExp, analysisInfo.sourceInfo, analysisInfo.assumptionType) - analysisInfo.assumptionAnalyzer.addPermissionDependencies(Set(chunk), newNodeId) - newChunk + analysisInfo.decider.registerDerivedChunk[QuantifiedChunk](chunk, {_ => + chunk.withSnapshotMap(snap)}, + chunk.perm, analysisInfo, isExhale=false, createLabel=false) } } \ No newline at end of file diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 616d88b53..519c58ac0 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -693,7 +693,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val permSummary = ResourcePermissionLookup(resource, pm, codomainQVars, s.program) - val chunkPerms = relevantChunks map (chunk => v.decider.assumptionAnalyzer.createLabelledConditionalChunks(v.decider, Set(chunk), chunk.perm, NoPerm)) + val chunkPerms = relevantChunks map (chunk => chunk.perm) val valueDefinitions = Forall( codomainQVars, @@ -837,7 +837,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { s, relevantChunks, codomainQVars, resource, optSmDomainDefinitionCondition, v) val smDef = SnapshotMapDefinition(resource, sm, valueDefs, optDomainDefinition.toSeq) - val chunkPerms = relevantChunks map (chunk => v.decider.assumptionAnalyzer.createLabelledConditionalChunks(v.decider, Set(chunk), chunk.perm, NoPerm)) + val chunkPerms = relevantChunks map (chunk => chunk.perm) val totalPermissions = BigPermSum(chunkPerms) if (Verifier.config.disableValueMapCaching()) { diff --git a/src/main/scala/state/Chunks.scala b/src/main/scala/state/Chunks.scala index 7da8623e9..e3ae34c74 100644 --- a/src/main/scala/state/Chunks.scala +++ b/src/main/scala/state/Chunks.scala @@ -42,9 +42,9 @@ object BasicChunk { snap: Term, snapExp: Option[ast.Exp], perm: Term, permExp: Option[ast.Exp], analysisInfo: AnalysisInfo, isExhale: Boolean=false): BasicChunk = { - val chunk = new BasicChunk(resourceID, id, args, argsExp, snap, snapExp, perm, permExp) - analysisInfo.assumptionAnalyzer.addPermissionNode(chunk, permExp, analysisInfo.sourceInfo, analysisInfo.assumptionType, isExhale) - chunk + analysisInfo.decider.registerChunk[BasicChunk]({finalPerm => + new BasicChunk(resourceID, id, args, argsExp, snap, snapExp, finalPerm, permExp)}, + perm, analysisInfo, isExhale) } def createDerivedChunk(oldChunks: Set[Chunk], @@ -62,7 +62,7 @@ object BasicChunk { val newNode = analysisInfo.assumptionAnalyzer.addPermissionNode(newChunk, permExp, analysisInfo.sourceInfo, analysisInfo.assumptionType, isExhale) analysisInfo.assumptionAnalyzer.addPermissionDependencies(oldChunks, newNode) analysisInfo.assumptionAnalyzer.addDependencyFromExhaleToInhale(newNode) - newChunk + newChunk // TODO ake: registerChunk } } @@ -150,9 +150,9 @@ object QuantifiedFieldChunk { hints: Seq[Term] = Nil, analysisInfo: AnalysisInfo, isExhale: Boolean=false): QuantifiedFieldChunk = { - val chunk = new QuantifiedFieldChunk(id, fvf, condition, conditionExp, permValue, permValueExp, invs, singletonRcvr, singletonRcvrExp, hints) - analysisInfo.assumptionAnalyzer.addPermissionNode(chunk, permValueExp, analysisInfo.sourceInfo, analysisInfo.assumptionType, isExhale) - chunk + analysisInfo.decider.registerChunk[QuantifiedFieldChunk]({perm => + new QuantifiedFieldChunk(id, fvf, condition, conditionExp, perm, permValueExp, invs, singletonRcvr, singletonRcvrExp, hints)}, + permValue, analysisInfo, isExhale) } } @@ -241,9 +241,9 @@ object QuantifiedPredicateChunk { hints: Seq[Term] = Nil, analysisInfo: AnalysisInfo, isExhale: Boolean=false): QuantifiedPredicateChunk = { - val chunk = new QuantifiedPredicateChunk(id, quantifiedVars, quantifiedVarExps, psf, condition, conditionExp, permValue, permValueExp, invs, singletonArgs, singletonArgExps, hints) - analysisInfo.assumptionAnalyzer.addPermissionNode(chunk, permValueExp, analysisInfo.sourceInfo, analysisInfo.assumptionType, isExhale) - chunk + analysisInfo.decider.registerChunk[QuantifiedPredicateChunk]({finalPerm => + new QuantifiedPredicateChunk(id, quantifiedVars, quantifiedVarExps, psf, condition, conditionExp, finalPerm, permValueExp, invs, singletonArgs, singletonArgExps, hints)}, + permValue, analysisInfo, isExhale) } } @@ -315,9 +315,9 @@ object QuantifiedMagicWandChunk { hints: Seq[Term] = Nil, analysisInfo: AnalysisInfo, isExhale: Boolean=false): QuantifiedMagicWandChunk = { - val chunk = new QuantifiedMagicWandChunk(id, quantifiedVars, quantifiedVarExps, wsf, perm, permExp, invs, singletonArgs, singletonArgExps, hints) - analysisInfo.assumptionAnalyzer.addPermissionNode(chunk, permExp, analysisInfo.sourceInfo, analysisInfo.assumptionType, isExhale) - chunk + analysisInfo.decider.registerChunk[QuantifiedMagicWandChunk]({finalPerm => + new QuantifiedMagicWandChunk(id, quantifiedVars, quantifiedVarExps, wsf, finalPerm, permExp, invs, singletonArgs, singletonArgExps, hints)}, + perm, analysisInfo, isExhale) } } @@ -395,9 +395,9 @@ object MagicWandChunk { permExp: Option[ast.Exp], analysisInfo: AnalysisInfo, isExhale: Boolean=false): MagicWandChunk = { - val chunk = new MagicWandChunk(id, bindings, args, argsExp, snap, perm, permExp) - analysisInfo.assumptionAnalyzer.addPermissionNode(chunk, permExp, analysisInfo.sourceInfo, analysisInfo.assumptionType, isExhale) - chunk + analysisInfo.decider.registerChunk[MagicWandChunk]({finalPerm => + new MagicWandChunk(id, bindings, args, argsExp, snap, finalPerm, permExp)}, + perm, analysisInfo, isExhale) } } From 31e9242b262b8bb8ab007c5fb625a4af2a709968 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 30 Jun 2025 12:26:44 +0200 Subject: [PATCH 126/474] add terms instead of exp to all nodes --- .../AssumptionAnalysisGraph.scala | 17 ++++----- .../AssumptionAnalyzer.scala | 38 +++++++++---------- src/main/scala/decider/Decider.scala | 4 +- src/main/scala/rules/ChunkSupporter.scala | 7 ++-- .../rules/MoreCompleteExhaleSupporter.scala | 4 +- .../scala/rules/QuantifiedChunkSupport.scala | 2 +- src/main/scala/state/Chunks.scala | 2 +- 7 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index b5f068a30..fcdb80fe3 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -172,6 +172,8 @@ trait AssumptionAnalysisNode { val sourceInfo: AnalysisSourceInfo val assumptionType: AssumptionType val isClosed: Boolean + val term: Term + def getTerm: Term = term override def toString: String = id.toString + " | " + getNodeString + " | " + sourceInfo.toString @@ -180,8 +182,6 @@ trait AssumptionAnalysisNode { } trait GeneralAssumptionNode extends AssumptionAnalysisNode { - val term: Term - def getTerm: Term = term override def getNodeType: String = "Assumption" } trait GeneralAssertionNode extends AssumptionAnalysisNode { @@ -190,7 +190,6 @@ trait GeneralAssertionNode extends AssumptionAnalysisNode { trait ChunkAnalysisInfo { val chunk: Chunk - val permAmount: Option[ast.Exp] def getChunk: Chunk = chunk } @@ -212,26 +211,26 @@ case class StringAssertionNode(description: String, term: Term, sourceInfo: Anal override def getNodeString: String = "assert " + description } -case class SimpleCheckNode(t: Term, sourceInfo: AnalysisSourceInfo, isClosed: Boolean) extends GeneralAssertionNode { +case class SimpleCheckNode(term: Term, sourceInfo: AnalysisSourceInfo, isClosed: Boolean) extends GeneralAssertionNode { val assumptionType: AssumptionType = Internal - override def getNodeString: String = "check " + t + override def getNodeString: String = "check " + term override def getNodeType: String = "Check" } -case class PermissionInhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean) extends GeneralAssumptionNode with ChunkAnalysisInfo { +case class PermissionInhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean) extends GeneralAssumptionNode with ChunkAnalysisInfo { override def getNodeString: String = "inhale " + chunk.getAnalysisInfo override def getNodeType: String = "Inhale" } -case class PermissionExhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, isClosed: Boolean) extends GeneralAssertionNode with ChunkAnalysisInfo { +case class PermissionExhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, isClosed: Boolean) extends GeneralAssertionNode with ChunkAnalysisInfo { val assumptionType: AssumptionType = Explicit override def getNodeType: String = "Exhale" override def getNodeString: String = "exhale " + chunk.getAnalysisInfo } -case class PermissionAssertNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, isClosed: Boolean) extends GeneralAssertionNode with ChunkAnalysisInfo { +case class PermissionAssertNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, isClosed: Boolean) extends GeneralAssertionNode with ChunkAnalysisInfo { val assumptionType: AssumptionType = Explicit - override def getNodeString: String = "assert " + permAmount.getOrElse("") + " for " + chunk.getAnalysisInfo + override def getNodeString: String = "assert " + term + " for " + chunk.getAnalysisInfo } case class LabelNode(term: Term) extends GeneralAssumptionNode { diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 38c97b3c8..df854aa47 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -22,10 +22,10 @@ trait AssumptionAnalyzer { enableCustomEdges_ = false } - def addPermissionInhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] - def addPermissionAssertNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo): Option[Int] - def addPermissionExhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo): Option[Int] - def addPermissionNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Explicit, isExhale: Boolean=false): Option[Int] + def addPermissionInhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] + def addPermissionAssertNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo): Option[Int] + def addPermissionExhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo): Option[Int] + def addPermissionNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Explicit, isExhale: Boolean=false): Option[Int] def addNodes(nodes: Iterable[AssumptionAnalysisNode]): Unit def addNode(node: AssumptionAnalysisNode): Unit def getNodes: Iterable[AssumptionAnalysisNode] @@ -39,7 +39,7 @@ trait AssumptionAnalyzer { def createAssertOrCheckNode(term: Term, assertion: Either[String, ast.Exp], analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] def getChunkNodeIds(oldChunks: Set[Chunk]): Set[Int] - def getNodeIds(terms: Set[Term]): Set[Int] + def getNodeIdsByTerm(terms: Set[Term]): Set[Int] def addDependencyFromExhaleToInhale(newChunkNodeId: Option[Int]): Unit def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNodeId: Option[Int]): Unit def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNodeId: Chunk): Unit @@ -208,27 +208,27 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { assumptionGraph.addEdges(axiomIds, assertionIds) } - override def addPermissionInhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { - val node = PermissionInhaleNode(chunk, permAmount, True /* TODO ake */, sourceInfo, assumptionType, enableCustomEdges_) + override def addPermissionInhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { + val node = PermissionInhaleNode(chunk, permAmount, sourceInfo, assumptionType, enableCustomEdges_) addNode(node) Some(node.id) } - override def addPermissionAssertNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo): Option[Int] = { + override def addPermissionAssertNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo): Option[Int] = { val node = PermissionAssertNode(chunk, permAmount, sourceInfo, enableCustomEdges_) addNode(node) addPermissionDependencies(Set(chunk), Some(node.id)) Some(node.id) } - override def addPermissionExhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo): Option[Int] = { + override def addPermissionExhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo): Option[Int] = { val node = PermissionExhaleNode(chunk, permAmount, sourceInfo, enableCustomEdges_) addNode(node) addPermissionDependencies(Set(chunk), Some(node.id)) Some(node.id) } - override def addPermissionNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Explicit, isExhale: Boolean=false): Option[Int] = { + override def addPermissionNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Explicit, isExhale: Boolean=false): Option[Int] = { if(isExhale) addPermissionExhaleNode(chunk, permAmount, sourceInfo) else addPermissionInhaleNode(chunk, permAmount, sourceInfo, assumptionType) } @@ -239,9 +239,9 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { .map(_.id).toSet } - override def getNodeIds(terms: Set[Term]): Set[Int] = { + override def getNodeIdsByTerm(terms: Set[Term]): Set[Int] = { assumptionGraph.nodes - .filter(t => t.isInstanceOf[GeneralAssumptionNode] && terms.contains(t.asInstanceOf[GeneralAssumptionNode].getTerm)) + .filter(t => terms.contains(t.getTerm)) .map(_.id).toSet } @@ -312,7 +312,7 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { override def createLabelledConditional(decider: Decider, sourceTerms: Iterable[Term], term: Term): Term = { if(term.equals(True)) return term - val sourceNodeIds = getNodeIds(sourceTerms.toSet) // TODO ake: maybe we have node id as argument + val sourceNodeIds = getNodeIdsByTerm(sourceTerms.toSet) // TODO ake: maybe we have node id as argument if(sourceNodeIds.isEmpty) { term } else { @@ -328,7 +328,7 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { override def createLabelledConditional(decider: Decider, sourceTerms: Iterable[Term], terms: Seq[Term]): Seq[Term] = { if(!(terms exists (t => !t.equals(True)))) return terms - val sourceNodeIds = getNodeIds(sourceTerms.toSet) // TODO ake: maybe we have node id as argument + val sourceNodeIds = getNodeIdsByTerm(sourceTerms.toSet) // TODO ake: maybe we have node id as argument if(sourceNodeIds.isEmpty) { terms } else { @@ -365,14 +365,14 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { override def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit = { } - override def addPermissionInhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None - override def addPermissionExhaleNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo): Option[Int] = None - override def addPermissionAssertNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo): Option[Int] = None - override def addPermissionNode(chunk: Chunk, permAmount: Option[ast.Exp], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isExhale: Boolean=false): Option[Int] = None + override def addPermissionInhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None + override def addPermissionExhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo): Option[Int] = None + override def addPermissionAssertNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo): Option[Int] = None + override def addPermissionNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isExhale: Boolean=false): Option[Int] = None override def getChunkNodeIds(oldChunks: Set[Chunk]): Set[Int] = Set.empty - override def getNodeIds(terms: Set[Term]): Set[Int] = Set.empty + override def getNodeIdsByTerm(terms: Set[Term]): Set[Int] = Set.empty override def addDependencyFromExhaleToInhale(newChunkNodeId: Option[Int]): Unit = {} override def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNode: Option[Int]): Unit = { diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index b98aec50e..3eda5859a 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -317,7 +317,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => (buildChunk(finalPerm), Some(labelNode.id)) } - val chunkNode = assumptionAnalyzer.addPermissionNode(chunk, chunk.permExp, analysisInfo.sourceInfo, analysisInfo.assumptionType, isExhale) + val chunkNode = assumptionAnalyzer.addPermissionNode(chunk, chunk.perm, analysisInfo.sourceInfo, analysisInfo.assumptionType, isExhale) if(chunkNode.isDefined && labelNodeId.isDefined) assumptionAnalyzer.addDependency(chunkNode.get, labelNodeId.get) chunk @@ -332,7 +332,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => (buildChunk(finalPerm), Some(labelNode.id)) } - val newChunkNode = assumptionAnalyzer.addPermissionNode(newChunk, newChunk.permExp, analysisInfo.sourceInfo, analysisInfo.assumptionType, isExhale) + val newChunkNode = assumptionAnalyzer.addPermissionNode(newChunk, newChunk.perm, analysisInfo.sourceInfo, analysisInfo.assumptionType, isExhale) assumptionAnalyzer.addPermissionDependencies(Set(sourceChunk), newChunkNode) if(newChunkNode.isDefined && labelNodeId.isDefined) assumptionAnalyzer.addDependency(newChunkNode.get, labelNodeId.get) diff --git a/src/main/scala/rules/ChunkSupporter.scala b/src/main/scala/rules/ChunkSupporter.scala index e5bde56f2..06b5ef89d 100644 --- a/src/main/scala/rules/ChunkSupporter.scala +++ b/src/main/scala/rules/ChunkSupporter.scala @@ -168,8 +168,9 @@ object chunkSupporter extends ChunkSupportRules { findChunk[NonQuantifiedChunk](h.values, id, args, v) match { case Some(ch) => if (s.assertReadAccessOnly) { - if (v.decider.check(Implies(IsPositive(perms), IsPositive(ch.perm)), Verifier.config.assertTimeout.getOrElse(0))) { - v.decider.assumptionAnalyzer.addPermissionAssertNode(ch, ch.permExp.map(pe => IsPositive(pe)(pe.pos, pe.info, pe.errT)), v.decider.analysisSourceInfoStack.getFullSourceInfo) + val termToCheck = Implies(IsPositive(perms), IsPositive(ch.perm)) + if (v.decider.check(termToCheck, Verifier.config.assertTimeout.getOrElse(0))) { + v.decider.assumptionAnalyzer.addPermissionAssertNode(ch, termToCheck, v.decider.analysisSourceInfoStack.getFullSourceInfo) (Complete(), s, h, Some(ch)) } else { (Incomplete(perms, permsExp), s, h, None) @@ -253,7 +254,7 @@ object chunkSupporter extends ChunkSupportRules { val findRes = findChunk[NonQuantifiedChunk](h.values, id, args, v) findRes match { case Some(ch) if v.decider.check(IsPositive(ch.perm), Verifier.config.checkTimeout()) => - v.decider.assumptionAnalyzer.addPermissionAssertNode(ch, ch.permExp.map(pe => IsPositive(pe)(pe.pos, pe.info, pe.errT)), v.decider.analysisSourceInfoStack.getFullSourceInfo) + v.decider.assumptionAnalyzer.addPermissionAssertNode(ch, IsPositive(ch.perm), v.decider.analysisSourceInfoStack.getFullSourceInfo) Q(s, ch.snap, v) case _ if v.decider.checkSmoke(true) => if (s.isInPackage) { diff --git a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala index 3bab009d3..d69f8f2ad 100644 --- a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala +++ b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala @@ -366,7 +366,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { pSumExp = eqExp.map(eq => ast.PermAdd(pSumExp.get, ast.CondExp(eq, ch.permExp.get, ast.NoPerm()())(eq.pos, eq.info, eq.errT))()) val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, pTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, pTakenExp.get)(pe.pos, pe.info, pe.errT)), v.decider.getAnalysisInfo).asInstanceOf[NonQuantifiedChunk] - v.decider.assumptionAnalyzer.addPermissionExhaleNode(ch, pTakenExp, v.decider.analysisSourceInfoStack.getFullSourceInfo) + v.decider.assumptionAnalyzer.addPermissionExhaleNode(ch, pTaken, v.decider.analysisSourceInfoStack.getFullSourceInfo) pNeeded = PermMinus(pNeeded, pTaken) pNeededExp = permsExp.map(pe => ast.PermSub(pNeededExp.get, pTakenExp.get)(pe.pos, pe.info, pe.errT)) @@ -482,7 +482,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { newFr = newFr.recordPathSymbol(permTaken.applicable.asInstanceOf[Function]).recordConstraint(constraint) - v.decider.assumptionAnalyzer.addPermissionExhaleNode(ch, permTakenExp, v.decider.analysisSourceInfoStack.getFullSourceInfo) + v.decider.assumptionAnalyzer.addPermissionExhaleNode(ch, permTaken, v.decider.analysisSourceInfoStack.getFullSourceInfo) GeneralChunk.withPerm(ch, PermMinus(ch.perm, permTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, permTakenExp.get)(pe.pos, pe.info, pe.errT)), v.decider.getAnalysisInfo).asInstanceOf[NonQuantifiedChunk] }) diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 519c58ac0..4b53d65d2 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -1728,7 +1728,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.getAnalysisInfo(AssumptionType.Internal)) } }else{ - v.decider.assumptionAnalyzer.addPermissionExhaleNode(ithChunk, ithPTakenExp, v.decider.analysisSourceInfoStack.getFullSourceInfo) + v.decider.assumptionAnalyzer.addPermissionExhaleNode(ithChunk, ithPTaken, v.decider.analysisSourceInfoStack.getFullSourceInfo) } } diff --git a/src/main/scala/state/Chunks.scala b/src/main/scala/state/Chunks.scala index e3ae34c74..b6ffe9225 100644 --- a/src/main/scala/state/Chunks.scala +++ b/src/main/scala/state/Chunks.scala @@ -59,7 +59,7 @@ object BasicChunk { analysisInfo: AnalysisInfo, isExhale: Boolean=false): BasicChunk = { val newChunk = new BasicChunk(resourceID, id, args, argsExp, snap, snapExp, perm, permExp) - val newNode = analysisInfo.assumptionAnalyzer.addPermissionNode(newChunk, permExp, analysisInfo.sourceInfo, analysisInfo.assumptionType, isExhale) + val newNode = analysisInfo.assumptionAnalyzer.addPermissionNode(newChunk, perm, analysisInfo.sourceInfo, analysisInfo.assumptionType, isExhale) analysisInfo.assumptionAnalyzer.addPermissionDependencies(oldChunks, newNode) analysisInfo.assumptionAnalyzer.addDependencyFromExhaleToInhale(newNode) newChunk // TODO ake: registerChunk From f9b3d51e84bbcebb065c646fc433c5532a157d96 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 30 Jun 2025 12:30:15 +0200 Subject: [PATCH 127/474] resolve state consolidator imprecision --- .../scala/resources/NonQuantifiedPropertyInterpreter.scala | 3 ++- src/main/scala/rules/StateConsolidator.scala | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala b/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala index 3f8b21520..118d96912 100644 --- a/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala +++ b/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala @@ -121,7 +121,8 @@ class NonQuantifiedPropertyInterpreter(heap: Iterable[Chunk], verifier: Verifier info: Info): (Term, Option[ast.Exp]) = { val conditionTerm = buildPathCondition(condition, info)._1 if (verifier.decider.check(conditionTerm, Verifier.config.checkTimeout())) { - buildPathCondition(thenDo, info) + val (term, exp) = buildPathCondition(thenDo, info) + (verifier.decider.assumptionAnalyzer.createLabelledConditional(verifier.decider, Set(conditionTerm), term), exp) } else { buildPathCondition(otherwise, info) } diff --git a/src/main/scala/rules/StateConsolidator.scala b/src/main/scala/rules/StateConsolidator.scala index 1954dfccd..57130721e 100644 --- a/src/main/scala/rules/StateConsolidator.scala +++ b/src/main/scala/rules/StateConsolidator.scala @@ -104,13 +104,17 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol val resource = Resources.resourceDescriptions(ch.resourceID) val pathCond = interpreter.buildPathConditionsForChunk(ch, resource.instanceProperties(s.mayAssumeUpperBounds)) v.decider.assumptionAnalyzer.addForcedChunkDependencies(Set(ch)) + v.decider.assumptionAnalyzer.enableCustomEdges() pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), AssumptionType.Internal)) v.decider.assumptionAnalyzer.unsetForcedDependencies() + v.decider.assumptionAnalyzer.disableCustomEdges() } Resources.resourceDescriptions foreach { case (id, desc) => val pathCond = interpreter.buildPathConditionsForResource(id, desc.delayedProperties(s.mayAssumeUpperBounds)) + v.decider.assumptionAnalyzer.enableCustomEdges() pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), AssumptionType.Internal)) + v.decider.assumptionAnalyzer.disableCustomEdges() } v.symbExLog.closeScope(sepIdentifier) From b371904281cd872c9d09d23c0a2f3d8bc12ae40a Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 30 Jun 2025 12:41:03 +0200 Subject: [PATCH 128/474] fix chunk analysis string --- src/main/scala/state/Chunks.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/scala/state/Chunks.scala b/src/main/scala/state/Chunks.scala index b6ffe9225..1aaa82d24 100644 --- a/src/main/scala/state/Chunks.scala +++ b/src/main/scala/state/Chunks.scala @@ -82,9 +82,9 @@ case class BasicChunk private (resourceID: BaseID, case PredicateID => require(snap.sort == sorts.Snap, s"A predicate chunk's snapshot ($snap) is expected to be of sort Snap, but found ${snap.sort}") } - override def getAnalysisInfo: String = perm + " for " + (resourceID match { - case PredicateID => id.name + "(" + (argsExp map (_.mkString(", "))).getOrElse("") + ")" - case FieldID => (argsExp map (_.head)).getOrElse("") + "." + id.name + override def getAnalysisInfo: String = perm.toString + " for " + (resourceID match { + case PredicateID => id.name + "(" + args.mkString(", ") + ")" + case FieldID => args.head.toString + "." + id.name }) From f3280a2ce34975eab4f73c496469cd4b23b4b8b0 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 30 Jun 2025 13:00:05 +0200 Subject: [PATCH 129/474] add examples --- .../dependencyAnalysisTests/new/meeting.vpr | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/test/resources/dependencyAnalysisTests/new/meeting.vpr b/src/test/resources/dependencyAnalysisTests/new/meeting.vpr index 33d4b0094..768876a3b 100644 --- a/src/test/resources/dependencyAnalysisTests/new/meeting.vpr +++ b/src/test/resources/dependencyAnalysisTests/new/meeting.vpr @@ -39,3 +39,19 @@ method infeasible3(x: Ref) assert a >= 0 } } + + +method currentPerm(x: Ref) + requires acc(x.f, 1/2) +{ + assert perm(x.f) > 1/4 +} + +method noAlias(a: Ref, b: Ref, c: Ref) + requires @dependency()(acc(a.f)) + requires @dependency()(acc(b.f, 1/2)) + requires @irrelevant()(acc(c.f, 1/2)) +{ + @testAssertion() + assert a != b +} From 853682a101892ede40404ab929a8cf6e3b9df973 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 30 Jun 2025 15:47:21 +0200 Subject: [PATCH 130/474] refactoring --- .../AssumptionAnalysisGraph.scala | 14 ++--- .../AssumptionAnalyzer.scala | 54 ++++++++++--------- src/main/scala/decider/Decider.scala | 2 +- src/main/scala/interfaces/state/Chunks.scala | 4 -- src/main/scala/rules/Executor.scala | 20 +++---- src/main/scala/rules/PredicateSupporter.scala | 2 +- .../scala/rules/QuantifiedChunkSupport.scala | 16 +++--- src/main/scala/rules/StateConsolidator.scala | 8 +-- src/main/scala/state/Chunks.scala | 23 -------- 9 files changed, 62 insertions(+), 81 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index fcdb80fe3..8d4caedf9 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -194,21 +194,21 @@ trait ChunkAnalysisInfo { } case class SimpleAssumptionNode(assumption: ast.Exp, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean) extends GeneralAssumptionNode { - override def getNodeString: String ="assume " + assumption.toString + override def getNodeString: String ="assume " + term.toString + ", " + assumption.toString } case class StringAssumptionNode(description: String, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean) extends GeneralAssumptionNode { - override def getNodeString: String = "assume " + description + override def getNodeString: String = "assume " + term.toString + ", " + description } case class SimpleAssertionNode(assertion: ast.Exp, term: Term, sourceInfo: AnalysisSourceInfo, isClosed: Boolean) extends GeneralAssertionNode { val assumptionType: AssumptionType = Explicit - override def getNodeString: String = "assert " + assertion.toString + override def getNodeString: String = "assert " + term.toString + ", " + assertion.toString } case class StringAssertionNode(description: String, term: Term, sourceInfo: AnalysisSourceInfo, isClosed: Boolean) extends GeneralAssertionNode { val assumptionType: AssumptionType = Explicit - override def getNodeString: String = "assert " + description + override def getNodeString: String = "assert " + term.toString + ", " + description } case class SimpleCheckNode(term: Term, sourceInfo: AnalysisSourceInfo, isClosed: Boolean) extends GeneralAssertionNode { @@ -218,19 +218,19 @@ case class SimpleCheckNode(term: Term, sourceInfo: AnalysisSourceInfo, isClosed: } case class PermissionInhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean) extends GeneralAssumptionNode with ChunkAnalysisInfo { - override def getNodeString: String = "inhale " + chunk.getAnalysisInfo + override def getNodeString: String = "inhale " + term.toString + ": " + chunk.toString override def getNodeType: String = "Inhale" } case class PermissionExhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, isClosed: Boolean) extends GeneralAssertionNode with ChunkAnalysisInfo { val assumptionType: AssumptionType = Explicit override def getNodeType: String = "Exhale" - override def getNodeString: String = "exhale " + chunk.getAnalysisInfo + override def getNodeString: String = "exhale " + term.toString + ": " + chunk.toString } case class PermissionAssertNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, isClosed: Boolean) extends GeneralAssertionNode with ChunkAnalysisInfo { val assumptionType: AssumptionType = Explicit - override def getNodeString: String = "assert " + term + " for " + chunk.getAnalysisInfo + override def getNodeString: String = "assert " + term + " for " + chunk.toString } case class LabelNode(term: Term) extends GeneralAssumptionNode { diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index df854aa47..f357fbf3c 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -12,14 +12,14 @@ import viper.silver.ast trait AssumptionAnalyzer { val assumptionGraph: AssumptionAnalysisGraph = new DefaultAssumptionAnalysisGraph() var forcedDependencies: List[Int] = List.empty - protected var enableCustomEdges_ = false + protected var isClosed_ = false - def enableCustomEdges(): Unit = { - enableCustomEdges_ = true + def disableTransitiveEdges(): Unit = { + isClosed_ = true } - def disableCustomEdges(): Unit = { - enableCustomEdges_ = false + def enableTransitiveEdges(): Unit = { + isClosed_ = false } def addPermissionInhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] @@ -40,7 +40,7 @@ trait AssumptionAnalyzer { def getChunkNodeIds(oldChunks: Set[Chunk]): Set[Int] def getNodeIdsByTerm(terms: Set[Term]): Set[Int] - def addDependencyFromExhaleToInhale(newChunkNodeId: Option[Int]): Unit + def addDependencyFromExhaleToInhale(inhaledChunk: Chunk, sourceInfo: AnalysisSourceInfo): Unit def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNodeId: Option[Int]): Unit def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNodeId: Chunk): Unit def addDependency(source: Int, dest: Int): Unit @@ -152,22 +152,22 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { override def addSingleAssumption(assumption: DebugExp, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { val node = - if(assumption.originalExp.isDefined) SimpleAssumptionNode(assumption.originalExp.get, assumption.term.getOrElse(True) /* TODO ake */, analysisSourceInfo, AssumptionAnalyzer.extractAssumptionTypeFromInfo(assumption.originalExp.get.info).getOrElse(assumptionType), enableCustomEdges_) - else StringAssumptionNode(assumption.description.getOrElse("unknown"), assumption.term.getOrElse(True) /* TODO ake */, analysisSourceInfo, assumptionType, enableCustomEdges_) + if(assumption.originalExp.isDefined) SimpleAssumptionNode(assumption.originalExp.get, assumption.term.getOrElse(True) /* TODO ake */, analysisSourceInfo, AssumptionAnalyzer.extractAssumptionTypeFromInfo(assumption.originalExp.get.info).getOrElse(assumptionType), isClosed_) + else StringAssumptionNode(assumption.description.getOrElse("unknown"), assumption.term.getOrElse(True) /* TODO ake */, analysisSourceInfo, assumptionType, isClosed_) addNode(node) Some(node.id) } override def addAssumption(assumption: ast.Exp, term: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { - val node = SimpleAssumptionNode(assumption, term, analysisSourceInfo, AssumptionAnalyzer.extractAssumptionTypeFromInfo(assumption.info).getOrElse(assumptionType), enableCustomEdges_) + val node = SimpleAssumptionNode(assumption, term, analysisSourceInfo, AssumptionAnalyzer.extractAssumptionTypeFromInfo(assumption.info).getOrElse(assumptionType), isClosed_) addNode(node) Some(node.id) } override def addAssumption(assumption: String, term: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { - val node = StringAssumptionNode(assumption, term, analysisSourceInfo, assumptionType, enableCustomEdges_) + val node = StringAssumptionNode(assumption, term, analysisSourceInfo, assumptionType, isClosed_) addNode(node) Some(node.id) } @@ -175,19 +175,19 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { override def addAssumptions(assumptions: Iterable[DebugExp], analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Seq[Int] = { val newNodes = assumptions.toSeq.map(a => if (a.originalExp.isDefined) SimpleAssumptionNode(a.originalExp.get, a.term.getOrElse(True) /* TODO ake */, if(analysisSourceInfo.isInstanceOf[NoAnalysisSourceInfo]) ExpAnalysisSourceInfo(a.originalExp.get) else analysisSourceInfo, - AssumptionAnalyzer.extractAssumptionTypeFromInfo(a.originalExp.get.info).getOrElse(assumptionType), enableCustomEdges_) - else StringAssumptionNode(a.description.getOrElse("unknown"), a.term.getOrElse(True) /* TODO ake */, analysisSourceInfo, AssumptionType.Internal, enableCustomEdges_) + AssumptionAnalyzer.extractAssumptionTypeFromInfo(a.originalExp.get.info).getOrElse(assumptionType), isClosed_) + else StringAssumptionNode(a.description.getOrElse("unknown"), a.term.getOrElse(True) /* TODO ake */, analysisSourceInfo, AssumptionType.Internal, isClosed_) ) newNodes foreach addNode newNodes.map(_.id) } override def createAssertOrCheckNode(term: Term, assertion: Either[String, ast.Exp], analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] = { - if(isCheck) return Some(SimpleCheckNode(term, analysisSourceInfo, enableCustomEdges_)) + if(isCheck) return Some(SimpleCheckNode(term, analysisSourceInfo, isClosed_)) Some(assertion match { - case Left(description) => StringAssertionNode(description, term, analysisSourceInfo, enableCustomEdges_) - case Right(exp) => SimpleAssertionNode(exp, term, analysisSourceInfo, enableCustomEdges_) + case Left(description) => StringAssertionNode(description, term, analysisSourceInfo, isClosed_) + case Right(exp) => SimpleAssertionNode(exp, term, analysisSourceInfo, isClosed_) }) } @@ -209,20 +209,20 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { } override def addPermissionInhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { - val node = PermissionInhaleNode(chunk, permAmount, sourceInfo, assumptionType, enableCustomEdges_) + val node = PermissionInhaleNode(chunk, permAmount, sourceInfo, assumptionType, isClosed_) addNode(node) Some(node.id) } override def addPermissionAssertNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo): Option[Int] = { - val node = PermissionAssertNode(chunk, permAmount, sourceInfo, enableCustomEdges_) + val node = PermissionAssertNode(chunk, permAmount, sourceInfo, isClosed_) addNode(node) addPermissionDependencies(Set(chunk), Some(node.id)) Some(node.id) } override def addPermissionExhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo): Option[Int] = { - val node = PermissionExhaleNode(chunk, permAmount, sourceInfo, enableCustomEdges_) + val node = PermissionExhaleNode(chunk, permAmount, sourceInfo, isClosed_) addNode(node) addPermissionDependencies(Set(chunk), Some(node.id)) Some(node.id) @@ -239,19 +239,25 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { .map(_.id).toSet } + private def getChunkNodeIdsWithSource(oldChunks: Set[Chunk], sourceInfo: AnalysisSourceInfo): Set[Int] = { + assumptionGraph.nodes + .filter(c => c.isInstanceOf[PermissionInhaleNode] && oldChunks.contains(c.asInstanceOf[ChunkAnalysisInfo].getChunk) && c.sourceInfo.equals(sourceInfo)) + .map(_.id).toSet + } + override def getNodeIdsByTerm(terms: Set[Term]): Set[Int] = { assumptionGraph.nodes .filter(t => terms.contains(t.getTerm)) .map(_.id).toSet } - override def addDependencyFromExhaleToInhale(newChunkNodeId: Option[Int]): Unit = { - if(newChunkNodeId.isEmpty) return - val newChunkNode = assumptionGraph.nodes.filter(_.id == newChunkNodeId.get).head + override def addDependencyFromExhaleToInhale(inhaledChunk: Chunk, sourceInfo: AnalysisSourceInfo): Unit = { + val inhaledChunkNodeIds = getChunkNodeIdsWithSource(Set(inhaledChunk), sourceInfo) + assert(inhaledChunkNodeIds.size == 1) val exhaleNodes = assumptionGraph.nodes - .filter(c => c.isInstanceOf[PermissionExhaleNode] && c.isClosed && c.sourceInfo.equals(newChunkNode.sourceInfo)) + .filter(c => c.isInstanceOf[PermissionExhaleNode] && c.isClosed && c.sourceInfo.equals(sourceInfo)) .map(_.id).toSet - assumptionGraph.addEdges(exhaleNodes, newChunkNodeId) + assumptionGraph.addEdges(exhaleNodes, inhaledChunkNodeIds) } override def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNodeId: Option[Int]): Unit = { @@ -374,7 +380,7 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { override def getChunkNodeIds(oldChunks: Set[Chunk]): Set[Int] = Set.empty override def getNodeIdsByTerm(terms: Set[Term]): Set[Int] = Set.empty - override def addDependencyFromExhaleToInhale(newChunkNodeId: Option[Int]): Unit = {} + override def addDependencyFromExhaleToInhale(inhaledChunk: Chunk, sourceInfo: AnalysisSourceInfo): Unit = {} override def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNode: Option[Int]): Unit = { } diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 3eda5859a..0cff95285 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -324,7 +324,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } def registerDerivedChunk[CH <: GeneralChunk](sourceChunk: CH, buildChunk: (Term => CH), perm: Term, analysisInfo: AnalysisInfo, isExhale: Boolean, createLabel: Boolean=true): CH = { - val (newChunk, labelNodeId) = if(!createLabel) { + val (newChunk, labelNodeId) = if(!createLabel || isExhale) { (buildChunk(perm), None) }else { val labelNode = assumptionAnalyzer.createAndAssumeLabelNode(this, Set()) diff --git a/src/main/scala/interfaces/state/Chunks.scala b/src/main/scala/interfaces/state/Chunks.scala index 35993cbd8..3febe041a 100644 --- a/src/main/scala/interfaces/state/Chunks.scala +++ b/src/main/scala/interfaces/state/Chunks.scala @@ -14,8 +14,6 @@ import viper.silver.ast trait Chunk { val perm: Term val permExp: Option[ast.Exp] - - def getAnalysisInfo: String } trait ChunkIdentifer @@ -64,7 +62,6 @@ trait NonQuantifiedChunk extends GeneralChunk { val args: Seq[Term] val argsExp: Option[Seq[ast.Exp]] val snap: Term - override def getAnalysisInfo: String = argsExp.getOrElse("") + " " + permExp.getOrElse("") override protected def applyCondition(newCond: Term, newCondExp: Option[ast.Exp]): NonQuantifiedChunk override protected def permMinus(perm: Term, permExp: Option[ast.Exp]): NonQuantifiedChunk override protected def permPlus(perm: Term, permExp: Option[ast.Exp]): NonQuantifiedChunk @@ -83,7 +80,6 @@ object NonQuantifiedChunk { trait QuantifiedChunk extends GeneralChunk { val quantifiedVars: Seq[Var] val quantifiedVarExps: Option[Seq[ast.LocalVarDecl]] - override def getAnalysisInfo: String = quantifiedVarExps.getOrElse("") + " " + permExp.getOrElse("") def snapshotMap: Term def valueAt(arguments: Seq[Term]): Term override protected def applyCondition(newCond: Term, newCondExp: Option[ast.Exp]): QuantifiedChunk diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 3ad4409c2..fce9885bc 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -402,7 +402,7 @@ object executor extends ExecutionRules { s2 } v2.decider.clearModel() - v2.decider.assumptionAnalyzer.enableCustomEdges() // inhaling new chunks should not depend on any of the rhs assertions + v2.decider.assumptionAnalyzer.disableTransitiveEdges() // inhaling new chunks should not depend on any of the rhs assertions val result = quantifiedChunkSupporter.removePermissions( s2p, relevantChunks, @@ -417,7 +417,7 @@ object executor extends ExecutionRules { chunkOrderHeuristics, v2 ) - v2.decider.assumptionAnalyzer.disableCustomEdges() + v2.decider.assumptionAnalyzer.enableTransitiveEdges() result match { case (Complete(), s3, remainingChunks, consumedChunks) => // TODO ake: what to do with consumedChunks? val h3 = Heap(remainingChunks ++ otherChunks) @@ -425,10 +425,11 @@ object executor extends ExecutionRules { v1.decider.prover.comment("Definitional axioms for singleton-FVF's value") val debugExp = Option.when(withExp)(DebugExp.createInstance("Definitional axioms for singleton-FVF's value", isInternal_ = true)) v1.decider.assumeDefinition(smValueDef, debugExp, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Internal)) - v1.decider.analysisSourceInfoStack.setForcedSource(ExpAnalysisSourceInfo(fa)) + v1.decider.assumptionAnalyzer.disableTransitiveEdges() val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(Seq(`?r`), Option.when(withExp)(Seq(ast.LocalVarDecl("r", ast.Ref)(ass.pos, ass.info, ass.errT))), field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), sm, s.program, v1, AssumptionType.Internal, isExhale=false) - v1.decider.analysisSourceInfoStack.removeForcedSource() + v1.decider.assumptionAnalyzer.addDependencyFromExhaleToInhale(ch, v1.decider.getAnalysisInfo.sourceInfo) + v1.decider.assumptionAnalyzer.enableTransitiveEdges() if (s3.heapDependentTriggers.contains(field)) { val debugExp2 = Option.when(withExp)(DebugExp.createInstance(s"FieldTrigger(${eRcvrNew.toString}.${field.name})")) v1.decider.assume(FieldTrigger(field.name, sm, tRcvr), debugExp2, AssumptionType.Internal) @@ -448,15 +449,16 @@ object executor extends ExecutionRules { val resource = fa.res(s.program) val ve = pve dueTo InsufficientPermission(fa) val description = s"consume ${ass.pos}: $ass" - v2.decider.assumptionAnalyzer.enableCustomEdges() // TODO ake: review implementation + v2.decider.assumptionAnalyzer.disableTransitiveEdges() // TODO ake: review implementation chunkSupporter.consume(s2, s2.h, resource, Seq(tRcvr), eRcvrNew.map(Seq(_)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), false, ve, v2, description)((s3, h3, _, consumedChunks, v3) => { - v2.decider.assumptionAnalyzer.disableCustomEdges() + v2.decider.assumptionAnalyzer.enableTransitiveEdges() val (tSnap, _) = ssaifyRhs(tRhs, rhs, rhsNew, field.name, field.typ, v3, s3, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit)) val id = BasicChunkIdentifier(field.name) - v2.decider.assumptionAnalyzer.enableCustomEdges() // inhaling the new chunk should not depend on any rhs assertions, dependency to exhaling old chunk is added nevertheless - val newChunk = BasicChunk.createDerivedChunk(Set.empty, FieldID, id, Seq(tRcvr), eRcvrNew.map(Seq(_)), tSnap, rhsNew, FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), + v2.decider.assumptionAnalyzer.disableTransitiveEdges() // inhaling the new chunk should not depend on any rhs assertions, dependency to exhaling old chunk is added nevertheless + val newChunk = BasicChunk.apply(FieldID, id, Seq(tRcvr), eRcvrNew.map(Seq(_)), tSnap, rhsNew, FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), v3.decider.getAnalysisInfo(AssumptionType.Internal)) - v2.decider.assumptionAnalyzer.disableCustomEdges() + v3.decider.assumptionAnalyzer.addDependencyFromExhaleToInhale(newChunk, v3.decider.getAnalysisInfo.sourceInfo) + v2.decider.assumptionAnalyzer.enableTransitiveEdges() chunkSupporter.produce(s3, h3, newChunk, v3)((s4, h4, v4) => { val s5 = s4.copy(h = h4) val (debugHeapName, _) = v4.getDebugOldLabel(s5, fa.pos) diff --git a/src/main/scala/rules/PredicateSupporter.scala b/src/main/scala/rules/PredicateSupporter.scala index ea5ccb9d6..92707655a 100644 --- a/src/main/scala/rules/PredicateSupporter.scala +++ b/src/main/scala/rules/PredicateSupporter.scala @@ -6,8 +6,8 @@ package viper.silicon.rules +import viper.silicon.assumptionAnalysis.AssumptionType import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType -import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.VerificationResult diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 4b53d65d2..77ad10c2f 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -736,7 +736,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v: Verifier) : (PermMapDefinition, PmCache) = { v.decider.analysisSourceInfoStack.setForcedSource("summarizing heap") - v.decider.assumptionAnalyzer.enableCustomEdges() + v.decider.assumptionAnalyzer.disableTransitiveEdges() val res = Verifier.config.mapCache(s.pmCache.get(resource, relevantChunks)) match { // TODO ake: do not get from cache when analysis is enabled case Some(pmDef) => v.decider.assume(pmDef.valueDefinitions, Option.when(withExp)(DebugExp.createInstance("value definitions", isInternal_ = true)), enforceAssumption = false, assumptionType=AssumptionType.Internal) @@ -749,7 +749,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { (pmDef, s.pmCache + ((resource, relevantChunks) -> pmDef)) } v.decider.analysisSourceInfoStack.removeForcedSource() - v.decider.assumptionAnalyzer.disableCustomEdges() + v.decider.assumptionAnalyzer.enableTransitiveEdges() res } @@ -778,7 +778,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optQVarsInstantiations: Option[Seq[Term]] = None) : (SnapshotMapDefinition, SnapshotMapCache) = { v.decider.analysisSourceInfoStack.setForcedSource("summarizing heap") - v.decider.assumptionAnalyzer.enableCustomEdges() + v.decider.assumptionAnalyzer.disableTransitiveEdges() def emitSnapshotMapDefinition(s: State, smDef: SnapshotMapDefinition, @@ -852,7 +852,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { emitSnapshotMapDefinition(s, smDef, v, optQVarsInstantiations) v.decider.analysisSourceInfoStack.removeForcedSource() - v.decider.assumptionAnalyzer.disableCustomEdges() + v.decider.assumptionAnalyzer.enableTransitiveEdges() (smDef, smCache) } @@ -865,7 +865,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optQVarsInstantiations: Option[Seq[Term]] = None) : (State, SnapshotMapDefinition, PermMapDefinition) = { v.decider.analysisSourceInfoStack.setForcedSource("summarizing heap") - v.decider.assumptionAnalyzer.enableCustomEdges() + v.decider.assumptionAnalyzer.disableTransitiveEdges() val (smDef, smCache) = summarisingSnapshotMap( s, resource, codomainQVars, relevantChunks, v, optSmDomainDefinitionCondition, optQVarsInstantiations) @@ -878,7 +878,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val s2 = s1.copy(pmCache = pmCache) v.decider.analysisSourceInfoStack.removeForcedSource() - v.decider.assumptionAnalyzer.disableCustomEdges() + v.decider.assumptionAnalyzer.enableTransitiveEdges() (s2, smDef, pmDef) } @@ -892,7 +892,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v: Verifier) : (State, PermMapDefinition) = { v.decider.analysisSourceInfoStack.setForcedSource("summarizing heap") - v.decider.assumptionAnalyzer.enableCustomEdges() + v.decider.assumptionAnalyzer.disableTransitiveEdges() val s1 = s val (pmDef, pmCache) = quantifiedChunkSupporter.summarisingPermissionMap( @@ -900,7 +900,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val s2 = s1.copy(pmCache = pmCache) v.decider.analysisSourceInfoStack.removeForcedSource() - v.decider.assumptionAnalyzer.disableCustomEdges() + v.decider.assumptionAnalyzer.enableTransitiveEdges() (s2, pmDef) } diff --git a/src/main/scala/rules/StateConsolidator.scala b/src/main/scala/rules/StateConsolidator.scala index 57130721e..a8fd4d1af 100644 --- a/src/main/scala/rules/StateConsolidator.scala +++ b/src/main/scala/rules/StateConsolidator.scala @@ -104,17 +104,17 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol val resource = Resources.resourceDescriptions(ch.resourceID) val pathCond = interpreter.buildPathConditionsForChunk(ch, resource.instanceProperties(s.mayAssumeUpperBounds)) v.decider.assumptionAnalyzer.addForcedChunkDependencies(Set(ch)) - v.decider.assumptionAnalyzer.enableCustomEdges() + v.decider.assumptionAnalyzer.disableTransitiveEdges() pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), AssumptionType.Internal)) v.decider.assumptionAnalyzer.unsetForcedDependencies() - v.decider.assumptionAnalyzer.disableCustomEdges() + v.decider.assumptionAnalyzer.enableTransitiveEdges() } Resources.resourceDescriptions foreach { case (id, desc) => val pathCond = interpreter.buildPathConditionsForResource(id, desc.delayedProperties(s.mayAssumeUpperBounds)) - v.decider.assumptionAnalyzer.enableCustomEdges() + v.decider.assumptionAnalyzer.disableTransitiveEdges() pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), AssumptionType.Internal)) - v.decider.assumptionAnalyzer.disableCustomEdges() + v.decider.assumptionAnalyzer.enableTransitiveEdges() } v.symbExLog.closeScope(sepIdentifier) diff --git a/src/main/scala/state/Chunks.scala b/src/main/scala/state/Chunks.scala index 1aaa82d24..70a021d6d 100644 --- a/src/main/scala/state/Chunks.scala +++ b/src/main/scala/state/Chunks.scala @@ -46,24 +46,6 @@ object BasicChunk { new BasicChunk(resourceID, id, args, argsExp, snap, snapExp, finalPerm, permExp)}, perm, analysisInfo, isExhale) } - - def createDerivedChunk(oldChunks: Set[Chunk], - resourceID: BaseID, - id: BasicChunkIdentifier, - args: Seq[Term], - argsExp: Option[Seq[ast.Exp]], - snap: Term, - snapExp: Option[ast.Exp], - perm: Term, - permExp: Option[ast.Exp], - analysisInfo: AnalysisInfo, - isExhale: Boolean=false): BasicChunk = { - val newChunk = new BasicChunk(resourceID, id, args, argsExp, snap, snapExp, perm, permExp) - val newNode = analysisInfo.assumptionAnalyzer.addPermissionNode(newChunk, perm, analysisInfo.sourceInfo, analysisInfo.assumptionType, isExhale) - analysisInfo.assumptionAnalyzer.addPermissionDependencies(oldChunks, newNode) - analysisInfo.assumptionAnalyzer.addDependencyFromExhaleToInhale(newNode) - newChunk // TODO ake: registerChunk - } } case class BasicChunk private (resourceID: BaseID, @@ -82,11 +64,6 @@ case class BasicChunk private (resourceID: BaseID, case PredicateID => require(snap.sort == sorts.Snap, s"A predicate chunk's snapshot ($snap) is expected to be of sort Snap, but found ${snap.sort}") } - override def getAnalysisInfo: String = perm.toString + " for " + (resourceID match { - case PredicateID => id.name + "(" + args.mkString(", ") + ")" - case FieldID => args.head.toString + "." + id.name - }) - override protected def applyCondition(newCond: Term, newCondExp: Option[ast.Exp]): BasicChunk = withPerm(Ite(newCond, perm, NoPerm), newCondExp.map(nce => ast.CondExp(nce, permExp.get, ast.NoPerm()())())) From 7cfe1f126ec7053047db817ba9fd6808dd3dfb10 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 30 Jun 2025 16:00:41 +0200 Subject: [PATCH 131/474] remove forced dependencies workaround --- .../assumptionAnalysis/AssumptionAnalyzer.scala | 15 --------------- src/main/scala/rules/StateConsolidator.scala | 14 ++++---------- 2 files changed, 4 insertions(+), 25 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index f357fbf3c..f94e4b888 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -11,7 +11,6 @@ import viper.silver.ast trait AssumptionAnalyzer { val assumptionGraph: AssumptionAnalysisGraph = new DefaultAssumptionAnalysisGraph() - var forcedDependencies: List[Int] = List.empty protected var isClosed_ = false def disableTransitiveEdges(): Unit = { @@ -53,18 +52,6 @@ trait AssumptionAnalyzer { def exportGraph(): Unit - def addForcedDependencies(ids: Set[Int]): Unit = { - forcedDependencies = forcedDependencies ++ ids - } - - def addForcedChunkDependencies(chunks: Set[Chunk]): Unit = { - addForcedDependencies(getChunkNodeIds(chunks)) - } - - def unsetForcedDependencies(): Unit = { - forcedDependencies = List.empty - } - def createAndAssumeLabelNode(decider: Decider, sourceNodeIds: Iterable[Int]): LabelNode = LabelNode(True) def createLabelledConditionalChunks(decider: Decider, sourceChunks: Iterable[Chunk], thenTerm: Term): Term = thenTerm def createLabelledConditional(decider: Decider, sourceTerms: Iterable[Term], term: Term): Term = term @@ -140,14 +127,12 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { override def addNodes(nodes: Iterable[AssumptionAnalysisNode]): Unit = { assumptionGraph.addNodes(nodes) - assumptionGraph.addEdges(forcedDependencies, nodes.map(_.id)) } override def getNodes: Iterable[AssumptionAnalysisNode] = assumptionGraph.nodes override def addNode(node: AssumptionAnalysisNode): Unit = { assumptionGraph.addNode(node) - assumptionGraph.addEdges(forcedDependencies, node.id) } override def addSingleAssumption(assumption: DebugExp, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { diff --git a/src/main/scala/rules/StateConsolidator.scala b/src/main/scala/rules/StateConsolidator.scala index a8fd4d1af..0175d0a92 100644 --- a/src/main/scala/rules/StateConsolidator.scala +++ b/src/main/scala/rules/StateConsolidator.scala @@ -83,9 +83,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol val (_functionRecorder, _mergedChunks, _newChunks, snapEqs) = singleMerge(functionRecorder, destChunks, newChunks, s.functionRecorderQuantifiedVariables().map(_._1), v) - v.decider.assumptionAnalyzer.addForcedChunkDependencies(_newChunks.toSet) snapEqs foreach (t => v.decider.assume(t, Option.when(withExp)(DebugExp.createInstance("Snapshot Equations", isInternal_ = true)), AssumptionType.Internal)) - v.decider.assumptionAnalyzer.unsetForcedDependencies() functionRecorder = _functionRecorder mergedChunks = _mergedChunks @@ -103,10 +101,8 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol mergedChunks.filter(_.isInstanceOf[BasicChunk]) foreach { case ch: BasicChunk => val resource = Resources.resourceDescriptions(ch.resourceID) val pathCond = interpreter.buildPathConditionsForChunk(ch, resource.instanceProperties(s.mayAssumeUpperBounds)) - v.decider.assumptionAnalyzer.addForcedChunkDependencies(Set(ch)) v.decider.assumptionAnalyzer.disableTransitiveEdges() - pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), AssumptionType.Internal)) - v.decider.assumptionAnalyzer.unsetForcedDependencies() + pathCond.foreach(p => v.decider.assume(v.decider.assumptionAnalyzer.createLabelledConditionalChunks(v.decider, Set(ch), p._1), Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), AssumptionType.Internal)) v.decider.assumptionAnalyzer.enableTransitiveEdges() } @@ -210,11 +206,9 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol // and returns the merged chunk or None, if the chunks could not be merged private def mergeChunks(fr1: FunctionRecorder, chunk1: Chunk, chunk2: Chunk, qvars: Seq[Var], v: Verifier): Option[(FunctionRecorder, Chunk, Term)] = { val result = mergeChunks1(fr1, chunk1, chunk2, qvars, v) - if(result.isDefined){ - val (_, newChunk, _) = result.get - v.decider.assumptionAnalyzer.addPermissionDependencies(Set(chunk1, chunk2), newChunk) - } - result + result.map({case (fRec, ch, snapEq) => + v.decider.assumptionAnalyzer.addPermissionDependencies(Set(chunk1, chunk2), ch) + (fRec, ch, v.decider.assumptionAnalyzer.createLabelledConditionalChunks(v.decider, Set(chunk1, chunk2), snapEq))}) } private def mergeChunks1(fr1: FunctionRecorder, chunk1: Chunk, chunk2: Chunk, qvars: Seq[Var], v: Verifier): Option[(FunctionRecorder, Chunk, Term)] = { From 3af6d0ff2abe8302b59fcd17d501cbef78ac79f6 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 30 Jun 2025 17:28:53 +0200 Subject: [PATCH 132/474] cleanup assumption analyzer interface and label nodes --- .../AssumptionAnalysisGraph.scala | 2 +- .../AssumptionAnalyzer.scala | 107 +++++------------- src/main/scala/decider/Decider.scala | 57 +++++----- src/main/scala/decider/PathConditions.scala | 6 +- .../NonQuantifiedPropertyInterpreter.scala | 4 +- src/main/scala/rules/Consumer.scala | 4 +- src/main/scala/rules/Evaluator.scala | 4 +- src/main/scala/rules/Executor.scala | 24 ++-- src/main/scala/rules/Joiner.scala | 10 +- .../scala/rules/QuantifiedChunkSupport.scala | 12 +- src/main/scala/rules/StateConsolidator.scala | 7 +- src/main/scala/state/State.scala | 4 +- .../functions/FunctionVerificationUnit.scala | 6 +- 13 files changed, 95 insertions(+), 152 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index 8d4caedf9..9260e36f6 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -21,7 +21,7 @@ object AssumptionAnalysisGraphHelper { } trait AssumptionAnalysisGraph { - var nodes: Seq[AssumptionAnalysisNode] + var nodes: mutable.Seq[AssumptionAnalysisNode] var edges: mutable.Map[Int, Set[Int]] var transitiveEdges: mutable.Map[Int, Set[Int]] = mutable.Map.empty diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index f94e4b888..fc823ba68 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -1,10 +1,8 @@ package viper.silicon.assumptionAnalysis import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType -import viper.silicon.debugger.DebugExp -import viper.silicon.decider.Decider -import viper.silicon.interfaces.state.Chunk -import viper.silicon.state.terms.{False, Implies, Ite, NoPerm, Term, True} +import viper.silicon.interfaces.state.{Chunk, GeneralChunk} +import viper.silicon.state.terms._ import viper.silicon.verifier.Verifier import viper.silver.ast @@ -29,9 +27,6 @@ trait AssumptionAnalyzer { def addNode(node: AssumptionAnalysisNode): Unit def getNodes: Iterable[AssumptionAnalysisNode] - def addSingleAssumption(assumption: DebugExp, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] - - def addAssumptions(assumptions: Iterable[DebugExp], analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Seq[Int] def addAssumption(assumption: ast.Exp, term: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] def addAssumption(assumption: String, term: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] @@ -52,14 +47,10 @@ trait AssumptionAnalyzer { def exportGraph(): Unit - def createAndAssumeLabelNode(decider: Decider, sourceNodeIds: Iterable[Int]): LabelNode = LabelNode(True) - def createLabelledConditionalChunks(decider: Decider, sourceChunks: Iterable[Chunk], thenTerm: Term): Term = thenTerm - def createLabelledConditional(decider: Decider, sourceTerms: Iterable[Term], term: Term): Term = term - def createLabelledConditional(decider: Decider, sourceTerms: Iterable[Term], terms: Seq[Term]): Seq[Term] = terms - def reassumeLabels(decider: Decider): Unit = {} - - def createLabelledConditional(decider: Decider, sourceNodeId: Int, term: Term): Term = term - + def createLabelNode(labelTerm: Term, sourceChunks: Iterable[Chunk], sourceTerms: Iterable[Term]): LabelNode = LabelNode(True) + def registerDerivedChunk[CH <: GeneralChunk](sourceChunk: CH, buildChunk: (Term => CH), perm: Term, labelNode: LabelNode, analysisInfo: AnalysisInfo, isExhale: Boolean, createLabel: Boolean=true): CH = buildChunk(perm) + def registerChunk[CH <: GeneralChunk](buildChunk: (Term => CH), perm: Term, labelNode: LabelNode, analysisInfo: AnalysisInfo, isExhale: Boolean): CH = buildChunk(perm) + def getReassumeLabelNodes: Iterable[LabelNode] = Set.empty } object AssumptionAnalyzer { @@ -135,14 +126,6 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { assumptionGraph.addNode(node) } - override def addSingleAssumption(assumption: DebugExp, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { - val node = - if(assumption.originalExp.isDefined) SimpleAssumptionNode(assumption.originalExp.get, assumption.term.getOrElse(True) /* TODO ake */, analysisSourceInfo, AssumptionAnalyzer.extractAssumptionTypeFromInfo(assumption.originalExp.get.info).getOrElse(assumptionType), isClosed_) - else StringAssumptionNode(assumption.description.getOrElse("unknown"), assumption.term.getOrElse(True) /* TODO ake */, analysisSourceInfo, assumptionType, isClosed_) - addNode(node) - Some(node.id) - } - override def addAssumption(assumption: ast.Exp, term: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { val node = SimpleAssumptionNode(assumption, term, analysisSourceInfo, AssumptionAnalyzer.extractAssumptionTypeFromInfo(assumption.info).getOrElse(assumptionType), isClosed_) @@ -157,16 +140,6 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { Some(node.id) } - override def addAssumptions(assumptions: Iterable[DebugExp], analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Seq[Int] = { - val newNodes = assumptions.toSeq.map(a => - if (a.originalExp.isDefined) SimpleAssumptionNode(a.originalExp.get, a.term.getOrElse(True) /* TODO ake */, if(analysisSourceInfo.isInstanceOf[NoAnalysisSourceInfo]) ExpAnalysisSourceInfo(a.originalExp.get) else analysisSourceInfo, - AssumptionAnalyzer.extractAssumptionTypeFromInfo(a.originalExp.get.info).getOrElse(assumptionType), isClosed_) - else StringAssumptionNode(a.description.getOrElse("unknown"), a.term.getOrElse(True) /* TODO ake */, analysisSourceInfo, AssumptionType.Internal, isClosed_) - ) - newNodes foreach addNode - newNodes.map(_.id) - } - override def createAssertOrCheckNode(term: Term, assertion: Either[String, ast.Exp], analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] = { if(isCheck) return Some(SimpleCheckNode(term, analysisSourceInfo, isClosed_)) @@ -279,66 +252,44 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { assumptionGraph.exportGraph(Verifier.config.assumptionAnalysisExportPath() + "/" + foldername.getOrElse("latestExport")) } - override def createLabelledConditionalChunks(decider: Decider, sourceChunks: Iterable[Chunk], thenTerm: Term): Term = { - val sourceNodeIds = getChunkNodeIds(sourceChunks.toSet) - val labelNode = createAndAssumeLabelNode(decider, sourceNodeIds) - Implies(labelNode.term, thenTerm) - } - private def createLabelNode(decider: Decider, sourceNodeIds: Iterable[Int]): LabelNode = { - val (label, _) = decider.fresh(ast.LocalVar("analysisLabel", ast.Bool)()) - val labelNode = LabelNode(label) + override def createLabelNode(labelTerm: Term, sourceChunks: Iterable[Chunk], sourceTerms: Iterable[Term]): LabelNode = { + val labelNode = LabelNode(labelTerm) addNode(labelNode) - assumptionGraph.addEdges(sourceNodeIds, labelNode.id) originalLabelNodes = originalLabelNodes :+ labelNode + assumptionGraph.addEdges(getChunkNodeIds(sourceChunks.toSet) ++ getNodeIdsByTerm(sourceTerms.toSet), labelNode.id) labelNode } - override def createAndAssumeLabelNode(decider: Decider, sourceNodeIds: Iterable[Int]): LabelNode = { - val labelNode = createLabelNode(decider, sourceNodeIds) - val smtLabel = AssumptionAnalyzer.createAssumptionLabel(Some(labelNode.id)) - decider.assumeLabel(labelNode.term, smtLabel) - labelNode + override def registerChunk[CH <: GeneralChunk](buildChunk: (Term => CH), perm: Term, labelNode: LabelNode, analysisInfo: AnalysisInfo, isExhale: Boolean): CH = { + val chunk = if(isExhale) buildChunk(perm) else buildChunk(Ite(labelNode.term, perm, NoPerm)) + val chunkNode = addPermissionNode(chunk, chunk.perm, analysisInfo.sourceInfo, analysisInfo.assumptionType, isExhale) + if(chunkNode.isDefined) + addDependency(chunkNode.get, labelNode.id) + chunk } - override def createLabelledConditional(decider: Decider, sourceTerms: Iterable[Term], term: Term): Term = { - if(term.equals(True)) return term - val sourceNodeIds = getNodeIdsByTerm(sourceTerms.toSet) // TODO ake: maybe we have node id as argument - if(sourceNodeIds.isEmpty) { - term - } else { - val labelNode = createAndAssumeLabelNode(decider, sourceNodeIds) - Implies(labelNode.term, term) - } + override def registerDerivedChunk[CH <: GeneralChunk](sourceChunk: CH, buildChunk: (Term => CH), perm: Term, labelNode: LabelNode, analysisInfo: AnalysisInfo, isExhale: Boolean, createLabel: Boolean=true): CH = { + val chunk = if(isExhale) buildChunk(perm) else buildChunk(Ite(labelNode.term, perm, NoPerm)) + val chunkNode = addPermissionNode(chunk, chunk.perm, analysisInfo.sourceInfo, analysisInfo.assumptionType, isExhale) + if(chunkNode.isDefined) + addDependency(chunkNode.get, labelNode.id) + addPermissionDependencies(Set(sourceChunk), chunkNode) + chunk } - override def createLabelledConditional(decider: Decider, sourceNodeId: Int, term: Term): Term = { - val labelNode = createAndAssumeLabelNode(decider, Set(sourceNodeId)) - Implies(labelNode.term, term) - } - override def createLabelledConditional(decider: Decider, sourceTerms: Iterable[Term], terms: Seq[Term]): Seq[Term] = { - if(!(terms exists (t => !t.equals(True)))) return terms - val sourceNodeIds = getNodeIdsByTerm(sourceTerms.toSet) // TODO ake: maybe we have node id as argument - if(sourceNodeIds.isEmpty) { - terms - } else { - val labelNode = createAndAssumeLabelNode(decider, sourceNodeIds) - terms map (t => Implies(labelNode.term, t)) - } - } - private def reassumeLabel(decider: Decider, oldLabelNode: LabelNode): Unit = { + private def createReassumeLabelNode(oldLabelNode: LabelNode): LabelNode = { val newLabelNode = LabelNode(oldLabelNode.term) // do not add to originalLabelNodes! addNode(newLabelNode) assumptionGraph.addEdges(Set(oldLabelNode.id), newLabelNode.id) - val smtLabel = AssumptionAnalyzer.createAssumptionLabel(Some(newLabelNode.id)) - decider.assumeLabel(oldLabelNode.term, smtLabel) + newLabelNode } - override def reassumeLabels(decider: Decider): Unit = { // TODO ake: work with scopes! - originalLabelNodes foreach (reassumeLabel(decider, _)) // assumes label + override def getReassumeLabelNodes: Iterable[LabelNode] = { // TODO ake: work with scopes! + originalLabelNodes map createReassumeLabelNode } } @@ -347,9 +298,6 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { override def getNodes: Iterable[AssumptionAnalysisNode] = Seq() override def addNode(node: AssumptionAnalysisNode): Unit = {} override def addNodes(nodes: Iterable[AssumptionAnalysisNode]): Unit = {} - - override def addAssumptions(assumptions: Iterable[DebugExp], analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Seq[Int] = Seq.empty - override def createAssertOrCheckNode(term: Term, assertion: Either[String, ast.Exp], analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] = None override def addAssertFalseNode(isCheck: Boolean, sourceInfo: AnalysisSourceInfo): Option[Int] = None @@ -372,9 +320,6 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { override def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNodeId: Chunk): Unit = {} def addDependency(source: Int, dest: Int): Unit = {} override def getMember: Option[ast.Member] = None - - override def addSingleAssumption(assumption: DebugExp, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None - override def addAssumption(assumption: ast.Exp, term: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None override def addAssumption(assumption: String, term: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 0cff95285..9e6cc70f3 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -14,7 +14,7 @@ import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.interfaces._ import viper.silicon.interfaces.decider._ -import viper.silicon.interfaces.state.GeneralChunk +import viper.silicon.interfaces.state.{Chunk, GeneralChunk} import viper.silicon.logger.records.data.{DeciderAssertRecord, DeciderAssumeRecord, ProverAssertRecord} import viper.silicon.state._ import viper.silicon.state.terms.{Term, _} @@ -63,6 +63,8 @@ trait Decider { def registerChunk[CH <: GeneralChunk](buildChunk: (Term => CH), perm: Term, analysisInfo: AnalysisInfo, isExhale: Boolean): CH def registerDerivedChunk[CH <: GeneralChunk](sourceChunk: CH, buildChunk: (Term => CH), perm: Term, analysisInfo: AnalysisInfo, isExhale: Boolean, createLabel: Boolean=true): CH + def wrapWithAssumptionAnalysisLabel(term: Term, sourceChunks: Iterable[Chunk] = Set.empty, sourceTerms: Iterable[Term] = Set.empty): Term + def wrapPermissionWithAssumptionAnalysisLabel(perm: Term, sourceChunks: Iterable[Chunk]): Term def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp], assumptionType: AssumptionType): Unit def assume(t: Term, debugExp: Option[DebugExp], assumptionType: AssumptionType): Unit @@ -308,36 +310,31 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } def registerChunk[CH <: GeneralChunk](buildChunk: (Term => CH), perm: Term, analysisInfo: AnalysisInfo, isExhale: Boolean): CH = { - - val (chunk, labelNodeId) = if(isExhale) { - (buildChunk(perm), None) - }else { - val labelNode = assumptionAnalyzer.createAndAssumeLabelNode(this, Set()) - val finalPerm = Ite(labelNode.term, perm, NoPerm) - (buildChunk(finalPerm), Some(labelNode.id)) - } - - val chunkNode = assumptionAnalyzer.addPermissionNode(chunk, chunk.perm, analysisInfo.sourceInfo, analysisInfo.assumptionType, isExhale) - if(chunkNode.isDefined && labelNodeId.isDefined) - assumptionAnalyzer.addDependency(chunkNode.get, labelNodeId.get) - chunk + assumptionAnalyzer.registerChunk(buildChunk, perm, createAnalysisLabelNode(), analysisInfo, isExhale) } def registerDerivedChunk[CH <: GeneralChunk](sourceChunk: CH, buildChunk: (Term => CH), perm: Term, analysisInfo: AnalysisInfo, isExhale: Boolean, createLabel: Boolean=true): CH = { - val (newChunk, labelNodeId) = if(!createLabel || isExhale) { - (buildChunk(perm), None) - }else { - val labelNode = assumptionAnalyzer.createAndAssumeLabelNode(this, Set()) - val finalPerm = Ite(labelNode.term, perm, NoPerm) - (buildChunk(finalPerm), Some(labelNode.id)) - } + assumptionAnalyzer.registerDerivedChunk(sourceChunk, buildChunk, perm, createAnalysisLabelNode(Set(sourceChunk), Set.empty), analysisInfo, isExhale) + } - val newChunkNode = assumptionAnalyzer.addPermissionNode(newChunk, newChunk.perm, analysisInfo.sourceInfo, analysisInfo.assumptionType, isExhale) - assumptionAnalyzer.addPermissionDependencies(Set(sourceChunk), newChunkNode) - if(newChunkNode.isDefined && labelNodeId.isDefined) - assumptionAnalyzer.addDependency(newChunkNode.get, labelNodeId.get) - newChunk + private def createAnalysisLabelNode(sourceChunks: Iterable[Chunk] = Set.empty, sourceTerms: Iterable[Term] = Set.empty): LabelNode = { + val (label, _) = fresh(ast.LocalVar("analysisLabel", ast.Bool)()) + val labelNode = assumptionAnalyzer.createLabelNode(label, sourceChunks, sourceTerms) + val smtLabel = AssumptionAnalyzer.createAssumptionLabel(Some(labelNode.id)) + assumeLabel(label, smtLabel) + labelNode + } + + def wrapWithAssumptionAnalysisLabel(term: Term, sourceChunks: Iterable[Chunk] = Set.empty, sourceTerms: Iterable[Term] = Set.empty): Term = { + if(!Verifier.config.enableAssumptionAnalysis()) return term + val labelNode = createAnalysisLabelNode(sourceChunks, sourceTerms) + Implies(labelNode.term, term) + } + def wrapPermissionWithAssumptionAnalysisLabel(perm: Term, sourceChunks: Iterable[Chunk]): Term = { + if(!Verifier.config.enableAssumptionAnalysis()) return perm + val labelNode = createAnalysisLabelNode(sourceChunks, Set.empty) + Ite(labelNode.term, perm, NoPerm) } def addDebugExp(e: DebugExp): Unit = { @@ -471,6 +468,10 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => prover.assume(term, assumptionLabel) } + def reassumeAssumptionAnalysisLabels(): Unit = { + assumptionAnalyzer.getReassumeLabelNodes foreach (node => decider.assumeLabel(node.term, AssumptionAnalyzer.createAssumptionLabel(Some(node.id)))) + } + /* Asserting facts */ def checkSmoke(isAssert: Boolean = false): Boolean = { @@ -480,7 +481,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => }else{ "" } if(_isReassumeAnalysisLabelsRequired) { - assumptionAnalyzer.reassumeLabels(this) + reassumeAssumptionAnalysisLabels() _isReassumeAnalysisLabelsRequired = false } @@ -553,7 +554,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => val sepIdentifier = symbExLog.openScope(assertRecord) if(_isReassumeAnalysisLabelsRequired) { - assumptionAnalyzer.reassumeLabels(this) + reassumeAssumptionAnalysisLabels() _isReassumeAnalysisLabelsRequired = false } diff --git a/src/main/scala/decider/PathConditions.scala b/src/main/scala/decider/PathConditions.scala index 37721ea18..b0c96b7c7 100644 --- a/src/main/scala/decider/PathConditions.scala +++ b/src/main/scala/decider/PathConditions.scala @@ -337,8 +337,8 @@ private trait LayeredPathConditionStackLike { val ignores = ignore.topLevelConjuncts for (layer <- layers) { - val actualBranchCondition = layer.branchCondition.map(a => v.decider.assumptionAnalyzer.createLabelledConditional(v.decider, Set(a), a)).getOrElse(True) - val relevantNonGlobals = (layer.nonGlobalAssumptions -- ignores).map(a => v.decider.assumptionAnalyzer.createLabelledConditional(v.decider, Set(a), a)) + val actualBranchCondition = layer.branchCondition.map(a => v.decider.wrapWithAssumptionAnalysisLabel(a, Set.empty, Set(a))).getOrElse(True) + val relevantNonGlobals = (layer.nonGlobalAssumptions -- ignores).map(a => v.decider.wrapWithAssumptionAnalysisLabel(a, Set.empty, Set(a))) val (trueNonGlobals, additionalGlobals) = if (!actualBranchCondition.existsDefined{ case t if qvars.contains(t) => }) { // The branch condition is independent of all quantified variables // Any assumptions that are also independent of all quantified variables can be treated as global assumptions. @@ -348,7 +348,7 @@ private trait LayeredPathConditionStackLike { (relevantNonGlobals, Seq()) } - globals ++= layer.globalAssumptions.map(a => v.decider.assumptionAnalyzer.createLabelledConditional(v.decider, Set(a), a)) ++ additionalGlobals + globals ++= layer.globalAssumptions.map(a => v.decider.wrapWithAssumptionAnalysisLabel(a, Set.empty, Set(a))) ++ additionalGlobals nonGlobals :+= Quantification( diff --git a/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala b/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala index 118d96912..2ed2b3fd8 100644 --- a/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala +++ b/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala @@ -122,7 +122,7 @@ class NonQuantifiedPropertyInterpreter(heap: Iterable[Chunk], verifier: Verifier val conditionTerm = buildPathCondition(condition, info)._1 if (verifier.decider.check(conditionTerm, Verifier.config.checkTimeout())) { val (term, exp) = buildPathCondition(thenDo, info) - (verifier.decider.assumptionAnalyzer.createLabelledConditional(verifier.decider, Set(conditionTerm), term), exp) + (verifier.decider.wrapWithAssumptionAnalysisLabel(term, Set.empty, Set(conditionTerm)), exp) } else { buildPathCondition(otherwise, info) } @@ -157,7 +157,7 @@ class NonQuantifiedPropertyInterpreter(heap: Iterable[Chunk], verifier: Verifier // TODO: Is it possible to get this behavior without having to check every tuple? if (!info.pm.values.exists(chunk eq _)) { val (resTerm, resExp) = builder(chunk) - Some((verifier.decider.assumptionAnalyzer.createLabelledConditionalChunks(verifier.decider, Set(chunk), resTerm), resExp)) + Some((verifier.decider.wrapWithAssumptionAnalysisLabel(resTerm, Set(chunk)), resExp)) } else { None } diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index 68e3c0f91..8b8bae7a7 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -572,9 +572,9 @@ object consumer extends ConsumptionRules { case Seq(entry1, entry2) => // Both branches are alive // TODO ake: precision? val branchConditions1 = entry1.pathConditions.branchConditions - val labelledBranchConditions1 = v.decider.assumptionAnalyzer.createLabelledConditional(v.decider, branchConditions1, And(branchConditions1)) + val labelledBranchConditions1 = v.decider.wrapWithAssumptionAnalysisLabel(And(branchConditions1), Set.empty, branchConditions1) val branchConditions2 = entry2.pathConditions.branchConditions - val labelledBranchConditions2 = v.decider.assumptionAnalyzer.createLabelledConditional(v.decider, branchConditions2, And(branchConditions2)) + val labelledBranchConditions2 = v.decider.wrapWithAssumptionAnalysisLabel(And(branchConditions2), Set.empty, branchConditions2) val mergedData = ( State.mergeHeap( entry1.data._1, labelledBranchConditions1, Option.when(withExp)(BigAnd(entry1.pathConditions.branchConditionExps.map(_._2.get))), diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index fad57827d..4f9c04c79 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -1662,7 +1662,7 @@ object evaluator extends EvaluationRules { val r = evals(s, remainingTriggerExpressions, _ => pve, v)((_, remainingTriggerTerms, _, v1) => { optRemainingTriggerTerms = Some(remainingTriggerTerms) - pcDelta = v1.decider.pcs.after(preMark).assumptions map (t => v1.decider.assumptionAnalyzer.createLabelledConditional(v.decider, Set(t), t)) //decider.π -- πPre + pcDelta = v1.decider.pcs.after(preMark).assumptions map (t => v1.decider.wrapWithAssumptionAnalysisLabel(t, Set.empty, Set(t))) //decider.π -- πPre pcDeltaExp = v1.decider.pcs.after(preMark).assumptionExps Success()}) @@ -1711,7 +1711,7 @@ object evaluator extends EvaluationRules { val joinDefEqs: Seq[(Term, Option[ast.Exp], Option[ast.Exp])] = entries map (entry =>{ // TODO ake: precision? val branchConditions = entry.pathConditions.branchConditions - val labelledBranchCondition = v.decider.assumptionAnalyzer.createLabelledConditional(v.decider, branchConditions, And(branchConditions)) + val labelledBranchCondition = v.decider.wrapWithAssumptionAnalysisLabel(And(branchConditions), Set.empty, branchConditions) (Implies(labelledBranchCondition, BuiltinEquals(joinTerm, entry.data._1)), Option.when(withExp)(ast.Implies(BigAnd(entry.pathConditions.branchConditionExps.map(bc => bc._1)), ast.EqCmp(joinExp.get, entry.data._2.get)())()), Option.when(withExp)(ast.Implies(BigAnd(entry.pathConditions.branchConditionExps.map(bc => bc._2.get)), ast.EqCmp(joinExp.get, entry.data._2.get)())())) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index fce9885bc..b1096106e 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -6,19 +6,11 @@ package viper.silicon.rules -import viper.silicon.debugger.DebugExp -import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.Config.JoinMode import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType -import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionAnalyzer, AssumptionType, ExpAnalysisSourceInfo, PermissionInhaleNode, StmtAnalysisSourceInfo, StringAnalysisSourceInfo} - -import scala.annotation.unused -import viper.silver.cfg.silver.SilverCfg -import viper.silver.cfg.silver.SilverCfg.{SilverBlock, SilverEdge} -import viper.silver.verifier.{CounterexampleTransformer, PartialVerificationError} -import viper.silver.verifier.errors._ -import viper.silver.verifier.reasons._ -import viper.silver.{ast, cfg} +import viper.silicon.assumptionAnalysis.{AssumptionAnalyzer, AssumptionType, StmtAnalysisSourceInfo} +import viper.silicon.common.collections.immutable.InsertionOrderedSet +import viper.silicon.debugger.DebugExp import viper.silicon.decider.RecordedPathConditions import viper.silicon.interfaces._ import viper.silicon.interfaces.state.{NonQuantifiedChunk, QuantifiedChunk} @@ -30,7 +22,15 @@ import viper.silicon.state.terms.predef.`?r` import viper.silicon.utils.ast.{BigAnd, extractPTypeFromExp, simplifyVariableName} import viper.silicon.utils.freshSnap import viper.silicon.verifier.Verifier +import viper.silver.cfg.silver.SilverCfg +import viper.silver.cfg.silver.SilverCfg.{SilverBlock, SilverEdge} import viper.silver.cfg.{ConditionalEdge, StatementBlock} +import viper.silver.verifier.errors._ +import viper.silver.verifier.reasons._ +import viper.silver.verifier.{CounterexampleTransformer, PartialVerificationError} +import viper.silver.{ast, cfg} + +import scala.annotation.unused trait ExecutionRules extends SymbolicExecutionRules { def exec(s: State, @@ -280,7 +280,7 @@ object executor extends ExecutionRules { intermediateResult combine executionFlowController.locally(s2, v1)((s3, v2) => { v2.decider.declareAndRecordAsFreshFunctions(ff1 -- v2.decider.freshFunctions) /* [BRANCH-PARALLELISATION] */ // TODO ake: pcs.assumptionExps without exps do not have a source, but setting the source here will result in all invariants having the same source - v2.decider.assume(pcs.assumptions map (t => v.decider.assumptionAnalyzer.createLabelledConditional(v2.decider, Set(t), t)), Some(pcs.assumptionExps), "Loop invariant", enforceAssumption=false, assumptionType=AssumptionType.LoopInvariant) + v2.decider.assume(pcs.assumptions map (t => v.decider.wrapWithAssumptionAnalysisLabel(t, Set.empty, Set(t))), Some(pcs.assumptionExps), "Loop invariant", enforceAssumption=false, assumptionType=AssumptionType.LoopInvariant) v2.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) if (v2.decider.checkSmoke()) Success() diff --git a/src/main/scala/rules/Joiner.scala b/src/main/scala/rules/Joiner.scala index 7e0bdd9cc..04dbd5cd5 100644 --- a/src/main/scala/rules/Joiner.scala +++ b/src/main/scala/rules/Joiner.scala @@ -7,20 +7,18 @@ package viper.silicon.rules import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType, StringAnalysisSourceInfo} -import viper.silicon.debugger.DebugExp import viper.silicon.common.collections.immutable.InsertionOrderedSet +import viper.silicon.debugger.DebugExp import viper.silicon.decider.RecordedPathConditions import viper.silicon.interfaces.{Success, VerificationResult} import viper.silicon.logger.records.structural.JoiningRecord import viper.silicon.state.State -import viper.silicon.state.terms.{And, Or, Term, True} +import viper.silicon.state.terms.{And, Or, Term} import viper.silicon.utils.ast.{BigAnd, BigOr} import viper.silicon.verifier.Verifier import viper.silver.ast import viper.silver.ast.NoPosition -import scala.annotation.unused - case class JoinDataEntry[D](s: State, data: D, pathConditions: RecordedPathConditions) { // Instead of merging states by calling State.merge, // we can directly merge JoinDataEntries to obtain new States, @@ -105,10 +103,10 @@ object joiner extends JoiningRules { val pcsExp = Option.when(withExp)(entry.pathConditions.conditionalizedExp) val comment = "Joined path conditions" v.decider.prover.comment(comment) - val pcsLabelled = v.decider.assumptionAnalyzer.createLabelledConditional(v.decider, pcsDependencies, pcs) + val pcsLabelled = pcs map (pcsEntry => v.decider.wrapWithAssumptionAnalysisLabel(pcsEntry, Set.empty, pcsDependencies)) v.decider.assume(pcsLabelled, pcsExp, comment, enforceAssumption = false, assumptionType=AssumptionType.Internal) val branchConditions = entry.pathConditions.branchConditions - val branchConditionsLabelled = v.decider.assumptionAnalyzer.createLabelledConditional(v.decider, branchConditions, And(branchConditions)) + val branchConditionsLabelled = v.decider.wrapWithAssumptionAnalysisLabel(And(branchConditions), Set.empty, branchConditions) feasibleBranches = branchConditionsLabelled :: feasibleBranches feasibleBranchesExp = feasibleBranchesExp.map(fbe => BigAnd(entry.pathConditions.branchConditionExps.map(_._1)) :: fbe) feasibleBranchesExpNew = feasibleBranchesExpNew.map(fbe => BigAnd(entry.pathConditions.branchConditionExps.map(_._2.get)) :: fbe) diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 77ad10c2f..6e5b83e64 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -31,7 +31,7 @@ import viper.silver.reporter.InternalWarningMessage import viper.silver.verifier.reasons.{InsufficientPermission, MagicWandChunkNotFound} import viper.silver.verifier.{ErrorReason, PartialVerificationError} -import scala.collection.immutable.ArraySeq +import scala.collection.immutable.{ArraySeq, Set} import scala.reflect.ClassTag case class InverseFunctions(condition: Term, @@ -551,14 +551,14 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { Forall( codomainQVar, - v.decider.assumptionAnalyzer.createLabelledConditionalChunks(v.decider, Set(chunk), Implies(effectiveCondition, BuiltinEquals(lookupSummary, lookupChunk))), + v.decider.wrapWithAssumptionAnalysisLabel(Implies(effectiveCondition, BuiltinEquals(lookupSummary, lookupChunk)), Set(chunk)), if (Verifier.config.disableISCTriggers()) Nil else Seq(Trigger(lookupSummary), Trigger(lookupChunk)), s"qp.fvfValDef${v.counter(this).next()}", isGlobal = relevantQvars.isEmpty) }) val resourceAndValueDefinitions = if (s.heapDependentTriggers.contains(field)){ - val chunkTriggers = relevantChunks map (chunk => v.decider.assumptionAnalyzer.createLabelledConditionalChunks(v.decider, Set(chunk), FieldTrigger(field.name, chunk.snapshotMap, codomainQVar))) + val chunkTriggers = relevantChunks map (chunk => v.decider.wrapWithAssumptionAnalysisLabel(FieldTrigger(field.name, chunk.snapshotMap, codomainQVar), Set(chunk))) val resourceTriggerDefinition = Forall( codomainQVar, @@ -639,7 +639,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { transformedOptSmDomainDefinitionCondition.getOrElse(True), /* Alternatively: qvarInDomainOfSummarisingSm */ IsPositive(chunk.perm).replace(snapToCodomainTermsSubstitution)) - val term = v.decider.assumptionAnalyzer.createLabelledConditionalChunks(v.decider, Set(chunk), Implies(effectiveCondition, And(snapshotNotUnit, BuiltinEquals(lookupSummary, lookupChunk)))) + val term = v.decider.wrapWithAssumptionAnalysisLabel(Implies(effectiveCondition, And(snapshotNotUnit, BuiltinEquals(lookupSummary, lookupChunk))), Set(chunk)) Forall( qvar, term, if (Verifier.config.disableISCTriggers()) Nil else Seq(Trigger(lookupSummary), Trigger(lookupChunk)), @@ -653,7 +653,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { } val resourceAndValueDefinitions = if (s.heapDependentTriggers.contains(resourceIdentifier)){ - val chunkTriggers = relevantChunks map (chunk => v.decider.assumptionAnalyzer.createLabelledConditionalChunks(v.decider, Set(chunk), ResourceTriggerFunction(resource, chunk.snapshotMap, Seq(qvar), s.program))) + val chunkTriggers = relevantChunks map (chunk => v.decider.wrapWithAssumptionAnalysisLabel(ResourceTriggerFunction(resource, chunk.snapshotMap, Seq(qvar), s.program), Set(chunk))) val resourceTriggerDefinition = Forall( qvar, @@ -711,7 +711,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { // TODO: Quantify over snapshot if resource is predicate. // Also check other places where a similar quantifier is constructed. - val chunkTriggerDefs = relevantChunks map (chunk => v.decider.assumptionAnalyzer.createLabelledConditionalChunks(v.decider, Set(chunk), ResourceTriggerFunction(resource, chunk.snapshotMap, codomainQVars, s.program))) + val chunkTriggerDefs = relevantChunks map (chunk => v.decider.wrapWithAssumptionAnalysisLabel(ResourceTriggerFunction(resource, chunk.snapshotMap, codomainQVars, s.program), Set(chunk))) val resourceTriggerDefinition = Forall( codomainQVars, diff --git a/src/main/scala/rules/StateConsolidator.scala b/src/main/scala/rules/StateConsolidator.scala index 0175d0a92..e441b32a5 100644 --- a/src/main/scala/rules/StateConsolidator.scala +++ b/src/main/scala/rules/StateConsolidator.scala @@ -7,7 +7,7 @@ package viper.silicon.rules import viper.silicon.Config -import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType, PermissionInhaleNode, StringAnalysisSourceInfo} +import viper.silicon.assumptionAnalysis.AssumptionType import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.state._ @@ -20,7 +20,6 @@ import viper.silicon.state.terms.predef.`?r` import viper.silicon.supporters.functions.FunctionRecorder import viper.silicon.verifier.Verifier import viper.silver.ast -import viper.silver.ast.NoPosition import scala.annotation.unused @@ -102,7 +101,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol val resource = Resources.resourceDescriptions(ch.resourceID) val pathCond = interpreter.buildPathConditionsForChunk(ch, resource.instanceProperties(s.mayAssumeUpperBounds)) v.decider.assumptionAnalyzer.disableTransitiveEdges() - pathCond.foreach(p => v.decider.assume(v.decider.assumptionAnalyzer.createLabelledConditionalChunks(v.decider, Set(ch), p._1), Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), AssumptionType.Internal)) + pathCond.foreach(p => v.decider.assume(v.decider.wrapWithAssumptionAnalysisLabel(p._1, Set(ch)), Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), AssumptionType.Internal)) v.decider.assumptionAnalyzer.enableTransitiveEdges() } @@ -208,7 +207,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol val result = mergeChunks1(fr1, chunk1, chunk2, qvars, v) result.map({case (fRec, ch, snapEq) => v.decider.assumptionAnalyzer.addPermissionDependencies(Set(chunk1, chunk2), ch) - (fRec, ch, v.decider.assumptionAnalyzer.createLabelledConditionalChunks(v.decider, Set(chunk1, chunk2), snapEq))}) + (fRec, ch, v.decider.wrapWithAssumptionAnalysisLabel(snapEq, Set(chunk1, chunk2)))}) } private def mergeChunks1(fr1: FunctionRecorder, chunk1: Chunk, chunk2: Chunk, qvars: Seq[Var], v: Verifier): Option[(FunctionRecorder, Chunk, Term)] = { diff --git a/src/main/scala/state/State.scala b/src/main/scala/state/State.scala index e8a144c7e..5dc2d234e 100644 --- a/src/main/scala/state/State.scala +++ b/src/main/scala/state/State.scala @@ -369,10 +369,10 @@ object State { val smDomainNeeded3 = smDomainNeeded1 || smDomainNeeded2 - val conditions1 = analysisInfo.assumptionAnalyzer.createLabelledConditional(analysisInfo.decider, pc1.branchConditions, And(pc1.branchConditions)) + val conditions1 = analysisInfo.decider.wrapWithAssumptionAnalysisLabel(And(pc1.branchConditions), Set.empty, pc1.branchConditions) val withExp = Verifier.config.enableDebugging() val conditions1Exp = if (withExp) Some(BigAnd(pc1.branchConditionExps.map(_._2.get))) else None - val conditions2 = analysisInfo.assumptionAnalyzer.createLabelledConditional(analysisInfo.decider, pc2.branchConditions, And(pc2.branchConditions)) + val conditions2 = analysisInfo.decider.wrapWithAssumptionAnalysisLabel(And(pc2.branchConditions), Set.empty, pc2.branchConditions) val conditions2Exp = if (withExp) Some(BigAnd(pc2.branchConditionExps.map(_._2.get))) else None val mergeStore = (g1: Store, g2: Store) => { diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index 7dcd14108..3c4b4e84a 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -226,8 +226,8 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver val preMark = decider.setPathConditionMark() produces(s0, toSf(`?s`), pres, ContractNotWellformed, v, AssumptionType.Explicit)((s1, _) => { val relevantPathConditionStack = decider.pcs.after(preMark) - phase1Data :+= Phase1Data(s1, relevantPathConditionStack.branchConditions map (t => v.decider.assumptionAnalyzer.createLabelledConditional(v.decider, Set(t), t)), relevantPathConditionStack.branchConditionExps, - relevantPathConditionStack.assumptions map (t => v.decider.assumptionAnalyzer.createLabelledConditional(v.decider, Set(t), t)), Option.when(evaluator.withExp)(relevantPathConditionStack.assumptionExps)) + phase1Data :+= Phase1Data(s1, relevantPathConditionStack.branchConditions map (t => v.decider.wrapWithAssumptionAnalysisLabel(t, Set.empty, Set(t))), relevantPathConditionStack.branchConditionExps, + relevantPathConditionStack.assumptions map (t => v.decider.wrapWithAssumptionAnalysisLabel(t, Set.empty, Set(t))), Option.when(evaluator.withExp)(relevantPathConditionStack.assumptionExps)) // The postcondition must be produced with a fresh snapshot (different from `?s`) because // the postcondition's snapshot structure is most likely different than that of the // precondition @@ -260,7 +260,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver case (fatalResult: FatalResult, _) => fatalResult case (intermediateResult, Phase1Data(sPre, bcsPre, bcsPreExp, pcsPre, pcsPreExp)) => intermediateResult && executionFlowController.locally(sPre, v)((s1, _) => { - val labelledBcsPre = v.decider.assumptionAnalyzer.createLabelledConditional(v.decider,bcsPre, And(bcsPre)) + val labelledBcsPre = v.decider.wrapWithAssumptionAnalysisLabel(And(bcsPre), Set.empty, bcsPre) decider.setCurrentBranchCondition(labelledBcsPre, (BigAnd(bcsPreExp.map(_._1)), Option.when(wExp)(BigAnd(bcsPreExp.map(_._2.get))))) // TODO ake: pcsPreExp are missing position infos sometimes (e.g. Snapshots) decider.assume(pcsPre, pcsPreExp, s"precondition of ${function.name}", enforceAssumption=false, assumptionType=annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit)) From e8ff0f70e8b4f93ec9d0aaecb7cffc7425d5100d Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 30 Jun 2025 17:43:19 +0200 Subject: [PATCH 133/474] remove StringAssumptionNode --- .../AssumptionAnalysisGraph.scala | 8 ++------ .../AssumptionAnalyzer.scala | 19 +++++-------------- src/main/scala/decider/Decider.scala | 8 ++++---- .../scala/interfaces/decider/Prover.scala | 2 +- 4 files changed, 12 insertions(+), 25 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index 9260e36f6..3586633d2 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -193,12 +193,8 @@ trait ChunkAnalysisInfo { def getChunk: Chunk = chunk } -case class SimpleAssumptionNode(assumption: ast.Exp, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean) extends GeneralAssumptionNode { - override def getNodeString: String ="assume " + term.toString + ", " + assumption.toString -} - -case class StringAssumptionNode(description: String, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean) extends GeneralAssumptionNode { - override def getNodeString: String = "assume " + term.toString + ", " + description +case class SimpleAssumptionNode(term: Term, description: Option[String], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean) extends GeneralAssumptionNode { + override def getNodeString: String = "assume " + term.toString + description.map(" (" + _ + ")").getOrElse("") } case class SimpleAssertionNode(assertion: ast.Exp, term: Term, sourceInfo: AnalysisSourceInfo, isClosed: Boolean) extends GeneralAssertionNode { diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index fc823ba68..48c8e5e52 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -27,8 +27,7 @@ trait AssumptionAnalyzer { def addNode(node: AssumptionAnalysisNode): Unit def getNodes: Iterable[AssumptionAnalysisNode] - def addAssumption(assumption: ast.Exp, term: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] - def addAssumption(assumption: String, term: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] + def addAssumption(assumption: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, description: Option[String] = None): Option[Int] def createAssertOrCheckNode(term: Term, assertion: Either[String, ast.Exp], analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] @@ -56,7 +55,6 @@ trait AssumptionAnalyzer { object AssumptionAnalyzer { val assumptionTypeAnnotationKey = "assumptionType" val enableAssumptionAnalysisAnnotationKey = "enableAssumptionAnalysis" - val noAssumptionAnalyzerSingelton = new NoAssumptionAnalyzer() private def extractAnnotationFromInfo(info: ast.Info, annotationKey: String): Option[Seq[String]] = { info.getAllInfos[ast.AnnotationInfo] @@ -127,15 +125,9 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { } - override def addAssumption(assumption: ast.Exp, term: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { - val node = SimpleAssumptionNode(assumption, term, analysisSourceInfo, AssumptionAnalyzer.extractAssumptionTypeFromInfo(assumption.info).getOrElse(assumptionType), isClosed_) - addNode(node) - Some(node.id) - } - - override def addAssumption(assumption: String, term: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { - val node = StringAssumptionNode(assumption, term, analysisSourceInfo, assumptionType, isClosed_) + override def addAssumption(assumption: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, description: Option[String]): Option[Int] = { + val node = SimpleAssumptionNode(assumption, description, analysisSourceInfo, assumptionType, isClosed_) addNode(node) Some(node.id) } @@ -160,7 +152,7 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { val assumptionIds = assumptionLabels.filter(AssumptionAnalyzer.isAssumptionLabel).map(AssumptionAnalyzer.getIdFromLabel) val assertionIdsFromUnsatCore = assumptionLabels.filter(AssumptionAnalyzer.isAssertionLabel).map(AssumptionAnalyzer.getIdFromLabel) val assertionIdFromLabel = AssumptionAnalyzer.getIdFromLabel(assertionLabel) - val assertionIds = assertionIdFromLabel +: assertionIdsFromUnsatCore // TODO ake: add check (not already contained) + val assertionIds = assertionIdFromLabel +: assertionIdsFromUnsatCore assumptionGraph.addEdges(assumptionIds, assertionIds) val axiomIds = assumptionLabels.filter(AssumptionAnalyzer.isAxiomLabel).map(AssumptionAnalyzer.getIdFromLabel) assumptionGraph.addEdges(axiomIds, assertionIds) @@ -320,8 +312,7 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { override def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNodeId: Chunk): Unit = {} def addDependency(source: Int, dest: Int): Unit = {} override def getMember: Option[ast.Member] = None - override def addAssumption(assumption: ast.Exp, term: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None - override def addAssumption(assumption: String, term: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None + override def addAssumption(term: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, description: Option[String]): Option[Int] = None override def exportGraph(): Unit = {} } \ No newline at end of file diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 9e6cc70f3..d1a216235 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -370,7 +370,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } val filteredAssumptionsWithLabels = filteredAssumptions map{case (t, _) => - val assumptionId: Option[Int] = assumptionAnalyzer.addAssumption(t.toString /* TODO ake */, t, analysisSourceInfo, assumptionType) + val assumptionId: Option[Int] = assumptionAnalyzer.addAssumption(t, analysisSourceInfo, assumptionType) (t, AssumptionAnalyzer.createAssumptionLabel(assumptionId)) } @@ -382,7 +382,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => def assume(assumptions: Seq[Term], debugExps: Option[Seq[DebugExp]], assumptionType: AssumptionType): Unit = { val assumptionsWithLabels = assumptions map (t => { - val assumptionId = assumptionAnalyzer.addAssumption(t.toString /* TODO ake */, t, analysisSourceInfoStack.getFullSourceInfo, assumptionType) + val assumptionId = assumptionAnalyzer.addAssumption(t, analysisSourceInfoStack.getFullSourceInfo, assumptionType) (t, AssumptionAnalyzer.createAssumptionLabel(assumptionId)) }) @@ -398,7 +398,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => // TODO ake: put after filtering val assumptionsWithLabels = assumptions map (t => { - val assumptionIds = assumptionAnalyzer.addAssumption(t.toString, t, analysisSourceInfoStack.getFullSourceInfo, assumptionType) + val assumptionIds = assumptionAnalyzer.addAssumption(t, analysisSourceInfoStack.getFullSourceInfo, assumptionType) (t, AssumptionAnalyzer.createAssumptionLabel(assumptionIds)) }) @@ -428,7 +428,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => addDebugExp(debugExp.get.withTerm(And(filteredTerms))) } val termsWithLabel = filteredTerms map (t => { - val assumptionId = assumptionAnalyzer.addAssumption(t.toString, t, analysisSourceInfoStack.getFullSourceInfo, assumptionType) + val assumptionId = assumptionAnalyzer.addAssumption(t, analysisSourceInfoStack.getFullSourceInfo, assumptionType) (t, AssumptionAnalyzer.createAssumptionLabel(assumptionId)) }) assumeWithoutSmokeChecks(InsertionOrderedSet(termsWithLabel)) diff --git a/src/main/scala/interfaces/decider/Prover.scala b/src/main/scala/interfaces/decider/Prover.scala index eb5e1a51c..4e926e3da 100644 --- a/src/main/scala/interfaces/decider/Prover.scala +++ b/src/main/scala/interfaces/decider/Prover.scala @@ -45,7 +45,7 @@ trait ProverLike { if(Verifier.config.enableAssumptionAnalysis()){ axioms.foreach(axiom => { - val id = if(axiom._2.isAnalysisEnabled) preambleAssumptionAnalyzer.addAssumption(axiom._2.toString, axiom._1, axiom._2, AssumptionType.Axiom) else None + val id = if(axiom._2.isAnalysisEnabled) preambleAssumptionAnalyzer.addAssumption(axiom._1, axiom._2, AssumptionType.Axiom) else None assume(axiom._1, AssumptionAnalyzer.createAxiomLabel(id)) }) } else{ From 296ea3f709de15a0432c7449118ce251b9446b6f Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 1 Jul 2025 10:10:56 +0200 Subject: [PATCH 134/474] review chunk register functions --- .../AssumptionAnalysisGraph.scala | 12 +-- .../AssumptionAnalyzer.scala | 98 ++++++++----------- src/main/scala/decider/Decider.scala | 37 +++++-- src/main/scala/interfaces/state/Chunks.scala | 14 +-- src/main/scala/rules/Executor.scala | 2 +- .../rules/MoreCompleteExhaleSupporter.scala | 2 +- src/main/scala/rules/StateConsolidator.scala | 4 +- .../scala/supporters/MethodSupporter.scala | 16 +-- .../dependencyAnalysisTests/all/aliasing.vpr | 15 ++- .../all/imprecision.vpr | 2 +- .../dependencyAnalysisTests/new/meeting.vpr | 15 +++ 11 files changed, 124 insertions(+), 93 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index 3586633d2..1371339e8 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -199,12 +199,12 @@ case class SimpleAssumptionNode(term: Term, description: Option[String], sourceI case class SimpleAssertionNode(assertion: ast.Exp, term: Term, sourceInfo: AnalysisSourceInfo, isClosed: Boolean) extends GeneralAssertionNode { val assumptionType: AssumptionType = Explicit - override def getNodeString: String = "assert " + term.toString + ", " + assertion.toString + override def getNodeString: String = "assert " + term.toString // TODO ake + ", " + assertion.toString } case class StringAssertionNode(description: String, term: Term, sourceInfo: AnalysisSourceInfo, isClosed: Boolean) extends GeneralAssertionNode { val assumptionType: AssumptionType = Explicit - override def getNodeString: String = "assert " + term.toString + ", " + description + override def getNodeString: String = "assert " + term.toString // TODO ake + ", " + description } case class SimpleCheckNode(term: Term, sourceInfo: AnalysisSourceInfo, isClosed: Boolean) extends GeneralAssertionNode { @@ -213,20 +213,20 @@ case class SimpleCheckNode(term: Term, sourceInfo: AnalysisSourceInfo, isClosed: override def getNodeType: String = "Check" } -case class PermissionInhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean) extends GeneralAssumptionNode with ChunkAnalysisInfo { - override def getNodeString: String = "inhale " + term.toString + ": " + chunk.toString +case class PermissionInhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean, labelNode: LabelNode) extends GeneralAssumptionNode with ChunkAnalysisInfo { + override def getNodeString: String = "inhale " + chunk.toString override def getNodeType: String = "Inhale" } case class PermissionExhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, isClosed: Boolean) extends GeneralAssertionNode with ChunkAnalysisInfo { val assumptionType: AssumptionType = Explicit override def getNodeType: String = "Exhale" - override def getNodeString: String = "exhale " + term.toString + ": " + chunk.toString + override def getNodeString: String = "exhale " + chunk.toString } case class PermissionAssertNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, isClosed: Boolean) extends GeneralAssertionNode with ChunkAnalysisInfo { val assumptionType: AssumptionType = Explicit - override def getNodeString: String = "assert " + term + " for " + chunk.toString + override def getNodeString: String = "assert " + term + " for chunk " + chunk.toString } case class LabelNode(term: Term) extends GeneralAssumptionNode { diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 48c8e5e52..bd4c6035b 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -19,36 +19,30 @@ trait AssumptionAnalyzer { isClosed_ = false } - def addPermissionInhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] - def addPermissionAssertNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo): Option[Int] - def addPermissionExhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo): Option[Int] - def addPermissionNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Explicit, isExhale: Boolean=false): Option[Int] - def addNodes(nodes: Iterable[AssumptionAnalysisNode]): Unit - def addNode(node: AssumptionAnalysisNode): Unit def getNodes: Iterable[AssumptionAnalysisNode] + def getChunkInhaleNode(chunk: Chunk): Option[PermissionInhaleNode] = None + def addNodes(nodes: Iterable[AssumptionAnalysisNode]): Unit + def addNode(node: AssumptionAnalysisNode): Unit def addAssumption(assumption: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, description: Option[String] = None): Option[Int] + def addPermissionAssertNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo): Option[Int] + def addPermissionExhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo): Option[Int] def createAssertOrCheckNode(term: Term, assertion: Either[String, ast.Exp], analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] + def addAssertFalseNode(isCheck: Boolean, sourceInfo: AnalysisSourceInfo): Option[Int] - def getChunkNodeIds(oldChunks: Set[Chunk]): Set[Int] - def getNodeIdsByTerm(terms: Set[Term]): Set[Int] - def addDependencyFromExhaleToInhale(inhaledChunk: Chunk, sourceInfo: AnalysisSourceInfo): Unit - def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNodeId: Option[Int]): Unit - def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNodeId: Chunk): Unit def addDependency(source: Int, dest: Int): Unit - def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit - - def addAssertFalseNode(isCheck: Boolean, sourceInfo: AnalysisSourceInfo): Option[Int] + def addPermissionDependencies(sourceChunks: Set[Chunk], sourceTerms: Set[Term], targetChunk: Chunk): Unit + def addDependencyFromExhaleToInhale(inhaledChunk: Chunk, sourceInfo: AnalysisSourceInfo): Unit def getMember: Option[ast.Member] def exportGraph(): Unit - def createLabelNode(labelTerm: Term, sourceChunks: Iterable[Chunk], sourceTerms: Iterable[Term]): LabelNode = LabelNode(True) - def registerDerivedChunk[CH <: GeneralChunk](sourceChunk: CH, buildChunk: (Term => CH), perm: Term, labelNode: LabelNode, analysisInfo: AnalysisInfo, isExhale: Boolean, createLabel: Boolean=true): CH = buildChunk(perm) - def registerChunk[CH <: GeneralChunk](buildChunk: (Term => CH), perm: Term, labelNode: LabelNode, analysisInfo: AnalysisInfo, isExhale: Boolean): CH = buildChunk(perm) + def createLabelNode(labelTerm: Term, sourceChunks: Iterable[Chunk], sourceTerms: Iterable[Term]): Option[LabelNode] = None + def registerInhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: (Term => CH), perm: Term, labelNode: Option[LabelNode], analysisInfo: AnalysisInfo, isExhale: Boolean): CH = buildChunk(perm) + def registerExhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: (Term => CH), perm: Term, analysisInfo: AnalysisInfo): CH = buildChunk(perm) def getReassumeLabelNodes: Iterable[LabelNode] = Set.empty } @@ -67,7 +61,6 @@ object AssumptionAnalyzer { if(annotation.isDefined && annotation.get.nonEmpty) AssumptionType.fromString(annotation.get.head) else None } - def extractEnableAnalysisFromInfo(info: ast.Info): Option[Boolean] = { val annotation = extractAnnotationFromInfo(info, enableAssumptionAnalysisAnnotationKey) if(annotation.isDefined && annotation.get.nonEmpty) annotation.get.head.toBooleanOption else None @@ -82,7 +75,7 @@ object AssumptionAnalyzer { (assumptionTypeAnnotationKey, Seq(assumptionType.toString)) )) - + // TODO ake: remove offsets def createAssumptionLabel(id: Option[Int], offset: Int = 0): String = { createLabel("assumption", id, offset) } @@ -158,8 +151,8 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { assumptionGraph.addEdges(axiomIds, assertionIds) } - override def addPermissionInhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { - val node = PermissionInhaleNode(chunk, permAmount, sourceInfo, assumptionType, isClosed_) + private def addPermissionInhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, labelNode: LabelNode): Option[Int] = { + val node = PermissionInhaleNode(chunk, permAmount, sourceInfo, assumptionType, isClosed_, labelNode) addNode(node) Some(node.id) } @@ -167,23 +160,26 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { override def addPermissionAssertNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo): Option[Int] = { val node = PermissionAssertNode(chunk, permAmount, sourceInfo, isClosed_) addNode(node) - addPermissionDependencies(Set(chunk), Some(node.id)) + addPermissionDependencies(Set(chunk), Set(), Some(node.id)) Some(node.id) } override def addPermissionExhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo): Option[Int] = { val node = PermissionExhaleNode(chunk, permAmount, sourceInfo, isClosed_) addNode(node) - addPermissionDependencies(Set(chunk), Some(node.id)) + addPermissionDependencies(Set(chunk), Set(), Some(node.id)) Some(node.id) } - override def addPermissionNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Explicit, isExhale: Boolean=false): Option[Int] = { - if(isExhale) addPermissionExhaleNode(chunk, permAmount, sourceInfo) - else addPermissionInhaleNode(chunk, permAmount, sourceInfo, assumptionType) + override def getChunkInhaleNode(chunk: Chunk): Option[PermissionInhaleNode] = { + val inhaleNode = assumptionGraph.nodes + .filter(c => c.isInstanceOf[PermissionInhaleNode] && chunk.equals(c.asInstanceOf[ChunkAnalysisInfo].getChunk)) + .map(_.asInstanceOf[PermissionInhaleNode]) + assert(inhaleNode.size == 1) + inhaleNode.headOption } - override def getChunkNodeIds(oldChunks: Set[Chunk]): Set[Int] = { + private def getChunkNodeIds(oldChunks: Set[Chunk]): Set[Int] = { assumptionGraph.nodes .filter(c => c.isInstanceOf[PermissionInhaleNode] && oldChunks.contains(c.asInstanceOf[ChunkAnalysisInfo].getChunk)) .map(_.id).toSet @@ -195,7 +191,7 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { .map(_.id).toSet } - override def getNodeIdsByTerm(terms: Set[Term]): Set[Int] = { + private def getNodeIdsByTerm(terms: Set[Term]): Set[Int] = { assumptionGraph.nodes .filter(t => terms.contains(t.getTerm)) .map(_.id).toSet @@ -205,23 +201,23 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { val inhaledChunkNodeIds = getChunkNodeIdsWithSource(Set(inhaledChunk), sourceInfo) assert(inhaledChunkNodeIds.size == 1) val exhaleNodes = assumptionGraph.nodes - .filter(c => c.isInstanceOf[PermissionExhaleNode] && c.isClosed && c.sourceInfo.equals(sourceInfo)) + .filter(c => c.isInstanceOf[PermissionExhaleNode] && c.sourceInfo.equals(sourceInfo)) .map(_.id).toSet assumptionGraph.addEdges(exhaleNodes, inhaledChunkNodeIds) } - override def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNodeId: Option[Int]): Unit = { + private def addPermissionDependencies(sourceChunks: Set[Chunk], sourceTerms: Set[Term], newChunkNodeId: Option[Int]): Unit = { if(newChunkNodeId.isEmpty) return - val oldChunkNodeIds = getChunkNodeIds(oldChunks).filter(id => id != newChunkNodeId.get) - assumptionGraph.addEdges(oldChunkNodeIds, newChunkNodeId.get) + val sourceNodeIds = getChunkNodeIds(sourceChunks).filter(id => id != newChunkNodeId.get) ++ getNodeIdsByTerm(sourceTerms) + assumptionGraph.addEdges(sourceNodeIds, newChunkNodeId.get) } - override def addPermissionDependencies(oldChunks: Set[Chunk], newChunk: Chunk): Unit = { + override def addPermissionDependencies(sourceChunks: Set[Chunk], sourceTerms: Set[Term], newChunk: Chunk): Unit = { val newChunkId = assumptionGraph.nodes .filter(c => c.isInstanceOf[PermissionInhaleNode] && c.isInstanceOf[ChunkAnalysisInfo] && newChunk.equals(c.asInstanceOf[ChunkAnalysisInfo].getChunk)) .map(_.id).toSet - addPermissionDependencies(oldChunks, newChunkId.headOption) + addPermissionDependencies(sourceChunks, sourceTerms, newChunkId.headOption) } override def addDependency(source: Int, dest: Int): Unit = { @@ -245,33 +241,31 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { } - override def createLabelNode(labelTerm: Term, sourceChunks: Iterable[Chunk], sourceTerms: Iterable[Term]): LabelNode = { + override def createLabelNode(labelTerm: Term, sourceChunks: Iterable[Chunk], sourceTerms: Iterable[Term]): Option[LabelNode] = { val labelNode = LabelNode(labelTerm) addNode(labelNode) originalLabelNodes = originalLabelNodes :+ labelNode assumptionGraph.addEdges(getChunkNodeIds(sourceChunks.toSet) ++ getNodeIdsByTerm(sourceTerms.toSet), labelNode.id) - labelNode + Some(labelNode) } - override def registerChunk[CH <: GeneralChunk](buildChunk: (Term => CH), perm: Term, labelNode: LabelNode, analysisInfo: AnalysisInfo, isExhale: Boolean): CH = { - val chunk = if(isExhale) buildChunk(perm) else buildChunk(Ite(labelNode.term, perm, NoPerm)) - val chunkNode = addPermissionNode(chunk, chunk.perm, analysisInfo.sourceInfo, analysisInfo.assumptionType, isExhale) - if(chunkNode.isDefined) - addDependency(chunkNode.get, labelNode.id) + override def registerExhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: (Term => CH), perm: Term, analysisInfo: AnalysisInfo): CH = { + val chunk = buildChunk(perm) + val chunkNode = addPermissionExhaleNode(chunk, chunk.perm, analysisInfo.sourceInfo) + addPermissionDependencies(sourceChunks, Set(), chunkNode) chunk } - override def registerDerivedChunk[CH <: GeneralChunk](sourceChunk: CH, buildChunk: (Term => CH), perm: Term, labelNode: LabelNode, analysisInfo: AnalysisInfo, isExhale: Boolean, createLabel: Boolean=true): CH = { - val chunk = if(isExhale) buildChunk(perm) else buildChunk(Ite(labelNode.term, perm, NoPerm)) - val chunkNode = addPermissionNode(chunk, chunk.perm, analysisInfo.sourceInfo, analysisInfo.assumptionType, isExhale) + override def registerInhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: (Term => CH), perm: Term, labelNodeOpt: Option[LabelNode], analysisInfo: AnalysisInfo, isExhale: Boolean): CH = { + val labelNode = labelNodeOpt.get + val chunk = buildChunk(Ite(labelNode.term, perm, NoPerm)) + val chunkNode = addPermissionInhaleNode(chunk, chunk.perm, analysisInfo.sourceInfo, analysisInfo.assumptionType, labelNode) if(chunkNode.isDefined) addDependency(chunkNode.get, labelNode.id) - addPermissionDependencies(Set(sourceChunk), chunkNode) + addPermissionDependencies(sourceChunks, Set(), chunkNode) chunk } - - private def createReassumeLabelNode(oldLabelNode: LabelNode): LabelNode = { val newLabelNode = LabelNode(oldLabelNode.term) // do not add to originalLabelNodes! @@ -296,20 +290,12 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { override def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit = { } - override def addPermissionInhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None override def addPermissionExhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo): Option[Int] = None override def addPermissionAssertNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo): Option[Int] = None - override def addPermissionNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isExhale: Boolean=false): Option[Int] = None - - - override def getChunkNodeIds(oldChunks: Set[Chunk]): Set[Int] = Set.empty - override def getNodeIdsByTerm(terms: Set[Term]): Set[Int] = Set.empty override def addDependencyFromExhaleToInhale(inhaledChunk: Chunk, sourceInfo: AnalysisSourceInfo): Unit = {} - override def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNode: Option[Int]): Unit = { - } - override def addPermissionDependencies(oldChunks: Set[Chunk], newChunkNodeId: Chunk): Unit = {} + override def addPermissionDependencies(sourceChunks: Set[Chunk], sourceTerms: Set[Term], newChunkNodeId: Chunk): Unit = {} def addDependency(source: Int, dest: Int): Unit = {} override def getMember: Option[ast.Member] = None diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index d1a216235..3e7a470b4 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -62,7 +62,7 @@ trait Decider { def startDebugSubExp(): Unit def registerChunk[CH <: GeneralChunk](buildChunk: (Term => CH), perm: Term, analysisInfo: AnalysisInfo, isExhale: Boolean): CH - def registerDerivedChunk[CH <: GeneralChunk](sourceChunk: CH, buildChunk: (Term => CH), perm: Term, analysisInfo: AnalysisInfo, isExhale: Boolean, createLabel: Boolean=true): CH + def registerDerivedChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: (Term => CH), perm: Term, analysisInfo: AnalysisInfo, isExhale: Boolean, createLabel: Boolean=true): CH def wrapWithAssumptionAnalysisLabel(term: Term, sourceChunks: Iterable[Chunk] = Set.empty, sourceTerms: Iterable[Term] = Set.empty): Term def wrapPermissionWithAssumptionAnalysisLabel(perm: Term, sourceChunks: Iterable[Chunk]): Term @@ -310,31 +310,48 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } def registerChunk[CH <: GeneralChunk](buildChunk: (Term => CH), perm: Term, analysisInfo: AnalysisInfo, isExhale: Boolean): CH = { - assumptionAnalyzer.registerChunk(buildChunk, perm, createAnalysisLabelNode(), analysisInfo, isExhale) + registerDerivedChunk[CH](Set.empty, buildChunk, perm, analysisInfo, isExhale) } - def registerDerivedChunk[CH <: GeneralChunk](sourceChunk: CH, buildChunk: (Term => CH), perm: Term, analysisInfo: AnalysisInfo, isExhale: Boolean, createLabel: Boolean=true): CH = { - assumptionAnalyzer.registerDerivedChunk(sourceChunk, buildChunk, perm, createAnalysisLabelNode(Set(sourceChunk), Set.empty), analysisInfo, isExhale) + def registerDerivedChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: (Term => CH), perm: Term, analysisInfo: AnalysisInfo, isExhale: Boolean, createLabel: Boolean=true): CH = { + if(!Verifier.config.enableAssumptionAnalysis()) + return buildChunk(perm) + + if(isExhale) + assumptionAnalyzer.registerExhaleChunk(sourceChunks, buildChunk, perm, analysisInfo) + else { + val labelNodeOpt = if(createLabel) getOrCreateAnalysisLabelNode() else getOrCreateAnalysisLabelNode(sourceChunks) + assumptionAnalyzer.registerInhaleChunk(sourceChunks, buildChunk, perm, labelNodeOpt, analysisInfo, createLabel) + } } - private def createAnalysisLabelNode(sourceChunks: Iterable[Chunk] = Set.empty, sourceTerms: Iterable[Term] = Set.empty): LabelNode = { + private def getOrCreateAnalysisLabelNode(sourceChunks: Iterable[Chunk] = Set.empty, sourceTerms: Iterable[Term] = Set.empty): Option[LabelNode] = { + if(!Verifier.config.enableAssumptionAnalysis()) + return None + + if(sourceChunks.size == 1 && sourceTerms.isEmpty){ + val chunkInhaleNode = assumptionAnalyzer.getChunkInhaleNode(sourceChunks.head) + return chunkInhaleNode.map(_.labelNode) + } val (label, _) = fresh(ast.LocalVar("analysisLabel", ast.Bool)()) val labelNode = assumptionAnalyzer.createLabelNode(label, sourceChunks, sourceTerms) - val smtLabel = AssumptionAnalyzer.createAssumptionLabel(Some(labelNode.id)) + val smtLabel = AssumptionAnalyzer.createAssumptionLabel(labelNode.map(_.id)) assumeLabel(label, smtLabel) labelNode } def wrapWithAssumptionAnalysisLabel(term: Term, sourceChunks: Iterable[Chunk] = Set.empty, sourceTerms: Iterable[Term] = Set.empty): Term = { if(!Verifier.config.enableAssumptionAnalysis()) return term - val labelNode = createAnalysisLabelNode(sourceChunks, sourceTerms) - Implies(labelNode.term, term) + + val labelNode = getOrCreateAnalysisLabelNode(sourceChunks, sourceTerms) + labelNode.map(n => Implies(n.term, term)).getOrElse(term) } def wrapPermissionWithAssumptionAnalysisLabel(perm: Term, sourceChunks: Iterable[Chunk]): Term = { if(!Verifier.config.enableAssumptionAnalysis()) return perm - val labelNode = createAnalysisLabelNode(sourceChunks, Set.empty) - Ite(labelNode.term, perm, NoPerm) + + val labelNode = getOrCreateAnalysisLabelNode(sourceChunks, Set.empty) + labelNode.map(n => Ite(n.term, perm, NoPerm)).getOrElse(perm) } def addDebugExp(e: DebugExp): Unit = { diff --git a/src/main/scala/interfaces/state/Chunks.scala b/src/main/scala/interfaces/state/Chunks.scala index 3febe041a..df29f6473 100644 --- a/src/main/scala/interfaces/state/Chunks.scala +++ b/src/main/scala/interfaces/state/Chunks.scala @@ -30,29 +30,29 @@ trait GeneralChunk extends Chunk { object GeneralChunk { def applyCondition(chunk: GeneralChunk, newCond: Term, newCondExp: Option[ast.Exp], analysisInfo: AnalysisInfo): GeneralChunk = { - analysisInfo.decider.registerDerivedChunk[GeneralChunk](chunk, {_ => + analysisInfo.decider.registerDerivedChunk[GeneralChunk](Set(chunk), {_ => chunk.applyCondition(newCond, newCondExp)}, chunk.perm, analysisInfo, isExhale=false, createLabel=false) } def permMinus(chunk: GeneralChunk, newPerm: Term, newPermExp: Option[ast.Exp], analysisInfo: AnalysisInfo, isExhale: Boolean=false): GeneralChunk = { - val newChunk = analysisInfo.decider.registerDerivedChunk[GeneralChunk](chunk, {finalPerm => + val newChunk = analysisInfo.decider.registerDerivedChunk[GeneralChunk](Set(chunk), {finalPerm => chunk.permMinus(finalPerm, newPermExp)}, newPerm, analysisInfo, isExhale=false, createLabel=false) - val exhaledChunk = analysisInfo.decider.registerDerivedChunk[GeneralChunk](chunk, {finalPerm => + val exhaledChunk = analysisInfo.decider.registerDerivedChunk[GeneralChunk](Set(chunk), {finalPerm => chunk.withPerm(finalPerm, newPermExp)}, newPerm, analysisInfo, isExhale=true, createLabel=false) newChunk } def permPlus(chunk: GeneralChunk, newPerm: Term, newPermExp: Option[ast.Exp], analysisInfo: AnalysisInfo, isExhale: Boolean=false): GeneralChunk = { - analysisInfo.decider.registerDerivedChunk[GeneralChunk](chunk, {finalPerm => + analysisInfo.decider.registerDerivedChunk[GeneralChunk](Set(chunk), {finalPerm => chunk.permPlus(finalPerm, newPermExp)}, newPerm, analysisInfo, isExhale, createLabel=true) } def withPerm(chunk: GeneralChunk, newPerm: Term, newPermExp: Option[ast.Exp], analysisInfo: AnalysisInfo, isExhale: Boolean=false): GeneralChunk = { - analysisInfo.decider.registerDerivedChunk[GeneralChunk](chunk, {finalPerm => + analysisInfo.decider.registerDerivedChunk[GeneralChunk](Set(chunk), {finalPerm => chunk.withPerm(finalPerm, newPermExp)}, newPerm, analysisInfo, isExhale, createLabel=true) } @@ -71,7 +71,7 @@ trait NonQuantifiedChunk extends GeneralChunk { object NonQuantifiedChunk { def withSnap(chunk: NonQuantifiedChunk, snap: Term, snapExp: Option[ast.Exp], analysisInfo: AnalysisInfo): NonQuantifiedChunk = { - analysisInfo.decider.registerDerivedChunk[NonQuantifiedChunk](chunk, {_ => + analysisInfo.decider.registerDerivedChunk[NonQuantifiedChunk](Set(chunk), {_ => chunk.withSnap(snap, snapExp)}, chunk.perm, analysisInfo, isExhale=false, createLabel=false) } @@ -90,7 +90,7 @@ trait QuantifiedChunk extends GeneralChunk { object QuantifiedChunk { def withSnapshotMap(chunk: QuantifiedChunk, snap: Term, analysisInfo: AnalysisInfo): QuantifiedChunk = { - analysisInfo.decider.registerDerivedChunk[QuantifiedChunk](chunk, {_ => + analysisInfo.decider.registerDerivedChunk[QuantifiedChunk](Set(chunk), {_ => chunk.withSnapshotMap(snap)}, chunk.perm, analysisInfo, isExhale=false, createLabel=false) } diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index b1096106e..c277d77f5 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -449,7 +449,7 @@ object executor extends ExecutionRules { val resource = fa.res(s.program) val ve = pve dueTo InsufficientPermission(fa) val description = s"consume ${ass.pos}: $ass" - v2.decider.assumptionAnalyzer.disableTransitiveEdges() // TODO ake: review implementation + v2.decider.assumptionAnalyzer.disableTransitiveEdges() // TODO ake: due to this we are missing edges from checks to exhale or within state consolidation chunkSupporter.consume(s2, s2.h, resource, Seq(tRcvr), eRcvrNew.map(Seq(_)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), false, ve, v2, description)((s3, h3, _, consumedChunks, v3) => { v2.decider.assumptionAnalyzer.enableTransitiveEdges() val (tSnap, _) = ssaifyRhs(tRhs, rhs, rhsNew, field.name, field.typ, v3, s3, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit)) diff --git a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala index d69f8f2ad..8cda22f68 100644 --- a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala +++ b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala @@ -104,7 +104,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { And(ch.args.zip(args).map { case (t1, t2) => t1 === t2 }) summarisingSnapshotDefinitions :+= - Implies(And(argumentEqualities, IsPositive(ch.perm)), `?s` === ch.snap) + v.decider.wrapWithAssumptionAnalysisLabel(Implies(And(argumentEqualities, IsPositive(ch.perm)), `?s` === ch.snap), Set(ch)) }) val taggedSummarisingSnapshot = diff --git a/src/main/scala/rules/StateConsolidator.scala b/src/main/scala/rules/StateConsolidator.scala index e441b32a5..ad6c90522 100644 --- a/src/main/scala/rules/StateConsolidator.scala +++ b/src/main/scala/rules/StateConsolidator.scala @@ -195,7 +195,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol private def findMatchingChunk(chunks: Iterable[Chunk], chunk: Chunk, v: Verifier): Option[Chunk] = { chunk match { case chunk: BasicChunk => - chunkSupporter.findChunk[BasicChunk](chunks, chunk.id, chunk.args, v) + chunkSupporter.findChunk[BasicChunk](chunks, chunk.id, chunk.args, v) // TODO ake: add edge from check chunk equality case chunk: QuantifiedChunk => quantifiedChunkSupporter.findChunk(chunks, chunk, v) case _ => None } @@ -206,7 +206,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol private def mergeChunks(fr1: FunctionRecorder, chunk1: Chunk, chunk2: Chunk, qvars: Seq[Var], v: Verifier): Option[(FunctionRecorder, Chunk, Term)] = { val result = mergeChunks1(fr1, chunk1, chunk2, qvars, v) result.map({case (fRec, ch, snapEq) => - v.decider.assumptionAnalyzer.addPermissionDependencies(Set(chunk1, chunk2), ch) + v.decider.assumptionAnalyzer.addPermissionDependencies(Set(chunk1, chunk2), Set(), ch) (fRec, ch, v.decider.wrapWithAssumptionAnalysisLabel(snapEq, Set(chunk1, chunk2)))}) } diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index 2e24d39e1..5a81f0a59 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -7,20 +7,20 @@ package viper.silicon.supporters import com.typesafe.scalalogging.Logger -import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionAnalyzer, AssumptionType, DefaultAssumptionAnalyzer, ExpAnalysisSourceInfo, NoAssumptionAnalyzer, StringAnalysisSourceInfo} -import viper.silver.ast -import viper.silver.components.StatefulComponent -import viper.silver.verifier.errors._ -import viper.silicon.interfaces._ +import viper.silicon.assumptionAnalysis.{AssumptionAnalyzer, AssumptionType} import viper.silicon.decider.Decider +import viper.silicon.interfaces._ import viper.silicon.logger.records.data.WellformednessCheckRecord import viper.silicon.rules.{consumer, executionFlowController, executor, producer} -import viper.silicon.state.{Heap, State, Store} import viper.silicon.state.State.OldHeaps -import viper.silicon.verifier.{Verifier, VerifierComponent} +import viper.silicon.state.{Heap, State, Store} import viper.silicon.utils.freshSnap +import viper.silicon.verifier.{Verifier, VerifierComponent} +import viper.silicon.{Map, toMap} +import viper.silver.ast +import viper.silver.components.StatefulComponent import viper.silver.reporter.AnnotationWarning -import viper.silicon.{Config, Map, toMap} +import viper.silver.verifier.errors._ /* TODO: Consider changing the DefaultMethodVerificationUnitProvider into a SymbolicExecutionRule */ diff --git a/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr b/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr index 3dc798447..1d864ae6a 100644 --- a/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr @@ -15,7 +15,7 @@ method maybeAlias(a: Ref, b: Ref, c: Bool, n: Int) } } -method aliasing(x: Ref, n: Int) +method aliasing1(x: Ref, n: Int) requires @dependency()(acc(x.f)) requires @irrelevant()(n > 0) requires @irrelevant()(x.f > n) @@ -31,3 +31,16 @@ method aliasing(x: Ref, n: Int) assert y.f > n } + +method aliasing2(x: Ref, y: Ref, n: Int) + requires @dependency()(acc(x.f, 1/2)) + requires @dependency()(acc(y.f, 1/2)) + requires @irrelevant()(n > 0) + requires @irrelevant()(x.f > n) +{ + if(@dependency()(x == y)){ + @testAssertion() + x.f := n + 1 + } +} + diff --git a/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr b/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr index 410b6c953..249312298 100644 --- a/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr @@ -1,6 +1,6 @@ field f: Int -// test fails + method permTest(a: Ref, b: Ref, n: Int) requires @dependency()(acc(a.f)) requires @irrelevant()(acc(b.f)) && @irrelevant()(b.f > 0) diff --git a/src/test/resources/dependencyAnalysisTests/new/meeting.vpr b/src/test/resources/dependencyAnalysisTests/new/meeting.vpr index 768876a3b..96015dc43 100644 --- a/src/test/resources/dependencyAnalysisTests/new/meeting.vpr +++ b/src/test/resources/dependencyAnalysisTests/new/meeting.vpr @@ -55,3 +55,18 @@ method noAlias(a: Ref, b: Ref, c: Ref) @testAssertion() assert a != b } + + +method permTest(a: Ref, b: Ref, n: Int) + requires @dependency()(acc(a.f)) + requires @irrelevant()(acc(b.f)) && @irrelevant()(b.f > 0) +{ + @dependency() + assume n > 0 + @irrelevant() + a.f := b.f + 2 + @dependency() + a.f := n + @testAssertion() + assert a.f >= 0 +} From ec3e979660984d318b0f378b822ae988e3b07787 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 1 Jul 2025 15:01:26 +0200 Subject: [PATCH 135/474] fix field assign soundness bug --- .../AnalysisSourceInfo.scala | 40 ++++++++++++++++--- .../AssumptionAnalysisGraph.scala | 8 ++-- .../AssumptionAnalyzer.scala | 7 ++++ src/main/scala/rules/Evaluator.scala | 5 +-- src/main/scala/rules/Executor.scala | 26 ++++++------ src/main/scala/rules/StateConsolidator.scala | 4 +- .../all/imprecision.vpr | 15 +++++++ .../all/method-sum.vpr | 1 - .../unitTests/quantifiedPermissions.vpr | 13 ++++++ src/test/scala/AssumptionAnalysisTests.scala | 24 ++++++----- 10 files changed, 105 insertions(+), 38 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala index fcca9a491..47e5142fd 100644 --- a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala +++ b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala @@ -6,9 +6,9 @@ import viper.silver.ast._ abstract class AnalysisSourceInfo { - override def toString: String = getStringForExport + override def toString: String = getPositionString - def getStringForExport: String = { + def getPositionString: String = { getPosition match { case NoPosition => "???" case filePos: AbstractSourcePosition => filePos.file.getFileName.toString + " @ line " + filePos.line @@ -19,8 +19,19 @@ abstract class AnalysisSourceInfo { def getPosition: Position + /** + * @return the analysis source info used for merging nodes + */ def getTopLevelSource: AnalysisSourceInfo = this + /** + * @return the analysis source info used for adding transitive edges within a source exp/stmt + */ + def getSourceForTransitiveEdges: AnalysisSourceInfo = getTopLevelSource + + /** + * @return the analysis source info used for joining graphs + */ def getFineGrainedSource: AnalysisSourceInfo = this def isAnalysisEnabled: Boolean = true @@ -68,14 +79,27 @@ case class StringAnalysisSourceInfo(description: String, position: Position) ext override def getPosition: Position = position } +case class TransitivityAnalysisSourceInfo(actualSource: AnalysisSourceInfo, transitivitySource: AnalysisSourceInfo) extends AnalysisSourceInfo { + + override def getPosition: Position = actualSource.getPosition + override def toString: String = getTopLevelSource.toString + + override def equals(obj: Any): Boolean = actualSource.equals(obj) + + override def getSourceForTransitiveEdges: AnalysisSourceInfo = transitivitySource + override def getTopLevelSource: AnalysisSourceInfo = actualSource.getTopLevelSource + override def getFineGrainedSource: AnalysisSourceInfo = actualSource.getFineGrainedSource + override def isAnalysisEnabled: Boolean = actualSource.isAnalysisEnabled +} + case class CompositeAnalysisSourceInfo(coarseGrainedSource: AnalysisSourceInfo, fineGrainedSource: AnalysisSourceInfo) extends AnalysisSourceInfo { - override def toString: String = coarseGrainedSource.toString + override def toString: String = getTopLevelSource.toString override def getPosition: Position = coarseGrainedSource.getPosition override def equals(obj: Any): Boolean = coarseGrainedSource.equals(obj) - override def getTopLevelSource: AnalysisSourceInfo = coarseGrainedSource - override def getFineGrainedSource: AnalysisSourceInfo = fineGrainedSource + override def getTopLevelSource: AnalysisSourceInfo = coarseGrainedSource.getTopLevelSource + override def getFineGrainedSource: AnalysisSourceInfo = fineGrainedSource.getFineGrainedSource override def isAnalysisEnabled: Boolean = coarseGrainedSource.isAnalysisEnabled && fineGrainedSource.isAnalysisEnabled } @@ -121,6 +145,8 @@ case class AnalysisSourceInfoStack() { sourceInfos = currSourceInfo.tail } + def getForcedSource: Option[AnalysisSourceInfo] = forcedMainSource + def setForcedSource(description: String): Unit = { forcedMainSource = Some(StringAnalysisSourceInfo(description, getFullSourceInfo.getPosition)) } @@ -129,6 +155,10 @@ case class AnalysisSourceInfoStack() { forcedMainSource = Some(source) } + def setForcedSource(sourceOpt: Option[AnalysisSourceInfo]): Unit = { + forcedMainSource = sourceOpt + } + def removeForcedSource(): Unit = { forcedMainSource = None } diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index 1371339e8..34b90cae9 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -77,10 +77,10 @@ trait AssumptionAnalysisGraph { res } - def getNodesPerSourceInfo(): mutable.HashMap[AnalysisSourceInfo, Seq[AssumptionAnalysisNode]] = { + def getNodesPerTransitivitySourceInfo(): mutable.HashMap[AnalysisSourceInfo, Seq[AssumptionAnalysisNode]] = { val res = new mutable.HashMap[AnalysisSourceInfo, Seq[AssumptionAnalysisNode]]() nodes foreach {n => - res.updateWith(n.sourceInfo.getTopLevelSource)({ + res.updateWith(n.sourceInfo.getSourceForTransitiveEdges)({ case Some(ns) => Some(ns ++ Seq(n)) case None => Some(Seq(n)) }) @@ -99,7 +99,7 @@ trait AssumptionAnalysisGraph { } def addTransitiveEdges(): Unit = { - val nodesPerSourceInfo = getNodesPerSourceInfo() + val nodesPerSourceInfo = getNodesPerTransitivitySourceInfo() nodesPerSourceInfo foreach {nodes => val asserts = nodes._2.filter(_.isInstanceOf[GeneralAssertionNode]) val assumes = nodes._2.filter(n => !n.isClosed && n.isInstanceOf[GeneralAssumptionNode]) @@ -128,7 +128,7 @@ trait AssumptionAnalysisGraph { private def exportNodes(fileName: String): Unit = { val sep = "#" def getNodeExportString(node: AssumptionAnalysisNode): String = { - val parts = Seq(node.id.toString, node.getNodeType, node.assumptionType.toString, node.getNodeString, node.sourceInfo.toString, node.sourceInfo.getStringForExport, node.sourceInfo.getFineGrainedSource.toString) + val parts = Seq(node.id.toString, node.getNodeType, node.assumptionType.toString, node.getNodeString, node.sourceInfo.toString, node.sourceInfo.getPositionString, node.sourceInfo.getFineGrainedSource.toString) parts.map(_.replace("#", "@")).mkString(sep) } val writer = new PrintWriter(fileName) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index bd4c6035b..d7b02fb9e 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -35,6 +35,7 @@ trait AssumptionAnalyzer { def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit def addPermissionDependencies(sourceChunks: Set[Chunk], sourceTerms: Set[Term], targetChunk: Chunk): Unit def addDependencyFromExhaleToInhale(inhaledChunk: Chunk, sourceInfo: AnalysisSourceInfo): Unit + def addCustomTransitiveDependency(sourceSourceInfo: AnalysisSourceInfo, targetSourceInfo: AnalysisSourceInfo): Unit = {} def getMember: Option[ast.Member] @@ -224,6 +225,12 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { assumptionGraph.addEdges(source, Set(dest)) } + override def addCustomTransitiveDependency(sourceSourceInfo: AnalysisSourceInfo, targetSourceInfo: AnalysisSourceInfo): Unit = { + val sourceNodes = assumptionGraph.nodes filter (n => n.isInstanceOf[GeneralAssertionNode] && n.sourceInfo.getSourceForTransitiveEdges.equals(sourceSourceInfo.getSourceForTransitiveEdges)) + val targetNodes = assumptionGraph.nodes filter (n => n.isInstanceOf[GeneralAssumptionNode] && n.sourceInfo.getSourceForTransitiveEdges.equals(targetSourceInfo.getSourceForTransitiveEdges)) + assumptionGraph.addEdges(sourceNodes map (_.id), targetNodes map (_.id)) + } + override def getMember: Option[ast.Member] = Some(member) override def exportGraph(): Unit = { diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 4f9c04c79..602b5a12b 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -828,13 +828,12 @@ object evaluator extends EvaluationRules { val auxNonGlobalsExp = auxExps.map(_._2) val commentGlobal = "Nested auxiliary terms: globals (aux)" v1.decider.prover.comment(commentGlobal) - v1.decider.analysisSourceInfoStack.setForcedSource(commentGlobal) + v1.decider.assumptionAnalyzer.disableTransitiveEdges() v1.decider.assume(tAuxGlobal, Option.when(withExp)(DebugExp.createInstance(description=commentGlobal, children=auxGlobalsExp.get)), enforceAssumption = false, assumptionType=AssumptionType.Internal) - v1.decider.analysisSourceInfoStack.removeForcedSource() val commentNonGlobals = "Nested auxiliary terms: non-globals (aux)" v1.decider.prover.comment(commentNonGlobals) v1.decider.assume(tAuxHeapIndep/*tAux*/, Option.when(withExp)(DebugExp.createInstance(description=commentNonGlobals, children=auxNonGlobalsExp.get)), enforceAssumption = false, assumptionType=AssumptionType.Internal) - + v1.decider.assumptionAnalyzer.enableTransitiveEdges() if (qantOp == Exists) { // For universal quantification, the non-global auxiliary assumptions will contain the information that // forall vars :: all function preconditions are fulfilled. diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index c277d77f5..0d458e350 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -8,7 +8,7 @@ package viper.silicon.rules import viper.silicon.Config.JoinMode import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType -import viper.silicon.assumptionAnalysis.{AssumptionAnalyzer, AssumptionType, StmtAnalysisSourceInfo} +import viper.silicon.assumptionAnalysis.{AssumptionAnalyzer, AssumptionType, ExpAnalysisSourceInfo, StmtAnalysisSourceInfo, TransitivityAnalysisSourceInfo} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.decider.RecordedPathConditions @@ -402,7 +402,8 @@ object executor extends ExecutionRules { s2 } v2.decider.clearModel() - v2.decider.assumptionAnalyzer.disableTransitiveEdges() // inhaling new chunks should not depend on any of the rhs assertions + val lhsSourceInfo = TransitivityAnalysisSourceInfo(v2.decider.analysisSourceInfoStack.getFullSourceInfo, ExpAnalysisSourceInfo(fa)) + v2.decider.analysisSourceInfoStack.setForcedSource(lhsSourceInfo) // splitting lhs and rhs to make permission flow analysis more precise val result = quantifiedChunkSupporter.removePermissions( s2p, relevantChunks, @@ -417,23 +418,23 @@ object executor extends ExecutionRules { chunkOrderHeuristics, v2 ) - v2.decider.assumptionAnalyzer.enableTransitiveEdges() + v2.decider.analysisSourceInfoStack.removeForcedSource() result match { case (Complete(), s3, remainingChunks, consumedChunks) => // TODO ake: what to do with consumedChunks? val h3 = Heap(remainingChunks ++ otherChunks) val (sm, smValueDef) = quantifiedChunkSupporter.singletonSnapshotMap(s3, field, Seq(tRcvr), tRhs, v2) v1.decider.prover.comment("Definitional axioms for singleton-FVF's value") val debugExp = Option.when(withExp)(DebugExp.createInstance("Definitional axioms for singleton-FVF's value", isInternal_ = true)) - v1.decider.assumeDefinition(smValueDef, debugExp, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Internal)) - v1.decider.assumptionAnalyzer.disableTransitiveEdges() + v1.decider.assumeDefinition(smValueDef, debugExp, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit)) + v1.decider.analysisSourceInfoStack.setForcedSource(lhsSourceInfo) // splitting lhs and rhs to make permission flow analysis more precise val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(Seq(`?r`), Option.when(withExp)(Seq(ast.LocalVarDecl("r", ast.Ref)(ass.pos, ass.info, ass.errT))), field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), sm, s.program, v1, AssumptionType.Internal, isExhale=false) - v1.decider.assumptionAnalyzer.addDependencyFromExhaleToInhale(ch, v1.decider.getAnalysisInfo.sourceInfo) - v1.decider.assumptionAnalyzer.enableTransitiveEdges() if (s3.heapDependentTriggers.contains(field)) { val debugExp2 = Option.when(withExp)(DebugExp.createInstance(s"FieldTrigger(${eRcvrNew.toString}.${field.name})")) v1.decider.assume(FieldTrigger(field.name, sm, tRcvr), debugExp2, AssumptionType.Internal) } + v1.decider.analysisSourceInfoStack.removeForcedSource() + v1.decider.assumptionAnalyzer.addCustomTransitiveDependency(lhsSourceInfo, v1.decider.analysisSourceInfoStack.getFullSourceInfo) val s4 = s3.copy(h = h3 + ch) val (debugHeapName, _) = v.getDebugOldLabel(s4, fa.pos) val s5 = if (withExp) s4.copy(oldHeaps = s4.oldHeaps + (debugHeapName -> magicWandSupporter.getEvalHeap(s4))) else s4 @@ -449,20 +450,21 @@ object executor extends ExecutionRules { val resource = fa.res(s.program) val ve = pve dueTo InsufficientPermission(fa) val description = s"consume ${ass.pos}: $ass" - v2.decider.assumptionAnalyzer.disableTransitiveEdges() // TODO ake: due to this we are missing edges from checks to exhale or within state consolidation + val lhsSourceInfo = TransitivityAnalysisSourceInfo(v2.decider.analysisSourceInfoStack.getFullSourceInfo, ExpAnalysisSourceInfo(fa)) + v2.decider.analysisSourceInfoStack.setForcedSource(lhsSourceInfo) // splitting lhs and rhs to make permission flow analysis more precise chunkSupporter.consume(s2, s2.h, resource, Seq(tRcvr), eRcvrNew.map(Seq(_)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), false, ve, v2, description)((s3, h3, _, consumedChunks, v3) => { - v2.decider.assumptionAnalyzer.enableTransitiveEdges() + v2.decider.analysisSourceInfoStack.removeForcedSource() val (tSnap, _) = ssaifyRhs(tRhs, rhs, rhsNew, field.name, field.typ, v3, s3, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit)) val id = BasicChunkIdentifier(field.name) - v2.decider.assumptionAnalyzer.disableTransitiveEdges() // inhaling the new chunk should not depend on any rhs assertions, dependency to exhaling old chunk is added nevertheless + v2.decider.analysisSourceInfoStack.setForcedSource(lhsSourceInfo) // splitting lhs and rhs to make permission flow analysis more precise val newChunk = BasicChunk.apply(FieldID, id, Seq(tRcvr), eRcvrNew.map(Seq(_)), tSnap, rhsNew, FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), v3.decider.getAnalysisInfo(AssumptionType.Internal)) - v3.decider.assumptionAnalyzer.addDependencyFromExhaleToInhale(newChunk, v3.decider.getAnalysisInfo.sourceInfo) - v2.decider.assumptionAnalyzer.enableTransitiveEdges() + v2.decider.analysisSourceInfoStack.removeForcedSource() chunkSupporter.produce(s3, h3, newChunk, v3)((s4, h4, v4) => { val s5 = s4.copy(h = h4) val (debugHeapName, _) = v4.getDebugOldLabel(s5, fa.pos) val s6 = if (withExp) s5.copy(oldHeaps = s5.oldHeaps + (debugHeapName -> magicWandSupporter.getEvalHeap(s5))) else s5 + v4.decider.assumptionAnalyzer.addCustomTransitiveDependency(lhsSourceInfo, v4.decider.analysisSourceInfoStack.getFullSourceInfo) Q(s6, v4) }) }) diff --git a/src/main/scala/rules/StateConsolidator.scala b/src/main/scala/rules/StateConsolidator.scala index ad6c90522..805e19244 100644 --- a/src/main/scala/rules/StateConsolidator.scala +++ b/src/main/scala/rules/StateConsolidator.scala @@ -60,6 +60,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol def consolidate(s: State, v: Verifier): State = { val comLog = new CommentRecord("state consolidation", s, v.decider.pcs) val sepIdentifier = v.symbExLog.openScope(comLog) + val prevForcedSource = v.decider.analysisSourceInfoStack.getForcedSource v.decider.analysisSourceInfoStack.setForcedSource("state consolidation") v.decider.prover.comment("[state consolidation]") v.decider.prover.saturate(config.proverSaturationTimeouts.beforeIteration) @@ -121,8 +122,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol reserveHeaps = mergedHeaps.tail) val s2 = assumeUpperPermissionBoundForQPFields(s1, v) - v.decider.analysisSourceInfoStack.removeForcedSource() - + v.decider.analysisSourceInfoStack.setForcedSource(prevForcedSource) s2 } diff --git a/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr b/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr index 249312298..1c183ea31 100644 --- a/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr @@ -54,3 +54,18 @@ method quantifiedPerm4(xs: Seq[Ref], ys: Seq[Ref]) @testAssertion() assert xs[0].f > 0 } + +method quantifiedPerm5(xs: Seq[Ref], ys: Seq[Ref]) + requires @dependency()(|xs| > 5) + requires @irrelevant()(|ys| > 3) +{ + @dependency() + inhale forall x: Ref :: x in xs ==> acc(x.f) + @irrelevant() + inhale forall y: Ref :: y in ys ==> acc(y.f) + + @irrelevant() + xs[0].f := ys[0].f + 2 + @testAssertion() + xs[0].f := 2 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/method-sum.vpr b/src/test/resources/dependencyAnalysisTests/all/method-sum.vpr index 9185796d1..e9c22ad06 100644 --- a/src/test/resources/dependencyAnalysisTests/all/method-sum.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/method-sum.vpr @@ -42,7 +42,6 @@ method sumClient2(x: Int, y: Int) assume x < y @irrelevant() var n: Int := sum(x, x) - @irrelevant() assert n >= 100 @dependency() diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr index 2eecc2134..829981be2 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr @@ -86,4 +86,17 @@ method quantifiedPermWrite3(xs: Seq[Ref]) { @testAssertion() assert xs[0] != xs[1] ==> xs[1].f > 0 +} + +method quantifiedPermWrite4(xs: Seq[Ref], ys: Seq[Ref]) + requires @dependency()(|xs| > 5) + requires @irrelevant()(|ys| > 3) +{ + @dependency() + inhale forall x: Ref :: x in xs ==> acc(x.f) + @irrelevant() + inhale forall y: Ref :: y in ys ==> acc(y.f) + + @testAssertion() + xs[0].f := 2 } \ No newline at end of file diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index dd3b7bf7f..aad06fcf7 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -16,13 +16,13 @@ import scala.util.{Failure, Success} /** * Annotations * @dependency() -> for assumptions that should be reported as a dependency - * @obsolete() -> for assumptions that should NOT be reported as a dependency + * @irrelevant() -> for assumptions that should NOT be reported as a dependency * @testAssertion() -> the queried assertion * * assumptions/assertions that are not annotated are ignored * * !!! THERE CAN ONLY BE 1 TEST ASSERTION PER METHOD, - * but multiple dependency/obsolete annotations are allowed + * but multiple dependency/irrelevant annotations are allowed * */ class AssumptionAnalysisTests extends AnyFunSuite { @@ -30,7 +30,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { val CHECK_PRECISION = true val ignores: Seq[String] = Seq("infeasible") - val obsoleteKeyword = "irrelevant" + val irrelevantKeyword = "irrelevant" val dependencyKeyword = "dependency" val testAssertionKeyword = "testAssertion" @@ -131,7 +131,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { val assumptionAnalyzers = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].assumptionAnalyzers val assumptionGraphs = assumptionAnalyzers map (_.assumptionGraph) - val stmtsWithAssumptionAnnotation: Set[Infoed] = extractAnnotatedStmts(program, { annotationInfo => annotationInfo.values.contains(obsoleteKeyword) || annotationInfo.values.contains(dependencyKeyword)}) + val stmtsWithAssumptionAnnotation: Set[Infoed] = extractAnnotatedStmts(program, { annotationInfo => annotationInfo.values.contains(irrelevantKeyword) || annotationInfo.values.contains(dependencyKeyword)}) val allAssumptionNodes = assumptionGraphs.flatMap(_.nodes.filter(_.isInstanceOf[GeneralAssumptionNode])) var errorMsgs = stmtsWithAssumptionAnnotation.map(checkNodeExists(allAssumptionNodes, _)).filter(_.isDefined).map(_.get).toSeq @@ -163,10 +163,12 @@ class AssumptionAnalysisTests extends AnyFunSuite { private def checkNodeExists(analysisNodes: List[AssumptionAnalysisNode], node: Infoed): Option[String] = { val pos = extractSourceLine(node.asInstanceOf[Positioned].pos) val annotationInfo = node.info.getUniqueInfo[AnnotationInfo] - .map(ai => ai.values.getOrElse(obsoleteKeyword, ai.values.getOrElse(dependencyKeyword, List.empty))).getOrElse(List.empty) + .map(ai => ai.values.getOrElse(irrelevantKeyword, ai.values.getOrElse(dependencyKeyword, List.empty))).getOrElse(List.empty) val assumptionType = annotationInfo.map(AssumptionType.fromString).filter(_.isDefined).map(_.get) val nodeExists = analysisNodes exists (analysisNode => { - extractSourceLine(analysisNode.sourceInfo.getPosition) == pos && + analysisNode.isInstanceOf[GeneralAssumptionNode] && + !analysisNode.asInstanceOf[GeneralAssumptionNode].assumptionType.equals(AssumptionType.Internal) && + extractSourceLine(analysisNode.sourceInfo.getPosition) == pos && assumptionType.forall(_.equals(analysisNode.assumptionType)) }) Option.when(!nodeExists)(s"Missing analysis node:\n${node.toString}\n$pos") @@ -180,7 +182,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { } private def checkTestAssertionNodeExists(assumptionAnalyzer: AssumptionAnalyzer): Seq[String] = { - val assumptionNodes = extractTestAssumptionNodesFromGraph(assumptionAnalyzer.assumptionGraph) ++ extractTestObsoleteAssumptionNodesFromGraph(assumptionAnalyzer.assumptionGraph) + val assumptionNodes = extractTestAssumptionNodesFromGraph(assumptionAnalyzer.assumptionGraph) ++ extractTestIrrelevantAssumptionNodesFromGraph(assumptionAnalyzer.assumptionGraph) val assertionNodes = extractTestAssertionNodesFromGraph(assumptionAnalyzer.assumptionGraph) if(assumptionNodes.nonEmpty && assertionNodes.isEmpty) Seq(s"Missing testAssertion for member: ${assumptionAnalyzer.getMember.map(_.name).getOrElse("unknown")}") @@ -200,7 +202,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { } def checkNonDependencies(assumptionGraph: AssumptionAnalysisGraph): Seq[String] = { - val assumptionNodes = extractTestObsoleteAssumptionNodesFromGraph(assumptionGraph) + val assumptionNodes = extractTestIrrelevantAssumptionNodesFromGraph(assumptionGraph) val assumptionsPerSource = assumptionNodes groupBy(n => extractSourceLine(n.sourceInfo.getPosition)) val assertionNodes = extractTestAssertionNodesFromGraph(assumptionGraph) @@ -226,17 +228,17 @@ class AssumptionAnalysisTests extends AnyFunSuite { def extractTestAssumptionNodesFromGraph(graph: AssumptionAnalysisGraph): Seq[AssumptionAnalysisNode] = { graph.nodes.filter(node => { (node.getNodeType.equals("Assumption") || node.getNodeType.equals("Inhale")) && - !node.assumptionType.equals(AssumptionType.Internal) && // TODO ake: really? + !node.assumptionType.equals(AssumptionType.Internal) && node.sourceInfo.toString.contains("@" + dependencyKeyword + "()") } ).toSeq } - def extractTestObsoleteAssumptionNodesFromGraph(graph: AssumptionAnalysisGraph): Seq[AssumptionAnalysisNode] = { + def extractTestIrrelevantAssumptionNodesFromGraph(graph: AssumptionAnalysisGraph): Seq[AssumptionAnalysisNode] = { graph.nodes.filter(node => { (node.getNodeType.equals("Assumption") || node.getNodeType.equals("Inhale")) && !node.assumptionType.equals(AssumptionType.Internal) && - node.sourceInfo.toString.contains("@" + obsoleteKeyword + "()") + node.sourceInfo.toString.contains("@" + irrelevantKeyword + "()") } ).toSeq } From 11823776b84fddc48d127ab42ccd7ae6be71d45f Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 2 Jul 2025 07:25:25 +0200 Subject: [PATCH 136/474] minor fix --- silver | 2 +- src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/silver b/silver index 77fc2f87d..b2b358431 160000 --- a/silver +++ b/silver @@ -1 +1 @@ -Subproject commit 77fc2f87d52f1068b098b189a55b2ff1c5927066 +Subproject commit b2b3584313abe988cebaf7d9e59b32a7cd0f65e8 diff --git a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala index 47e5142fd..76ed56398 100644 --- a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala +++ b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala @@ -42,8 +42,7 @@ case class NoAnalysisSourceInfo() extends AnalysisSourceInfo { } case class ExpAnalysisSourceInfo(source: ast.Exp) extends AnalysisSourceInfo { - override def toString: String = (if(source.info.getSourceString.isEmpty) source.toString else source.info.getSourceString) + - " (" + super.toString + ")" + override def toString: String = source.toString + " (" + super.toString + ")" override def getPosition: Position = source.pos @@ -59,8 +58,7 @@ case class ExpAnalysisSourceInfo(source: ast.Exp) extends AnalysisSourceInfo { } case class StmtAnalysisSourceInfo(source: ast.Stmt) extends AnalysisSourceInfo { - override def toString: String = (if(source.info.getSourceString.isEmpty) source.toString() else source.info.getSourceString) + - " (" + super.toString + ")" + override def toString: String = source.toString + " (" + super.toString + ")" override def getPosition: Position = source.pos override def equals(obj: Any): Boolean = { From 62af2ef9e2c11d712de43a9a28b45d6deae77bd2 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 2 Jul 2025 07:48:32 +0200 Subject: [PATCH 137/474] Revert "minor fix" This reverts commit 11823776b84fddc48d127ab42ccd7ae6be71d45f. --- silver | 2 +- src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/silver b/silver index b2b358431..77fc2f87d 160000 --- a/silver +++ b/silver @@ -1 +1 @@ -Subproject commit b2b3584313abe988cebaf7d9e59b32a7cd0f65e8 +Subproject commit 77fc2f87d52f1068b098b189a55b2ff1c5927066 diff --git a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala index 76ed56398..47e5142fd 100644 --- a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala +++ b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala @@ -42,7 +42,8 @@ case class NoAnalysisSourceInfo() extends AnalysisSourceInfo { } case class ExpAnalysisSourceInfo(source: ast.Exp) extends AnalysisSourceInfo { - override def toString: String = source.toString + " (" + super.toString + ")" + override def toString: String = (if(source.info.getSourceString.isEmpty) source.toString else source.info.getSourceString) + + " (" + super.toString + ")" override def getPosition: Position = source.pos @@ -58,7 +59,8 @@ case class ExpAnalysisSourceInfo(source: ast.Exp) extends AnalysisSourceInfo { } case class StmtAnalysisSourceInfo(source: ast.Stmt) extends AnalysisSourceInfo { - override def toString: String = source.toString + " (" + super.toString + ")" + override def toString: String = (if(source.info.getSourceString.isEmpty) source.toString() else source.info.getSourceString) + + " (" + super.toString + ")" override def getPosition: Position = source.pos override def equals(obj: Any): Boolean = { From f238e31a684662f82be5854fbb4a9bc995a9235f Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 2 Jul 2025 12:05:58 +0200 Subject: [PATCH 138/474] minor fixes --- .../scala/assumptionAnalysis/AnalysisSourceInfo.scala | 2 +- .../assumptionAnalysis/AssumptionAnalysisGraph.scala | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala index 47e5142fd..00c2bb9d9 100644 --- a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala +++ b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala @@ -86,7 +86,7 @@ case class TransitivityAnalysisSourceInfo(actualSource: AnalysisSourceInfo, tran override def equals(obj: Any): Boolean = actualSource.equals(obj) - override def getSourceForTransitiveEdges: AnalysisSourceInfo = transitivitySource + override def getSourceForTransitiveEdges: AnalysisSourceInfo = transitivitySource.getTopLevelSource override def getTopLevelSource: AnalysisSourceInfo = actualSource.getTopLevelSource override def getFineGrainedSource: AnalysisSourceInfo = actualSource.getFineGrainedSource override def isAnalysisEnabled: Boolean = actualSource.isAnalysisEnabled diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index 34b90cae9..e5e55db2c 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -77,10 +77,10 @@ trait AssumptionAnalysisGraph { res } - def getNodesPerTransitivitySourceInfo(): mutable.HashMap[AnalysisSourceInfo, Seq[AssumptionAnalysisNode]] = { - val res = new mutable.HashMap[AnalysisSourceInfo, Seq[AssumptionAnalysisNode]]() + def getNodesPerTransitivitySourceInfo(): mutable.HashMap[String, Seq[AssumptionAnalysisNode]] = { + val res = new mutable.HashMap[String, Seq[AssumptionAnalysisNode]]() nodes foreach {n => - res.updateWith(n.sourceInfo.getSourceForTransitiveEdges)({ + res.updateWith(n.sourceInfo.getSourceForTransitiveEdges.toString)({ case Some(ns) => Some(ns ++ Seq(n)) case None => Some(Seq(n)) }) @@ -90,7 +90,7 @@ trait AssumptionAnalysisGraph { def addTransitiveEdges(source: AssumptionAnalysisNode, targets: Iterable[AssumptionAnalysisNode]): Unit = { val oldTargets = transitiveEdges.getOrElse(source.id, Set.empty) - val newTargets = targets map(_.id) + val newTargets = targets map(_.id) filter(_ > source.id) if(newTargets.nonEmpty) transitiveEdges.update(source.id, oldTargets ++ newTargets) } @@ -102,7 +102,7 @@ trait AssumptionAnalysisGraph { val nodesPerSourceInfo = getNodesPerTransitivitySourceInfo() nodesPerSourceInfo foreach {nodes => val asserts = nodes._2.filter(_.isInstanceOf[GeneralAssertionNode]) - val assumes = nodes._2.filter(n => !n.isClosed && n.isInstanceOf[GeneralAssumptionNode]) + val assumes = nodes._2.filter(n => !n.isClosed && n.isInstanceOf[GeneralAssumptionNode] && !n.isInstanceOf[LabelNode]) addTransitiveEdges(asserts, assumes) val checks = asserts.filter(_.isInstanceOf[SimpleCheckNode]) val notChecks = nodes._2.filter(n => !n.isClosed && !n.isInstanceOf[SimpleCheckNode]) From 272f6d56f1dc77db327ccf7b12ec865a64e611eb Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 3 Jul 2025 11:22:48 +0200 Subject: [PATCH 139/474] handle infeasibility (PoC) --- silver | 2 +- .../AssumptionAnalysisGraph.scala | 11 +++++- .../AssumptionAnalyzer.scala | 33 +++++++++++++---- src/main/scala/decider/Decider.scala | 28 +++++++++++---- src/main/scala/rules/Brancher.scala | 36 ++++++++++++++----- 5 files changed, 86 insertions(+), 24 deletions(-) diff --git a/silver b/silver index 77fc2f87d..1cde18f4f 160000 --- a/silver +++ b/silver @@ -1 +1 @@ -Subproject commit 77fc2f87d52f1068b098b189a55b2ff1c5927066 +Subproject commit 1cde18f4fa827500ed4a3ff13c57a5e9e9fce840 diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index e5e55db2c..9f6789f7f 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -2,7 +2,7 @@ package viper.silicon.assumptionAnalysis import viper.silicon.assumptionAnalysis.AssumptionType._ import viper.silicon.interfaces.state.Chunk -import viper.silicon.state.terms.Term +import viper.silicon.state.terms.{False, Term} import viper.silver.ast import viper.silver.ast.Position @@ -238,3 +238,12 @@ case class LabelNode(term: Term) extends GeneralAssumptionNode { override def getNodeString: String = "assume " + description } +case class InfeasibilityNode(sourceInfo: AnalysisSourceInfo) extends GeneralAssertionNode { + val term: Term = False + val assumptionType: AssumptionType = AssumptionType.Internal + val isClosed: Boolean = true + val description: String = "False" + + override def getNodeString: String = "infeasible" +} + diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index d7b02fb9e..1604297ea 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -10,6 +10,7 @@ import viper.silver.ast trait AssumptionAnalyzer { val assumptionGraph: AssumptionAnalysisGraph = new DefaultAssumptionAnalysisGraph() protected var isClosed_ = false + protected var infeasibilityNode: Option[Int] = None def disableTransitiveEdges(): Unit = { isClosed_ = true @@ -19,6 +20,16 @@ trait AssumptionAnalyzer { isClosed_ = false } + def getinfeasibilityNode: Option[Int] = infeasibilityNode + + def setinfeasibilityNode(nodeId: Int): Unit = { + infeasibilityNode = Some(nodeId) + } + + def unsetinfeasibilityNode(): Unit = { + infeasibilityNode = None + } + def getNodes: Iterable[AssumptionAnalysisNode] def getChunkInhaleNode(chunk: Chunk): Option[PermissionInhaleNode] = None @@ -30,8 +41,9 @@ trait AssumptionAnalyzer { def createAssertOrCheckNode(term: Term, assertion: Either[String, ast.Exp], analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] def addAssertFalseNode(isCheck: Boolean, sourceInfo: AnalysisSourceInfo): Option[Int] + def addInfeasibilityNode(isCheck: Boolean, sourceInfo: AnalysisSourceInfo): Option[Int] = None - def addDependency(source: Int, dest: Int): Unit + def addDependency(source: Option[Int], dest: Option[Int]): Unit def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit def addPermissionDependencies(sourceChunks: Set[Chunk], sourceTerms: Set[Term], targetChunk: Chunk): Unit def addDependencyFromExhaleToInhale(inhaledChunk: Chunk, sourceInfo: AnalysisSourceInfo): Unit @@ -141,6 +153,12 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { node.map(_.id) } + override def addInfeasibilityNode(isCheck: Boolean, sourceInfo: AnalysisSourceInfo): Option[Int] = { + val node = InfeasibilityNode(sourceInfo) + addNode(node) + Some(node.id) + } + override def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit = { val assumptionLabels = dep.replace("(", "").replace(")", "").split(" ") val assumptionIds = assumptionLabels.filter(AssumptionAnalyzer.isAssumptionLabel).map(AssumptionAnalyzer.getIdFromLabel) @@ -221,8 +239,9 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { addPermissionDependencies(sourceChunks, sourceTerms, newChunkId.headOption) } - override def addDependency(source: Int, dest: Int): Unit = { - assumptionGraph.addEdges(source, Set(dest)) + override def addDependency(source: Option[Int], dest: Option[Int]): Unit = { + if(source.isDefined && dest.isDefined) + assumptionGraph.addEdges(source.get, Set(dest.get)) } override def addCustomTransitiveDependency(sourceSourceInfo: AnalysisSourceInfo, targetSourceInfo: AnalysisSourceInfo): Unit = { @@ -268,7 +287,7 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { val chunk = buildChunk(Ite(labelNode.term, perm, NoPerm)) val chunkNode = addPermissionInhaleNode(chunk, chunk.perm, analysisInfo.sourceInfo, analysisInfo.assumptionType, labelNode) if(chunkNode.isDefined) - addDependency(chunkNode.get, labelNode.id) + addDependency(chunkNode, Some(labelNode.id)) addPermissionDependencies(sourceChunks, Set(), chunkNode) chunk } @@ -293,7 +312,7 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { override def addNodes(nodes: Iterable[AssumptionAnalysisNode]): Unit = {} override def createAssertOrCheckNode(term: Term, assertion: Either[String, ast.Exp], analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] = None - override def addAssertFalseNode(isCheck: Boolean, sourceInfo: AnalysisSourceInfo): Option[Int] = None + override def addInfeasibilityNode(isCheck: Boolean, sourceInfo: AnalysisSourceInfo): Option[Int] = None override def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit = { } @@ -303,9 +322,11 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { override def addDependencyFromExhaleToInhale(inhaledChunk: Chunk, sourceInfo: AnalysisSourceInfo): Unit = {} override def addPermissionDependencies(sourceChunks: Set[Chunk], sourceTerms: Set[Term], newChunkNodeId: Chunk): Unit = {} - def addDependency(source: Int, dest: Int): Unit = {} + def addDependency(source: Option[Int], dest: Option[Int]): Unit = {} override def getMember: Option[ast.Member] = None override def addAssumption(term: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, description: Option[String]): Option[Int] = None override def exportGraph(): Unit = {} + + override def addAssertFalseNode(isCheck: Boolean, sourceInfo: AnalysisSourceInfo): Option[Int] = None } \ No newline at end of file diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 3e7a470b4..a7f3290e7 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -75,6 +75,7 @@ trait Decider { def assumeLabel(term: Term, assumptionLabel: String): Unit def check(t: Term, timeout: Int): Boolean + def checkAndGetInfeasibilityNode(t: Term, timeout: Int): (Boolean, Option[Int]) /* TODO: Consider changing assert such that * 1. It passes State and Operations to the continuation @@ -492,10 +493,10 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => /* Asserting facts */ def checkSmoke(isAssert: Boolean = false): Boolean = { - val label = if(Verifier.config.enableAssumptionAnalysis()){ + val (label, checkNodeId) = if(Verifier.config.enableAssumptionAnalysis()){ val nodeId = assumptionAnalyzer.addAssertFalseNode(!isAssert, analysisSourceInfoStack.getFullSourceInfo) // TODO ake: add node only if it can be verified - AssumptionAnalyzer.createAssertionLabel(nodeId) - }else{ "" } + (AssumptionAnalyzer.createAssertionLabel(nodeId), nodeId) + }else{ ("", None) } if(_isReassumeAnalysisLabelsRequired) { reassumeAssumptionAnalysisLabels() @@ -504,13 +505,26 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => val timeout = if (isAssert) Verifier.config.assertTimeout.toOption else Verifier.config.checkTimeout.toOption val result = prover.check(timeout, label) == Unsat - if(result) + if(result) { assumptionAnalyzer.processUnsatCoreAndAddDependencies(prover.getLastUnsatCore, label) + val infeasibleNodeId = assumptionAnalyzer.addInfeasibilityNode(!isAssert, analysisSourceInfoStack.getFullSourceInfo) + assumptionAnalyzer.addDependency(checkNodeId, infeasibleNodeId) + } result } def check(t: Term, timeout: Int): Boolean = { - deciderAssert(t, Left(""), Some(timeout), isCheck=true) + deciderAssert(t, Left(""), Some(timeout), isCheck=true)._1 + } + + def checkAndGetInfeasibilityNode(t: Term, timeout: Int): (Boolean, Option[Int]) = { + val (success, checkNode) = deciderAssert(t, Left(""), Some(timeout), isCheck=true) + var infeasibilityNodeId: Option[Int] = None + if(success){ + infeasibilityNodeId = assumptionAnalyzer.addInfeasibilityNode(true, analysisSourceInfoStack.getFullSourceInfo) + assumptionAnalyzer.addDependency(checkNode.map(_.id), infeasibilityNodeId) + } + (success, infeasibilityNodeId) } @@ -523,7 +537,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } private def assert(t: Term, e: Either[String, ast.Exp], timeout: Option[Int])(Q: Boolean => VerificationResult): VerificationResult = { - val success = deciderAssert(t, e, timeout) + val (success, _) = deciderAssert(t, e, timeout) // If the SMT query was not successful, store it (possibly "overwriting" // any previously saved query), otherwise discard any query we had saved @@ -549,7 +563,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => if(result || !isCheck) assertNode foreach assumptionAnalyzer.addNode symbExLog.closeScope(sepIdentifier) - result + (result, assertNode) } private def isKnownToBeTrue(t: Term) = { diff --git a/src/main/scala/rules/Brancher.scala b/src/main/scala/rules/Brancher.scala index 7bd463cb9..c9962f71f 100644 --- a/src/main/scala/rules/Brancher.scala +++ b/src/main/scala/rules/Brancher.scala @@ -54,7 +54,6 @@ object brancher extends BranchingRules { * (2) the branch condition contains a quantified variable */ val skipPathFeasibilityCheck = ( - // Verifier.config.enableAssumptionAnalysis() || TODO ake: review infeasibility check fromShortCircuitingAnd || ( s.quantifiedVariables.nonEmpty && s.quantifiedVariables.map(_._1).exists(condition.freeVariables.contains)) @@ -63,15 +62,23 @@ object brancher extends BranchingRules { val sourceInfo = ExpAnalysisSourceInfo(conditionExp._1) v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo) /* True if the then-branch is to be explored */ - val executeThenBranch = ( - skipPathFeasibilityCheck - || !v.decider.check(negatedCondition, Verifier.config.checkTimeout())) + val executeThenBranch = (skipPathFeasibilityCheck + || !v.decider.check(negatedCondition, Verifier.config.checkTimeout())) + + val thenInfeasibilityNode = if(Verifier.config.enableAssumptionAnalysis() && !skipPathFeasibilityCheck){ + val (_, node) = v.decider.checkAndGetInfeasibilityNode(negatedCondition, Verifier.config.checkTimeout()) + node + }else None /* False if the then-branch is to be explored */ - val executeElseBranch = ( - !executeThenBranch /* Assumes that ast least one branch is feasible */ - || skipPathFeasibilityCheck - || !v.decider.check(condition, Verifier.config.checkTimeout())) + val executeElseBranch = (!executeThenBranch /* Assumes that ast least one branch is feasible */ + || skipPathFeasibilityCheck + || !v.decider.check(condition, Verifier.config.checkTimeout())) + + val elseInfeasibilityNode = if(Verifier.config.enableAssumptionAnalysis() && !skipPathFeasibilityCheck){ + val (_, node) = v.decider.checkAndGetInfeasibilityNode(condition, Verifier.config.checkTimeout()) + node + }else None v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) val parallelizeElseBranch = s.parallelizeBranches && executeThenBranch && executeElseBranch @@ -106,6 +113,9 @@ object brancher extends BranchingRules { val elseBranchVerificationTask: Verifier => VerificationResult = if (executeElseBranch) { + // TODO ake +// val needsInfeasibilityFlag = elseInfeasibilityNode.isDefined && v.decider.assumptionAnalyzer.getInfeasibleNode.isEmpty +// if(needsInfeasibilityFlag) v.decider.assumptionAnalyzer.setInfeasibleNode(elseInfeasibilityNode.get) /* [BRANCH-PARALLELISATION] */ /* Compute the following sets * 1. only if the else-branch needs to be explored @@ -177,6 +187,8 @@ object brancher extends BranchingRules { macrosOfElseBranchDecider = v1.decider.freshMacros.drop(nMacrosOfElseBranchDeciderBefore) } } + +// if(needsInfeasibilityFlag) v1.decider.assumptionAnalyzer.unsetInfeasibleNode() TODO ake result }) } @@ -202,9 +214,12 @@ object brancher extends BranchingRules { val res = { val thenRes = if (executeThenBranch) { + // TODO ake +// val needsInfeasibilityFlag = thenInfeasibilityNode.isDefined && v.decider.assumptionAnalyzer.getInfeasibleNode.isEmpty +// if(needsInfeasibilityFlag) v.decider.assumptionAnalyzer.setInfeasibleNode(thenInfeasibilityNode.get) v.symbExLog.markReachable(uidBranchPoint) v.decider.analysisSourceInfoStack.setAnalysisSourceInfo(currentAnalysisSourceInfos) - executionFlowController.locally(s, v)((s1, v1) => { + val res = executionFlowController.locally(s, v)((s1, v1) => { v1.decider.prover.comment(s"[then-branch: $cnt | $condition]") val sourceInfo = ExpAnalysisSourceInfo(conditionExp._1) v1.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo) @@ -213,6 +228,8 @@ object brancher extends BranchingRules { fThen(v1.stateConsolidator(s1).consolidateOptionally(s1, v1), v1) }) +// if(needsInfeasibilityFlag) v.decider.assumptionAnalyzer.unsetInfeasibleNode() TODO ake + res } else { Unreachable() } @@ -221,6 +238,7 @@ object brancher extends BranchingRules { v.reporter.report(BranchFailureMessage("silicon", s.currentMember.get.asInstanceOf[ast.Member with Serializable], condenseToViperResult(Seq(thenRes)).asInstanceOf[Failure])) } + thenRes }.combine({ From 0e228945fb269f9f7cd774063704cc64f3f48535 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 3 Jul 2025 11:57:13 +0200 Subject: [PATCH 140/474] remove label nodes before exporting the graph --- .../AssumptionAnalysisGraph.scala | 17 +++++++++++++++-- .../scala/verifier/DefaultMainVerifier.scala | 2 +- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index 9f6789f7f..39f49ac30 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -90,7 +90,7 @@ trait AssumptionAnalysisGraph { def addTransitiveEdges(source: AssumptionAnalysisNode, targets: Iterable[AssumptionAnalysisNode]): Unit = { val oldTargets = transitiveEdges.getOrElse(source.id, Set.empty) - val newTargets = targets map(_.id) filter(_ > source.id) + val newTargets = targets map(_.id) // filter(_ > source.id) does not work due to loop invariants if(newTargets.nonEmpty) transitiveEdges.update(source.id, oldTargets ++ newTargets) } @@ -110,6 +110,19 @@ trait AssumptionAnalysisGraph { } } + private def removeNode(node: AssumptionAnalysisNode): Unit = { + val id = node.id + val predecessors = (edges filter { case (_, t) => t.contains(id) }).keys + val successors = edges.getOrElse(id, Set.empty) + edges.remove(id) + predecessors foreach (pid => edges.update(pid, edges.getOrElse(pid, Set.empty).filter(_ != id))) + addEdges(predecessors, successors) + } + + def removeLabelNodes(): Unit = { + nodes filter (_.isInstanceOf[LabelNode]) foreach removeNode + } + def exportGraph(dirName: String): Unit = { val directory = new File(dirName) directory.mkdir() @@ -117,7 +130,7 @@ trait AssumptionAnalysisGraph { exportEdges(dirName + "/edges.csv") } - def exportEdges(fileName: String): Unit = { + private def exportEdges(fileName: String): Unit = { val writer = new PrintWriter(fileName) writer.println("source,target,label") edges foreach (e => e._2 foreach (t => writer.println(e._1 + "," + t + ",direct"))) diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index cde54b8e9..10041df6f 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -315,11 +315,11 @@ class DefaultMainVerifier(config: Config, if(Verifier.config.enableAssumptionAnalysis()){ val assumptionAnalyzers = verificationResults.filter(_.assumptionAnalyzer.isInstanceOf[DefaultAssumptionAnalyzer]).map(_.assumptionAnalyzer) + assumptionAnalyzers.foreach(_.assumptionGraph.removeLabelNodes()) assumptionAnalyzers.foreach(_.assumptionGraph.addTransitiveEdges()) assumptionAnalyzers foreach (_.exportGraph()) if(reporter.isInstanceOf[DependencyAnalysisReporter]) reporter.asInstanceOf[DependencyAnalysisReporter].assumptionAnalyzers = assumptionAnalyzers - logger debug s"assumption analyzers ${assumptionAnalyzers.mkString(", ")}" } if (Verifier.config.startDebuggerAutomatically()){ From 6a9f1795a9f6a7f041d618f12ed650fcdeffa67e Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 4 Jul 2025 10:53:10 +0200 Subject: [PATCH 141/474] distinguish between explicit and implicit assertions --- .../assumptionAnalysis/AnalysisInfo.scala | 13 ++- .../AssumptionAnalysisGraph.scala | 21 ++--- .../AssumptionAnalyzer.scala | 42 +++++----- src/main/scala/decider/Decider.scala | 40 ++++----- src/main/scala/interfaces/state/Chunks.scala | 4 +- src/main/scala/rules/ChunkSupporter.scala | 55 +++++++----- src/main/scala/rules/Consumer.scala | 84 +++++++++++-------- src/main/scala/rules/Evaluator.scala | 24 +++--- src/main/scala/rules/Executor.scala | 12 +-- src/main/scala/rules/HavocSupporter.scala | 2 +- .../rules/MoreCompleteExhaleSupporter.scala | 51 ++++++----- .../scala/rules/PermissionSupporter.scala | 4 +- .../scala/rules/QuantifiedChunkSupport.scala | 57 +++++++------ .../scala/supporters/MethodSupporter.scala | 6 +- .../functions/FunctionVerificationUnit.scala | 2 +- 15 files changed, 221 insertions(+), 196 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AnalysisInfo.scala b/src/main/scala/assumptionAnalysis/AnalysisInfo.scala index 02c2eace2..bbdc54e2d 100644 --- a/src/main/scala/assumptionAnalysis/AnalysisInfo.scala +++ b/src/main/scala/assumptionAnalysis/AnalysisInfo.scala @@ -6,10 +6,21 @@ object AssumptionType extends Enumeration { def fromString(s: String): Option[Value] = values.find(_.toString == s) } +object AssertionType extends Enumeration { + type AssertionType = Value + val Explicit, Implicit = Value + + def fromString(s: String): Option[Value] = values.find(_.toString == s) +} import viper.silicon.assumptionAnalysis.AssumptionType._ +import viper.silicon.assumptionAnalysis.AssertionType._ import viper.silicon.decider.Decider -case class AnalysisInfo(decider: Decider, assumptionAnalyzer: AssumptionAnalyzer, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType) { +case class AnalysisInfo(decider: Decider, assumptionAnalyzer: AssumptionAnalyzer, sourceInfo: AnalysisSourceInfo, + assumptionType: AssumptionType, assertionType: AssertionType=AssertionType.Implicit) { + def withAssumptionType(newAssumptionType: AssumptionType): AnalysisInfo = { + copy(assumptionType=newAssumptionType) + } } diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index 39f49ac30..a5d5b6b4c 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -1,6 +1,6 @@ package viper.silicon.assumptionAnalysis -import viper.silicon.assumptionAnalysis.AssumptionType._ +import viper.silicon.assumptionAnalysis.AssumptionType.{AssumptionType, _} import viper.silicon.interfaces.state.Chunk import viper.silicon.state.terms.{False, Term} import viper.silver.ast @@ -210,18 +210,11 @@ case class SimpleAssumptionNode(term: Term, description: Option[String], sourceI override def getNodeString: String = "assume " + term.toString + description.map(" (" + _ + ")").getOrElse("") } -case class SimpleAssertionNode(assertion: ast.Exp, term: Term, sourceInfo: AnalysisSourceInfo, isClosed: Boolean) extends GeneralAssertionNode { - val assumptionType: AssumptionType = Explicit - override def getNodeString: String = "assert " + term.toString // TODO ake + ", " + assertion.toString +case class SimpleAssertionNode(term: Term, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo, isClosed: Boolean) extends GeneralAssertionNode { + override def getNodeString: String = "assert " + term.toString } -case class StringAssertionNode(description: String, term: Term, sourceInfo: AnalysisSourceInfo, isClosed: Boolean) extends GeneralAssertionNode { - val assumptionType: AssumptionType = Explicit - override def getNodeString: String = "assert " + term.toString // TODO ake + ", " + description -} - -case class SimpleCheckNode(term: Term, sourceInfo: AnalysisSourceInfo, isClosed: Boolean) extends GeneralAssertionNode { - val assumptionType: AssumptionType = Internal +case class SimpleCheckNode(term: Term, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo, isClosed: Boolean) extends GeneralAssertionNode { override def getNodeString: String = "check " + term override def getNodeType: String = "Check" } @@ -231,14 +224,12 @@ case class PermissionInhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSo override def getNodeType: String = "Inhale" } -case class PermissionExhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, isClosed: Boolean) extends GeneralAssertionNode with ChunkAnalysisInfo { - val assumptionType: AssumptionType = Explicit +case class PermissionExhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean) extends GeneralAssertionNode with ChunkAnalysisInfo { override def getNodeType: String = "Exhale" override def getNodeString: String = "exhale " + chunk.toString } -case class PermissionAssertNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, isClosed: Boolean) extends GeneralAssertionNode with ChunkAnalysisInfo { - val assumptionType: AssumptionType = Explicit +case class PermissionAssertNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean) extends GeneralAssertionNode with ChunkAnalysisInfo { override def getNodeString: String = "assert " + term + " for chunk " + chunk.toString } diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 1604297ea..d10b2fcb8 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -36,11 +36,11 @@ trait AssumptionAnalyzer { def addNodes(nodes: Iterable[AssumptionAnalysisNode]): Unit def addNode(node: AssumptionAnalysisNode): Unit def addAssumption(assumption: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, description: Option[String] = None): Option[Int] - def addPermissionAssertNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo): Option[Int] - def addPermissionExhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo): Option[Int] + def addPermissionAssertNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] + def addPermissionExhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] - def createAssertOrCheckNode(term: Term, assertion: Either[String, ast.Exp], analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] - def addAssertFalseNode(isCheck: Boolean, sourceInfo: AnalysisSourceInfo): Option[Int] + def createAssertOrCheckNode(term: Term, assumptionType: AssumptionType, analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] + def addAssertFalseNode(isCheck: Boolean, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo): Option[Int] def addInfeasibilityNode(isCheck: Boolean, sourceInfo: AnalysisSourceInfo): Option[Int] = None def addDependency(source: Option[Int], dest: Option[Int]): Unit @@ -138,17 +138,15 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { Some(node.id) } - override def createAssertOrCheckNode(term: Term, assertion: Either[String, ast.Exp], analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] = { - if(isCheck) return Some(SimpleCheckNode(term, analysisSourceInfo, isClosed_)) - - Some(assertion match { - case Left(description) => StringAssertionNode(description, term, analysisSourceInfo, isClosed_) - case Right(exp) => SimpleAssertionNode(exp, term, analysisSourceInfo, isClosed_) - }) + override def createAssertOrCheckNode(term: Term, assumptionType: AssumptionType, analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] = { + if(isCheck) + Some(SimpleCheckNode(term, assumptionType, analysisSourceInfo, isClosed_)) + else + Some(SimpleAssertionNode(term, assumptionType, analysisSourceInfo, isClosed_)) } - override def addAssertFalseNode(isCheck: Boolean, sourceInfo: AnalysisSourceInfo): Option[Int] = { - val node = createAssertOrCheckNode(False, Right(ast.FalseLit()()), sourceInfo, isCheck) + override def addAssertFalseNode(isCheck: Boolean, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo): Option[Int] = { + val node = createAssertOrCheckNode(False, assumptionType, sourceInfo, isCheck) addNode(node.get) node.map(_.id) } @@ -176,15 +174,15 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { Some(node.id) } - override def addPermissionAssertNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo): Option[Int] = { - val node = PermissionAssertNode(chunk, permAmount, sourceInfo, isClosed_) + override def addPermissionAssertNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { + val node = PermissionAssertNode(chunk, permAmount, sourceInfo, assumptionType, isClosed_) addNode(node) addPermissionDependencies(Set(chunk), Set(), Some(node.id)) Some(node.id) } - override def addPermissionExhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo): Option[Int] = { - val node = PermissionExhaleNode(chunk, permAmount, sourceInfo, isClosed_) + override def addPermissionExhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { + val node = PermissionExhaleNode(chunk, permAmount, sourceInfo, assumptionType, isClosed_) addNode(node) addPermissionDependencies(Set(chunk), Set(), Some(node.id)) Some(node.id) @@ -277,7 +275,7 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { override def registerExhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: (Term => CH), perm: Term, analysisInfo: AnalysisInfo): CH = { val chunk = buildChunk(perm) - val chunkNode = addPermissionExhaleNode(chunk, chunk.perm, analysisInfo.sourceInfo) + val chunkNode = addPermissionExhaleNode(chunk, chunk.perm, analysisInfo.sourceInfo, analysisInfo.assumptionType) addPermissionDependencies(sourceChunks, Set(), chunkNode) chunk } @@ -310,14 +308,14 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { override def getNodes: Iterable[AssumptionAnalysisNode] = Seq() override def addNode(node: AssumptionAnalysisNode): Unit = {} override def addNodes(nodes: Iterable[AssumptionAnalysisNode]): Unit = {} - override def createAssertOrCheckNode(term: Term, assertion: Either[String, ast.Exp], analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] = None + override def createAssertOrCheckNode(term: Term, assumptionType: AssumptionType, analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] = None override def addInfeasibilityNode(isCheck: Boolean, sourceInfo: AnalysisSourceInfo): Option[Int] = None override def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit = { } - override def addPermissionExhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo): Option[Int] = None - override def addPermissionAssertNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo): Option[Int] = None + override def addPermissionExhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None + override def addPermissionAssertNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None override def addDependencyFromExhaleToInhale(inhaledChunk: Chunk, sourceInfo: AnalysisSourceInfo): Unit = {} @@ -328,5 +326,5 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { override def addAssumption(term: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, description: Option[String]): Option[Int] = None override def exportGraph(): Unit = {} - override def addAssertFalseNode(isCheck: Boolean, sourceInfo: AnalysisSourceInfo): Option[Int] = None + override def addAssertFalseNode(isCheck: Boolean, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo): Option[Int] = None } \ No newline at end of file diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index a7f3290e7..1f748f883 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -52,7 +52,7 @@ trait Decider { def pushScope(): Unit def popScope(): Unit - def checkSmoke(isAssert: Boolean = false): Boolean + def checkSmoke(isAssert: Boolean = false, assumptionType: AssumptionType=AssumptionType.Implicit): Boolean def setCurrentBranchCondition(t: Term, te: (ast.Exp, Option[ast.Exp])): Unit def setPathConditionMark(): Mark @@ -74,15 +74,14 @@ trait Decider { def assume(terms: Iterable[Term], debugExp: Option[DebugExp], enforceAssumption: Boolean, assumptionType: AssumptionType): Unit def assumeLabel(term: Term, assumptionLabel: String): Unit - def check(t: Term, timeout: Int): Boolean - def checkAndGetInfeasibilityNode(t: Term, timeout: Int): (Boolean, Option[Int]) + def check(t: Term, timeout: Int, assumptionType: AssumptionType=AssumptionType.Implicit): Boolean + def checkAndGetInfeasibilityNode(t: Term, timeout: Int, assumptionType: AssumptionType=AssumptionType.Implicit): (Boolean, Option[Int]) /* TODO: Consider changing assert such that * 1. It passes State and Operations to the continuation * 2. The implementation reacts to a failing assertion by e.g. a state consolidation */ - def assert(t: Term, description: String, timeout: Option[Int])(Q: Boolean => VerificationResult): VerificationResult - def assert(t: Term, e: Option[ast.Exp], timeout: Option[Int] = Verifier.config.assertTimeout.toOption)(Q: Boolean => VerificationResult): VerificationResult + def assert(t: Term, assumptionType: AssumptionType=AssumptionType.Implicit, timeout: Option[Int] = Verifier.config.assertTimeout.toOption)(Q: Boolean => VerificationResult): VerificationResult def fresh(id: String, sort: Sort, ptype: Option[PType]): Var def fresh(id: String, argSorts: Seq[Sort], resultSort: Sort): Function @@ -492,9 +491,9 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => /* Asserting facts */ - def checkSmoke(isAssert: Boolean = false): Boolean = { + def checkSmoke(isAssert: Boolean = false, assumptionType: AssumptionType=AssumptionType.Implicit): Boolean = { val (label, checkNodeId) = if(Verifier.config.enableAssumptionAnalysis()){ - val nodeId = assumptionAnalyzer.addAssertFalseNode(!isAssert, analysisSourceInfoStack.getFullSourceInfo) // TODO ake: add node only if it can be verified + val nodeId = assumptionAnalyzer.addAssertFalseNode(!isAssert, assumptionType, analysisSourceInfoStack.getFullSourceInfo) // TODO ake: add node only if it can be verified (AssumptionAnalyzer.createAssertionLabel(nodeId), nodeId) }else{ ("", None) } @@ -513,31 +512,22 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => result } - def check(t: Term, timeout: Int): Boolean = { - deciderAssert(t, Left(""), Some(timeout), isCheck=true)._1 + def check(t: Term, timeout: Int, assumptionType: AssumptionType=AssumptionType.Implicit): Boolean = { + deciderAssert(t, assumptionType, Some(timeout), isCheck=true)._1 } - def checkAndGetInfeasibilityNode(t: Term, timeout: Int): (Boolean, Option[Int]) = { - val (success, checkNode) = deciderAssert(t, Left(""), Some(timeout), isCheck=true) + def checkAndGetInfeasibilityNode(t: Term, timeout: Int, assumptionType: AssumptionType=AssumptionType.Implicit): (Boolean, Option[Int]) = { + val (success, checkNode) = deciderAssert(t, assumptionType, Some(timeout), isCheck=true) var infeasibilityNodeId: Option[Int] = None if(success){ - infeasibilityNodeId = assumptionAnalyzer.addInfeasibilityNode(true, analysisSourceInfoStack.getFullSourceInfo) + infeasibilityNodeId = assumptionAnalyzer.addInfeasibilityNode(isCheck = true, analysisSourceInfoStack.getFullSourceInfo) assumptionAnalyzer.addDependency(checkNode.map(_.id), infeasibilityNodeId) } (success, infeasibilityNodeId) } - - def assert(t: Term, description: String, timeout: Option[Int])(Q: Boolean => VerificationResult): VerificationResult = { - assert(t, Left(description), timeout)(Q) - } - - def assert(t: Term, e: Option[ast.Exp], timeout: Option[Int] = Verifier.config.assertTimeout.toOption)(Q: Boolean => VerificationResult): VerificationResult = { - assert(t, e.map(Right(_)).getOrElse(Left("unknown assertion")), timeout)(Q) - } - - private def assert(t: Term, e: Either[String, ast.Exp], timeout: Option[Int])(Q: Boolean => VerificationResult): VerificationResult = { - val (success, _) = deciderAssert(t, e, timeout) + def assert(t: Term, assumptionType: AssumptionType=AssumptionType.Implicit, timeout: Option[Int] = Verifier.config.assertTimeout.toOption)(Q: Boolean => VerificationResult): VerificationResult = { + val (success, _) = deciderAssert(t, assumptionType, timeout) // If the SMT query was not successful, store it (possibly "overwriting" // any previously saved query), otherwise discard any query we had saved @@ -551,12 +541,12 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => Q(success) } - private def deciderAssert(t: Term, e: Either[String, ast.Exp], timeout: Option[Int], isCheck: Boolean=false) = { + private def deciderAssert(t: Term, assumptionType: AssumptionType, timeout: Option[Int], isCheck: Boolean=false) = { val assertRecord = new DeciderAssertRecord(t, timeout) val sepIdentifier = symbExLog.openScope(assertRecord) val asserted = if(Verifier.config.enableAssumptionAnalysis()) t.equals(True) else isKnownToBeTrue(t) - val assertNode = if(!asserted) assumptionAnalyzer.createAssertOrCheckNode(t, e, decider.analysisSourceInfoStack.getFullSourceInfo, isCheck) else None + val assertNode = if(!asserted) assumptionAnalyzer.createAssertOrCheckNode(t, assumptionType, decider.analysisSourceInfoStack.getFullSourceInfo, isCheck) else None val result = asserted || proverAssert(t, timeout, AssumptionAnalyzer.createAssertionLabel(assertNode map (_.id))) diff --git a/src/main/scala/interfaces/state/Chunks.scala b/src/main/scala/interfaces/state/Chunks.scala index df29f6473..435f4ff0b 100644 --- a/src/main/scala/interfaces/state/Chunks.scala +++ b/src/main/scala/interfaces/state/Chunks.scala @@ -35,10 +35,10 @@ object GeneralChunk { chunk.perm, analysisInfo, isExhale=false, createLabel=false) } - def permMinus(chunk: GeneralChunk, newPerm: Term, newPermExp: Option[ast.Exp], analysisInfo: AnalysisInfo, isExhale: Boolean=false): GeneralChunk = { + def permMinus(chunk: GeneralChunk, newPerm: Term, newPermExp: Option[ast.Exp], analysisInfo: AnalysisInfo): GeneralChunk = { val newChunk = analysisInfo.decider.registerDerivedChunk[GeneralChunk](Set(chunk), {finalPerm => chunk.permMinus(finalPerm, newPermExp)}, - newPerm, analysisInfo, isExhale=false, createLabel=false) + newPerm, analysisInfo.withAssumptionType(AssumptionType.Internal), isExhale=false, createLabel=false) val exhaledChunk = analysisInfo.decider.registerDerivedChunk[GeneralChunk](Set(chunk), {finalPerm => chunk.withPerm(finalPerm, newPermExp)}, newPerm, analysisInfo, isExhale=true, createLabel=false) diff --git a/src/main/scala/rules/ChunkSupporter.scala b/src/main/scala/rules/ChunkSupporter.scala index 06b5ef89d..1307cbe60 100644 --- a/src/main/scala/rules/ChunkSupporter.scala +++ b/src/main/scala/rules/ChunkSupporter.scala @@ -7,6 +7,7 @@ package viper.silicon.rules import viper.silicon.assumptionAnalysis.AssumptionType +import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.state._ import viper.silicon.interfaces.{Success, VerificationResult} @@ -33,7 +34,8 @@ trait ChunkSupportRules extends SymbolicExecutionRules { returnSnap: Boolean, ve: VerificationError, v: Verifier, - description: String) + description: String, + assumptionType: AssumptionType=AssumptionType.Implicit) (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult @@ -47,7 +49,8 @@ trait ChunkSupportRules extends SymbolicExecutionRules { args: Seq[Term], argsExp: Option[Seq[ast.Exp]], ve: VerificationError, - v: Verifier) + v: Verifier, + assumptionType: AssumptionType=AssumptionType.Implicit) (Q: (State, Heap, Term, Verifier) => VerificationResult) : VerificationResult @@ -76,10 +79,11 @@ object chunkSupporter extends ChunkSupportRules { returnSnap: Boolean, ve: VerificationError, v: Verifier, - description: String) + description: String, + assumptionType: AssumptionType=AssumptionType.Implicit) (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { - consume2(s, h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v)((s2, h2, optSnap, consumedChunks, v2) => + consume2(s, h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, assumptionType)((s2, h2, optSnap, consumedChunks, v2) => optSnap match { case Some(snap) => Q(s2, h2, Some(snap.convert(sorts.Snap)), consumedChunks, v2) @@ -107,13 +111,14 @@ object chunkSupporter extends ChunkSupportRules { permsExp: Option[ast.Exp], returnSnap: Boolean, ve: VerificationError, - v: Verifier) + v: Verifier, + assumptionType: AssumptionType) (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { val id = ChunkIdentifier(resource, s.program) if (s.exhaleExt) { val failure = createFailure(ve, v, s, "chunk consume in package") - magicWandSupporter.transfer(s, perms, permsExp, failure, Seq(), v)(consumeGreedy(_, _, id, args, _, _, _))((s1, optCh, v1) => + magicWandSupporter.transfer(s, perms, permsExp, failure, Seq(), v)(consumeGreedy(_, _, id, args, _, _, _, assumptionType))((s1, optCh, v1) => if (returnSnap){ // TODO ake: check that optCh is indeed the consumed chunks Q(s1, h, optCh.flatMap(ch => Some(ch.snap)), optCh, v1) } else { @@ -122,11 +127,11 @@ object chunkSupporter extends ChunkSupportRules { } else { executionFlowController.tryOrFail3[Heap, Option[Term], Iterable[Chunk]](s.copy(h = h), v)((s1, v1, QS) => if (s1.moreCompleteExhale) { - moreCompleteExhaleSupporter.consumeComplete(s1, s1.h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v1)((s2, h2, snap2, consumedChunks, v2) => { + moreCompleteExhaleSupporter.consumeComplete(s1, s1.h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v1, assumptionType)((s2, h2, snap2, consumedChunks, v2) => { QS(s2.copy(h = s.h), h2, snap2, consumedChunks, v2) }) } else { - consumeGreedy(s1, s1.h, id, args, perms, permsExp, v1) match { + consumeGreedy(s1, s1.h, id, args, perms, permsExp, v1, assumptionType) match { case (Complete(), s2, h2, optCh2) => val snap = optCh2 match { case Some(ch) if returnSnap => @@ -138,7 +143,7 @@ object chunkSupporter extends ChunkSupportRules { case _ => None } QS(s2.copy(h = s.h), h2, snap, optCh2.toList, v1) - case _ if v1.decider.checkSmoke(true) => + case _ if v1.decider.checkSmoke(isAssert=true, assumptionType) => Success() // TODO: Mark branch as dead? case _ => createFailure(ve, v1, s1, "consuming chunk", true) @@ -154,7 +159,8 @@ object chunkSupporter extends ChunkSupportRules { args: Seq[Term], perms: Term, permsExp: Option[ast.Exp], - v: Verifier) + v: Verifier, + assumptionType: AssumptionType) : (ConsumptionResult, State, Heap, Option[NonQuantifiedChunk]) = { val consumeExact = terms.utils.consumeExactRead(perms, s.constrainableARPs) @@ -169,8 +175,9 @@ object chunkSupporter extends ChunkSupportRules { case Some(ch) => if (s.assertReadAccessOnly) { val termToCheck = Implies(IsPositive(perms), IsPositive(ch.perm)) - if (v.decider.check(termToCheck, Verifier.config.assertTimeout.getOrElse(0))) { - v.decider.assumptionAnalyzer.addPermissionAssertNode(ch, termToCheck, v.decider.analysisSourceInfoStack.getFullSourceInfo) + if (v.decider.check(termToCheck, Verifier.config.assertTimeout.getOrElse(0), assumptionType)) { + // TODO ake: can be removed (probably?) + v.decider.assumptionAnalyzer.addPermissionAssertNode(ch, termToCheck, v.decider.analysisSourceInfoStack.getFullSourceInfo, assumptionType) (Complete(), s, h, Some(ch)) } else { (Incomplete(perms, permsExp), s, h, None) @@ -180,21 +187,21 @@ object chunkSupporter extends ChunkSupportRules { val toTakeExp = permsExp.map(pe => buildMinExp(Seq(ch.permExp.get, pe), ast.Perm)) val newPermExp = permsExp.map(pe => ast.PermSub(ch.permExp.get, toTakeExp.get)(pe.pos, pe.info, pe.errT)) val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, toTake), newPermExp, v.decider.getAnalysisInfo(AssumptionType.Internal)).asInstanceOf[NonQuantifiedChunk] - val takenChunk = Some(GeneralChunk.withPerm(ch, toTake, toTakeExp, v.decider.getAnalysisInfo, isExhale=true).asInstanceOf[NonQuantifiedChunk]) + val takenChunk = Some(GeneralChunk.withPerm(ch, toTake, toTakeExp, v.decider.getAnalysisInfo(assumptionType), isExhale=true).asInstanceOf[NonQuantifiedChunk]) // TODO ake: casting! var newHeap = h - ch - if (!v.decider.check(newChunk.perm === NoPerm, Verifier.config.checkTimeout())) { + if (!v.decider.check(newChunk.perm === NoPerm, Verifier.config.checkTimeout(), AssumptionType.Internal)) { newHeap = newHeap + newChunk assumeProperties(newChunk, newHeap) } val remainingExp = permsExp.map(pe => ast.PermSub(pe, toTakeExp.get)(pe.pos, pe.info, pe.errT)) (ConsumptionResult(PermMinus(perms, toTake), remainingExp, Seq(), v, 0), s, newHeap, takenChunk) } else { - if (v.decider.check(ch.perm !== NoPerm, Verifier.config.checkTimeout())) { + if (v.decider.check(ch.perm !== NoPerm, Verifier.config.checkTimeout(), assumptionType)) { val constraintExp = permsExp.map(pe => ast.PermLtCmp(pe, ch.permExp.get)(pe.pos, pe.info, pe.errT)) v.decider.assume(PermLess(perms, ch.perm), Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp)), AssumptionType.Implicit) val newPermExp = permsExp.map(pe => ast.PermSub(ch.permExp.get, pe)(pe.pos, pe.info, pe.errT)) val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, perms), newPermExp, v.decider.getAnalysisInfo(AssumptionType.Internal)).asInstanceOf[NonQuantifiedChunk] - val takenChunk = GeneralChunk.withPerm(ch, perms, permsExp, v.decider.getAnalysisInfo, isExhale=true).asInstanceOf[NonQuantifiedChunk] + val takenChunk = GeneralChunk.withPerm(ch, perms, permsExp, v.decider.getAnalysisInfo(assumptionType), isExhale=true).asInstanceOf[NonQuantifiedChunk] // TODO ake: casting! val newHeap = h - ch + newChunk assumeProperties(newChunk, newHeap) (Complete(), s, newHeap, Some(takenChunk)) @@ -203,7 +210,7 @@ object chunkSupporter extends ChunkSupportRules { } } case None => - if (consumeExact && s.retrying && v.decider.check(perms === NoPerm, Verifier.config.checkTimeout())) { + if (consumeExact && s.retrying && v.decider.check(perms === NoPerm, Verifier.config.checkTimeout(), assumptionType)) { (Complete(), s, h, None) } else { (Incomplete(perms, permsExp), s, h, None) @@ -227,7 +234,8 @@ object chunkSupporter extends ChunkSupportRules { args: Seq[Term], argsExp: Option[Seq[ast.Exp]], ve: VerificationError, - v: Verifier) + v: Verifier, + assumptionType: AssumptionType=AssumptionType.Implicit) (Q: (State, Heap, Term, Verifier) => VerificationResult) : VerificationResult = { @@ -235,7 +243,7 @@ object chunkSupporter extends ChunkSupportRules { val lookupFunction = if (s1.moreCompleteExhale) moreCompleteExhaleSupporter.lookupComplete _ else lookupGreedy _ - lookupFunction(s1, s1.h, resource, args, argsExp, ve, v1)((s2, tSnap, v2) => + lookupFunction(s1, s1.h, resource, args, argsExp, ve, v1, assumptionType)((s2, tSnap, v2) => QS(s2.copy(h = s.h), s2.h, tSnap, v2)) })(Q) } @@ -246,17 +254,18 @@ object chunkSupporter extends ChunkSupportRules { args: Seq[Term], argsExp: Option[Seq[ast.Exp]], ve: VerificationError, - v: Verifier) + v: Verifier, + assumptionType: AssumptionType=AssumptionType.Implicit) (Q: (State, Term, Verifier) => VerificationResult) : VerificationResult = { val id = ChunkIdentifier(resource, s.program) val findRes = findChunk[NonQuantifiedChunk](h.values, id, args, v) findRes match { - case Some(ch) if v.decider.check(IsPositive(ch.perm), Verifier.config.checkTimeout()) => - v.decider.assumptionAnalyzer.addPermissionAssertNode(ch, IsPositive(ch.perm), v.decider.analysisSourceInfoStack.getFullSourceInfo) + case Some(ch) if v.decider.check(IsPositive(ch.perm), Verifier.config.checkTimeout(), assumptionType) => + v.decider.assumptionAnalyzer.addPermissionAssertNode(ch, IsPositive(ch.perm), v.decider.analysisSourceInfoStack.getFullSourceInfo, assumptionType) Q(s, ch.snap, v) - case _ if v.decider.checkSmoke(true) => + case _ if v.decider.checkSmoke(isAssert=true, assumptionType) => if (s.isInPackage) { val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) Q(s, snap, v) diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index 8b8bae7a7..37b495da3 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -7,6 +7,7 @@ package viper.silicon.rules import viper.silicon.Config.JoinMode +import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType, ExpAnalysisSourceInfo, StringAnalysisSourceInfo} import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.VerificationResult @@ -33,13 +34,14 @@ trait ConsumptionRules extends SymbolicExecutionRules { * @param returnSnap Whether a snapshot should be returned or not. * @param pve The error to report in case the consumption fails. * @param v The verifier to use. + * @param assumptionType The assumption type used for assumption analysis and proof coverage * @param Q The continuation to invoke if the consumption succeeded, with the following * arguments: state (1st argument) and verifier (4th argument) resulting from the * consumption, a heap snapshot (2bd argument )representing the values of the * consumed partial heap, and the chunks (3rd argument) that were consumed * @return The result of the continuation. */ - def consume(s: State, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier) + def consume(s: State, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, assumptionType: AssumptionType=AssumptionType.Implicit) (Q: (State, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult @@ -55,6 +57,7 @@ trait ConsumptionRules extends SymbolicExecutionRules { * @param pvef The error to report in case a consumption fails. Given assertions `as`, an error * `pvef(as_i)` will be reported if consuming assertion `as_i` fails. * @param v @see [[consume]] + * @param assumptionType @see [[consume]] * @param Q @see [[consume]] * @return @see [[consume]] */ @@ -62,7 +65,8 @@ trait ConsumptionRules extends SymbolicExecutionRules { as: Seq[ast.Exp], returnSnap: Boolean, pvef: ast.Exp => PartialVerificationError, - v: Verifier) + v: Verifier, + assumptionType: AssumptionType=AssumptionType.Implicit) (Q: (State, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult } @@ -76,11 +80,11 @@ object consumer extends ConsumptionRules { */ /** @inheritdoc */ - def consume(s: State, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier) + def consume(s: State, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, assumptionType: AssumptionType=AssumptionType.Implicit) (Q: (State, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { - consumeR(s, s.h, a.whenExhaling, returnSnap, pve, v)((s1, h1, snap, consumedChunks, v1) => { + consumeR(s, s.h, a.whenExhaling, returnSnap, pve, v, assumptionType)((s1, h1, snap, consumedChunks, v1) => { val s2 = s1.copy(h = h1, partiallyConsumedHeap = s.partiallyConsumedHeap) Q(s2, snap, consumedChunks, v1)}) @@ -91,7 +95,8 @@ object consumer extends ConsumptionRules { as: Seq[ast.Exp], returnSnap: Boolean, pvef: ast.Exp => PartialVerificationError, - v: Verifier) + v: Verifier, + assumptionType: AssumptionType=AssumptionType.Implicit) (Q: (State, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { @@ -106,7 +111,7 @@ object consumer extends ConsumptionRules { allPves ++= pves }) - consumeTlcs(s, s.h, allTlcs.result(), returnSnap, allPves.result(), v)((s1, h1, snap1, chunks, v1) => { + consumeTlcs(s, s.h, allTlcs.result(), returnSnap, allPves.result(), v, assumptionType)((s1, h1, snap1, chunks, v1) => { val s2 = s1.copy(h = h1, partiallyConsumedHeap = s.partiallyConsumedHeap) Q(s2, snap1, chunks, v1) @@ -118,7 +123,8 @@ object consumer extends ConsumptionRules { tlcs: Seq[ast.Exp], returnSnap: Boolean, pves: Seq[PartialVerificationError], - v: Verifier) + v: Verifier, + assumptionType: AssumptionType) (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { @@ -129,10 +135,10 @@ object consumer extends ConsumptionRules { val pve = pves.head if (tlcs.tail.isEmpty) - wrappedConsumeTlc(s, h, a, returnSnap, pve, v)(Q) + wrappedConsumeTlc(s, h, a, returnSnap, pve, v, assumptionType)(Q) else - wrappedConsumeTlc(s, h, a, returnSnap, pve, v)((s1, h1, snap1, consumedChunksHead, v1) => { - consumeTlcs(s1, h1, tlcs.tail, returnSnap, pves.tail, v1)((s2, h2, snap2, consumedChunksTail, v2) => + wrappedConsumeTlc(s, h, a, returnSnap, pve, v, assumptionType)((s1, h1, snap1, consumedChunksHead, v1) => { + consumeTlcs(s1, h1, tlcs.tail, returnSnap, pves.tail, v1, assumptionType)((s2, h2, snap2, consumedChunksTail, v2) => (snap1, snap2) match { case (Some(sn1), Some(sn2)) if returnSnap => Q(s2, h2, Some(Combine(sn1, sn2)), consumedChunksHead ++ consumedChunksTail, v2) @@ -143,14 +149,14 @@ object consumer extends ConsumptionRules { } } - private def consumeR(s: State, h: Heap, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier) + private def consumeR(s: State, h: Heap, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, assumptionType: AssumptionType=AssumptionType.Implicit) (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { val tlcs = a.topLevelConjuncts val pves = Seq.fill(tlcs.length)(pve) - consumeTlcs(s, h, tlcs, returnSnap, pves, v)(Q) + consumeTlcs(s, h, tlcs, returnSnap, pves, v, assumptionType)(Q) } /** Wrapper/decorator for consume that injects the following operations: @@ -162,7 +168,8 @@ object consumer extends ConsumptionRules { a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, - v: Verifier) + v: Verifier, + assumptionType: AssumptionType) (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { @@ -179,14 +186,14 @@ object consumer extends ConsumptionRules { val sourceInfo = ExpAnalysisSourceInfo(a) v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo) - consumeTlc(s1, h0, a, returnSnap, pve, v1)((s2, h2, snap2, consumedChunks, v2) => { + consumeTlc(s1, h0, a, returnSnap, pve, v1, assumptionType)((s2, h2, snap2, consumedChunks, v2) => { v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) v2.symbExLog.closeScope(sepIdentifier) QS(s2, h2, snap2, consumedChunks, v2)}) })(Q) } - private def consumeTlc(s: State, h: Heap, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier) + private def consumeTlc(s: State, h: Heap, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, assumptionType: AssumptionType) (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { @@ -207,7 +214,7 @@ object consumer extends ConsumptionRules { case imp @ ast.Implies(e0, a0) if !a.isPure && s.moreJoins.id >= JoinMode.Impure.id => val impliesRecord = new ImpliesRecord(imp, s, v.decider.pcs, "consume") val uidImplies = v.symbExLog.openScope(impliesRecord) - consumeConditionalTlcMoreJoins(s, h, e0, a0, None, uidImplies, returnSnap, pve, v)(Q) + consumeConditionalTlcMoreJoins(s, h, e0, a0, None, uidImplies, returnSnap, pve, v, assumptionType)(Q) case imp @ ast.Implies(e0, a0) if !a.isPure => val impliesRecord = new ImpliesRecord(imp, s, v.decider.pcs, "consume") @@ -215,7 +222,7 @@ object consumer extends ConsumptionRules { evaluator.eval(s, e0, pve, v)((s1, t0, e0New, v1) => branch(s1, t0, (e0, e0New), v1)( - (s2, v2) => consumeR(s2, h, a0, returnSnap, pve, v2)((s3, h1, t1, consumedChunks, v3) => { + (s2, v2) => consumeR(s2, h, a0, returnSnap, pve, v2, assumptionType)((s3, h1, t1, consumedChunks, v3) => { v3.symbExLog.closeScope(uidImplies) Q(s3, h1, t1, consumedChunks, v3) }), @@ -227,7 +234,7 @@ object consumer extends ConsumptionRules { case ite @ ast.CondExp(e0, a1, a2) if !a.isPure && s.moreJoins.id >= JoinMode.Impure.id => val condExpRecord = new CondExpRecord(ite, s, v.decider.pcs, "consume") val uidCondExp = v.symbExLog.openScope(condExpRecord) - consumeConditionalTlcMoreJoins(s, h, e0, a1, Some(a2), uidCondExp, returnSnap, pve, v)(Q) + consumeConditionalTlcMoreJoins(s, h, e0, a1, Some(a2), uidCondExp, returnSnap, pve, v, assumptionType)(Q) case ite @ ast.CondExp(e0, a1, a2) if !a.isPure => val condExpRecord = new CondExpRecord(ite, s, v.decider.pcs, "consume") @@ -235,11 +242,11 @@ object consumer extends ConsumptionRules { eval(s, e0, pve, v)((s1, t0, e0New, v1) => branch(s1, t0, (e0, e0New), v1)( - (s2, v2) => consumeR(s2, h, a1, returnSnap, pve, v2)((s3, h1, t1, consumedChunks, v3) => { + (s2, v2) => consumeR(s2, h, a1, returnSnap, pve, v2, assumptionType)((s3, h1, t1, consumedChunks, v3) => { v3.symbExLog.closeScope(uidCondExp) Q(s3, h1, t1, consumedChunks, v3) }), - (s2, v2) => consumeR(s2, h, a2, returnSnap, pve, v2)((s3, h1, t1, consumedChunks, v3) => { + (s2, v2) => consumeR(s2, h, a2, returnSnap, pve, v2, assumptionType)((s3, h1, t1, consumedChunks, v3) => { v3.symbExLog.closeScope(uidCondExp) Q(s3, h1, t1, consumedChunks, v3) }))) @@ -282,7 +289,8 @@ object consumer extends ConsumptionRules { negativePermissionReason = NegativePermission(acc.perm), notInjectiveReason = QPAssertionNotInjective(acc.loc), insufficientPermissionReason = InsufficientPermission(acc.loc), - v1)((s2, h2, snap, consumedChunks, v2) => Q(s2.copy(constrainableARPs = s.constrainableARPs), h2, snap, consumedChunks, v2)) + v1, + assumptionType)((s2, h2, snap, consumedChunks, v2) => Q(s2.copy(constrainableARPs = s.constrainableARPs), h2, snap, consumedChunks, v2)) case (s1, _, _, _, _, None, v1) => Q(s1, h, if (returnSnap) Some(Unit) else None, Seq.empty, v1) } @@ -328,7 +336,8 @@ object consumer extends ConsumptionRules { negativePermissionReason = NegativePermission(acc.perm), notInjectiveReason = QPAssertionNotInjective(acc.loc), insufficientPermissionReason = InsufficientPermission(acc.loc), - v1)((s2, h2, snap, consumedChunks, v2) => Q(s2.copy(constrainableARPs = s.constrainableARPs), h2, snap, consumedChunks, v2)) + v1, + assumptionType)((s2, h2, snap, consumedChunks, v2) => Q(s2.copy(constrainableARPs = s.constrainableARPs), h2, snap, consumedChunks, v2)) case (s1, _, _, _, _, None, v1) => Q(s1, h, if (returnSnap) Some(Unit) else None, Seq.empty, v1) } @@ -370,7 +379,8 @@ object consumer extends ConsumptionRules { negativePermissionReason = NegativePermission(ePerm), notInjectiveReason = sys.error("Quantified wand not injective"), /*ReceiverNotInjective(...)*/ insufficientPermissionReason = MagicWandChunkNotFound(wand), /*InsufficientPermission(...)*/ - v1)((s2, h2, snap, consumedChunks, v2) => Q(s2.copy(constrainableARPs = s.constrainableARPs), h2, snap, consumedChunks, v2)) + v1, + assumptionType)((s2, h2, snap, consumedChunks, v2) => Q(s2.copy(constrainableARPs = s.constrainableARPs), h2, snap, consumedChunks, v2)) case (s1, _, _, _, _, None, v1) => Q(s1.copy(constrainableARPs = s.constrainableARPs), h, if (returnSnap) Some(Unit) else None, Seq.empty, v1) } @@ -410,7 +420,8 @@ object consumer extends ConsumptionRules { returnSnap, None, pve, - v2 + v2, + assumptionType )((s3, h3, snap, consumedChunks, v3) => { val s4 = s3.copy(constrainableARPs = s1.constrainableARPs, partiallyConsumedHeap = Some(h3)) @@ -455,7 +466,8 @@ object consumer extends ConsumptionRules { returnSnap, None, pve, - v2 + v2, + assumptionType )((s3, h3, snap, consumedChunks, v3) => { val s4 = s3.copy(constrainableARPs = s1.constrainableARPs, partiallyConsumedHeap = Some(h3)) @@ -464,7 +476,7 @@ object consumer extends ConsumptionRules { case let: ast.Let if !let.isPure => letSupporter.handle[ast.Exp](s, let, pve, v)((s1, g1, body, v1) => { val s2 = s1.copy(g = s1.g + g1) - consumeR(s2, h, body, returnSnap, pve, v1)(Q)}) + consumeR(s2, h, body, returnSnap, pve, v1, assumptionType)(Q)}) case ast.AccessPredicate(locacc: ast.LocationAccess, perm) => eval(s, perm, pve, v)((s1, tPerm, permNew, v1) => @@ -478,7 +490,7 @@ object consumer extends ConsumptionRules { val lossExp = permNew.map(p => ast.PermMul(p, s3.permissionScalingFactorExp.get)(p.pos, p.info, p.errT)) val ve = pve dueTo InsufficientPermission(locacc) val description = s"consume ${a.pos}: $a" - chunkSupporter.consume(s3, h, resource, tArgs, eArgs, loss, lossExp, returnSnap, ve, v3, description)((s4, h1, snap1, consumedChunks, v4) => { + chunkSupporter.consume(s3, h, resource, tArgs, eArgs, loss, lossExp, returnSnap, ve, v3, description, assumptionType)((s4, h1, snap1, consumedChunks, v4) => { val s5 = s4.copy(partiallyConsumedHeap = Some(h1), constrainableARPs = s.constrainableARPs) Q(s5, h1, snap1, consumedChunks, v4)})}))) @@ -521,7 +533,8 @@ object consumer extends ConsumptionRules { returnSnap, None, pve, - v1 + v1, + assumptionType )((s3, h3, snap, consumedChunks, v3) => { val s4 = s3.copy(constrainableARPs = s1.constrainableARPs, partiallyConsumedHeap = Some(h3)) @@ -531,11 +544,11 @@ object consumer extends ConsumptionRules { magicWandSupporter.evaluateWandArguments(s, wand, pve, v)((s1, tArgs, eArgs, v1) => { val ve = pve dueTo MagicWandChunkNotFound(wand) val description = s"consume wand $wand" - chunkSupporter.consume(s1, h, wand, tArgs, eArgs, FullPerm, Option.when(withExp)(ast.FullPerm()(wand.pos, wand.info, wand.errT)), returnSnap, ve, v1, description)(Q) + chunkSupporter.consume(s1, h, wand, tArgs, eArgs, FullPerm, Option.when(withExp)(ast.FullPerm()(wand.pos, wand.info, wand.errT)), returnSnap, ve, v1, description, assumptionType)(Q) }) case _ => - evalAndAssert(s, a, returnSnap, pve, v)((s1, t, v1) => { + evalAndAssert(s, a, returnSnap, pve, v, assumptionType)((s1, t, v1) => { Q(s1, h, t, Seq.empty, v1) }) } @@ -544,20 +557,21 @@ object consumer extends ConsumptionRules { private def consumeConditionalTlcMoreJoins(s: State, h: Heap, e0: ast.Exp, a1: ast.Exp, a2: Option[ast.Exp], scopeUid: Int, returnSnap: Boolean, - pve: PartialVerificationError, v: Verifier) + pve: PartialVerificationError, v: Verifier, + assumptionType: AssumptionType) (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { eval(s, e0, pve, v)((s1, t0, e0New, v1) => joiner.join[(Heap, Option[Term], Iterable[Chunk]), (Heap, Option[Term], Iterable[Chunk])](s1, v1, resetState = false)((s1, v1, QB) => { branch(s1.copy(parallelizeBranches = false), t0, (e0, e0New), v1)( (s2, v2) => - consumeR(s2.copy(parallelizeBranches = s1.parallelizeBranches), h, a1, returnSnap, pve, v2)((s3, h1, t1, consumedChunks, v3) => { + consumeR(s2.copy(parallelizeBranches = s1.parallelizeBranches), h, a1, returnSnap, pve, v2, assumptionType)((s3, h1, t1, consumedChunks, v3) => { v3.symbExLog.closeScope(scopeUid) QB(s3, (h1, t1, consumedChunks), v3) }), (s2, v2) => a2 match { - case Some(a2) => consumeR(s2.copy(parallelizeBranches = s1.parallelizeBranches), h, a2, returnSnap, pve, v2)((s3, h1, t1, consumedChunks, v3) => { + case Some(a2) => consumeR(s2.copy(parallelizeBranches = s1.parallelizeBranches), h, a2, returnSnap, pve, v2, assumptionType)((s3, h1, t1, consumedChunks, v3) => { v3.symbExLog.closeScope(scopeUid) QB(s3, (h1, t1, consumedChunks), v3) }) @@ -601,7 +615,7 @@ object consumer extends ConsumptionRules { } - private def evalAndAssert(s: State, e: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier) + private def evalAndAssert(s: State, e: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, assumptionType: AssumptionType) (Q: (State, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -628,7 +642,7 @@ object consumer extends ConsumptionRules { Quantification(q, vars, Implies(transformed, body), trgs, name, isGlob, weight) case _ => t } - v2.decider.assert(termToAssert, Some(e)) { + v2.decider.assert(termToAssert, assumptionType) { case true => v2.decider.assume(t, Option.when(withExp)(e), eNew, AssumptionType.Internal) QS(s3, v2) diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 602b5a12b..0fe7d97df 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -240,7 +240,7 @@ object evaluator extends EvaluationRules { } else { val toAssert = IsPositive(totalPermissions.replace(`?r`, tRcvr)) val toAssertExp = Option.when(withExp)(perms.IsPositive(ast.CurrentPerm(fa)())(fa.pos, fa.info, fa.errT)) - v1.decider.assert(toAssert, toAssertExp) { + v1.decider.assert(toAssert) { case false => createFailure(pve dueTo InsufficientPermission(fa), v1, s1, toAssert, toAssertExp) case true => @@ -270,7 +270,7 @@ object evaluator extends EvaluationRules { val totalPermissions = permVal.replace(relevantChunks.head.quantifiedVars, Seq(tRcvr)) (IsPositive(totalPermissions), Option.when(withExp)(ast.PermGtCmp(ast.CurrentPerm(fa)(fa.pos, fa.info, fa.errT), ast.NoPerm()())(fa.pos, fa.info, fa.errT))) } - v1.decider.assert(permCheck, permCheckExp) { + v1.decider.assert(permCheck) { case false => createFailure(pve dueTo InsufficientPermission(fa), v1, s1, permCheck, permCheckExp) case true => @@ -302,7 +302,7 @@ object evaluator extends EvaluationRules { val totalPermissions = PermLookup(fa.field.name, pmDef1.pm, tRcvr) (IsPositive(totalPermissions), Option.when(withExp)(ast.PermGtCmp(ast.CurrentPerm(fa)(fa.pos, fa.info, fa.errT), ast.NoPerm()())(fa.pos, fa.info, fa.errT))) } - v1.decider.assert(permCheck, permCheckExp) { + v1.decider.assert(permCheck) { case false => createFailure(pve dueTo InsufficientPermission(fa), v1, s2, permCheck, permCheckExp) case true => @@ -987,7 +987,7 @@ object evaluator extends EvaluationRules { evals(s, eArgs, _ => pve, v)((s1, tArgs, eArgsNew, v1) => eval(s1, ePerm.getOrElse(ast.FullPerm()(u.pos, u.info, u.errT)), pve, v1)((s2, tPerm, ePermNew, v2) => // TODO: Replace with permissionSupporter.assertNotNegative - v2.decider.assert(IsPositive(tPerm), Option.when(withExp)(ast.PermGtCmp(ePerm.getOrElse(ast.FullPerm()()), ast.NoPerm()())(u.pos, u.info, u.errT))) { + v2.decider.assert(IsPositive(tPerm)) { case true => joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s2, v2)((s3, v3, QB) => { val s4 = s3.incCycleCounter(predicate) @@ -1079,9 +1079,9 @@ object evaluator extends EvaluationRules { val idxLtLength = Less(t1, SeqLength(t0)) val idxLtLengthExp = Option.when(withExp)(ast.LtCmp(e1, ast.SeqLength(e0)())(si.pos, si.info, si.errT)) val idxLtLengthExpNew = esNew.map(es => ast.LtCmp(es(1), ast.SeqLength(es.head)())(si.pos, si.info, si.errT)) - v1.decider.assert(idxGe0, idxGe0Exp) { + v1.decider.assert(idxGe0) { case true => - v1.decider.assert(idxLtLength, idxLtLengthExp) { + v1.decider.assert(idxLtLength) { case true => Q(s1, SeqAt(t0, t1), eNew, v1) case false => @@ -1095,7 +1095,7 @@ object evaluator extends EvaluationRules { val failure1 = createFailure(pve dueTo SeqIndexNegative(e0, e1), v1, s1, idxGe0, idxGe0ExpNew) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { v1.decider.assume(idxGe0, idxGe0Exp, idxGe0ExpNew, AssumptionType.Explicit) - v1.decider.assert(idxLtLength, idxLtLengthExp) { + v1.decider.assert(idxLtLength) { case true => failure1 combine Q(s1, SeqAt(t0, t1), eNew, v1) case false => @@ -1130,9 +1130,9 @@ object evaluator extends EvaluationRules { val idxLtLength = Less(t1, SeqLength(t0)) val idxLtLengthExp = Option.when(withExp)(ast.LtCmp(e1, ast.SeqLength(e0)())(seqUp.pos, seqUp.info, seqUp.errT)) val idxLtLengthExpNew = Option.when(withExp)(ast.LtCmp(esNew.get(1), ast.SeqLength(esNew.get(0))())(seqUp.pos, seqUp.info, seqUp.errT)) - v1.decider.assert(idxGe0, idxGe0Exp) { + v1.decider.assert(idxGe0) { case true => - v1.decider.assert(idxLtLength, idxLtLengthExp) { + v1.decider.assert(idxLtLength) { case true => Q(s1, SeqUpdate(t0, t1, t2), eNew, v1) case false => @@ -1145,7 +1145,7 @@ object evaluator extends EvaluationRules { val failure1 = createFailure(pve dueTo SeqIndexNegative(e0, e1), v1, s1, idxGe0, idxGe0ExpNew) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { v1.decider.assume(idxGe0, idxGe0Exp, idxGe0ExpNew, AssumptionType.Explicit) - v1.decider.assert(idxLtLength, idxLtLengthExp) { + v1.decider.assert(idxLtLength) { case true => failure1 combine Q(s1, SeqUpdate(t0, t1, t2), eNew, v1) case false => @@ -1265,7 +1265,7 @@ object evaluator extends EvaluationRules { val eNew = esNew.map(es => ast.MapLookup(es(0), es(1))(e.pos, e.info, e.errT)) val assertExp = Option.when(withExp)(ast.MapContains(key, base)(ml.pos, ml.info, ml.errT)) val assertExpNew = Option.when(withExp)(ast.MapContains(esNew.get(1), esNew.get(0))(ml.pos, ml.info, ml.errT)) - v1.decider.assert(SetIn(keyT, MapDomain(baseT)), assertExp) { + v1.decider.assert(SetIn(keyT, MapDomain(baseT))) { case true => Q(s1, MapLookup(baseT, keyT), eNew, v1) case false => val failure1 = createFailure(pve dueTo MapKeyNotContained(base, key), v1, s1, SetIn(keyT, MapDomain(baseT)), assertExpNew) @@ -1525,7 +1525,7 @@ object evaluator extends EvaluationRules { val (notZeroExp, notZeroExpNew) = if (withExp) { (Some(ast.NeCmp(eDivisor, ast.IntLit(0)())(eDivisor.pos, eDivisor.info, eDivisor.errT)), Some(ast.NeCmp(eDivisorNew.get, ast.IntLit(0)())(eDivisor.pos, eDivisor.info, eDivisor.errT))) } else { (None, None) } - v.decider.assert(tDivisor !== tZero, notZeroExp){ + v.decider.assert(tDivisor !== tZero){ case true => Q(s, t, v) case false => val failure = createFailure(pve dueTo DivisionByZero(eDivisor), v, s, tDivisor !== tZero, notZeroExpNew) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 0d458e350..5cd5fbad6 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -517,13 +517,13 @@ object executor extends ExecutionRules { case exhale @ ast.Exhale(a) => val pve = ExhaleFailed(exhale) - consume(s, a, false, pve, v)((s1, _, consumedChunks, v1) => + consume(s, a, false, pve, v, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit))((s1, _, consumedChunks, v1) => Q(s1, v1)) case assert @ ast.Assert(a: ast.FalseLit) if !s.isInPackage => /* "assert false" triggers a smoke check. If successful, we backtrack. */ executionFlowController.tryOrFail0(s.copy(h = magicWandSupporter.getEvalHeap(s)), v)((s1, v1, QS) => { - if (v1.decider.checkSmoke(true)) + if (v1.decider.checkSmoke(isAssert=true, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit))) QS(s1.copy(h = s.h), v1) else createFailure(AssertFailed(assert) dueTo AssertionFalse(a), v1, s1, False, true, Option.when(withExp)(a)) @@ -531,7 +531,7 @@ object executor extends ExecutionRules { case assert @ ast.Assert(a) if Verifier.config.disableSubsumption() => val r = - consume(s, a, false, AssertFailed(assert), v)((_, _, _, _) => + consume(s, a, false, AssertFailed(assert), v, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit))((_, _, _, _) => Success()) r combine Q(s, v) @@ -547,11 +547,11 @@ object executor extends ExecutionRules { * hUsed (reserveHeaps.head) instead of consuming them. hUsed is later discarded and replaced * by s.h. By copying hUsed to s.h the contained permissions remain available inside the wand. */ - consume(s, a, false, pve, v)((s2, _, _, v1) => { + consume(s, a, false, pve, v, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit))((s2, _, _, v1) => { Q(s2.copy(h = s2.reserveHeaps.head), v1) }) } else - consume(s, a, false, pve, v)((s1, _, _, v1) => { + consume(s, a, false, pve, v, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit))((s1, _, _, v1) => { val s2 = s1.copy(h = s.h, reserveHeaps = s.reserveHeaps) Q(s2, v1)}) @@ -614,7 +614,7 @@ object executor extends ExecutionRules { tArgs zip Seq.fill(tArgs.size)(None) val s2 = s1.copy(g = Store(fargs.zip(argsWithExp)), recordVisited = true) - consumes(s2, meth.pres, false, _ => pvePre, v1)((s3, _, consumedChunks, v2) => { // TODO ake: add edges from consumedChunks + consumes(s2, meth.pres, false, _ => pvePre, v1)((s3, _, consumedChunks, v2) => { v2.symbExLog.closeScope(preCondId) val postCondLog = new CommentRecord("Postcondition", s3, v2.decider.pcs) val postCondId = v2.symbExLog.openScope(postCondLog) diff --git a/src/main/scala/rules/HavocSupporter.scala b/src/main/scala/rules/HavocSupporter.scala index 94764fa21..de92a9545 100644 --- a/src/main/scala/rules/HavocSupporter.scala +++ b/src/main/scala/rules/HavocSupporter.scala @@ -131,7 +131,7 @@ object havocSupporter extends SymbolicExecutionRules { val comment = "QP receiver injectivity check is well-defined" val injectivityDebugExp = Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)) v.decider.assume(FunctionPreconditionTransformer.transform(receiverInjectivityCheck, s.program), injectivityDebugExp, AssumptionType.Internal) - v.decider.assert(receiverInjectivityCheck, comment, Verifier.config.assertTimeout.toOption) { + v.decider.assert(receiverInjectivityCheck, timeout=Verifier.config.assertTimeout.toOption) { case false => createFailure(pve dueTo notInjectiveReason, v, s1, receiverInjectivityCheck, "QP receiver injective") case true => // Generate the inverse axioms diff --git a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala index 8cda22f68..2126a5184 100644 --- a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala +++ b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala @@ -7,6 +7,7 @@ package viper.silicon.rules import viper.silicon.assumptionAnalysis.AssumptionType +import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.state._ import viper.silicon.interfaces.{Success, VerificationResult} @@ -196,7 +197,8 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { args: Seq[Term], argsExp: Option[Seq[ast.Exp]], ve: VerificationError, - v: Verifier) + v: Verifier, + assumptionType: AssumptionType=AssumptionType.Implicit) (Q: (State, Term, Verifier) => VerificationResult) : VerificationResult = { @@ -217,7 +219,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { } else { summarise(s, relevantChunks, resource, args, argsExp, None, v)((s1, snap, permSum, permSumExp, v1) => { val assertExp = permSumExp.map(e => IsPositive(e)(e.pos, e.info, e.errT)) - v.decider.assert(IsPositive(permSum), assertExp) { + v.decider.assert(IsPositive(permSum), assumptionType) { case true => Q(s1, snap, v1) case false => @@ -236,14 +238,15 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { permsExp: Option[ast.Exp], returnSnap: Boolean, ve: VerificationError, - v: Verifier) + v: Verifier, + assumptionType: AssumptionType=AssumptionType.Implicit) (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { if (!s.assertReadAccessOnly) - actualConsumeComplete(s, h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v)(Q) + actualConsumeComplete(s, h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, assumptionType)(Q) else - summariseHeapAndAssertReadAccess(s, h, resource, perms, permsExp, args, argsExp, returnSnap, ve, v)(Q) + summariseHeapAndAssertReadAccess(s, h, resource, perms, permsExp, args, argsExp, returnSnap, ve, v, assumptionType)(Q) } private def summariseHeapAndAssertReadAccess(s: State, @@ -255,7 +258,8 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { argsExp: Option[Seq[ast.Exp]], returnSnap: Boolean, ve: VerificationError, - v: Verifier) + v: Verifier, + assumptionType: AssumptionType) (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { @@ -264,7 +268,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { if (returnSnap) { summarise(s, relevantChunks, resource, args, argsExp, None, v)((s1, snap, permSum, permSumExp, v1) => - v.decider.assert(Implies(IsPositive(perm), IsPositive(permSum)), Option.when(withExp)(ast.Implies(IsPositive(permExp.get)(), IsPositive(permSumExp.get)())(permExp.get.pos, permExp.get.info, permExp.get.errT))) { + v.decider.assert(Implies(IsPositive(perm), IsPositive(permSum)), assumptionType) { case true => Q(s1, h, Some(snap), relevantChunks, v1) case false => @@ -272,7 +276,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { }) } else { val (s1, permSum, permSumExp) = permSummariseOnly(s, relevantChunks, resource, args, argsExp) - v.decider.assert(Implies(IsPositive(perm), IsPositive(permSum)), Option.when(withExp)(ast.Implies(IsPositive(permExp.get)(), IsPositive(permSumExp.get)())(permExp.get.pos, permExp.get.info, permExp.get.errT))) { + v.decider.assert(Implies(IsPositive(perm), IsPositive(permSum)), assumptionType) { case true => Q(s1, h, None, relevantChunks, v) case false => @@ -290,7 +294,8 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { permsExp: Option[ast.Exp], returnSnap: Boolean, ve: VerificationError, - v: Verifier) + v: Verifier, + assumptionType: AssumptionType) (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { @@ -305,13 +310,13 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { if (relevantChunks.isEmpty) { val assertExp = permsExp.map(pe => ast.EqCmp(pe, ast.NoPerm()())(pe.pos, pe.info, pe.errT)) // if no permission is exhaled, return none - v.decider.assert(perms === NoPerm, assertExp) { + v.decider.assert(perms === NoPerm, assumptionType) { case true => Q(s, h, None, Seq.empty, v) case false => createFailure(ve, v, s, perms === NoPerm, assertExp) } } else { if (!terms.utils.consumeExactRead(perms, s.constrainableARPs)) { - actualConsumeCompleteConstrainable(s, relevantChunks, resource, args, argsExp, perms, permsExp, returnSnap, ve, v)((s1, updatedChunks, optSnap, v2) => { + actualConsumeCompleteConstrainable(s, relevantChunks, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, assumptionType)((s1, updatedChunks, optSnap, v2) => { Q(s1, Heap(updatedChunks ++ otherChunks), optSnap, updatedChunks, v2) // TODO ake: updatedChunks != consumedChunks }) } else { @@ -365,17 +370,17 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { pSum = PermPlus(pSum, Ite(eq, ch.perm, NoPerm)) pSumExp = eqExp.map(eq => ast.PermAdd(pSumExp.get, ast.CondExp(eq, ch.permExp.get, ast.NoPerm()())(eq.pos, eq.info, eq.errT))()) - val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, pTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, pTakenExp.get)(pe.pos, pe.info, pe.errT)), v.decider.getAnalysisInfo).asInstanceOf[NonQuantifiedChunk] - v.decider.assumptionAnalyzer.addPermissionExhaleNode(ch, pTaken, v.decider.analysisSourceInfoStack.getFullSourceInfo) + val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, pTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, pTakenExp.get)(pe.pos, pe.info, pe.errT)), v.decider.getAnalysisInfo(AssumptionType.Internal)).asInstanceOf[NonQuantifiedChunk] + v.decider.assumptionAnalyzer.addPermissionExhaleNode(ch, pTaken, v.decider.analysisSourceInfoStack.getFullSourceInfo, assumptionType) pNeeded = PermMinus(pNeeded, pTaken) pNeededExp = permsExp.map(pe => ast.PermSub(pNeededExp.get, pTakenExp.get)(pe.pos, pe.info, pe.errT)) - if (!v.decider.check(IsNonPositive(newChunk.perm), Verifier.config.splitTimeout())) { + if (!v.decider.check(IsNonPositive(newChunk.perm), Verifier.config.splitTimeout(), AssumptionType.Internal)) { newChunks.append(newChunk) } consumedChunks.append(ch) - moreNeeded = !v.decider.check(pNeeded === NoPerm, Verifier.config.splitTimeout()) + moreNeeded = !v.decider.check(pNeeded === NoPerm, Verifier.config.splitTimeout(), AssumptionType.Internal) } else { newChunks.append(ch) } @@ -398,7 +403,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { if (returnSnap) { summarise(s0, relevantChunks.toSeq, resource, args, argsExp, Some(definiteAlias.map(_.snap)), v)((s1, snap, _, _, v1) => { - val condSnap = Some(if (v1.decider.check(IsPositive(perms), Verifier.config.checkTimeout())) { + val condSnap = Some(if (v1.decider.check(IsPositive(perms), Verifier.config.checkTimeout(), AssumptionType.Internal)) { snap } else { Ite(IsPositive(perms), snap.convert(sorts.Snap), Unit) @@ -407,7 +412,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { Q(s1, newHeap, condSnap, consumedChunks, v1) } else { val assertExp = pNeededExp.map(pn => ast.EqCmp(pn, ast.NoPerm()())(pn.pos, pn.info, pn.errT)) - v1.decider.assert(pNeeded === NoPerm, assertExp) { + v1.decider.assert(pNeeded === NoPerm, assumptionType) { case true => Q(s1, newHeap, condSnap, consumedChunks, v1) case false => @@ -420,7 +425,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { Q(s0, newHeap, None, consumedChunks, v) } else { val assertExp = pNeededExp.map(pn => ast.EqCmp(pn, ast.NoPerm()())(pn.pos, pn.info, pn.errT)) - v.decider.assert(pNeeded === NoPerm, assertExp) { + v.decider.assert(pNeeded === NoPerm, assumptionType) { case true => Q(s0, newHeap, None, consumedChunks, v) case false => @@ -441,7 +446,8 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { permsExp: Option[ast.Exp], returnSnap: Boolean, ve: VerificationError, - v: Verifier) + v: Verifier, + assumptionType: AssumptionType) (Q: (State, ListBuffer[NonQuantifiedChunk], Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -482,8 +488,8 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { newFr = newFr.recordPathSymbol(permTaken.applicable.asInstanceOf[Function]).recordConstraint(constraint) - v.decider.assumptionAnalyzer.addPermissionExhaleNode(ch, permTaken, v.decider.analysisSourceInfoStack.getFullSourceInfo) - GeneralChunk.withPerm(ch, PermMinus(ch.perm, permTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, permTakenExp.get)(pe.pos, pe.info, pe.errT)), v.decider.getAnalysisInfo).asInstanceOf[NonQuantifiedChunk] + v.decider.assumptionAnalyzer.addPermissionExhaleNode(ch, permTaken, v.decider.analysisSourceInfoStack.getFullSourceInfo, assumptionType) + GeneralChunk.withPerm(ch, PermMinus(ch.perm, permTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, permTakenExp.get)(pe.pos, pe.info, pe.errT)), v.decider.getAnalysisInfo(AssumptionType.Internal)).asInstanceOf[NonQuantifiedChunk] // TODO ake: casting }) val totalTakenBounds = @@ -501,8 +507,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { val s1 = s.copy(functionRecorder = newFr) - val totalPermTakenImplExp = Option.when(withExp)(ast.Implies(ast.PermLtCmp(ast.NoPerm()(), permsExp.get)(), ast.NeCmp(totalPermTakenExp.get, ast.NoPerm()())())(permsExp.get.pos, permsExp.get.info, permsExp.get.errT)) - v.decider.assert(Implies(PermLess(NoPerm, perms), totalPermTaken !== NoPerm), totalPermTakenImplExp) { + v.decider.assert(Implies(PermLess(NoPerm, perms), totalPermTaken !== NoPerm), assumptionType) { case true => val constraintExp = permsExp.map(pe => ast.EqCmp(pe, totalPermTakenExp.get)()) v.decider.assume(perms === totalPermTaken, Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp)), AssumptionType.Internal) diff --git a/src/main/scala/rules/PermissionSupporter.scala b/src/main/scala/rules/PermissionSupporter.scala index b4d24bd64..e9d46d5a3 100644 --- a/src/main/scala/rules/PermissionSupporter.scala +++ b/src/main/scala/rules/PermissionSupporter.scala @@ -24,7 +24,7 @@ object permissionSupporter extends SymbolicExecutionRules { Q(s, v) case _ => val assertExp = ePermNew.map(ep => perms.IsNonNegative(ep)(ep.pos, ep.info, ep.errT)) - v.decider.assert(perms.IsNonNegative(tPerm), assertExp) { + v.decider.assert(perms.IsNonNegative(tPerm)) { case true => Q(s, v) case false => createFailure(pve dueTo NegativePermission(ePerm), v, s, perms.IsNonNegative(tPerm), assertExp) @@ -41,7 +41,7 @@ object permissionSupporter extends SymbolicExecutionRules { Q(s, v) case _ => val assertExp = Option.when(withExp)(perms.IsPositive(ePerm)(ePerm.pos, ePerm.info, ePerm.errT)) - v.decider.assert(perms.IsPositive(tPerm), assertExp) { + v.decider.assert(perms.IsPositive(tPerm)) { case true => Q(s, v) case false => createFailure(pve dueTo NonPositivePermission(ePerm), v, s, perms.IsPositive(tPerm), assertExp) } diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 6e5b83e64..085344152 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -1025,9 +1025,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val nonNegImplication = Implies(tCond, perms.IsNonNegative(tPerm)) val nonNegImplicationExp = eCond.map(c => ast.Implies(c, ast.PermGeCmp(ePerm.get, ast.NoPerm()())())(c.pos, c.info, c.errT)) val nonNegTerm = Forall(qvars, Implies(FunctionPreconditionTransformer.transform(nonNegImplication, s.program), nonNegImplication), Nil) - val nonNegExp = qvarExps.map(qv => ast.Forall(qv, Nil, nonNegImplicationExp.get)()) // TODO: Replace by QP-analogue of permissionSupporter.assertNotNegative - v.decider.assert(nonNegTerm, nonNegExp) { // TODO ake: verify this + v.decider.assert(nonNegTerm, assumptionType) { case true => /* TODO: Can we omit/simplify the injectivity check in certain situations? */ @@ -1049,7 +1048,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.prover.comment(comment) val debugExp = Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)) v.decider.assume(FunctionPreconditionTransformer.transform(receiverInjectivityCheck, s.program), debugExp, AssumptionType.Internal) - v.decider.assert(receiverInjectivityCheck, comment, Verifier.config.checkTimeout.toOption) { + v.decider.assert(receiverInjectivityCheck, assumptionType, timeout=Verifier.config.checkTimeout.toOption) { case true => val ax = inverseFunctions.axiomInversesOfInvertibles val inv = inverseFunctions.copy(axiomInversesOfInvertibles = Forall(ax.vars, ax.body, effectiveTriggers)) @@ -1058,7 +1057,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.prover.comment(comment) val definitionalAxiomMark = v.decider.setPathConditionMark() v.decider.assume(inv.definitionalAxioms.map(a => FunctionPreconditionTransformer.transform(a, s.program)), - Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, assumptionType=assumptionType) // TODO ake: assumptionType? + Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, assumptionType=assumptionType) v.decider.assume(inv.definitionalAxioms, Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, assumptionType=assumptionType) val conservedPcs = if (s.recordPcs) (s.conservedPcs.head :+ v.decider.pcs.after(definitionalAxiomMark)) +: s.conservedPcs.tail @@ -1208,7 +1207,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { negativePermissionReason: => ErrorReason, notInjectiveReason: => ErrorReason, insufficientPermissionReason: => ErrorReason, - v: Verifier) + v: Verifier, + assumptionType: AssumptionType=AssumptionType.Implicit) (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { @@ -1266,7 +1266,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val nonNegTerm = Forall(qvars, Implies(FunctionPreconditionTransformer.transform(nonNegImplication, s.program), nonNegImplication), Nil) val nonNegExp = qvarExps.map(qv => ast.Forall(qv, Nil, nonNegImplicationExp.get)()) // TODO: Replace by QP-analogue of permissionSupporter.assertNotNegative - v.decider.assert(nonNegTerm, nonNegExp) { + v.decider.assert(nonNegTerm, assumptionType) { case true => val hints = quantifiedChunkSupporter.extractHints(Some(tCond), tArgs) val chunkOrderHeuristics = @@ -1306,7 +1306,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.prover.comment("Check receiver injectivity") val debugExp = Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)) v.decider.assume(FunctionPreconditionTransformer.transform(receiverInjectivityCheck, s.program), debugExp, AssumptionType.Internal) - v.decider.assert(receiverInjectivityCheck, comment, Verifier.config.checkTimeout.toOption) { + v.decider.assert(receiverInjectivityCheck, assumptionType, Verifier.config.checkTimeout.toOption) { case true => val qvarsToInvOfLoc = inverseFunctions.qvarsToInversesOf(formalQVars) val condOfInvOfLoc = tCond.replace(qvarsToInvOfLoc) @@ -1359,7 +1359,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { rPerm, rPermExp, chunkOrderHeuristics, - v2) + v2, + assumptionType) val optSmDomainDefinitionCondition2 = if (s3.smDomainNeeded) Some(And(condOfInvOfLoc, IsPositive(lossOfInvOfLoc), And(imagesOfFormalQVars))) else None @@ -1390,7 +1391,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { qid, v2, s.program, - AssumptionType.Explicit, + assumptionType, isExhale = true ) val debugExp = Option.when(withExp)(DebugExp.createInstance("Inverse functions for quantified permission", isInternal_ = true)) @@ -1426,7 +1427,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { lossOfInvOfLoc, lossExp, chunkOrderHeuristics, - v + v, + assumptionType ) permissionRemovalResult match { case (Complete(), s2, remainingChunks, consumedChunks) => @@ -1468,7 +1470,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { returnSnap: Boolean, optChunkOrderHeuristic: Option[Seq[QuantifiedBasicChunk] => Seq[QuantifiedBasicChunk]], pve: PartialVerificationError, - v: Verifier) + v: Verifier, + assumptionType: AssumptionType=AssumptionType.Implicit) (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) : VerificationResult = { @@ -1504,7 +1507,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { rPerm, rPermExp, chunkOrderHeuristics, - v + v, + assumptionType ) val h2 = Heap(remainingChunks ++ otherChunks) val (smDef1, smCache1) = @@ -1523,7 +1527,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { } val consumedChunk = quantifiedChunkSupporter.createSingletonQuantifiedChunk( - codomainQVars, codomainQVarsExp, resource, arguments, argumentsExp, permsTaken, permsTakenExp, smDef1.sm, s.program, v1, AssumptionType.Explicit, isExhale=true) + codomainQVars, codomainQVarsExp, resource, arguments, argumentsExp, permsTaken, permsTakenExp, smDef1.sm, s.program, v1, assumptionType, isExhale=true) val s3 = s2.copy(functionRecorder = s2.functionRecorder.recordFvfAndDomain(smDef1), smCache = smCache1) (result, s3, h2, Some(consumedChunk)) // TODO ake: or consumedChunks? @@ -1553,7 +1557,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { permissions, permissionsExp, chunkOrderHeuristics, - v + v, + assumptionType ) result match { case (Complete(), s1, remainingChunks, consumedChunks) => @@ -1591,7 +1596,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { condition: Term, perms: Term, permsExp: Option[ast.Exp], - v: Verifier) + v: Verifier, + assumptionType: AssumptionType=AssumptionType.Implicit) : ConsumptionResult = { var permsAvailable: Term = NoPerm @@ -1608,7 +1614,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { // final check val result = - if (v.decider.check(tookEnoughCheck, Verifier.config.assertTimeout.getOrElse(0)) /* This check is a must-check, i.e. an assert */ ) + if (v.decider.check(tookEnoughCheck, Verifier.config.assertTimeout.getOrElse(0), assumptionType) /* This check is a must-check, i.e. an assert */ ) Complete() else Incomplete(PermMinus(permsAvailable, perms), permsAvailableExp.map(pa => ast.PermSub(pa, permsExp.get)())) @@ -1631,7 +1637,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { perms: Term, // p(rs) permsExp: Option[ast.Exp], // p(rs) chunkOrderHeuristic: Seq[QuantifiedBasicChunk] => Seq[QuantifiedBasicChunk], - v: Verifier) + v: Verifier, + assumptionType: AssumptionType=AssumptionType.Implicit) : (ConsumptionResult, State, Seq[QuantifiedBasicChunk], Seq[QuantifiedBasicChunk]) = { val rmPermRecord = new CommentRecord("removePermissions", s, v.decider.pcs) @@ -1648,7 +1655,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val constrainPermissions = !consumeExactRead(perms, s.constrainableARPs) if (s.assertReadAccessOnly) { - val result = assertReadPermission(s, candidates, codomainQVars, condition, perms, permsExp, v) + val result = assertReadPermission(s, candidates, codomainQVars, condition, perms, permsExp, v, assumptionType) return (result, s, relevantChunks, Seq.empty) } @@ -1714,21 +1721,21 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.assume(permissionConstraint, permissionConstraintExp, permissionConstraintExp, AssumptionType.Internal) remainingChunks = - remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.getAnalysisInfo(AssumptionType.Internal)) // TODO ake: assumption Type? + remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.getAnalysisInfo(assumptionType)) } else { v.decider.prover.comment(s"Chunk depleted?") - val chunkDepleted = v.decider.check(depletedCheck, Verifier.config.splitTimeout()) + val chunkDepleted = v.decider.check(depletedCheck, Verifier.config.splitTimeout(), AssumptionType.Internal) if (!chunkDepleted) { val unusedCheck = Forall(codomainQVars, ithPTaken === NoPerm, Nil) - val chunkUnused = v.decider.check(unusedCheck, Verifier.config.checkTimeout()) + val chunkUnused = v.decider.check(unusedCheck, Verifier.config.checkTimeout(), AssumptionType.Internal) if (chunkUnused) { remainingChunks = remainingChunks :+ ithChunk } else { remainingChunks = - remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.getAnalysisInfo(AssumptionType.Internal)) + remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.getAnalysisInfo(assumptionType)) } }else{ - v.decider.assumptionAnalyzer.addPermissionExhaleNode(ithChunk, ithPTaken, v.decider.analysisSourceInfoStack.getFullSourceInfo) + v.decider.assumptionAnalyzer.addPermissionExhaleNode(ithChunk, ithPTaken, v.decider.analysisSourceInfoStack.getFullSourceInfo, assumptionType) } } @@ -1741,7 +1748,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { Forall(codomainQVars, Implies(condition, ithPNeeded === NoPerm), Nil) v.decider.prover.comment(s"Intermediate check if already taken enough permissions") - success = if (v.decider.check(tookEnoughCheck, Verifier.config.splitTimeout())) { + success = if (v.decider.check(tookEnoughCheck, Verifier.config.splitTimeout(), assumptionType)) { Complete() } else { Incomplete(ithPNeeded, ithPNeededExp) @@ -1751,7 +1758,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.prover.comment("Final check if taken enough permissions") success = - if (success.isComplete || v.decider.check(tookEnoughCheck, Verifier.config.assertTimeout.getOrElse(0)) /* This check is a must-check, i.e. an assert */) + if (success.isComplete || v.decider.check(tookEnoughCheck, Verifier.config.assertTimeout.getOrElse(0), assumptionType) /* This check is a must-check, i.e. an assert */) Complete() else success diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index 5a81f0a59..cc56a846c 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -108,13 +108,13 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif val s4 = s3.copy(h = Heap()) val impLog = new WellformednessCheckRecord(posts, s, v.decider.pcs) val sepIdentifier = symbExLog.openScope(impLog) - produces(s4, freshSnap, posts, ContractNotWellformed, v3, AssumptionType.Explicit)((_, _) => { + produces(s4, freshSnap, posts, ContractNotWellformed, v3, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit))((_, _) => { symbExLog.closeScope(sepIdentifier) Success()})}) && { executionFlowController.locally(s2a, v2)((s3, v3) => { - exec(s3, body, v3)((s4, v4) =>{ // TODO ake: assumption type? - consumes(s4, posts, false, postViolated, v4)((_, _, _, _) => { + exec(s3, body, v3)((s4, v4) =>{ + consumes(s4, posts, false, postViolated, v4, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit))((_, _, _, _) => { Success() })})}) } )})}) diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index 3c4b4e84a..1cb92abc1 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -272,7 +272,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver Some(DebugExp.createInstance(e, eNew)) } else { None } decider.assume(BuiltinEquals(data.formalResult, tBody), debugExp, AssumptionType.Implicit) - consumes(s2, posts, false, postconditionViolated, v)((s3, _, _, _) => { + consumes(s2, posts, false, postconditionViolated, v, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit))((s3, _, _, _) => { recorders :+= s3.functionRecorder Success()})})})} From 67078076718f09707a8f640978df3a8ecdb80bb1 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 4 Jul 2025 11:03:42 +0200 Subject: [PATCH 142/474] cleanup --- .../AssumptionAnalysisGraph.scala | 2 +- .../assumptionAnalysis/AssumptionAnalyzer.scala | 13 ++++++------- src/test/scala/AssumptionAnalysisTests.scala | 9 +++------ 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index a5d5b6b4c..21319875a 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -238,7 +238,7 @@ case class LabelNode(term: Term) extends GeneralAssumptionNode { val assumptionType: AssumptionType = AssumptionType.Internal val isClosed: Boolean = true val description: String = term.toString - override def getNodeType: String = "Assumption" // TODO ake: change to Label once supported + override def getNodeType: String = "Label" override def getNodeString: String = "assume " + description } diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index d10b2fcb8..e56876abd 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -88,21 +88,20 @@ object AssumptionAnalyzer { (assumptionTypeAnnotationKey, Seq(assumptionType.toString)) )) - // TODO ake: remove offsets - def createAssumptionLabel(id: Option[Int], offset: Int = 0): String = { - createLabel("assumption", id, offset) + def createAssumptionLabel(id: Option[Int]): String = { + createLabel("assumption", id) } - def createAssertionLabel(id: Option[Int], offset: Int = 0): String = { - createLabel("assertion", id, offset) + def createAssertionLabel(id: Option[Int]): String = { + createLabel("assertion", id) } def createAxiomLabel(id: Option[Int]): String = { createLabel("axiom", id) } - private def createLabel(description: String, id: Option[Int], offset: Int = 0): String = { - if (id.isDefined) description + "_" + id.get + "_" + offset + private def createLabel(description: String, id: Option[Int]): String = { + if (id.isDefined) description + "_" + id.get else "" } diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index aad06fcf7..783c4a7f3 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -124,17 +124,14 @@ class AssumptionAnalysisTests extends AnyFunSuite { val program: Program = tests.loadProgram(filePrefix, fileName, frontend) val result = frontend.verifier.verify(program) - result match { - case verifier.Failure(failure) => assert(false, s"Verification failed for ${filePrefix + fileName + ".vpr"}: ${failure.mkString(",")}") - case _ => - } + assert(!result.isInstanceOf[verifier.Failure], s"Verification failed for ${filePrefix + fileName + ".vpr"}: $result") val assumptionAnalyzers = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].assumptionAnalyzers val assumptionGraphs = assumptionAnalyzers map (_.assumptionGraph) val stmtsWithAssumptionAnnotation: Set[Infoed] = extractAnnotatedStmts(program, { annotationInfo => annotationInfo.values.contains(irrelevantKeyword) || annotationInfo.values.contains(dependencyKeyword)}) val allAssumptionNodes = assumptionGraphs.flatMap(_.nodes.filter(_.isInstanceOf[GeneralAssumptionNode])) - var errorMsgs = stmtsWithAssumptionAnnotation.map(checkNodeExists(allAssumptionNodes, _)).filter(_.isDefined).map(_.get).toSeq + var errorMsgs = stmtsWithAssumptionAnnotation.map(checkAssumptionNodeExists(allAssumptionNodes, _)).filter(_.isDefined).map(_.get).toSeq errorMsgs ++= assumptionAnalyzers flatMap checkTestAssertionNodeExists errorMsgs ++= assumptionGraphs flatMap checkDependencies val warnMsgs = assumptionGraphs flatMap checkNonDependencies @@ -160,7 +157,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { nodesWithAnnotation } - private def checkNodeExists(analysisNodes: List[AssumptionAnalysisNode], node: Infoed): Option[String] = { + private def checkAssumptionNodeExists(analysisNodes: List[AssumptionAnalysisNode], node: Infoed): Option[String] = { val pos = extractSourceLine(node.asInstanceOf[Positioned].pos) val annotationInfo = node.info.getUniqueInfo[AnnotationInfo] .map(ai => ai.values.getOrElse(irrelevantKeyword, ai.values.getOrElse(dependencyKeyword, List.empty))).getOrElse(List.empty) From 0545e0d8ff8ee458a7414ad6fbf2bea83a990533 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 4 Jul 2025 11:17:59 +0200 Subject: [PATCH 143/474] cleanup --- .../assumptionAnalysis/AssumptionAnalysisGraph.scala | 6 +++--- .../scala/assumptionAnalysis/AssumptionAnalyzer.scala | 10 ++++++---- src/main/scala/rules/ChunkSupporter.scala | 5 +++-- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index 21319875a..28162f604 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -229,9 +229,9 @@ case class PermissionExhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSo override def getNodeString: String = "exhale " + chunk.toString } -case class PermissionAssertNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean) extends GeneralAssertionNode with ChunkAnalysisInfo { - override def getNodeString: String = "assert " + term + " for chunk " + chunk.toString -} +//case class PermissionAssertNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean) extends GeneralAssertionNode with ChunkAnalysisInfo { +// override def getNodeString: String = "assert " + term + " for chunk " + chunk.toString +//} case class LabelNode(term: Term) extends GeneralAssumptionNode { val sourceInfo: AnalysisSourceInfo = NoAnalysisSourceInfo() diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index e56876abd..709e1e2ed 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -173,11 +173,13 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { Some(node.id) } + // TODO ake: remove override def addPermissionAssertNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { - val node = PermissionAssertNode(chunk, permAmount, sourceInfo, assumptionType, isClosed_) - addNode(node) - addPermissionDependencies(Set(chunk), Set(), Some(node.id)) - Some(node.id) +// val node = PermissionAssertNode(chunk, permAmount, sourceInfo, assumptionType, isClosed_) +// addNode(node) +// addPermissionDependencies(Set(chunk), Set(), Some(node.id)) +// Some(node.id) + None } override def addPermissionExhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { diff --git a/src/main/scala/rules/ChunkSupporter.scala b/src/main/scala/rules/ChunkSupporter.scala index 1307cbe60..f1b82f4d8 100644 --- a/src/main/scala/rules/ChunkSupporter.scala +++ b/src/main/scala/rules/ChunkSupporter.scala @@ -177,7 +177,7 @@ object chunkSupporter extends ChunkSupportRules { val termToCheck = Implies(IsPositive(perms), IsPositive(ch.perm)) if (v.decider.check(termToCheck, Verifier.config.assertTimeout.getOrElse(0), assumptionType)) { // TODO ake: can be removed (probably?) - v.decider.assumptionAnalyzer.addPermissionAssertNode(ch, termToCheck, v.decider.analysisSourceInfoStack.getFullSourceInfo, assumptionType) +// v.decider.assumptionAnalyzer.addPermissionAssertNode(ch, termToCheck, v.decider.analysisSourceInfoStack.getFullSourceInfo, assumptionType) (Complete(), s, h, Some(ch)) } else { (Incomplete(perms, permsExp), s, h, None) @@ -263,7 +263,8 @@ object chunkSupporter extends ChunkSupportRules { val findRes = findChunk[NonQuantifiedChunk](h.values, id, args, v) findRes match { case Some(ch) if v.decider.check(IsPositive(ch.perm), Verifier.config.checkTimeout(), assumptionType) => - v.decider.assumptionAnalyzer.addPermissionAssertNode(ch, IsPositive(ch.perm), v.decider.analysisSourceInfoStack.getFullSourceInfo, assumptionType) + // TODO ake +// v.decider.assumptionAnalyzer.addPermissionAssertNode(ch, IsPositive(ch.perm), v.decider.analysisSourceInfoStack.getFullSourceInfo, assumptionType) Q(s, ch.snap, v) case _ if v.decider.checkSmoke(isAssert=true, assumptionType) => if (s.isInPackage) { From afd0c71e05eeeebb0d374c42e9b33566e207b95f Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 4 Jul 2025 12:13:45 +0200 Subject: [PATCH 144/474] implement proof coverage computation --- .../AssumptionAnalysisGraph.scala | 29 +++++++++---------- .../AssumptionAnalyzer.scala | 21 ++++++++++++-- .../scala/verifier/DefaultMainVerifier.scala | 4 ++- .../unitTests/loops.vpr | 2 ++ 4 files changed, 38 insertions(+), 18 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index 28162f604..59fa64c57 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -1,9 +1,8 @@ package viper.silicon.assumptionAnalysis -import viper.silicon.assumptionAnalysis.AssumptionType.{AssumptionType, _} +import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType import viper.silicon.interfaces.state.Chunk import viper.silicon.state.terms.{False, Term} -import viper.silver.ast import viper.silver.ast.Position import java.io.{File, PrintWriter} @@ -45,7 +44,7 @@ trait AssumptionAnalysisGraph { false } - def getNodesByProperties(nodeType: Option[String], assumptionType: Option[AssumptionType], sourceInfo: Option[String], position: Option[Position]): Seq[AssumptionAnalysisNode] = { + def getNodesByProperties(nodeType: Option[String], assumptionType: Option[AssumptionType], sourceInfo: Option[String], position: Option[Position]): mutable.Seq[AssumptionAnalysisNode] = { nodes filter (node => nodeType.forall(node.getNodeType.equals) && assumptionType.forall(node.assumptionType.equals) && @@ -77,15 +76,12 @@ trait AssumptionAnalysisGraph { res } - def getNodesPerTransitivitySourceInfo(): mutable.HashMap[String, Seq[AssumptionAnalysisNode]] = { - val res = new mutable.HashMap[String, Seq[AssumptionAnalysisNode]]() - nodes foreach {n => - res.updateWith(n.sourceInfo.getSourceForTransitiveEdges.toString)({ - case Some(ns) => Some(ns ++ Seq(n)) - case None => Some(Seq(n)) - }) - } - res + private def getNodesPerTransitivitySourceInfo: Map[String, mutable.Seq[AssumptionAnalysisNode]] = { + nodes.groupBy(_.sourceInfo.getSourceForTransitiveEdges.toString) + } + + def getNodesPerSourceInfo: Map[String, mutable.Seq[AssumptionAnalysisNode]] = { + nodes.groupBy(_.sourceInfo.getTopLevelSource.toString) } def addTransitiveEdges(source: AssumptionAnalysisNode, targets: Iterable[AssumptionAnalysisNode]): Unit = { @@ -99,7 +95,7 @@ trait AssumptionAnalysisGraph { } def addTransitiveEdges(): Unit = { - val nodesPerSourceInfo = getNodesPerTransitivitySourceInfo() + val nodesPerSourceInfo = getNodesPerTransitivitySourceInfo nodesPerSourceInfo foreach {nodes => val asserts = nodes._2.filter(_.isInstanceOf[GeneralAssertionNode]) val assumes = nodes._2.filter(n => !n.isClosed && n.isInstanceOf[GeneralAssumptionNode] && !n.isInstanceOf[LabelNode]) @@ -110,7 +106,7 @@ trait AssumptionAnalysisGraph { } } - private def removeNode(node: AssumptionAnalysisNode): Unit = { + private def removeAllEdgesForNode(node: AssumptionAnalysisNode): Unit = { val id = node.id val predecessors = (edges filter { case (_, t) => t.contains(id) }).keys val successors = edges.getOrElse(id, Set.empty) @@ -120,7 +116,8 @@ trait AssumptionAnalysisGraph { } def removeLabelNodes(): Unit = { - nodes filter (_.isInstanceOf[LabelNode]) foreach removeNode + nodes filter (_.isInstanceOf[LabelNode]) foreach removeAllEdgesForNode + nodes = nodes filter (!_.isInstanceOf[LabelNode]) } def exportGraph(dirName: String): Unit = { @@ -150,6 +147,8 @@ trait AssumptionAnalysisGraph { nodes foreach (n => writer.println(getNodeExportString(n).replace("\n", " "))) writer.close() } + + } diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 709e1e2ed..0d5fd39d4 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -57,6 +57,8 @@ trait AssumptionAnalyzer { def registerInhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: (Term => CH), perm: Term, labelNode: Option[LabelNode], analysisInfo: AnalysisInfo, isExhale: Boolean): CH = buildChunk(perm) def registerExhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: (Term => CH), perm: Term, analysisInfo: AnalysisInfo): CH = buildChunk(perm) def getReassumeLabelNodes: Iterable[LabelNode] = Set.empty + + def computeProofCoverage(): Unit = {} } object AssumptionAnalyzer { @@ -118,6 +120,7 @@ object AssumptionAnalyzer { class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { protected var originalLabelNodes: List[LabelNode] = List.empty + protected var proofCoverage: Double = 0.0 override def addNodes(nodes: Iterable[AssumptionAnalysisNode]): Unit = { assumptionGraph.addNodes(nodes) @@ -129,8 +132,6 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { assumptionGraph.addNode(node) } - - override def addAssumption(assumption: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, description: Option[String]): Option[Int] = { val node = SimpleAssumptionNode(assumption, description, analysisSourceInfo, assumptionType, isClosed_) addNode(node) @@ -302,6 +303,22 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { override def getReassumeLabelNodes: Iterable[LabelNode] = { // TODO ake: work with scopes! originalLabelNodes map createReassumeLabelNode } + + override def computeProofCoverage(): Unit = { + val explicitAssertionNodes = assumptionGraph.getNodesByProperties(Some("Assertion"), Some(AssumptionType.Explicit), None, None) + val explicitAssertionNodeIds: Set[Int] = (explicitAssertionNodes map (_.id)).toSet + val nodesPerSourceInfo = assumptionGraph.getNodesPerSourceInfo filter {case (_, nodes) => + nodes exists (node => !node.assumptionType.equals(AssumptionType.Internal)) + } + val coveredNodes = nodesPerSourceInfo filter { case (_, nodes) => + val nodeIds = (nodes map (_.id)).toSet + // it is either an explicit assertion itself or it has a dependency to an explicit assertion + nodeIds.intersect(explicitAssertionNodeIds).nonEmpty || + assumptionGraph.existsAnyDependency(nodeIds, explicitAssertionNodeIds) + } + proofCoverage = coveredNodes.size.toDouble / nodesPerSourceInfo.size.toDouble + val a = 1 + } } class NoAssumptionAnalyzer extends AssumptionAnalyzer { diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index 10041df6f..fbd46b4d8 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -318,8 +318,10 @@ class DefaultMainVerifier(config: Config, assumptionAnalyzers.foreach(_.assumptionGraph.removeLabelNodes()) assumptionAnalyzers.foreach(_.assumptionGraph.addTransitiveEdges()) assumptionAnalyzers foreach (_.exportGraph()) - if(reporter.isInstanceOf[DependencyAnalysisReporter]) + assumptionAnalyzers foreach (_.computeProofCoverage()) + if(reporter.isInstanceOf[DependencyAnalysisReporter]) { reporter.asInstanceOf[DependencyAnalysisReporter].assumptionAnalyzers = assumptionAnalyzers + } } if (Verifier.config.startDebuggerAutomatically()){ diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/loops.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/loops.vpr index f164c36c5..d98d416d6 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/loops.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/loops.vpr @@ -59,7 +59,9 @@ method loop3(){ method loop4(){ var i: Int var res: Int + @dependency() res := 0 + @irrelevant() i := 10 while(@irrelevant()(i > 0)) invariant @irrelevant()(i <= 10) From e220f7720dc68260610fa1d25b909eba9eb1744f Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 4 Jul 2025 19:05:27 +0200 Subject: [PATCH 145/474] implement fully automated tests --- .../AnalysisSourceInfo.scala | 17 ++-- .../AssumptionAnalysisGraph.scala | 39 ++++----- .../AssumptionAnalyzer.scala | 9 +-- src/main/scala/rules/MagicWandSupporter.scala | 5 +- .../scala/rules/QuantifiedChunkSupport.scala | 4 +- src/test/scala/AssumptionAnalysisTests.scala | 79 +++++++++++++++---- 6 files changed, 100 insertions(+), 53 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala index 00c2bb9d9..2d95d3ef1 100644 --- a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala +++ b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala @@ -4,18 +4,23 @@ import viper.silicon.verifier.Verifier import viper.silver.ast import viper.silver.ast._ - -abstract class AnalysisSourceInfo { - override def toString: String = getPositionString - - def getPositionString: String = { - getPosition match { +object AnalysisSourceInfo { + def extractPositionString(p: Position): String = { + p match { case NoPosition => "???" case filePos: AbstractSourcePosition => filePos.file.getFileName.toString + " @ line " + filePos.line case column: HasLineColumn => "line " + column.line.toString case VirtualPosition(identifier) => "label " + identifier } } +} + +abstract class AnalysisSourceInfo { + override def toString: String = getPositionString + + def getPositionString: String = { + AnalysisSourceInfo.extractPositionString(getPosition) + } def getPosition: Position diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index 59fa64c57..f88b8f339 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -44,7 +44,7 @@ trait AssumptionAnalysisGraph { false } - def getNodesByProperties(nodeType: Option[String], assumptionType: Option[AssumptionType], sourceInfo: Option[String], position: Option[Position]): mutable.Seq[AssumptionAnalysisNode] = { + private def getNodesByProperties(nodeType: Option[String], assumptionType: Option[AssumptionType], sourceInfo: Option[String], position: Option[Position]): mutable.Seq[AssumptionAnalysisNode] = { nodes filter (node => nodeType.forall(node.getNodeType.equals) && assumptionType.forall(node.assumptionType.equals) && @@ -53,44 +53,41 @@ trait AssumptionAnalysisGraph { ) } - def getExplicitAndAssertNodesOnly(): Seq[AssumptionAnalysisNode] = { - nodes.filter(n => n.assumptionType.equals(AssumptionType.Explicit) || n.isInstanceOf[GeneralAssertionNode]) + def getExplicitAssertionNodes: Set[AssumptionAnalysisNode] = { + (getNodesByProperties(Some("Assertion"), Some(AssumptionType.Explicit), None, None) ++ + getNodesByProperties(Some("Exhale"), Some(AssumptionType.Explicit), None, None)).toSet } - def getImplicitNodesOnly(): Seq[AssumptionAnalysisNode] = { - getNodesByAssumptionType(AssumptionType.Implicit) + def getNonInternalAssumptionNodesPerSource: Map[String, mutable.Seq[AssumptionAnalysisNode]] = { + getNodesPerSourceInfo filter {case (_, nodes) => + nodes exists (node => + node.isInstanceOf[GeneralAssumptionNode] && + !node.assumptionType.equals(AssumptionType.Internal) && + !node.assumptionType.equals(AssumptionType.Axiom)) + } } - def getNodesByAssumptionType(assumptionType: AssumptionType): Seq[AssumptionAnalysisNode] = { - nodes.filter(n => n.assumptionType.equals(assumptionType)) - } - def getNodesPerChunk(): mutable.HashMap[Chunk, Seq[AssumptionAnalysisNode]] = { - val res = new mutable.HashMap[Chunk, Seq[AssumptionAnalysisNode]]() - nodes filter (_.isInstanceOf[ChunkAnalysisInfo]) foreach {n => - res.updateWith(n.asInstanceOf[ChunkAnalysisInfo].getChunk)({ - case Some(ns) => Some(ns ++ Seq(n)) - case None => Some(Seq(n)) - }) - } - res + def getNodesPerChunk: Map[Chunk, mutable.Seq[AssumptionAnalysisNode]] = { + nodes.filter (_.isInstanceOf[ChunkAnalysisInfo]) + .groupBy(_.asInstanceOf[ChunkAnalysisInfo].getChunk) } private def getNodesPerTransitivitySourceInfo: Map[String, mutable.Seq[AssumptionAnalysisNode]] = { nodes.groupBy(_.sourceInfo.getSourceForTransitiveEdges.toString) } - def getNodesPerSourceInfo: Map[String, mutable.Seq[AssumptionAnalysisNode]] = { + private def getNodesPerSourceInfo: Map[String, mutable.Seq[AssumptionAnalysisNode]] = { nodes.groupBy(_.sourceInfo.getTopLevelSource.toString) } - def addTransitiveEdges(source: AssumptionAnalysisNode, targets: Iterable[AssumptionAnalysisNode]): Unit = { + private def addTransitiveEdges(source: AssumptionAnalysisNode, targets: Iterable[AssumptionAnalysisNode]): Unit = { val oldTargets = transitiveEdges.getOrElse(source.id, Set.empty) val newTargets = targets map(_.id) // filter(_ > source.id) does not work due to loop invariants if(newTargets.nonEmpty) transitiveEdges.update(source.id, oldTargets ++ newTargets) } - def addTransitiveEdges(source: Iterable[AssumptionAnalysisNode], targets: Iterable[AssumptionAnalysisNode]): Unit = { + private def addTransitiveEdges(source: Iterable[AssumptionAnalysisNode], targets: Iterable[AssumptionAnalysisNode]): Unit = { source foreach (s => addTransitiveEdges(s, targets)) } @@ -147,8 +144,6 @@ trait AssumptionAnalysisGraph { nodes foreach (n => writer.println(getNodeExportString(n).replace("\n", " "))) writer.close() } - - } diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 0d5fd39d4..985ad1cdd 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -305,11 +305,9 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { } override def computeProofCoverage(): Unit = { - val explicitAssertionNodes = assumptionGraph.getNodesByProperties(Some("Assertion"), Some(AssumptionType.Explicit), None, None) - val explicitAssertionNodeIds: Set[Int] = (explicitAssertionNodes map (_.id)).toSet - val nodesPerSourceInfo = assumptionGraph.getNodesPerSourceInfo filter {case (_, nodes) => - nodes exists (node => !node.assumptionType.equals(AssumptionType.Internal)) - } + val explicitAssertionNodes = assumptionGraph.getExplicitAssertionNodes + val explicitAssertionNodeIds = explicitAssertionNodes map (_.id) + val nodesPerSourceInfo = assumptionGraph.getNonInternalAssumptionNodesPerSource val coveredNodes = nodesPerSourceInfo filter { case (_, nodes) => val nodeIds = (nodes map (_.id)).toSet // it is either an explicit assertion itself or it has a dependency to an explicit assertion @@ -317,7 +315,6 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { assumptionGraph.existsAnyDependency(nodeIds, explicitAssertionNodeIds) } proofCoverage = coveredNodes.size.toDouble / nodesPerSourceInfo.size.toDouble - val a = 1 } } diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index d522c422c..674125215 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -328,7 +328,7 @@ object magicWandSupporter extends SymbolicExecutionRules { val (sm, smValueDef) = quantifiedChunkSupporter.singletonSnapshotMap(s5, wand, args, snapshotTerm, v4) v4.decider.prover.comment("Definitional axioms for singleton-SM's value") val debugExp = Option.when(withExp)(DebugExp.createInstance("Definitional axioms for singleton-SM's value", isInternal_ = true)) - v4.decider.assumeDefinition(smValueDef, debugExp, AssumptionType.Internal) + v4.decider.assumeDefinition(smValueDef, debugExp, assumptionType) val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(formalVars, formalVarExps, wand, args, Option.when(withExp)(bodyVars), FullPerm, Option.when(withExp)(ast.FullPerm()()), sm, s.program, v4, assumptionType, isExhale=false) val conservedPcs = s5.conservedPcs.head :+ v4.decider.pcs.after(preMark).definitionsOnly @@ -401,9 +401,12 @@ object magicWandSupporter extends SymbolicExecutionRules { // Execute proof script, i.e. the part written after the magic wand wrapped by curly braces. // The proof script should transform the current state such that we can consume the wand's RHS. + val prevSourceInfo = v2.decider.analysisSourceInfoStack.getAnalysisSourceInfos + v2.decider.analysisSourceInfoStack.setAnalysisSourceInfo(List.empty) executor.exec(s2, proofScriptCfg, v2)((proofScriptState, proofScriptVerifier) => { // TODO ake: propagate assumption type! // Consume the wand's RHS and produce a snapshot which records all the values of variables on the RHS. // This part indirectly calls the methods `this.transfer` and `this.consumeFromMultipleHeaps`. + v2.decider.analysisSourceInfoStack.setAnalysisSourceInfo(prevSourceInfo) consume( proofScriptState.copy(oldHeaps = s2.oldHeaps, reserveCfgs = proofScriptState.reserveCfgs.tail), wand.right, true, pve, proofScriptVerifier diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 085344152..2a199a87f 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -1026,7 +1026,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val nonNegImplicationExp = eCond.map(c => ast.Implies(c, ast.PermGeCmp(ePerm.get, ast.NoPerm()())())(c.pos, c.info, c.errT)) val nonNegTerm = Forall(qvars, Implies(FunctionPreconditionTransformer.transform(nonNegImplication, s.program), nonNegImplication), Nil) // TODO: Replace by QP-analogue of permissionSupporter.assertNotNegative - v.decider.assert(nonNegTerm, assumptionType) { + v.decider.assert(nonNegTerm) { case true => /* TODO: Can we omit/simplify the injectivity check in certain situations? */ @@ -1048,7 +1048,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.prover.comment(comment) val debugExp = Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)) v.decider.assume(FunctionPreconditionTransformer.transform(receiverInjectivityCheck, s.program), debugExp, AssumptionType.Internal) - v.decider.assert(receiverInjectivityCheck, assumptionType, timeout=Verifier.config.checkTimeout.toOption) { + v.decider.assert(receiverInjectivityCheck, timeout=Verifier.config.checkTimeout.toOption) { case true => val ax = inverseFunctions.axiomInversesOfInvertibles val inv = inverseFunctions.copy(axiomInversesOfInvertibles = Forall(ax.vars, ax.body, effectiveTriggers)) diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index 783c4a7f3..1830b8c43 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -3,7 +3,8 @@ import org.scalatest.funsuite.AnyFunSuite import viper.silicon.SiliconFrontend import viper.silicon.assumptionAnalysis._ import viper.silver.ast.utility.{DiskLoader, ViperStrategy} -import viper.silver.ast._ +import viper.silver.ast +import viper.silver.ast.utility.rewriter.Traverse import viper.silver.frontend.SilFrontend import viper.silver.verifier @@ -49,8 +50,8 @@ class AssumptionAnalysisTests extends AnyFunSuite { ) testDirectories foreach createTests -// test("dependencyAnalysisTests/all" + "/" + "imprecision"){ -// executeTest("dependencyAnalysisTests/all" + "/", "imprecision", frontend) +// test("dependencyAnalysisTests/all" + "/" + "misc"){ +// executeTest("dependencyAnalysisTests/unitTests" + "/", "misc", frontend) // } @@ -122,13 +123,15 @@ class AssumptionAnalysisTests extends AnyFunSuite { frontend: SilFrontend) : Unit = { - val program: Program = tests.loadProgram(filePrefix, fileName, frontend) + val program: ast.Program = tests.loadProgram(filePrefix, fileName, frontend) val result = frontend.verifier.verify(program) assert(!result.isInstanceOf[verifier.Failure], s"Verification failed for ${filePrefix + fileName + ".vpr"}: $result") val assumptionAnalyzers = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].assumptionAnalyzers + + assumptionAnalyzers foreach (aa => removeDependenciesAndVerify(program, aa)) val assumptionGraphs = assumptionAnalyzers map (_.assumptionGraph) - val stmtsWithAssumptionAnnotation: Set[Infoed] = extractAnnotatedStmts(program, { annotationInfo => annotationInfo.values.contains(irrelevantKeyword) || annotationInfo.values.contains(dependencyKeyword)}) + val stmtsWithAssumptionAnnotation: Set[ast.Infoed] = extractAnnotatedStmts(program, { annotationInfo => annotationInfo.values.contains(irrelevantKeyword) || annotationInfo.values.contains(dependencyKeyword)}) val allAssumptionNodes = assumptionGraphs.flatMap(_.nodes.filter(_.isInstanceOf[GeneralAssumptionNode])) var errorMsgs = stmtsWithAssumptionAnnotation.map(checkAssumptionNodeExists(allAssumptionNodes, _)).filter(_.isDefined).map(_.get).toSeq @@ -143,12 +146,12 @@ class AssumptionAnalysisTests extends AnyFunSuite { assert(check, "\n" + errorMsgs.mkString("\n")) } - private def extractAnnotatedStmts(program: Program, annotationFiler: (AnnotationInfo => Boolean)): Set[Infoed] = { - var nodesWithAnnotation: Set[Infoed] = Set.empty - val newP: Program = ViperStrategy.Slim({ - case s: Seqn => s - case n: Infoed => - val annotationInfo = n.info.getUniqueInfo[AnnotationInfo] + private def extractAnnotatedStmts(program: ast.Program, annotationFiler: (ast.AnnotationInfo => Boolean)): Set[ast.Infoed] = { + var nodesWithAnnotation: Set[ast.Infoed] = Set.empty + val newP: ast.Program = ViperStrategy.Slim({ + case s: ast.Seqn => s + case n: ast.Infoed => + val annotationInfo = n.info.getUniqueInfo[ast.AnnotationInfo] .filter(annotationFiler) if (annotationInfo.isDefined) nodesWithAnnotation += n @@ -157,9 +160,9 @@ class AssumptionAnalysisTests extends AnyFunSuite { nodesWithAnnotation } - private def checkAssumptionNodeExists(analysisNodes: List[AssumptionAnalysisNode], node: Infoed): Option[String] = { - val pos = extractSourceLine(node.asInstanceOf[Positioned].pos) - val annotationInfo = node.info.getUniqueInfo[AnnotationInfo] + private def checkAssumptionNodeExists(analysisNodes: List[AssumptionAnalysisNode], node: ast.Infoed): Option[String] = { + val pos = extractSourceLine(node.asInstanceOf[ast.Positioned].pos) + val annotationInfo = node.info.getUniqueInfo[ast.AnnotationInfo] .map(ai => ai.values.getOrElse(irrelevantKeyword, ai.values.getOrElse(dependencyKeyword, List.empty))).getOrElse(List.empty) val assumptionType = annotationInfo.map(AssumptionType.fromString).filter(_.isDefined).map(_.get) val nodeExists = analysisNodes exists (analysisNode => { @@ -171,9 +174,9 @@ class AssumptionAnalysisTests extends AnyFunSuite { Option.when(!nodeExists)(s"Missing analysis node:\n${node.toString}\n$pos") } - def extractSourceLine(pos: Position): Int = { + def extractSourceLine(pos: ast.Position): Int = { pos match { - case column: HasLineColumn => column.line + case column: ast.HasLineColumn => column.line case _ => -1 } } @@ -239,4 +242,48 @@ class AssumptionAnalysisTests extends AnyFunSuite { } ).toSeq } + + def removeDependenciesAndVerify(program: ast.Program, assumptionAnalyzer: AssumptionAnalyzer): Unit = { + val explicitAssertionNodes = assumptionAnalyzer.assumptionGraph.getExplicitAssertionNodes + val explicitAssertionNodeIds = explicitAssertionNodes map (_.id) + val dependencies = assumptionAnalyzer.assumptionGraph.nodes filter (node => + node.isInstanceOf[GeneralAssumptionNode] && + !node.assumptionType.equals(AssumptionType.Internal) && + assumptionAnalyzer.assumptionGraph.existsAnyDependency(Set(node.id), explicitAssertionNodeIds)) + val crucialNodes = explicitAssertionNodes ++ dependencies + val crucialNodesWithStmtInfo = crucialNodes filter (_.sourceInfo.getTopLevelSource.isInstanceOf[StmtAnalysisSourceInfo]) map (_.sourceInfo.getTopLevelSource.asInstanceOf[StmtAnalysisSourceInfo]) + val crucialNodesWithExpInfo = crucialNodes filter (_.sourceInfo.getTopLevelSource.isInstanceOf[ExpAnalysisSourceInfo]) map (_.sourceInfo.getTopLevelSource.asInstanceOf[ExpAnalysisSourceInfo]) + val newProgram: ast.Program = ViperStrategy.Slim({ + case s: ast.Seqn => s + case meth@ast.Method(name, inVars, outVars, pres, posts, body) => + ast.Method(name, inVars, outVars, pres filter (isCrucialExp(_, crucialNodesWithExpInfo)), + posts filter (isCrucialExp(_, crucialNodesWithExpInfo)), body)(meth.pos, meth.info, meth.errT) + case ifStmt@ast.If(cond, thenBody, elseBody) if !isCrucialExp(cond, crucialNodesWithExpInfo) => + ast.Seqn(Seq( + ast.LocalVarDeclStmt(ast.LocalVarDecl("nonDetermBool", ast.Bool)())(), + ast.If(ast.LocalVar("nonDetermBool", ast.Bool)(), thenBody, elseBody)()) + , Seq())(ifStmt.pos, ifStmt.info, ifStmt.errT) + case ifStmt: ast.If => ifStmt + case whileStmt@ast.While(cond, invs, body) if !isCrucialExp(cond, crucialNodesWithExpInfo) => + ast.Seqn(Seq( + ast.LocalVarDeclStmt(ast.LocalVarDecl("nonDetermBool", ast.Bool)())(), + ast.While(ast.LocalVar("nonDetermBool", ast.Bool)(), invs filter (isCrucialExp(_, crucialNodesWithExpInfo)), body)(whileStmt.pos, whileStmt.info, whileStmt.errT)) + , Seq())(whileStmt.pos, whileStmt.info, whileStmt.errT) + case whileStmt@ast.While(cond, invs, body) => + ast.While(cond, invs filter (isCrucialExp(_, crucialNodesWithExpInfo)), body)(whileStmt.pos, whileStmt.info, whileStmt.errT) + // TODO ake: method calls -> join graphs first? + case s: ast.Stmt if !isCrucialStmt(s, crucialNodesWithStmtInfo) => + ast.Inhale(ast.TrueLit()())() + }, Traverse.BottomUp).execute(program) + val result = frontend.verifier.verify(newProgram) + assert(!result.isInstanceOf[verifier.Failure], s"Failed to verify new program ${newProgram.toString()}") + } + + private def isCrucialExp(exp: ast.Exp, crucialNodesWithExpInfo: Set[ExpAnalysisSourceInfo]): Boolean = { + crucialNodesWithExpInfo exists(n => n.getPositionString.equals(AnalysisSourceInfo.extractPositionString(exp.pos))) // TODO ake: currently we compare only lines not columns! + } + + private def isCrucialStmt(stmt: ast.Stmt, crucialNodesWithStmtInfo: Set[StmtAnalysisSourceInfo]): Boolean = { + crucialNodesWithStmtInfo exists(n => n.source.pos.equals(stmt.pos)) + } } From c76ffd02121de38ae90efebe357f2975de0b1bac Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 7 Jul 2025 11:01:12 +0200 Subject: [PATCH 146/474] implement fully automated tests --- .../assumptionAnalysis/AnalysisInfo.scala | 2 +- .../AssumptionAnalysisGraph.scala | 4 +- .../AssumptionAnalyzer.scala | 16 +++ .../scala/supporters/MethodSupporter.scala | 2 +- .../functions/FunctionVerificationUnit.scala | 2 +- .../scala/verifier/DefaultMainVerifier.scala | 4 +- .../unitTests/permissions.vpr | 11 ++ src/test/scala/AssumptionAnalysisTests.scala | 114 +++++++++++++----- 8 files changed, 122 insertions(+), 33 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AnalysisInfo.scala b/src/main/scala/assumptionAnalysis/AnalysisInfo.scala index bbdc54e2d..fe60fa1df 100644 --- a/src/main/scala/assumptionAnalysis/AnalysisInfo.scala +++ b/src/main/scala/assumptionAnalysis/AnalysisInfo.scala @@ -2,7 +2,7 @@ package viper.silicon.assumptionAnalysis object AssumptionType extends Enumeration { type AssumptionType = Value - val Explicit, LoopInvariant, PathCondition, Rewrite, Implicit, Internal, Axiom = Value + val Explicit, LoopInvariant, PathCondition, Rewrite, Implicit, Internal, Axiom, Postcondition = Value def fromString(s: String): Option[Value] = values.find(_.toString == s) } diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index f88b8f339..7631e55bd 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -55,7 +55,9 @@ trait AssumptionAnalysisGraph { def getExplicitAssertionNodes: Set[AssumptionAnalysisNode] = { (getNodesByProperties(Some("Assertion"), Some(AssumptionType.Explicit), None, None) ++ - getNodesByProperties(Some("Exhale"), Some(AssumptionType.Explicit), None, None)).toSet + getNodesByProperties(Some("Assertion"), Some(AssumptionType.Postcondition), None, None) ++ + getNodesByProperties(Some("Exhale"), Some(AssumptionType.Explicit), None, None) ++ + getNodesByProperties(Some("Exhale"), Some(AssumptionType.Postcondition), None, None)).toSet } def getNonInternalAssumptionNodesPerSource: Map[String, mutable.Seq[AssumptionAnalysisNode]] = { diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 985ad1cdd..439dfd344 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -116,6 +116,22 @@ object AssumptionAnalyzer { def isAssumptionLabel(label: String): Boolean = label.startsWith("assumption_") def isAxiomLabel(label: String): Boolean = label.startsWith("axiom_") + + def joinGraphs(assumptionAnalysisGraphs: Set[AssumptionAnalysisGraph]): AssumptionAnalysisGraph = { + val newGraph = new DefaultAssumptionAnalysisGraph + assumptionAnalysisGraphs foreach (graph => newGraph.addNodes(graph.nodes)) + assumptionAnalysisGraphs foreach (graph => graph.edges foreach {case (s, t) => newGraph.addEdges(s, t)}) + assumptionAnalysisGraphs foreach (graph => graph.transitiveEdges foreach {case (s, t) => newGraph.addEdges(s, t)}) // TODO ake: add transitive edges + val types = Set(AssumptionType.Implicit, AssumptionType.Explicit) + val relevantAssumptionNodes = newGraph.nodes filter (node => node.isInstanceOf[GeneralAssumptionNode] && types.contains(node.assumptionType)) + newGraph.nodes filter (node => node.isInstanceOf[GeneralAssertionNode] && node.assumptionType.equals(AssumptionType.Postcondition)) foreach {node => + val nodeSourceInfoString = node.sourceInfo.getTopLevelSource.toString + val assumptionNodesForJoin = relevantAssumptionNodes filter (aNode => aNode.sourceInfo.getFineGrainedSource.toString.equals(nodeSourceInfoString)) + newGraph.addEdges(node.id, assumptionNodesForJoin map (_.id)) + } + newGraph + } + } class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index cc56a846c..a16ac4b9b 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -114,7 +114,7 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif && { executionFlowController.locally(s2a, v2)((s3, v3) => { exec(s3, body, v3)((s4, v4) =>{ - consumes(s4, posts, false, postViolated, v4, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit))((_, _, _, _) => { + consumes(s4, posts, false, postViolated, v4, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Postcondition))((_, _, _, _) => { Success() })})}) } )})}) diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index 1cb92abc1..8033b0cc1 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -272,7 +272,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver Some(DebugExp.createInstance(e, eNew)) } else { None } decider.assume(BuiltinEquals(data.formalResult, tBody), debugExp, AssumptionType.Implicit) - consumes(s2, posts, false, postconditionViolated, v, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit))((s3, _, _, _) => { + consumes(s2, posts, false, postconditionViolated, v, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Postcondition))((s3, _, _, _) => { recorders :+= s3.functionRecorder Success()})})})} diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index fbd46b4d8..f67ec0e7a 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -8,7 +8,7 @@ package viper.silicon.verifier import viper.silicon.Config.{ExhaleMode, JoinMode} import viper.silicon._ -import viper.silicon.assumptionAnalysis.{DefaultAssumptionAnalyzer, DependencyAnalysisReporter} +import viper.silicon.assumptionAnalysis.{AssumptionAnalyzer, DefaultAssumptionAnalyzer, DependencyAnalysisReporter} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.SiliconDebugger import viper.silicon.decider.SMTLib2PreambleReader @@ -319,6 +319,8 @@ class DefaultMainVerifier(config: Config, assumptionAnalyzers.foreach(_.assumptionGraph.addTransitiveEdges()) assumptionAnalyzers foreach (_.exportGraph()) assumptionAnalyzers foreach (_.computeProofCoverage()) + val joinedGraph = AssumptionAnalyzer.joinGraphs(assumptionAnalyzers.map(_.assumptionGraph).toSet) + joinedGraph.exportGraph("graphExports/joinedGraphs") if(reporter.isInstanceOf[DependencyAnalysisReporter]) { reporter.asInstanceOf[DependencyAnalysisReporter].assumptionAnalyzers = assumptionAnalyzers } diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr index 79a8e74c9..12e8c96d9 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr @@ -92,4 +92,15 @@ method noAlias(a: Ref, b: Ref, c: Ref) { @testAssertion() assert a != b +} + +method exhaleInhale(a: Ref) + requires @dependency()(acc(a.f)) +{ + @dependency() + exhale acc(a.f, 1/2) + @dependency() + inhale acc(a.f, 1/2) + @testAssertion() + assert perm(a.f) == 1/1 } \ No newline at end of file diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index 1830b8c43..9e9e1ce65 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -4,11 +4,12 @@ import viper.silicon.SiliconFrontend import viper.silicon.assumptionAnalysis._ import viper.silver.ast.utility.{DiskLoader, ViperStrategy} import viper.silver.ast +import viper.silver.ast.{If, Program, Seqn, Stmt} import viper.silver.ast.utility.rewriter.Traverse import viper.silver.frontend.SilFrontend import viper.silver.verifier -import java.io.File +import java.io.{File, PrintWriter} import java.nio.file.{Files, Path, Paths} import scala.jdk.CollectionConverters.IterableHasAsScala import scala.util.{Failure, Success} @@ -28,7 +29,7 @@ import scala.util.{Failure, Success} */ class AssumptionAnalysisTests extends AnyFunSuite { - val CHECK_PRECISION = true + val CHECK_PRECISION = false val ignores: Seq[String] = Seq("infeasible") val irrelevantKeyword = "irrelevant" @@ -47,11 +48,16 @@ class AssumptionAnalysisTests extends AnyFunSuite { val testDirectories: Seq[String] = Seq( "dependencyAnalysisTests/unitTests", "dependencyAnalysisTests/all", +// "examples/binary-search", +// "examples/graph-copy", +// "examples/graph-marking", +// "examples/max_array", +// "examples/quickselect", ) testDirectories foreach createTests // test("dependencyAnalysisTests/all" + "/" + "misc"){ -// executeTest("dependencyAnalysisTests/unitTests" + "/", "misc", frontend) +// executeTest("dependencyAnalysisTests/all/", "list", frontend) // } @@ -63,11 +69,14 @@ class AssumptionAnalysisTests extends AnyFunSuite { for (filePath: Path <- dirContent.sorted if Files.isReadable(filePath) if !Files.isDirectory(filePath)){ - val fileName = filePath.getFileName.toString.replace(".vpr", "") - if(!ignores.contains(fileName)) - test(dirName + "/" + fileName){ - executeTest(dirName + "/", fileName, frontend) - } + val rawFileName = filePath.getFileName.toString + if(rawFileName.endsWith(".vpr")){ + val fileName = rawFileName.replace(".vpr", "") + if(!ignores.contains(fileName)) + test(dirName + "/" + fileName){ + executeTest(dirName + "/", fileName, frontend) + } + } } } @@ -125,11 +134,23 @@ class AssumptionAnalysisTests extends AnyFunSuite { val program: ast.Program = tests.loadProgram(filePrefix, fileName, frontend) val result = frontend.verifier.verify(program) - assert(!result.isInstanceOf[verifier.Failure], s"Verification failed for ${filePrefix + fileName + ".vpr"}: $result") + if(result.isInstanceOf[verifier.Failure]) { + println("Program does not verify. Skip test.") + return + } val assumptionAnalyzers = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].assumptionAnalyzers - assumptionAnalyzers foreach (aa => removeDependenciesAndVerify(program, aa)) + val fullGraph = AssumptionAnalyzer.joinGraphs(assumptionAnalyzers.map(_.assumptionGraph).toSet) + var id: Int = 0 + assumptionAnalyzers foreach (aa => { + val explicitAssertionNodes = aa.assumptionGraph.getExplicitAssertionNodes + explicitAssertionNodes.foreach{node => + removeNonDependenciesAndVerify(program, Set(node), fullGraph, "src/test/resources/" + filePrefix + fileName + s"_test$id.out") + id += 1 + } + }) + val assumptionGraphs = assumptionAnalyzers map (_.assumptionGraph) val stmtsWithAssumptionAnnotation: Set[ast.Infoed] = extractAnnotatedStmts(program, { annotationInfo => annotationInfo.values.contains(irrelevantKeyword) || annotationInfo.values.contains(dependencyKeyword)}) val allAssumptionNodes = assumptionGraphs.flatMap(_.nodes.filter(_.isInstanceOf[GeneralAssumptionNode])) @@ -243,40 +264,77 @@ class AssumptionAnalysisTests extends AnyFunSuite { ).toSeq } - def removeDependenciesAndVerify(program: ast.Program, assumptionAnalyzer: AssumptionAnalyzer): Unit = { + def removeNonDependenciesAndVerify(program: ast.Program, assumptionAnalyzer: AssumptionAnalyzer, fullGraph: AssumptionAnalysisGraph, exportFileName: String): Unit = { val explicitAssertionNodes = assumptionAnalyzer.assumptionGraph.getExplicitAssertionNodes - val explicitAssertionNodeIds = explicitAssertionNodes map (_.id) - val dependencies = assumptionAnalyzer.assumptionGraph.nodes filter (node => - node.isInstanceOf[GeneralAssumptionNode] && - !node.assumptionType.equals(AssumptionType.Internal) && - assumptionAnalyzer.assumptionGraph.existsAnyDependency(Set(node.id), explicitAssertionNodeIds)) - val crucialNodes = explicitAssertionNodes ++ dependencies + removeNonDependenciesAndVerify(program, explicitAssertionNodes, fullGraph, exportFileName) + } + + def removeNonDependenciesAndVerify(program: ast.Program, nodesToAnalyze: Set[AssumptionAnalysisNode], fullGraph: AssumptionAnalysisGraph, exportFileName: String): Unit = { + val explicitAssertionNodeIds = nodesToAnalyze map (_.id) + + val dependencies = fullGraph.nodes filter (node => + ((node.isInstanceOf[GeneralAssumptionNode] && !node.assumptionType.equals(AssumptionType.Internal)) || + (node.isInstanceOf[GeneralAssertionNode] && node.assumptionType.equals(AssumptionType.Postcondition))) && + fullGraph.existsAnyDependency(Set(node.id), explicitAssertionNodeIds)) + + val crucialNodes = nodesToAnalyze ++ dependencies + val (newProgram, cleanseFactor) = cleanseProgram(program, crucialNodes) + val result = frontend.verifier.verify(newProgram) + val writer = new PrintWriter(exportFileName) + writer.println("// test result: " + !result.isInstanceOf[verifier.Failure]) + writer.println("// cleanse factor: " + cleanseFactor) + writer.println(newProgram.toString()) + writer.close() + assert(!result.isInstanceOf[verifier.Failure], s"Failed to verify new program ${newProgram.toString()}") + } + + private def cleanseProgram(program: Program, crucialNodes: Set[AssumptionAnalysisNode]): (ast.Program, Double) = { val crucialNodesWithStmtInfo = crucialNodes filter (_.sourceInfo.getTopLevelSource.isInstanceOf[StmtAnalysisSourceInfo]) map (_.sourceInfo.getTopLevelSource.asInstanceOf[StmtAnalysisSourceInfo]) val crucialNodesWithExpInfo = crucialNodes filter (_.sourceInfo.getTopLevelSource.isInstanceOf[ExpAnalysisSourceInfo]) map (_.sourceInfo.getTopLevelSource.asInstanceOf[ExpAnalysisSourceInfo]) - val newProgram: ast.Program = ViperStrategy.Slim({ - case s: ast.Seqn => s + var total = 0 + var removed = 0 + + + val newProgram: Program = ViperStrategy.Slim({ + case s: Seqn => s case meth@ast.Method(name, inVars, outVars, pres, posts, body) => - ast.Method(name, inVars, outVars, pres filter (isCrucialExp(_, crucialNodesWithExpInfo)), - posts filter (isCrucialExp(_, crucialNodesWithExpInfo)), body)(meth.pos, meth.info, meth.errT) + val newPres = pres filter (isCrucialExp(_, crucialNodesWithExpInfo)) + val newPosts = posts filter (isCrucialExp(_, crucialNodesWithExpInfo)) + total += pres.size + posts.size + removed += (pres.size-newPres.size) + (posts.size-newPosts.size) + ast.Method(name, inVars, outVars, newPres, newPosts, body)(meth.pos, meth.info, meth.errT) case ifStmt@ast.If(cond, thenBody, elseBody) if !isCrucialExp(cond, crucialNodesWithExpInfo) => + total += 1 + removed += 1 ast.Seqn(Seq( ast.LocalVarDeclStmt(ast.LocalVarDecl("nonDetermBool", ast.Bool)())(), ast.If(ast.LocalVar("nonDetermBool", ast.Bool)(), thenBody, elseBody)()) , Seq())(ifStmt.pos, ifStmt.info, ifStmt.errT) - case ifStmt: ast.If => ifStmt + case ifStmt: If => + total += 1 + ifStmt case whileStmt@ast.While(cond, invs, body) if !isCrucialExp(cond, crucialNodesWithExpInfo) => + val newInvs = invs filter (isCrucialExp(_, crucialNodesWithExpInfo)) + total += 1 + invs.size + removed += 1 + (invs.size-newInvs.size) ast.Seqn(Seq( ast.LocalVarDeclStmt(ast.LocalVarDecl("nonDetermBool", ast.Bool)())(), - ast.While(ast.LocalVar("nonDetermBool", ast.Bool)(), invs filter (isCrucialExp(_, crucialNodesWithExpInfo)), body)(whileStmt.pos, whileStmt.info, whileStmt.errT)) + ast.While(ast.LocalVar("nonDetermBool", ast.Bool)(), newInvs, body)(whileStmt.pos, whileStmt.info, whileStmt.errT)) , Seq())(whileStmt.pos, whileStmt.info, whileStmt.errT) case whileStmt@ast.While(cond, invs, body) => - ast.While(cond, invs filter (isCrucialExp(_, crucialNodesWithExpInfo)), body)(whileStmt.pos, whileStmt.info, whileStmt.errT) - // TODO ake: method calls -> join graphs first? - case s: ast.Stmt if !isCrucialStmt(s, crucialNodesWithStmtInfo) => + val newInvs = invs filter (isCrucialExp(_, crucialNodesWithExpInfo)) + total += 1 + invs.size + removed += (invs.size-newInvs.size) + ast.While(cond, newInvs, body)(whileStmt.pos, whileStmt.info, whileStmt.errT) + case s: Stmt if !isCrucialStmt(s, crucialNodesWithStmtInfo) => + total += 1 + removed += 1 ast.Inhale(ast.TrueLit()())() + case s: Stmt => + total += 1 + s }, Traverse.BottomUp).execute(program) - val result = frontend.verifier.verify(newProgram) - assert(!result.isInstanceOf[verifier.Failure], s"Failed to verify new program ${newProgram.toString()}") + (newProgram, removed.toDouble/total.toDouble) } private def isCrucialExp(exp: ast.Exp, crucialNodesWithExpInfo: Set[ExpAnalysisSourceInfo]): Boolean = { From fe07c7aba811e396742d12d955025b2897f534b4 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 7 Jul 2025 15:41:46 +0200 Subject: [PATCH 147/474] implement fully automated tests --- .../assumptionAnalysis/AnalysisInfo.scala | 2 +- src/main/scala/interfaces/state/Chunks.scala | 2 +- src/main/scala/rules/Brancher.scala | 3 +- src/main/scala/rules/ChunkSupporter.scala | 4 +-- src/main/scala/rules/Consumer.scala | 6 ++-- src/main/scala/rules/Evaluator.scala | 14 ++++---- src/main/scala/rules/Executor.scala | 8 ++--- src/main/scala/rules/PredicateSupporter.scala | 8 ++--- src/main/scala/rules/Producer.scala | 4 +-- .../scala/rules/QuantifiedChunkSupport.scala | 4 +-- src/test/scala/AssumptionAnalysisTests.scala | 34 +++++++++---------- 11 files changed, 45 insertions(+), 44 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AnalysisInfo.scala b/src/main/scala/assumptionAnalysis/AnalysisInfo.scala index fe60fa1df..0c80502fd 100644 --- a/src/main/scala/assumptionAnalysis/AnalysisInfo.scala +++ b/src/main/scala/assumptionAnalysis/AnalysisInfo.scala @@ -2,7 +2,7 @@ package viper.silicon.assumptionAnalysis object AssumptionType extends Enumeration { type AssumptionType = Value - val Explicit, LoopInvariant, PathCondition, Rewrite, Implicit, Internal, Axiom, Postcondition = Value + val Explicit, LoopInvariant, PathCondition, Rewrite, Implicit, Internal, Axiom, Trigger, Postcondition = Value def fromString(s: String): Option[Value] = values.find(_.toString == s) } diff --git a/src/main/scala/interfaces/state/Chunks.scala b/src/main/scala/interfaces/state/Chunks.scala index 435f4ff0b..69c6c393e 100644 --- a/src/main/scala/interfaces/state/Chunks.scala +++ b/src/main/scala/interfaces/state/Chunks.scala @@ -38,7 +38,7 @@ object GeneralChunk { def permMinus(chunk: GeneralChunk, newPerm: Term, newPermExp: Option[ast.Exp], analysisInfo: AnalysisInfo): GeneralChunk = { val newChunk = analysisInfo.decider.registerDerivedChunk[GeneralChunk](Set(chunk), {finalPerm => chunk.permMinus(finalPerm, newPermExp)}, - newPerm, analysisInfo.withAssumptionType(AssumptionType.Internal), isExhale=false, createLabel=false) + newPerm, analysisInfo.withAssumptionType(AssumptionType.Implicit), isExhale=false, createLabel=false) val exhaledChunk = analysisInfo.decider.registerDerivedChunk[GeneralChunk](Set(chunk), {finalPerm => chunk.withPerm(finalPerm, newPermExp)}, newPerm, analysisInfo, isExhale=true, createLabel=false) diff --git a/src/main/scala/rules/Brancher.scala b/src/main/scala/rules/Brancher.scala index c9962f71f..2a057de48 100644 --- a/src/main/scala/rules/Brancher.scala +++ b/src/main/scala/rules/Brancher.scala @@ -54,7 +54,8 @@ object brancher extends BranchingRules { * (2) the branch condition contains a quantified variable */ val skipPathFeasibilityCheck = ( - fromShortCircuitingAnd + Verifier.config.enableAssumptionAnalysis() + || fromShortCircuitingAnd || ( s.quantifiedVariables.nonEmpty && s.quantifiedVariables.map(_._1).exists(condition.freeVariables.contains)) ) diff --git a/src/main/scala/rules/ChunkSupporter.scala b/src/main/scala/rules/ChunkSupporter.scala index f1b82f4d8..dac6df061 100644 --- a/src/main/scala/rules/ChunkSupporter.scala +++ b/src/main/scala/rules/ChunkSupporter.scala @@ -186,7 +186,7 @@ object chunkSupporter extends ChunkSupportRules { val toTake = PermMin(ch.perm, perms) val toTakeExp = permsExp.map(pe => buildMinExp(Seq(ch.permExp.get, pe), ast.Perm)) val newPermExp = permsExp.map(pe => ast.PermSub(ch.permExp.get, toTakeExp.get)(pe.pos, pe.info, pe.errT)) - val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, toTake), newPermExp, v.decider.getAnalysisInfo(AssumptionType.Internal)).asInstanceOf[NonQuantifiedChunk] + val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, toTake), newPermExp, v.decider.getAnalysisInfo(AssumptionType.Implicit)).asInstanceOf[NonQuantifiedChunk] val takenChunk = Some(GeneralChunk.withPerm(ch, toTake, toTakeExp, v.decider.getAnalysisInfo(assumptionType), isExhale=true).asInstanceOf[NonQuantifiedChunk]) // TODO ake: casting! var newHeap = h - ch if (!v.decider.check(newChunk.perm === NoPerm, Verifier.config.checkTimeout(), AssumptionType.Internal)) { @@ -200,7 +200,7 @@ object chunkSupporter extends ChunkSupportRules { val constraintExp = permsExp.map(pe => ast.PermLtCmp(pe, ch.permExp.get)(pe.pos, pe.info, pe.errT)) v.decider.assume(PermLess(perms, ch.perm), Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp)), AssumptionType.Implicit) val newPermExp = permsExp.map(pe => ast.PermSub(ch.permExp.get, pe)(pe.pos, pe.info, pe.errT)) - val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, perms), newPermExp, v.decider.getAnalysisInfo(AssumptionType.Internal)).asInstanceOf[NonQuantifiedChunk] + val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, perms), newPermExp, v.decider.getAnalysisInfo(AssumptionType.Implicit)).asInstanceOf[NonQuantifiedChunk] val takenChunk = GeneralChunk.withPerm(ch, perms, permsExp, v.decider.getAnalysisInfo(assumptionType), isExhale=true).asInstanceOf[NonQuantifiedChunk] // TODO ake: casting! val newHeap = h - ch + newChunk assumeProperties(newChunk, newHeap) diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index 37b495da3..92b0eeef5 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -396,7 +396,7 @@ object consumer extends ConsumptionRules { quantifiedChunkSupporter.summarisingSnapshotMap( s2, field, Seq(`?r`), relevantChunks, v2) val debugExp = Option.when(withExp)(DebugExp.createInstance(s"Field Trigger: ${eRcvrNew.get.toString}.${field.name}")) - v2.decider.assume(FieldTrigger(field.name, smDef1.sm, tRcvr), debugExp, AssumptionType.Internal) + v2.decider.assume(FieldTrigger(field.name, smDef1.sm, tRcvr), debugExp, AssumptionType.Trigger) // v2.decider.assume(PermAtMost(tPerm, FullPerm())) s2.copy(smCache = smCache1) } else { @@ -442,7 +442,7 @@ object consumer extends ConsumptionRules { quantifiedChunkSupporter.summarisingSnapshotMap( s2, predicate, s2.predicateFormalVarMap(predicate), relevantChunks, v2) val debugExp = Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}(${eArgsNew.mkString(", ")}))", isInternal_ = true)) - v2.decider.assume(PredicateTrigger(predicate.name, smDef1.sm, tArgs), debugExp, AssumptionType.Internal) + v2.decider.assume(PredicateTrigger(predicate.name, smDef1.sm, tArgs), debugExp, AssumptionType.Trigger) s2.copy(smCache = smCache1) } else { s2 @@ -513,7 +513,7 @@ object consumer extends ConsumptionRules { val argsString = bodyVarsNew.mkString(", ") val predName = MagicWandIdentifier(wand, s.program).toString val debugExp = Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger($predName($argsString))", isInternal_ = true)) - v1.decider.assume(PredicateTrigger(predName, smDef1.sm, tArgs), debugExp, AssumptionType.Internal) + v1.decider.assume(PredicateTrigger(predName, smDef1.sm, tArgs), debugExp, AssumptionType.Trigger) s1.copy(smCache = smCache1) } else { s1 diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 0fe7d97df..cbd63397c 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -229,7 +229,7 @@ object evaluator extends EvaluationRules { if (s1.heapDependentTriggers.contains(fa.field)){ val trigger = FieldTrigger(fa.field.name, fvfDef.sm, tRcvr) val triggerExp = Option.when(withExp)(DebugExp.createInstance(s"FieldTrigger(${eRcvr.toString()}.${fa.field.name})")) - v1.decider.assume(trigger, triggerExp, AssumptionType.Internal) + v1.decider.assume(trigger, triggerExp, AssumptionType.Trigger) } if (s1.triggerExp) { val fvfLookup = Lookup(fa.field.name, fvfDef.sm, tRcvr) @@ -260,7 +260,7 @@ object evaluator extends EvaluationRules { if (s1.heapDependentTriggers.contains(fa.field)) { val trigger = FieldTrigger(fa.field.name, relevantChunks.head.fvf, tRcvr) val triggerExp = Option.when(withExp)(DebugExp.createInstance(s"FieldTrigger(${eRcvr.toString()}.${fa.field.name})")) - v1.decider.assume(trigger, triggerExp, AssumptionType.Internal) + v1.decider.assume(trigger, triggerExp, AssumptionType.Trigger) } val (permCheck, permCheckExp) = if (s1.triggerExp) { @@ -293,7 +293,7 @@ object evaluator extends EvaluationRules { if (s2.heapDependentTriggers.contains(fa.field)) { val trigger = FieldTrigger(fa.field.name, smDef1.sm, tRcvr) val triggerExp = Option.when(withExp)(DebugExp.createInstance(s"FieldTrigger(${eRcvr.toString()}.${fa.field.name})")) - v1.decider.assume(trigger, triggerExp, AssumptionType.Internal) + v1.decider.assume(trigger, triggerExp, AssumptionType.Trigger) } val (permCheck, permCheckExp) = if (s2.triggerExp) { @@ -563,7 +563,7 @@ object evaluator extends EvaluationRules { val (s2, pmDef) = if (s1.heapDependentTriggers.contains(MagicWandIdentifier(wand, s1.program))) { val (s2, smDef, pmDef) = quantifiedChunkSupporter.heapSummarisingMaps(s1, wand, formalVars, relevantChunks, v1) val debugExp = Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${identifier.toString}($eArgsString))", isInternal_ = true)) - v1.decider.assume(PredicateTrigger(identifier.toString, smDef.sm, args), debugExp, AssumptionType.Internal) + v1.decider.assume(PredicateTrigger(identifier.toString, smDef.sm, args), debugExp, AssumptionType.Trigger) (s2, pmDef) } else { val (pmDef, pmCache) = @@ -580,7 +580,7 @@ object evaluator extends EvaluationRules { val (s2, pmDef) = if (s1.heapDependentTriggers.contains(field)) { val (s2, smDef, pmDef) = quantifiedChunkSupporter.heapSummarisingMaps(s1, field, Seq(`?r`), relevantChunks, v1) val debugExp = Option.when(withExp)(DebugExp.createInstance(s"Field Trigger: ${eArgsNew.head}.${field.name}")) - v1.decider.assume(FieldTrigger(field.name, smDef.sm, args.head), debugExp, AssumptionType.Internal) + v1.decider.assume(FieldTrigger(field.name, smDef.sm, args.head), debugExp, AssumptionType.Trigger) (s2, pmDef) } else { val (pmDef, pmCache) = @@ -606,7 +606,7 @@ object evaluator extends EvaluationRules { if (s2.heapDependentTriggers.contains(predicate)){ val trigger = PredicateTrigger(predicate.name, smDef.sm, args) val argsString = eArgsNew.mkString(", ") - v1.decider.assume(trigger, Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}($argsString))", isInternal_ = true)), AssumptionType.Internal) + v1.decider.assume(trigger, Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}($argsString))", isInternal_ = true)), AssumptionType.Trigger) } (s2, PredicatePermLookup(identifier.toString, pmDef.pm, args)) } @@ -1755,7 +1755,7 @@ object evaluator extends EvaluationRules { } val triggerString = exps.mkString(", ") - v.decider.assume(triggerAxioms, Option.when(withExp)(DebugExp.createInstance(s"Heap Triggers ($triggerString)")), enforceAssumption = false, assumptionType=AssumptionType.Internal) + v.decider.assume(triggerAxioms, Option.when(withExp)(DebugExp.createInstance(s"Heap Triggers ($triggerString)")), enforceAssumption = false, assumptionType=AssumptionType.Trigger) var fr = s.functionRecorder for (smDef <- smDefs){ fr = fr.recordFvfAndDomain(smDef) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 5cd5fbad6..c254fcca5 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -396,7 +396,7 @@ object executor extends ExecutionRules { quantifiedChunkSupporter.summarisingSnapshotMap( s2, field, Seq(`?r`), relevantChunks, v1) val debugExp = Option.when(withExp)(DebugExp.createInstance(s"Field Trigger: (${eRcvrNew.toString()}).${field.name}")) - v2.decider.assume(FieldTrigger(field.name, smDef1.sm, tRcvr), debugExp, AssumptionType.Internal) + v2.decider.assume(FieldTrigger(field.name, smDef1.sm, tRcvr), debugExp, AssumptionType.Trigger) s2.copy(smCache = smCache1) } else { s2 @@ -431,7 +431,7 @@ object executor extends ExecutionRules { field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), sm, s.program, v1, AssumptionType.Internal, isExhale=false) if (s3.heapDependentTriggers.contains(field)) { val debugExp2 = Option.when(withExp)(DebugExp.createInstance(s"FieldTrigger(${eRcvrNew.toString}.${field.name})")) - v1.decider.assume(FieldTrigger(field.name, sm, tRcvr), debugExp2, AssumptionType.Internal) + v1.decider.assume(FieldTrigger(field.name, sm, tRcvr), debugExp2, AssumptionType.Trigger) } v1.decider.analysisSourceInfoStack.removeForcedSource() v1.decider.assumptionAnalyzer.addCustomTransitiveDependency(lhsSourceInfo, v1.decider.analysisSourceInfoStack.getFullSourceInfo) @@ -670,7 +670,7 @@ object executor extends ExecutionRules { val eArgsStr = eArgsNew.mkString(", ") val debugExp = Option.when(withExp)(DebugExp.createInstance(Some(s"PredicateTrigger(${predicate.name}($eArgsStr))"), Some(pa), Some(ast.PredicateAccess(eArgsNew.get, predicateName)(pa.pos, pa.info, pa.errT)), None, isInternal_ = true, InsertionOrderedSet.empty)) - v2.decider.assume(PredicateTrigger(predicate.name, smDef1.sm, tArgs), debugExp, AssumptionType.Internal) + v2.decider.assume(PredicateTrigger(predicate.name, smDef1.sm, tArgs), debugExp, AssumptionType.Trigger) smCache1 } else { s2.smCache @@ -722,7 +722,7 @@ object executor extends ExecutionRules { quantifiedChunkSupporter.summarisingSnapshotMap( s2, wand, formalVars, relevantChunks, v1) v1.decider.assume(PredicateTrigger(ch.id.toString, smDef.sm, ch.singletonArgs.get), - Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${ch.id.toString}(${ch.singletonArgExps.get}))", isInternal_ = true)), AssumptionType.Internal) + Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${ch.id.toString}(${ch.singletonArgExps.get}))", isInternal_ = true)), AssumptionType.Trigger) smCache case _ => s2.smCache } diff --git a/src/main/scala/rules/PredicateSupporter.scala b/src/main/scala/rules/PredicateSupporter.scala index 92707655a..357ac8e28 100644 --- a/src/main/scala/rules/PredicateSupporter.scala +++ b/src/main/scala/rules/PredicateSupporter.scala @@ -80,7 +80,7 @@ object predicateSupporter extends PredicateSupportRules { val predTrigger = App(s1a.predicateData(predicate).triggerFunction, snap.get.convert(terms.sorts.Snap) +: tArgs) val eArgsString = eArgs.mkString(", ") - v1.decider.assume(predTrigger, Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}($eArgsString))")), AssumptionType.Internal) + v1.decider.assume(predTrigger, Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}($eArgsString))")), AssumptionType.Trigger) } val s2 = s1a.setConstrainable(constrainableWildcards, false) if (s2.qpPredicates.contains(predicate)) { @@ -104,7 +104,7 @@ object predicateSupporter extends PredicateSupportRules { s2, predicate, s2.predicateFormalVarMap(predicate), relevantChunks, v1) val eArgsString = eArgs.mkString(", ") v1.decider.assume(PredicateTrigger(predicate.name, smDef1.sm, tArgs), - Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}($eArgsString))")), AssumptionType.Internal) + Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}($eArgsString))")), AssumptionType.Trigger) smCache1 } else { s2.smCache @@ -175,7 +175,7 @@ object predicateSupporter extends PredicateSupportRules { App(s4.predicateData(predicate).triggerFunction, snap.get.convert(terms.sorts.Snap) +: tArgs) val eargs = eArgs.mkString(", ") - v2.decider.assume(predicateTrigger, Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}($eargs))")), AssumptionType.Internal) + v2.decider.assume(predicateTrigger, Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}($eargs))")), AssumptionType.Trigger) } Q(s4.copy(g = s.g, permissionScalingFactor = s.permissionScalingFactor, @@ -194,7 +194,7 @@ object predicateSupporter extends PredicateSupportRules { val predicateTrigger = App(s4.predicateData(predicate).triggerFunction, snap.get +: tArgs) val eargs = eArgs.mkString(", ") - v2.decider.assume(predicateTrigger, Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${pa.predicateName}($eargs))")), AssumptionType.Internal) + v2.decider.assume(predicateTrigger, Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${pa.predicateName}($eargs))")), AssumptionType.Trigger) } val s5 = s4.copy(g = s.g, permissionScalingFactor = s.permissionScalingFactor, diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index 34faa2fb3..6251acc0a 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -390,7 +390,7 @@ object producer extends ProductionRules { && !Verifier.config.disableFunctionUnfoldTrigger()) { val argsString = eArgsNew.mkString(", ") val debugExp = Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}($argsString))", isInternal_ = true)) - v3.decider.assume(App(s3.predicateData(predicate).triggerFunction, snap1 +: tArgs), debugExp, AssumptionType.Internal) + v3.decider.assume(App(s3.predicateData(predicate).triggerFunction, snap1 +: tArgs), debugExp, AssumptionType.Trigger) } Q(s3.copy(h = h3), v3)}) }}))) @@ -421,7 +421,7 @@ object producer extends ProductionRules { s1, wand, formalVars, relevantChunks, v1) val argsStr = bodyVarsNew.mkString(", ") val debugExp = Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${ch.id.toString}($argsStr))", isInternal_ = true)) - v1.decider.assume(PredicateTrigger(ch.id.toString, smDef1.sm, args), debugExp, AssumptionType.Internal) + v1.decider.assume(PredicateTrigger(ch.id.toString, smDef1.sm, args), debugExp, AssumptionType.Trigger) smCache1 } else { s1.smCache diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 2a199a87f..81cb29a2e 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -1109,7 +1109,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val qvarsToInv = inv.qvarsToInversesOf(codomainVars) val condOfInv = tCond.replace(qvarsToInv) v.decider.assume(Forall(codomainVars, Implies(condOfInv, trigger), Trigger(inv.inversesOf(codomainVars))), - Option.when(withExp)(DebugExp.createInstance("Inverse Trigger", isInternal_ = true)), AssumptionType.Internal) + Option.when(withExp)(DebugExp.createInstance("Inverse Trigger", isInternal_ = true)), AssumptionType.Trigger) smCache1 } else { s.smCache @@ -1167,7 +1167,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val (smDef1, smCache1) = quantifiedChunkSupporter.summarisingSnapshotMap( s, resource, formalQVars, relevantChunks, v) - v.decider.assume(resourceTriggerFactory(smDef1.sm), Option.when(withExp)(DebugExp.createInstance("Resource Trigger", isInternal_ = true)), AssumptionType.Internal) + v.decider.assume(resourceTriggerFactory(smDef1.sm), Option.when(withExp)(DebugExp.createInstance("Resource Trigger", isInternal_ = true)), AssumptionType.Trigger) smCache1 } else { s.smCache diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index 9e9e1ce65..81c1a5f07 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -2,12 +2,11 @@ import org.scalatest.funsuite.AnyFunSuite import viper.silicon.SiliconFrontend import viper.silicon.assumptionAnalysis._ +import viper.silver.ast.utility.rewriter.Traverse import viper.silver.ast.utility.{DiskLoader, ViperStrategy} -import viper.silver.ast import viper.silver.ast.{If, Program, Seqn, Stmt} -import viper.silver.ast.utility.rewriter.Traverse import viper.silver.frontend.SilFrontend -import viper.silver.verifier +import viper.silver.{ast, verifier} import java.io.{File, PrintWriter} import java.nio.file.{Files, Path, Paths} @@ -48,16 +47,19 @@ class AssumptionAnalysisTests extends AnyFunSuite { val testDirectories: Seq[String] = Seq( "dependencyAnalysisTests/unitTests", "dependencyAnalysisTests/all", -// "examples/binary-search", + "dependencyAnalysisTests/quick", + "examples/binary-search", // "examples/graph-copy", // "examples/graph-marking", -// "examples/max_array", -// "examples/quickselect", + "examples/max_array", + "examples/quickselect", + "examples/longest-common-prefix", + "examples/tree-delete-min", ) testDirectories foreach createTests // test("dependencyAnalysisTests/all" + "/" + "misc"){ -// executeTest("dependencyAnalysisTests/all/", "list", frontend) +// executeTest("examples/max_array/", "max-array-standard", frontend) // } @@ -142,11 +144,13 @@ class AssumptionAnalysisTests extends AnyFunSuite { val assumptionAnalyzers = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].assumptionAnalyzers val fullGraph = AssumptionAnalyzer.joinGraphs(assumptionAnalyzers.map(_.assumptionGraph).toSet) + val triggerNodes = fullGraph.nodes.filter(node => node.sourceInfo.getTopLevelSource.toString.contains("@trigger()")) var id: Int = 0 assumptionAnalyzers foreach (aa => { - val explicitAssertionNodes = aa.assumptionGraph.getExplicitAssertionNodes - explicitAssertionNodes.foreach{node => - removeNonDependenciesAndVerify(program, Set(node), fullGraph, "src/test/resources/" + filePrefix + fileName + s"_test$id.out") + val explicitAssertionNodes = aa.assumptionGraph.getExplicitAssertionNodes.groupBy(_.sourceInfo.getPositionString) + + explicitAssertionNodes.foreach{case (_, nodes) => + removeNonDependenciesAndVerify(program, nodes ++ triggerNodes, fullGraph, "src/test/resources/" + filePrefix + fileName + s"_test$id.out") id += 1 } }) @@ -264,13 +268,9 @@ class AssumptionAnalysisTests extends AnyFunSuite { ).toSeq } - def removeNonDependenciesAndVerify(program: ast.Program, assumptionAnalyzer: AssumptionAnalyzer, fullGraph: AssumptionAnalysisGraph, exportFileName: String): Unit = { - val explicitAssertionNodes = assumptionAnalyzer.assumptionGraph.getExplicitAssertionNodes - removeNonDependenciesAndVerify(program, explicitAssertionNodes, fullGraph, exportFileName) - } - def removeNonDependenciesAndVerify(program: ast.Program, nodesToAnalyze: Set[AssumptionAnalysisNode], fullGraph: AssumptionAnalysisGraph, exportFileName: String): Unit = { - val explicitAssertionNodeIds = nodesToAnalyze map (_.id) + val sourcePositionsToAnalyze = nodesToAnalyze map (_.sourceInfo.getPositionString) + val explicitAssertionNodeIds = fullGraph.nodes.filter(n => sourcePositionsToAnalyze.contains(n.sourceInfo.getPositionString)).map(_.id).toSet val dependencies = fullGraph.nodes filter (node => ((node.isInstanceOf[GeneralAssumptionNode] && !node.assumptionType.equals(AssumptionType.Internal)) || @@ -342,6 +342,6 @@ class AssumptionAnalysisTests extends AnyFunSuite { } private def isCrucialStmt(stmt: ast.Stmt, crucialNodesWithStmtInfo: Set[StmtAnalysisSourceInfo]): Boolean = { - crucialNodesWithStmtInfo exists(n => n.source.pos.equals(stmt.pos)) + crucialNodesWithStmtInfo exists(n => n.getPositionString.equals(AnalysisSourceInfo.extractPositionString(stmt.pos))) } } From 5e2c08e207008ef512a2adffe5464508f9870307 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 7 Jul 2025 16:53:14 +0200 Subject: [PATCH 148/474] refactor tests --- .../generated/snippets.json | 14 - .../generated/test-templates.vpr | 61 -- .../dependencyAnalysisTests/quick/test.vpr | 10 + src/test/scala/AssumptionAnalysisTests.scala | 527 +++++++++--------- 4 files changed, 268 insertions(+), 344 deletions(-) delete mode 100644 src/test/resources/dependencyAnalysisTests/generated/snippets.json delete mode 100644 src/test/resources/dependencyAnalysisTests/generated/test-templates.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/quick/test.vpr diff --git a/src/test/resources/dependencyAnalysisTests/generated/snippets.json b/src/test/resources/dependencyAnalysisTests/generated/snippets.json deleted file mode 100644 index 30b1459cc..000000000 --- a/src/test/resources/dependencyAnalysisTests/generated/snippets.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "addition": { - "initString": "var x: Int", - "assumptionString": "@dependency()\nassume x > 0", - "bodyString": "@dependency()\nx := x + x + 5", - "assertionString": "assert x > 0" - }, - "ref": { - "initString": "var x: Ref := new(f)", - "assumptionString": "assume x.f > 0", - "bodyString": "x.f := x.f + x.f + 5", - "assertionString": "assert x.f > 0" - } -} diff --git a/src/test/resources/dependencyAnalysisTests/generated/test-templates.vpr b/src/test/resources/dependencyAnalysisTests/generated/test-templates.vpr deleted file mode 100644 index 04c8265be..000000000 --- a/src/test/resources/dependencyAnalysisTests/generated/test-templates.vpr +++ /dev/null @@ -1,61 +0,0 @@ -field f: Int -field f2: Int -field bf: Bool -field r: Ref - - -method dummyTest() -{ - ##INIT## - - ##ASSUMPTIONS## - - ##BODY## - - @testAssertion() - ##ASSERTION## -} - - -method basic(a: Int) -{ - var b: Int - @irrelevant() - assume a > 0 - - ##INIT## - - ##ASSUMPTIONS## - - @irrelevant() - b := a * 2 - - ##BODY## - - @testAssertion() - ##ASSERTION## -} - -method basicBranch(a: Int) -{ - var b: Int - @irrelevant() - assume a > 0 - - ##INIT## - - if(a >= 0){ - ##ASSUMPTIONS## - @irrelevant() - b := a * 2 - }else{ - // infeasible - @irrelevant() - b := a + 1 - } - - ##BODY## - - @testAssertion() - ##ASSERTION## -} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/quick/test.vpr b/src/test/resources/dependencyAnalysisTests/quick/test.vpr new file mode 100644 index 000000000..a94169f83 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/quick/test.vpr @@ -0,0 +1,10 @@ + +method foo(a: Int, b: Int) + requires a > 0 + ensures a >= 0 +{ + @trigger() + assume b >= 0 + @trigger() + assert b >= -15 +} \ No newline at end of file diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index 81c1a5f07..3ca418b69 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -2,122 +2,80 @@ import org.scalatest.funsuite.AnyFunSuite import viper.silicon.SiliconFrontend import viper.silicon.assumptionAnalysis._ +import viper.silver.ast.utility.ViperStrategy import viper.silver.ast.utility.rewriter.Traverse -import viper.silver.ast.utility.{DiskLoader, ViperStrategy} -import viper.silver.ast.{If, Program, Seqn, Stmt} +import viper.silver.ast._ import viper.silver.frontend.SilFrontend +import viper.silver.verifier.VerificationResult import viper.silver.{ast, verifier} -import java.io.{File, PrintWriter} +import java.io.PrintWriter import java.nio.file.{Files, Path, Paths} import scala.jdk.CollectionConverters.IterableHasAsScala -import scala.util.{Failure, Success} - - -/** - * Annotations - * @dependency() -> for assumptions that should be reported as a dependency - * @irrelevant() -> for assumptions that should NOT be reported as a dependency - * @testAssertion() -> the queried assertion - * - * assumptions/assertions that are not annotated are ignored - * - * !!! THERE CAN ONLY BE 1 TEST ASSERTION PER METHOD, - * but multiple dependency/irrelevant annotations are allowed - * - */ + + class AssumptionAnalysisTests extends AnyFunSuite { val CHECK_PRECISION = false val ignores: Seq[String] = Seq("infeasible") + val testDirectories: Seq[String] = Seq( + "dependencyAnalysisTests", +// "dependencyAnalysisTests/unitTests", +// "dependencyAnalysisTests/all", +// "dependencyAnalysisTests/quick", + "examples/binary-search", + // "examples/graph-copy", + // "examples/graph-marking", +// "examples/max_array", +// "examples/quickselect", +// "examples/longest-common-prefix", +// "examples/tree-delete-min", + ) val irrelevantKeyword = "irrelevant" val dependencyKeyword = "dependency" val testAssertionKeyword = "testAssertion" - val GENERATE_TESTS = false - if(GENERATE_TESTS) - generateTests("dependencyAnalysisTests/", "test-templates") - - val commandLineArguments: Seq[String] = - Seq("--timeout", "100" /* seconds */, "--enableAssumptionAnalysis", "--z3Args", "proof=true unsat-core=true") - + Seq("--timeout", "100" /* seconds */ , "--enableAssumptionAnalysis", "--z3Args", "proof=true unsat-core=true") - val testDirectories: Seq[String] = Seq( - "dependencyAnalysisTests/unitTests", - "dependencyAnalysisTests/all", - "dependencyAnalysisTests/quick", - "examples/binary-search", -// "examples/graph-copy", -// "examples/graph-marking", - "examples/max_array", - "examples/quickselect", - "examples/longest-common-prefix", - "examples/tree-delete-min", - ) testDirectories foreach createTests -// test("dependencyAnalysisTests/all" + "/" + "misc"){ -// executeTest("examples/max_array/", "max-array-standard", frontend) -// } + // test("dependencyAnalysisTests/all" + "/" + "misc"){ + // executeTest("examples/max_array/", "max-array-standard", frontend) + // } +// def createTests(dirName: String): Unit = { +// val path = getClass.getClassLoader.getResource(dirName) +// createTests(path) +// } def createTests(dirName: String): Unit = { - val path = getClass.getClassLoader.getResource(dirName) - val directoryStream = Files.newDirectoryStream(Paths.get(path.toURI)).asScala + val path = Paths.get(getClass.getClassLoader.getResource(dirName).toURI) + createTests(path, dirName) + } + + def createTests(path: Path, dirName: String): Unit = { + val directoryStream = Files.newDirectoryStream(path).asScala val dirContent = directoryStream.toList for (filePath: Path <- dirContent.sorted - if Files.isReadable(filePath) - if !Files.isDirectory(filePath)){ - val rawFileName = filePath.getFileName.toString - if(rawFileName.endsWith(".vpr")){ - val fileName = rawFileName.replace(".vpr", "") - if(!ignores.contains(fileName)) - test(dirName + "/" + fileName){ - executeTest(dirName + "/", fileName, frontend) - } + if Files.isReadable(filePath)) { + if(Files.isDirectory(filePath)){ + createTests(filePath, dirName + "/" + filePath.getFileName.toString) + }else{ + val rawFileName = filePath.getFileName.toString + if (rawFileName.endsWith(".vpr")) { + val fileName = rawFileName.replace(".vpr", "") + if (!ignores.contains(fileName)) + test(dirName + "/" + fileName) { + executeTest(dirName + "/", fileName, frontend) + } + } } } } - def generateTests(filePrefix: String, - fileName: String): Unit = { - val path = getClass.getClassLoader.getResource(filePrefix + fileName + ".vpr") - val content: String = DiskLoader.loadContent(Paths.get(path.toURI)) match { - case Failure(exception) => throw exception - case Success(value) => value - } - - val jsonPath = getClass.getClassLoader.getResource(filePrefix + "snippets" + ".json") - val jsonContent: String = DiskLoader.loadContent(Paths.get(jsonPath.toURI)) match { - case Failure(exception) => throw exception - case Success(value) => value - } - val json = upickle.default.read[Map[String, Map[String, String]]](jsonContent) - - json foreach{case (testname, replacements) => generateSingleTestFile(filePrefix, fileName + "_" + testname, content, replacements)} - } - - def generateSingleTestFile(filePrefix: String, fileName: String, template: String, replacements: Map[String, String]): Unit = { - var newString = template - val initPlaceholder = "##INIT##" - val assumptionPlaceholder = "##ASSUMPTIONS##" - val bodyPlaceholder = "##BODY##" - val assertionPlaceholder = "##ASSERTION##" - - newString = newString.replaceAll(initPlaceholder, replacements("initString")) - newString = newString.replaceAll(assumptionPlaceholder, replacements("assumptionString")) - newString = newString.replaceAll(bodyPlaceholder, replacements("bodyString")) - newString = newString.replaceAll(assertionPlaceholder, replacements("assertionString")) - - // write generated file - val path2 = Paths.get("src/test/resources/" + filePrefix + fileName + "_generated" + ".vpr").toString - val pw = new java.io.PrintWriter(new File(path2)) - try pw.write(newString) finally pw.close() - } - def frontend: SiliconFrontend = { val reporter = DependencyAnalysisReporter() val fe = new SiliconFrontend(reporter) @@ -131,217 +89,248 @@ class AssumptionAnalysisTests extends AnyFunSuite { def executeTest(filePrefix: String, fileName: String, - frontend: SilFrontend) - : Unit = { + frontend: SilFrontend): Unit = { - val program: ast.Program = tests.loadProgram(filePrefix, fileName, frontend) + val program: Program = tests.loadProgram(filePrefix, fileName, frontend) val result = frontend.verifier.verify(program) - if(result.isInstanceOf[verifier.Failure]) { + if (result.isInstanceOf[verifier.Failure]) { println("Program does not verify. Skip test.") return } val assumptionAnalyzers = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].assumptionAnalyzers - val fullGraph = AssumptionAnalyzer.joinGraphs(assumptionAnalyzers.map(_.assumptionGraph).toSet) - val triggerNodes = fullGraph.nodes.filter(node => node.sourceInfo.getTopLevelSource.toString.contains("@trigger()")) - var id: Int = 0 - assumptionAnalyzers foreach (aa => { - val explicitAssertionNodes = aa.assumptionGraph.getExplicitAssertionNodes.groupBy(_.sourceInfo.getPositionString) + PruningTest(filePrefix + "/" + fileName, program, assumptionAnalyzers).execute() - explicitAssertionNodes.foreach{case (_, nodes) => - removeNonDependenciesAndVerify(program, nodes ++ triggerNodes, fullGraph, "src/test/resources/" + filePrefix + fileName + s"_test$id.out") - id += 1 - } - }) - - val assumptionGraphs = assumptionAnalyzers map (_.assumptionGraph) - val stmtsWithAssumptionAnnotation: Set[ast.Infoed] = extractAnnotatedStmts(program, { annotationInfo => annotationInfo.values.contains(irrelevantKeyword) || annotationInfo.values.contains(dependencyKeyword)}) - val allAssumptionNodes = assumptionGraphs.flatMap(_.nodes.filter(_.isInstanceOf[GeneralAssumptionNode])) - - var errorMsgs = stmtsWithAssumptionAnnotation.map(checkAssumptionNodeExists(allAssumptionNodes, _)).filter(_.isDefined).map(_.get).toSeq - errorMsgs ++= assumptionAnalyzers flatMap checkTestAssertionNodeExists - errorMsgs ++= assumptionGraphs flatMap checkDependencies - val warnMsgs = assumptionGraphs flatMap checkNonDependencies - if(CHECK_PRECISION) - errorMsgs ++= warnMsgs - else if(warnMsgs.nonEmpty) println(warnMsgs.mkString("\n")) // TODO ake: warning - - val check = errorMsgs.isEmpty - assert(check, "\n" + errorMsgs.mkString("\n")) + AnnotatedTest(program, assumptionAnalyzers).execute() } - private def extractAnnotatedStmts(program: ast.Program, annotationFiler: (ast.AnnotationInfo => Boolean)): Set[ast.Infoed] = { - var nodesWithAnnotation: Set[ast.Infoed] = Set.empty - val newP: ast.Program = ViperStrategy.Slim({ - case s: ast.Seqn => s - case n: ast.Infoed => - val annotationInfo = n.info.getUniqueInfo[ast.AnnotationInfo] - .filter(annotationFiler) - if (annotationInfo.isDefined) - nodesWithAnnotation += n - n - }).execute(program) - nodesWithAnnotation - } + /** + * (Almost) Fully automated test, which takes a program and its assumption analysis results and, + * for each explicit assertion, builds a new program that only contains said assertion and + * all its dependencies. The test passes if all new programs verify successfully. + * + * Statements that are only required as a trigger need to be manually annotated with @trigger() by the user. + */ + case class PruningTest(fileName: String, program: Program, assumptionAnalyzers: List[AssumptionAnalyzer]) { + private val fullGraph: AssumptionAnalysisGraph = AssumptionAnalyzer.joinGraphs(assumptionAnalyzers.map(_.assumptionGraph).toSet) + + def execute(): Unit = { + val triggerNodes = fullGraph.nodes.filter(node => node.sourceInfo.getTopLevelSource.toString.contains("@trigger()")) + var id: Int = 0 + fullGraph.getExplicitAssertionNodes.groupBy(_.sourceInfo.getPositionString).foreach { case (_, nodes) => + pruneAndVerify(nodes ++ triggerNodes, "src/test/resources/" + fileName + s"_test$id.out") + id += 1 + } + } - private def checkAssumptionNodeExists(analysisNodes: List[AssumptionAnalysisNode], node: ast.Infoed): Option[String] = { - val pos = extractSourceLine(node.asInstanceOf[ast.Positioned].pos) - val annotationInfo = node.info.getUniqueInfo[ast.AnnotationInfo] - .map(ai => ai.values.getOrElse(irrelevantKeyword, ai.values.getOrElse(dependencyKeyword, List.empty))).getOrElse(List.empty) - val assumptionType = annotationInfo.map(AssumptionType.fromString).filter(_.isDefined).map(_.get) - val nodeExists = analysisNodes exists (analysisNode => { - analysisNode.isInstanceOf[GeneralAssumptionNode] && - !analysisNode.asInstanceOf[GeneralAssumptionNode].assumptionType.equals(AssumptionType.Internal) && - extractSourceLine(analysisNode.sourceInfo.getPosition) == pos && - assumptionType.forall(_.equals(analysisNode.assumptionType)) - }) - Option.when(!nodeExists)(s"Missing analysis node:\n${node.toString}\n$pos") - } + private def pruneAndVerify(nodesToAnalyze: Set[AssumptionAnalysisNode], exportFileName: String): Unit = { + val sourcePositionsToAnalyze = nodesToAnalyze map (_.sourceInfo.getPositionString) + val explicitAssertionNodeIds = fullGraph.nodes.filter(n => sourcePositionsToAnalyze.contains(n.sourceInfo.getPositionString)).map(_.id).toSet - def extractSourceLine(pos: ast.Position): Int = { - pos match { - case column: ast.HasLineColumn => column.line - case _ => -1 + val dependencies = fullGraph.nodes filter (node => + ((node.isInstanceOf[GeneralAssumptionNode] && !node.assumptionType.equals(AssumptionType.Internal)) || + (node.isInstanceOf[GeneralAssertionNode] && node.assumptionType.equals(AssumptionType.Postcondition))) && + fullGraph.existsAnyDependency(Set(node.id), explicitAssertionNodeIds)) + + val crucialNodes = nodesToAnalyze ++ dependencies + val (newProgram, pruningFactor) = getPrunedProgram(crucialNodes) + val result = frontend.verifier.verify(newProgram) + exportPrunedProgram(exportFileName, newProgram, pruningFactor, result) + assert(!result.isInstanceOf[verifier.Failure], s"Failed to verify new program ${newProgram.toString()}") } - } - private def checkTestAssertionNodeExists(assumptionAnalyzer: AssumptionAnalyzer): Seq[String] = { - val assumptionNodes = extractTestAssumptionNodesFromGraph(assumptionAnalyzer.assumptionGraph) ++ extractTestIrrelevantAssumptionNodesFromGraph(assumptionAnalyzer.assumptionGraph) - val assertionNodes = extractTestAssertionNodesFromGraph(assumptionAnalyzer.assumptionGraph) - if(assumptionNodes.nonEmpty && assertionNodes.isEmpty) - Seq(s"Missing testAssertion for member: ${assumptionAnalyzer.getMember.map(_.name).getOrElse("unknown")}") - else - Seq.empty - } + private def exportPrunedProgram(exportFileName: String, newProgram: Program, pruningFactor: Double, result: VerificationResult): Unit = { + val writer = new PrintWriter(exportFileName) + writer.println("// test result: " + !result.isInstanceOf[verifier.Failure]) + writer.println("// cleanse factor: " + pruningFactor) + writer.println(newProgram.toString()) + writer.close() + } + + private def getPrunedProgram(crucialNodes: Set[AssumptionAnalysisNode]): (ast.Program, Double) = { + val crucialNodesWithStmtInfo = crucialNodes filter (_.sourceInfo.getTopLevelSource.isInstanceOf[StmtAnalysisSourceInfo]) map (_.sourceInfo.getTopLevelSource.asInstanceOf[StmtAnalysisSourceInfo]) + val crucialNodesWithExpInfo = crucialNodes filter (_.sourceInfo.getTopLevelSource.isInstanceOf[ExpAnalysisSourceInfo]) map (_.sourceInfo.getTopLevelSource.asInstanceOf[ExpAnalysisSourceInfo]) + var total = 0 + var removed = 0 + + val newProgram: Program = ViperStrategy.Slim({ + case s: Seqn => s + case meth@ast.Method(name, inVars, outVars, pres, posts, body) => + val newPres = pres filter (isCrucialExp(_, crucialNodesWithExpInfo)) + val newPosts = posts filter (isCrucialExp(_, crucialNodesWithExpInfo)) + total += pres.size + posts.size + removed += (pres.size - newPres.size) + (posts.size - newPosts.size) + ast.Method(name, inVars, outVars, newPres, newPosts, body)(meth.pos, meth.info, meth.errT) + case ifStmt@ast.If(cond, thenBody, elseBody) if !isCrucialExp(cond, crucialNodesWithExpInfo) => + total += 1 + removed += 1 + ast.Seqn(Seq( + ast.LocalVarDeclStmt(ast.LocalVarDecl("nonDetermBool", ast.Bool)())(), + ast.If(ast.LocalVar("nonDetermBool", ast.Bool)(), thenBody, elseBody)()) + , Seq())(ifStmt.pos, ifStmt.info, ifStmt.errT) + case ifStmt: If => + total += 1 + ifStmt + case whileStmt@ast.While(cond, invs, body) if !isCrucialExp(cond, crucialNodesWithExpInfo) => + val newInvs = invs filter (isCrucialExp(_, crucialNodesWithExpInfo)) + total += 1 + invs.size + removed += 1 + (invs.size - newInvs.size) + ast.Seqn(Seq( + ast.LocalVarDeclStmt(ast.LocalVarDecl("nonDetermBool", ast.Bool)())(), + ast.While(ast.LocalVar("nonDetermBool", ast.Bool)(), newInvs, body)(whileStmt.pos, whileStmt.info, whileStmt.errT)) + , Seq())(whileStmt.pos, whileStmt.info, whileStmt.errT) + case whileStmt@ast.While(cond, invs, body) => + val newInvs = invs filter (isCrucialExp(_, crucialNodesWithExpInfo)) + total += 1 + invs.size + removed += (invs.size - newInvs.size) + ast.While(cond, newInvs, body)(whileStmt.pos, whileStmt.info, whileStmt.errT) + case s: Stmt if !isCrucialStmt(s, crucialNodesWithStmtInfo) => + total += 1 + removed += 1 + ast.Inhale(ast.TrueLit()())() + case s: Stmt => + total += 1 + s + }, Traverse.BottomUp).execute(program) + (newProgram, removed.toDouble / total.toDouble) + } - def checkDependencies(assumptionGraph: AssumptionAnalysisGraph): Seq[String] = { - val assumptionNodes = extractTestAssumptionNodesFromGraph(assumptionGraph) - val assumptionsPerSource = assumptionNodes groupBy(n => extractSourceLine(n.sourceInfo.getPosition)) - val assertionNodes = extractTestAssertionNodesFromGraph(assumptionGraph) + private def isCrucialExp(exp: ast.Exp, crucialNodesWithExpInfo: Set[ExpAnalysisSourceInfo]): Boolean = { + crucialNodesWithExpInfo exists (n => n.getPositionString.equals(AnalysisSourceInfo.extractPositionString(exp.pos))) // TODO ake: currently we compare only lines not columns! + } - assumptionsPerSource.map({ case (_, assumptions) => - val hasDeps = checkDependenciesForSingleSource(assumptionGraph, assumptions, assertionNodes) - Option.when(!hasDeps)(s"Missing dependency: ${assumptions.head.sourceInfo.toString}") - }).filter(_.isDefined).map(_.get).toSeq + private def isCrucialStmt(stmt: ast.Stmt, crucialNodesWithStmtInfo: Set[StmtAnalysisSourceInfo]): Boolean = { + crucialNodesWithStmtInfo exists (n => n.getPositionString.equals(AnalysisSourceInfo.extractPositionString(stmt.pos))) + } } - def checkNonDependencies(assumptionGraph: AssumptionAnalysisGraph): Seq[String] = { - val assumptionNodes = extractTestIrrelevantAssumptionNodesFromGraph(assumptionGraph) - val assumptionsPerSource = assumptionNodes groupBy(n => extractSourceLine(n.sourceInfo.getPosition)) - val assertionNodes = extractTestAssertionNodesFromGraph(assumptionGraph) + /** + * Takes a Viper program and its assumption analysis results and checks whether the analysis found the + * assumptions, assertions and dependencies between them, as annotated by the user. + * + * Annotations + * @dependency() -> for assumptions that should be reported as a dependency + * @irrelevant() -> for assumptions that should NOT be reported as a dependency + * @testAssertion() -> the queried assertion + * + * !!! THERE CAN ONLY BE 1 TEST ASSERTION PER METHOD, + * but multiple dependency/irrelevant annotations are allowed + * + */ + case class AnnotatedTest(program: Program, assumptionAnalyzers: List[AssumptionAnalyzer]) { + def execute(): Unit = { + val assumptionGraphs = assumptionAnalyzers map (_.assumptionGraph) + val stmtsWithAssumptionAnnotation: Set[Infoed] = extractAnnotatedStmts({ annotationInfo => annotationInfo.values.contains(irrelevantKeyword) || annotationInfo.values.contains(dependencyKeyword) }) + val allAssumptionNodes = assumptionGraphs.flatMap(_.nodes.filter(_.isInstanceOf[GeneralAssumptionNode])) + + var errorMsgs = stmtsWithAssumptionAnnotation.map(checkAssumptionNodeExists(allAssumptionNodes, _)).filter(_.isDefined).map(_.get).toSeq + errorMsgs ++= assumptionAnalyzers flatMap checkTestAssertionNodeExists + errorMsgs ++= assumptionGraphs flatMap checkDependencies + val warnMsgs = assumptionGraphs flatMap checkNonDependencies + if (CHECK_PRECISION) + errorMsgs ++= warnMsgs + else if (warnMsgs.nonEmpty) println(warnMsgs.mkString("\n")) // TODO ake: warning + + val check = errorMsgs.isEmpty + assert(check, "\n" + errorMsgs.mkString("\n")) + } - assumptionsPerSource.map({case (_, assumptions) => - val hasDependency = checkDependenciesForSingleSource(assumptionGraph, assumptions, assertionNodes) - Option.when(hasDependency)(s"Unexpected dependency: ${assumptions.head.sourceInfo.toString}") - }).filter(_.isDefined).map(_.get).toSeq - } + private def extractAnnotatedStmts(annotationFilter: (ast.AnnotationInfo => Boolean)): Set[ast.Infoed] = { + var nodesWithAnnotation: Set[ast.Infoed] = Set.empty + val newP: ast.Program = ViperStrategy.Slim({ + case s: ast.Seqn => s + case n: ast.Infoed => + val annotationInfo = n.info.getUniqueInfo[ast.AnnotationInfo] + .filter(annotationFilter) + if (annotationInfo.isDefined) + nodesWithAnnotation += n + n + }).execute(program) + nodesWithAnnotation + } - def checkDependenciesForSingleSource(assumptionGraph: AssumptionAnalysisGraph, assumptions: Seq[AssumptionAnalysisNode], assertions: Seq[AssumptionAnalysisNode]): Boolean = { - assumptions exists (assumption => { - assertions exists (assertion => assumptionGraph.existsAnyDependency(Set(assumption.id), Set(assertion.id))) - }) - } + private def checkAssumptionNodeExists(analysisNodes: List[AssumptionAnalysisNode], node: ast.Infoed): Option[String] = { + val pos = extractSourceLine(node.asInstanceOf[ast.Positioned].pos) + val annotationInfo = node.info.getUniqueInfo[ast.AnnotationInfo] + .map(ai => ai.values.getOrElse(irrelevantKeyword, ai.values.getOrElse(dependencyKeyword, List.empty))).getOrElse(List.empty) + val assumptionType = annotationInfo.map(AssumptionType.fromString).filter(_.isDefined).map(_.get) + val nodeExists = analysisNodes exists (analysisNode => { + analysisNode.isInstanceOf[GeneralAssumptionNode] && + !analysisNode.asInstanceOf[GeneralAssumptionNode].assumptionType.equals(AssumptionType.Internal) && + extractSourceLine(analysisNode.sourceInfo.getPosition) == pos && + assumptionType.forall(_.equals(analysisNode.assumptionType)) + }) + Option.when(!nodeExists)(s"Missing analysis node:\n${node.toString}\n$pos") + } - def extractTestAssertionNodesFromGraph(graph: AssumptionAnalysisGraph): Seq[AssumptionAnalysisNode] = { - graph.nodes.filter(node => - (node.getNodeType.equals("Assertion") || node.getNodeType.equals("Exhale") || node.getNodeType.equals("Check")) && - node.sourceInfo.toString.contains("@" + testAssertionKeyword + "()") - ).toSeq - } + private def extractSourceLine(pos: ast.Position): Int = { + pos match { + case column: ast.HasLineColumn => column.line + case _ => -1 + } + } - def extractTestAssumptionNodesFromGraph(graph: AssumptionAnalysisGraph): Seq[AssumptionAnalysisNode] = { - graph.nodes.filter(node => { - (node.getNodeType.equals("Assumption") || node.getNodeType.equals("Inhale")) && - !node.assumptionType.equals(AssumptionType.Internal) && - node.sourceInfo.toString.contains("@" + dependencyKeyword + "()") + private def checkTestAssertionNodeExists(assumptionAnalyzer: AssumptionAnalyzer): Seq[String] = { + val assumptionNodes = extractTestAssumptionNodesFromGraph(assumptionAnalyzer.assumptionGraph) ++ extractTestIrrelevantAssumptionNodesFromGraph(assumptionAnalyzer.assumptionGraph) + val assertionNodes = extractTestAssertionNodesFromGraph(assumptionAnalyzer.assumptionGraph) + if (assumptionNodes.nonEmpty && assertionNodes.isEmpty) + Seq(s"Missing testAssertion for member: ${assumptionAnalyzer.getMember.map(_.name).getOrElse("unknown")}") + else + Seq.empty } - ).toSeq - } - def extractTestIrrelevantAssumptionNodesFromGraph(graph: AssumptionAnalysisGraph): Seq[AssumptionAnalysisNode] = { - graph.nodes.filter(node => { - (node.getNodeType.equals("Assumption") || node.getNodeType.equals("Inhale")) && - !node.assumptionType.equals(AssumptionType.Internal) && - node.sourceInfo.toString.contains("@" + irrelevantKeyword + "()") + private def checkDependencies(assumptionGraph: AssumptionAnalysisGraph): Seq[String] = { + val assumptionNodes = extractTestAssumptionNodesFromGraph(assumptionGraph) + val assumptionsPerSource = assumptionNodes groupBy (n => extractSourceLine(n.sourceInfo.getPosition)) + val assertionNodes = extractTestAssertionNodesFromGraph(assumptionGraph) + + assumptionsPerSource.map({ case (_, assumptions) => + val hasDeps = checkDependenciesForSingleSource(assumptionGraph, assumptions, assertionNodes) + Option.when(!hasDeps)(s"Missing dependency: ${assumptions.head.sourceInfo.toString}") + }).filter(_.isDefined).map(_.get).toSeq } - ).toSeq - } - def removeNonDependenciesAndVerify(program: ast.Program, nodesToAnalyze: Set[AssumptionAnalysisNode], fullGraph: AssumptionAnalysisGraph, exportFileName: String): Unit = { - val sourcePositionsToAnalyze = nodesToAnalyze map (_.sourceInfo.getPositionString) - val explicitAssertionNodeIds = fullGraph.nodes.filter(n => sourcePositionsToAnalyze.contains(n.sourceInfo.getPositionString)).map(_.id).toSet - - val dependencies = fullGraph.nodes filter (node => - ((node.isInstanceOf[GeneralAssumptionNode] && !node.assumptionType.equals(AssumptionType.Internal)) || - (node.isInstanceOf[GeneralAssertionNode] && node.assumptionType.equals(AssumptionType.Postcondition))) && - fullGraph.existsAnyDependency(Set(node.id), explicitAssertionNodeIds)) - - val crucialNodes = nodesToAnalyze ++ dependencies - val (newProgram, cleanseFactor) = cleanseProgram(program, crucialNodes) - val result = frontend.verifier.verify(newProgram) - val writer = new PrintWriter(exportFileName) - writer.println("// test result: " + !result.isInstanceOf[verifier.Failure]) - writer.println("// cleanse factor: " + cleanseFactor) - writer.println(newProgram.toString()) - writer.close() - assert(!result.isInstanceOf[verifier.Failure], s"Failed to verify new program ${newProgram.toString()}") - } + private def checkNonDependencies(assumptionGraph: AssumptionAnalysisGraph): Seq[String] = { + val assumptionNodes = extractTestIrrelevantAssumptionNodesFromGraph(assumptionGraph) + val assumptionsPerSource = assumptionNodes groupBy (n => extractSourceLine(n.sourceInfo.getPosition)) + val assertionNodes = extractTestAssertionNodesFromGraph(assumptionGraph) - private def cleanseProgram(program: Program, crucialNodes: Set[AssumptionAnalysisNode]): (ast.Program, Double) = { - val crucialNodesWithStmtInfo = crucialNodes filter (_.sourceInfo.getTopLevelSource.isInstanceOf[StmtAnalysisSourceInfo]) map (_.sourceInfo.getTopLevelSource.asInstanceOf[StmtAnalysisSourceInfo]) - val crucialNodesWithExpInfo = crucialNodes filter (_.sourceInfo.getTopLevelSource.isInstanceOf[ExpAnalysisSourceInfo]) map (_.sourceInfo.getTopLevelSource.asInstanceOf[ExpAnalysisSourceInfo]) - var total = 0 - var removed = 0 - - - val newProgram: Program = ViperStrategy.Slim({ - case s: Seqn => s - case meth@ast.Method(name, inVars, outVars, pres, posts, body) => - val newPres = pres filter (isCrucialExp(_, crucialNodesWithExpInfo)) - val newPosts = posts filter (isCrucialExp(_, crucialNodesWithExpInfo)) - total += pres.size + posts.size - removed += (pres.size-newPres.size) + (posts.size-newPosts.size) - ast.Method(name, inVars, outVars, newPres, newPosts, body)(meth.pos, meth.info, meth.errT) - case ifStmt@ast.If(cond, thenBody, elseBody) if !isCrucialExp(cond, crucialNodesWithExpInfo) => - total += 1 - removed += 1 - ast.Seqn(Seq( - ast.LocalVarDeclStmt(ast.LocalVarDecl("nonDetermBool", ast.Bool)())(), - ast.If(ast.LocalVar("nonDetermBool", ast.Bool)(), thenBody, elseBody)()) - , Seq())(ifStmt.pos, ifStmt.info, ifStmt.errT) - case ifStmt: If => - total += 1 - ifStmt - case whileStmt@ast.While(cond, invs, body) if !isCrucialExp(cond, crucialNodesWithExpInfo) => - val newInvs = invs filter (isCrucialExp(_, crucialNodesWithExpInfo)) - total += 1 + invs.size - removed += 1 + (invs.size-newInvs.size) - ast.Seqn(Seq( - ast.LocalVarDeclStmt(ast.LocalVarDecl("nonDetermBool", ast.Bool)())(), - ast.While(ast.LocalVar("nonDetermBool", ast.Bool)(), newInvs, body)(whileStmt.pos, whileStmt.info, whileStmt.errT)) - , Seq())(whileStmt.pos, whileStmt.info, whileStmt.errT) - case whileStmt@ast.While(cond, invs, body) => - val newInvs = invs filter (isCrucialExp(_, crucialNodesWithExpInfo)) - total += 1 + invs.size - removed += (invs.size-newInvs.size) - ast.While(cond, newInvs, body)(whileStmt.pos, whileStmt.info, whileStmt.errT) - case s: Stmt if !isCrucialStmt(s, crucialNodesWithStmtInfo) => - total += 1 - removed += 1 - ast.Inhale(ast.TrueLit()())() - case s: Stmt => - total += 1 - s - }, Traverse.BottomUp).execute(program) - (newProgram, removed.toDouble/total.toDouble) - } + assumptionsPerSource.map({ case (_, assumptions) => + val hasDependency = checkDependenciesForSingleSource(assumptionGraph, assumptions, assertionNodes) + Option.when(hasDependency)(s"Unexpected dependency: ${assumptions.head.sourceInfo.toString}") + }).filter(_.isDefined).map(_.get).toSeq + } - private def isCrucialExp(exp: ast.Exp, crucialNodesWithExpInfo: Set[ExpAnalysisSourceInfo]): Boolean = { - crucialNodesWithExpInfo exists(n => n.getPositionString.equals(AnalysisSourceInfo.extractPositionString(exp.pos))) // TODO ake: currently we compare only lines not columns! - } + private def checkDependenciesForSingleSource(assumptionGraph: AssumptionAnalysisGraph, assumptions: Seq[AssumptionAnalysisNode], assertions: Seq[AssumptionAnalysisNode]): Boolean = { + assumptions exists (assumption => { + assertions exists (assertion => assumptionGraph.existsAnyDependency(Set(assumption.id), Set(assertion.id))) + }) + } - private def isCrucialStmt(stmt: ast.Stmt, crucialNodesWithStmtInfo: Set[StmtAnalysisSourceInfo]): Boolean = { - crucialNodesWithStmtInfo exists(n => n.getPositionString.equals(AnalysisSourceInfo.extractPositionString(stmt.pos))) + private def extractTestAssertionNodesFromGraph(graph: AssumptionAnalysisGraph): Seq[AssumptionAnalysisNode] = { + graph.nodes.filter(node => + (node.getNodeType.equals("Assertion") || node.getNodeType.equals("Exhale") || node.getNodeType.equals("Check")) && + node.sourceInfo.toString.contains("@" + testAssertionKeyword + "()") + ).toSeq + } + + private def extractTestAssumptionNodesFromGraph(graph: AssumptionAnalysisGraph): Seq[AssumptionAnalysisNode] = { + graph.nodes.filter(node => { + (node.getNodeType.equals("Assumption") || node.getNodeType.equals("Inhale")) && + !node.assumptionType.equals(AssumptionType.Internal) && + node.sourceInfo.toString.contains("@" + dependencyKeyword + "()") + } + ).toSeq + } + + private def extractTestIrrelevantAssumptionNodesFromGraph(graph: AssumptionAnalysisGraph): Seq[AssumptionAnalysisNode] = { + graph.nodes.filter(node => { + (node.getNodeType.equals("Assumption") || node.getNodeType.equals("Inhale")) && + !node.assumptionType.equals(AssumptionType.Internal) && + node.sourceInfo.toString.contains("@" + irrelevantKeyword + "()") + } + ).toSeq + } } } From e2a40ecfa32d854859df8e17a4a6b9b247667050 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 8 Jul 2025 11:12:26 +0200 Subject: [PATCH 149/474] fix infeasibility issues --- .../AssumptionAnalysisGraph.scala | 13 +++++++-- src/main/scala/decider/Decider.scala | 17 ++++++++--- src/main/scala/decider/PathConditions.scala | 12 ++++++++ src/main/scala/rules/Brancher.scala | 28 +++++++------------ src/main/scala/rules/ChunkSupporter.scala | 13 ++++----- src/main/scala/rules/Executor.scala | 2 +- .../rules/MoreCompleteExhaleSupporter.scala | 2 +- .../dependencyAnalysisTests/all/list.vpr | 2 +- .../dependencyAnalysisTests/new/meeting.vpr | 13 +++++++++ src/test/scala/AssumptionAnalysisTests.scala | 22 ++++----------- 10 files changed, 74 insertions(+), 50 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index 7631e55bd..ce1eb2aa5 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -44,6 +44,14 @@ trait AssumptionAnalysisGraph { false } + def getAllNonInternalDependencies(nodeIdsToAnalyze: Set[Int]): Set[AssumptionAnalysisNode] = { + nodes.filter(node => + ((node.isInstanceOf[GeneralAssumptionNode] && !node.assumptionType.equals(AssumptionType.Internal)) || + (node.isInstanceOf[GeneralAssertionNode] && node.assumptionType.equals(AssumptionType.Postcondition)) + || node.isInstanceOf[InfeasibilityNode]) && + existsAnyDependency(Set(node.id), nodeIdsToAnalyze)).toSet + } + private def getNodesByProperties(nodeType: Option[String], assumptionType: Option[AssumptionType], sourceInfo: Option[String], position: Option[Position]): mutable.Seq[AssumptionAnalysisNode] = { nodes filter (node => nodeType.forall(node.getNodeType.equals) && @@ -238,12 +246,13 @@ case class LabelNode(term: Term) extends GeneralAssumptionNode { override def getNodeString: String = "assume " + description } -case class InfeasibilityNode(sourceInfo: AnalysisSourceInfo) extends GeneralAssertionNode { +case class InfeasibilityNode(sourceInfo: AnalysisSourceInfo) extends AssumptionAnalysisNode { val term: Term = False - val assumptionType: AssumptionType = AssumptionType.Internal + val assumptionType: AssumptionType = AssumptionType.Implicit val isClosed: Boolean = true val description: String = "False" + override def getNodeType: String = "Infeasible" override def getNodeString: String = "infeasible" } diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 1f748f883..a6614d916 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -505,9 +505,14 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => val timeout = if (isAssert) Verifier.config.assertTimeout.toOption else Verifier.config.checkTimeout.toOption val result = prover.check(timeout, label) == Unsat if(result) { - assumptionAnalyzer.processUnsatCoreAndAddDependencies(prover.getLastUnsatCore, label) - val infeasibleNodeId = assumptionAnalyzer.addInfeasibilityNode(!isAssert, analysisSourceInfoStack.getFullSourceInfo) - assumptionAnalyzer.addDependency(checkNodeId, infeasibleNodeId) + if(pcs.getCurrentInfeasibilityNode.isDefined){ + assumptionAnalyzer.addDependency(pcs.getCurrentInfeasibilityNode, checkNodeId) + }else { + assumptionAnalyzer.processUnsatCoreAndAddDependencies(prover.getLastUnsatCore, label) + val infeasibleNodeId = assumptionAnalyzer.addInfeasibilityNode(!isAssert, analysisSourceInfoStack.getFullSourceInfo) + assumptionAnalyzer.addDependency(checkNodeId, infeasibleNodeId) + pcs.setCurrentInfeasibilityNode(checkNodeId) + } } result } @@ -582,7 +587,11 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => val result = prover.assert(t, timeout, label) if(result) - assumptionAnalyzer.processUnsatCoreAndAddDependencies(prover.getLastUnsatCore, label) + if(pcs.getCurrentInfeasibilityNode.isDefined) { + assumptionAnalyzer.addDependency(pcs.getCurrentInfeasibilityNode, Some(AssumptionAnalyzer.getIdFromLabel(label))) + }else{ + assumptionAnalyzer.processUnsatCoreAndAddDependencies(prover.getLastUnsatCore, label) + } symbExLog.whenEnabled { assertRecord.statistics = Some(symbExLog.deltaStatistics(prover.statistics())) diff --git a/src/main/scala/decider/PathConditions.scala b/src/main/scala/decider/PathConditions.scala index b0c96b7c7..86337efb4 100644 --- a/src/main/scala/decider/PathConditions.scala +++ b/src/main/scala/decider/PathConditions.scala @@ -70,6 +70,8 @@ trait PathConditionStack extends RecordedPathConditions { def popScope(): Unit def mark(): Mark def popUntilMark(mark: Mark): Unit + def setCurrentInfeasibilityNode(node: Option[Int]): Unit + def getCurrentInfeasibilityNode: Option[Int] def startDebugSubExp(): Unit def finishDebugSubExp(description : String): Unit @@ -93,6 +95,7 @@ private class PathConditionStackLayer private var _branchCondition: Option[Term] = None private var _branchConditionExp: Option[(ast.Exp, Option[ast.Exp])] = None + private var _infeasibilityNodeId: Option[Int] = None private var _globalAssumptions: InsertionOrderedSet[Term] = InsertionOrderedSet.empty private var _nonGlobalAssumptions: InsertionOrderedSet[Term] = InsertionOrderedSet.empty private var _globalAssumptionDebugExps: InsertionOrderedSet[DebugExp] = InsertionOrderedSet.empty @@ -105,6 +108,10 @@ private class PathConditionStackLayer def branchCondition: Option[Term] = _branchCondition def branchConditionExp: Option[(ast.Exp, Option[ast.Exp])] = _branchConditionExp + def infeasibilityNodeId: Option[Int] = _infeasibilityNodeId + def setInfeasibilityNodeId(id: Option[Int]): Unit = { + _infeasibilityNodeId = id + } def globalAssumptions: InsertionOrderedSet[Term] = _globalAssumptions def globalDefiningAssumptions: InsertionOrderedSet[Term] = _globalDefiningAssumptions def nonGlobalDefiningAssumptions: InsertionOrderedSet[Term] = _nonGlobalDefiningAssumptions @@ -471,6 +478,11 @@ private[decider] class LayeredPathConditionStack layers.head.branchConditionExp = conditionExp } + def setCurrentInfeasibilityNode(node: Option[Int]): Unit = { + layers.head.setInfeasibilityNodeId(node) + } + def getCurrentInfeasibilityNode: Option[Int] = layers.map(_.infeasibilityNodeId).find(_.isDefined).flatten + def startDebugSubExp(): Unit = { layers.head.startDebugSubExp() } diff --git a/src/main/scala/rules/Brancher.scala b/src/main/scala/rules/Brancher.scala index 2a057de48..08f5ac98d 100644 --- a/src/main/scala/rules/Brancher.scala +++ b/src/main/scala/rules/Brancher.scala @@ -54,8 +54,7 @@ object brancher extends BranchingRules { * (2) the branch condition contains a quantified variable */ val skipPathFeasibilityCheck = ( - Verifier.config.enableAssumptionAnalysis() - || fromShortCircuitingAnd + fromShortCircuitingAnd || ( s.quantifiedVariables.nonEmpty && s.quantifiedVariables.map(_._1).exists(condition.freeVariables.contains)) ) @@ -66,20 +65,20 @@ object brancher extends BranchingRules { val executeThenBranch = (skipPathFeasibilityCheck || !v.decider.check(negatedCondition, Verifier.config.checkTimeout())) - val thenInfeasibilityNode = if(Verifier.config.enableAssumptionAnalysis() && !skipPathFeasibilityCheck){ + val thenInfeasibilityNode: Option[Int] = if(Verifier.config.enableAssumptionAnalysis() && !executeThenBranch) { val (_, node) = v.decider.checkAndGetInfeasibilityNode(negatedCondition, Verifier.config.checkTimeout()) node - }else None + } else None /* False if the then-branch is to be explored */ val executeElseBranch = (!executeThenBranch /* Assumes that ast least one branch is feasible */ || skipPathFeasibilityCheck || !v.decider.check(condition, Verifier.config.checkTimeout())) - val elseInfeasibilityNode = if(Verifier.config.enableAssumptionAnalysis() && !skipPathFeasibilityCheck){ + val elseInfeasibilityNode: Option[Int] = if(Verifier.config.enableAssumptionAnalysis() && !executeElseBranch) { val (_, node) = v.decider.checkAndGetInfeasibilityNode(condition, Verifier.config.checkTimeout()) node - }else None + } else None v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) val parallelizeElseBranch = s.parallelizeBranches && executeThenBranch && executeElseBranch @@ -113,10 +112,7 @@ object brancher extends BranchingRules { val currentAnalysisSourceInfos = v.decider.analysisSourceInfoStack.getAnalysisSourceInfos val elseBranchVerificationTask: Verifier => VerificationResult = - if (executeElseBranch) { - // TODO ake -// val needsInfeasibilityFlag = elseInfeasibilityNode.isDefined && v.decider.assumptionAnalyzer.getInfeasibleNode.isEmpty -// if(needsInfeasibilityFlag) v.decider.assumptionAnalyzer.setInfeasibleNode(elseInfeasibilityNode.get) + if (executeElseBranch || Verifier.config.enableAssumptionAnalysis()) { /* [BRANCH-PARALLELISATION] */ /* Compute the following sets * 1. only if the else-branch needs to be explored @@ -165,6 +161,7 @@ object brancher extends BranchingRules { v1.decider.prover.comment(s"[else-branch: $cnt | $negatedCondition]") val sourceInfo = ExpAnalysisSourceInfo(conditionExp._1) v1.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo) + v1.decider.pcs.setCurrentInfeasibilityNode(elseInfeasibilityNode) v1.decider.setCurrentBranchCondition(negatedCondition, (negatedConditionExp, negatedConditionExpNew)) v1.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) @@ -188,8 +185,6 @@ object brancher extends BranchingRules { macrosOfElseBranchDecider = v1.decider.freshMacros.drop(nMacrosOfElseBranchDeciderBefore) } } - -// if(needsInfeasibilityFlag) v1.decider.assumptionAnalyzer.unsetInfeasibleNode() TODO ake result }) } @@ -198,7 +193,7 @@ object brancher extends BranchingRules { } val elseBranchFuture: Future[Seq[VerificationResult]] = - if (executeElseBranch) { + if (executeElseBranch || Verifier.config.enableAssumptionAnalysis()) { if (parallelizeElseBranch) { /* [BRANCH-PARALLELISATION] */ v.verificationPoolManager.queueVerificationTask(v0 => { @@ -214,22 +209,19 @@ object brancher extends BranchingRules { } val res = { - val thenRes = if (executeThenBranch) { - // TODO ake -// val needsInfeasibilityFlag = thenInfeasibilityNode.isDefined && v.decider.assumptionAnalyzer.getInfeasibleNode.isEmpty -// if(needsInfeasibilityFlag) v.decider.assumptionAnalyzer.setInfeasibleNode(thenInfeasibilityNode.get) + val thenRes = if (executeThenBranch || Verifier.config.enableAssumptionAnalysis()) { v.symbExLog.markReachable(uidBranchPoint) v.decider.analysisSourceInfoStack.setAnalysisSourceInfo(currentAnalysisSourceInfos) val res = executionFlowController.locally(s, v)((s1, v1) => { v1.decider.prover.comment(s"[then-branch: $cnt | $condition]") val sourceInfo = ExpAnalysisSourceInfo(conditionExp._1) v1.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo) + v1.decider.pcs.setCurrentInfeasibilityNode(thenInfeasibilityNode) v1.decider.setCurrentBranchCondition(condition, conditionExp) v1.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) fThen(v1.stateConsolidator(s1).consolidateOptionally(s1, v1), v1) }) -// if(needsInfeasibilityFlag) v.decider.assumptionAnalyzer.unsetInfeasibleNode() TODO ake res } else { Unreachable() diff --git a/src/main/scala/rules/ChunkSupporter.scala b/src/main/scala/rules/ChunkSupporter.scala index dac6df061..5e5088b8e 100644 --- a/src/main/scala/rules/ChunkSupporter.scala +++ b/src/main/scala/rules/ChunkSupporter.scala @@ -143,8 +143,11 @@ object chunkSupporter extends ChunkSupportRules { case _ => None } QS(s2.copy(h = s.h), h2, snap, optCh2.toList, v1) - case _ if v1.decider.checkSmoke(isAssert=true, assumptionType) => - Success() // TODO: Mark branch as dead? + case (_, s2, h2, _) if v1.decider.checkSmoke(isAssert=true, assumptionType) => + if(Verifier.config.enableAssumptionAnalysis()) + QS(s2.copy(h = s.h), h2, None, List.empty, v1) + else + Success() // TODO: Mark branch as dead? case _ => createFailure(ve, v1, s1, "consuming chunk", true) } @@ -176,8 +179,6 @@ object chunkSupporter extends ChunkSupportRules { if (s.assertReadAccessOnly) { val termToCheck = Implies(IsPositive(perms), IsPositive(ch.perm)) if (v.decider.check(termToCheck, Verifier.config.assertTimeout.getOrElse(0), assumptionType)) { - // TODO ake: can be removed (probably?) -// v.decider.assumptionAnalyzer.addPermissionAssertNode(ch, termToCheck, v.decider.analysisSourceInfoStack.getFullSourceInfo, assumptionType) (Complete(), s, h, Some(ch)) } else { (Incomplete(perms, permsExp), s, h, None) @@ -263,11 +264,9 @@ object chunkSupporter extends ChunkSupportRules { val findRes = findChunk[NonQuantifiedChunk](h.values, id, args, v) findRes match { case Some(ch) if v.decider.check(IsPositive(ch.perm), Verifier.config.checkTimeout(), assumptionType) => - // TODO ake -// v.decider.assumptionAnalyzer.addPermissionAssertNode(ch, IsPositive(ch.perm), v.decider.analysisSourceInfoStack.getFullSourceInfo, assumptionType) Q(s, ch.snap, v) case _ if v.decider.checkSmoke(isAssert=true, assumptionType) => - if (s.isInPackage) { + if (s.isInPackage || Verifier.config.enableAssumptionAnalysis()) { val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) Q(s, snap, v) } else { diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index c254fcca5..5c255dcf8 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -282,7 +282,7 @@ object executor extends ExecutionRules { // TODO ake: pcs.assumptionExps without exps do not have a source, but setting the source here will result in all invariants having the same source v2.decider.assume(pcs.assumptions map (t => v.decider.wrapWithAssumptionAnalysisLabel(t, Set.empty, Set(t))), Some(pcs.assumptionExps), "Loop invariant", enforceAssumption=false, assumptionType=AssumptionType.LoopInvariant) v2.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) - if (v2.decider.checkSmoke()) + if (v2.decider.checkSmoke() && !Verifier.config.enableAssumptionAnalysis()) Success() else { execs(s3, stmts, v2)((s4, v3) => { diff --git a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala index 2126a5184..aa386fafe 100644 --- a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala +++ b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala @@ -207,7 +207,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { if (relevantChunks.isEmpty) { if (v.decider.checkSmoke(true)) { - if (s.isInPackage) { + if (s.isInPackage || Verifier.config.enableAssumptionAnalysis()) { val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) Q(s, snap, v) } else { diff --git a/src/test/resources/dependencyAnalysisTests/all/list.vpr b/src/test/resources/dependencyAnalysisTests/all/list.vpr index 70578c9e1..9e6566c19 100644 --- a/src/test/resources/dependencyAnalysisTests/all/list.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/list.vpr @@ -23,7 +23,7 @@ method appendList1(this: Ref, e: Int) @irrelevant() assume 0 <= this.elem && this.elem < 100 - if (this.next == null) { // TODO ake: node does not exist? + if (@irrelevant()(this.next == null)) { // TODO ake: node does not exist? var n: Ref @dependency() diff --git a/src/test/resources/dependencyAnalysisTests/new/meeting.vpr b/src/test/resources/dependencyAnalysisTests/new/meeting.vpr index 96015dc43..a1ccd0b67 100644 --- a/src/test/resources/dependencyAnalysisTests/new/meeting.vpr +++ b/src/test/resources/dependencyAnalysisTests/new/meeting.vpr @@ -40,6 +40,19 @@ method infeasible3(x: Ref) } } +method foo(a: Int, b:Int, x: Ref) + requires b >= 0 + ensures a >= 0 +{ + if(b < 0){ + x.f := a + }else{ + inhale a > 10 + } + + assert a > 0 +} + method currentPerm(x: Ref) requires acc(x.f, 1/2) diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index 3ca418b69..0a1f5e366 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -17,17 +17,15 @@ import scala.jdk.CollectionConverters.IterableHasAsScala class AssumptionAnalysisTests extends AnyFunSuite { val CHECK_PRECISION = false - val ignores: Seq[String] = Seq("infeasible") + val ignores: Seq[String] = Seq() val testDirectories: Seq[String] = Seq( "dependencyAnalysisTests", -// "dependencyAnalysisTests/unitTests", -// "dependencyAnalysisTests/all", // "dependencyAnalysisTests/quick", "examples/binary-search", // "examples/graph-copy", // "examples/graph-marking", -// "examples/max_array", -// "examples/quickselect", + "examples/max_array", + "examples/quickselect", // "examples/longest-common-prefix", // "examples/tree-delete-min", ) @@ -45,11 +43,6 @@ class AssumptionAnalysisTests extends AnyFunSuite { // executeTest("examples/max_array/", "max-array-standard", frontend) // } -// def createTests(dirName: String): Unit = { -// val path = getClass.getClassLoader.getResource(dirName) -// createTests(path) -// } - def createTests(dirName: String): Unit = { val path = Paths.get(getClass.getClassLoader.getResource(dirName).toURI) createTests(path, dirName) @@ -128,10 +121,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { val sourcePositionsToAnalyze = nodesToAnalyze map (_.sourceInfo.getPositionString) val explicitAssertionNodeIds = fullGraph.nodes.filter(n => sourcePositionsToAnalyze.contains(n.sourceInfo.getPositionString)).map(_.id).toSet - val dependencies = fullGraph.nodes filter (node => - ((node.isInstanceOf[GeneralAssumptionNode] && !node.assumptionType.equals(AssumptionType.Internal)) || - (node.isInstanceOf[GeneralAssertionNode] && node.assumptionType.equals(AssumptionType.Postcondition))) && - fullGraph.existsAnyDependency(Set(node.id), explicitAssertionNodeIds)) + val dependencies = fullGraph.getAllNonInternalDependencies(explicitAssertionNodeIds) val crucialNodes = nodesToAnalyze ++ dependencies val (newProgram, pruningFactor) = getPrunedProgram(crucialNodes) @@ -317,7 +307,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { private def extractTestAssumptionNodesFromGraph(graph: AssumptionAnalysisGraph): Seq[AssumptionAnalysisNode] = { graph.nodes.filter(node => { - (node.getNodeType.equals("Assumption") || node.getNodeType.equals("Inhale")) && + (node.getNodeType.equals("Assumption") || node.getNodeType.equals("Inhale") || node.getNodeType.equals("Infeasible")) && !node.assumptionType.equals(AssumptionType.Internal) && node.sourceInfo.toString.contains("@" + dependencyKeyword + "()") } @@ -326,7 +316,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { private def extractTestIrrelevantAssumptionNodesFromGraph(graph: AssumptionAnalysisGraph): Seq[AssumptionAnalysisNode] = { graph.nodes.filter(node => { - (node.getNodeType.equals("Assumption") || node.getNodeType.equals("Inhale")) && + (node.getNodeType.equals("Assumption") || node.getNodeType.equals("Inhale") || node.getNodeType.equals("Infeasible")) && !node.assumptionType.equals(AssumptionType.Internal) && node.sourceInfo.toString.contains("@" + irrelevantKeyword + "()") } From 5dfe6506644f4b6b19a1a709d0819cc43c67ddb6 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 8 Jul 2025 12:11:40 +0200 Subject: [PATCH 150/474] update tests --- src/test/resources/andrea/quickTest.vpr | 19 ++++++++++--------- .../all/infeasible.vpr | 8 ++++---- src/test/scala/AssumptionAnalysisTests.scala | 12 ++++++++++-- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/test/resources/andrea/quickTest.vpr b/src/test/resources/andrea/quickTest.vpr index 004e2e6d6..191881d83 100644 --- a/src/test/resources/andrea/quickTest.vpr +++ b/src/test/resources/andrea/quickTest.vpr @@ -1,12 +1,13 @@ field f: Int -method quantifiedExhalePartially(xs: Seq[Ref]) { - var res: Int - assume @dependency()(|xs| > 5) - //inhale @dependency()(forall x: Ref :: x in xs ==> acc(x.f)) - inhale (forall x: Ref :: x in xs ==> acc(x.f)) // TODO ake: missing dependency - - exhale forall x: Ref :: x in xs ==> acc(x.f, 1/2) - @testAssertion() - res := xs[1].f + 1 +method infeasibleBranchNoPerm(a: Int, b: Ref) + requires a > 0 //@dependency()(a > 0) +{ + var res: Int + if(a < 0){ + //@irrelevant() TODO ake: nodes missing (infeasible path) + res := b.f + 1 + //@testAssertion() + assert false + } } \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/infeasible.vpr b/src/test/resources/dependencyAnalysisTests/all/infeasible.vpr index e5b6eb718..1f323118a 100644 --- a/src/test/resources/dependencyAnalysisTests/all/infeasible.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/infeasible.vpr @@ -37,13 +37,13 @@ method infeasibleBranchPerm(a: Ref, b: Ref) method infeasibleBranchNoPerm(a: Int, b: Ref) - requires a > 0 //@dependency()(a > 0) + requires @dependency()(a > 0) { var res: Int - if(a < 0){ - //@irrelevant() TODO ake: nodes missing (infeasible path) + if(@dependency()(a < 0)){ + @irrelevant() res := b.f + 1 - //@testAssertion() + @testAssertion() assert false } } \ No newline at end of file diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index 0a1f5e366..564f32ec5 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -26,8 +26,8 @@ class AssumptionAnalysisTests extends AnyFunSuite { // "examples/graph-marking", "examples/max_array", "examples/quickselect", -// "examples/longest-common-prefix", -// "examples/tree-delete-min", + "examples/longest-common-prefix", + "examples/tree-delete-min", ) val irrelevantKeyword = "irrelevant" @@ -175,6 +175,14 @@ class AssumptionAnalysisTests extends AnyFunSuite { total += 1 + invs.size removed += (invs.size - newInvs.size) ast.While(cond, newInvs, body)(whileStmt.pos, whileStmt.info, whileStmt.errT) +// case fieldAssign @ FieldAssign(lhs, _) if !isCrucialStmt(fieldAssign, crucialNodesWithStmtInfo) => // TODO ake: index into sequences need to be checked! +// total += 1 +// removed += 1 +// ast.Quasihavoc(Some(ast.PermGeCmp(ast.CurrentPerm(lhs)(), ast.FullPerm()())()), lhs)(fieldAssign.pos, fieldAssign.info, fieldAssign.errT) + case ass @ ast.LocalVarAssign(lhs, _) if !isCrucialStmt(ass, crucialNodesWithStmtInfo) => // TODO ake: is this valid`? + total += 1 + removed += 1 + ast.LocalVarDeclStmt(ast.LocalVarDecl(lhs.name, lhs.typ)())(ass.pos, ass.info, ass.errT) case s: Stmt if !isCrucialStmt(s, crucialNodesWithStmtInfo) => total += 1 removed += 1 From 1d58f31dde59d277405fb10adabf1712cabe4f92 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 8 Jul 2025 21:03:47 +0200 Subject: [PATCH 151/474] update tests --- src/test/scala/AssumptionAnalysisTests.scala | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index 564f32ec5..6c5c266c3 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -62,7 +62,11 @@ class AssumptionAnalysisTests extends AnyFunSuite { val fileName = rawFileName.replace(".vpr", "") if (!ignores.contains(fileName)) test(dirName + "/" + fileName) { - executeTest(dirName + "/", fileName, frontend) + try{ + executeTest(dirName + "/", fileName, frontend) + }catch{ + case t: Throwable => fail(t.getMessage) + } } } } @@ -86,8 +90,8 @@ class AssumptionAnalysisTests extends AnyFunSuite { val program: Program = tests.loadProgram(filePrefix, fileName, frontend) val result = frontend.verifier.verify(program) - if (result.isInstanceOf[verifier.Failure]) { - println("Program does not verify. Skip test.") + if(result.isInstanceOf[verifier.Failure]) { + cancel("Program does not verify. Skip test.") return } From 2a66d7881e5108fa653f3fcf9bcdfae5ea3a1192 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 9 Jul 2025 08:56:34 +0200 Subject: [PATCH 152/474] fix labels and goto for pruning tests --- src/test/scala/AssumptionAnalysisTests.scala | 31 ++++++++++++-------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index 6c5c266c3..a131f9f8c 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -19,15 +19,17 @@ class AssumptionAnalysisTests extends AnyFunSuite { val CHECK_PRECISION = false val ignores: Seq[String] = Seq() val testDirectories: Seq[String] = Seq( - "dependencyAnalysisTests", +// "dependencyAnalysisTests", + "dependencyAnalysisTests/unitTests", // "dependencyAnalysisTests/quick", - "examples/binary-search", +// "examples/binary-search", // "examples/graph-copy", // "examples/graph-marking", - "examples/max_array", - "examples/quickselect", - "examples/longest-common-prefix", - "examples/tree-delete-min", +// "examples/max_array", +// "examples/quickselect", +// "examples/longest-common-prefix", +// "examples/tree-delete-min", +// "fromSilver" ) val irrelevantKeyword = "irrelevant" @@ -148,8 +150,8 @@ class AssumptionAnalysisTests extends AnyFunSuite { var total = 0 var removed = 0 - val newProgram: Program = ViperStrategy.Slim({ - case s: Seqn => s + val newProgram: ast.Program = ViperStrategy.Slim({ + case s @(_: ast.Seqn | _: ast.Goto) => s case meth@ast.Method(name, inVars, outVars, pres, posts, body) => val newPres = pres filter (isCrucialExp(_, crucialNodesWithExpInfo)) val newPosts = posts filter (isCrucialExp(_, crucialNodesWithExpInfo)) @@ -183,10 +185,15 @@ class AssumptionAnalysisTests extends AnyFunSuite { // total += 1 // removed += 1 // ast.Quasihavoc(Some(ast.PermGeCmp(ast.CurrentPerm(lhs)(), ast.FullPerm()())()), lhs)(fieldAssign.pos, fieldAssign.info, fieldAssign.errT) - case ass @ ast.LocalVarAssign(lhs, _) if !isCrucialStmt(ass, crucialNodesWithStmtInfo) => // TODO ake: is this valid`? - total += 1 - removed += 1 - ast.LocalVarDeclStmt(ast.LocalVarDecl(lhs.name, lhs.typ)())(ass.pos, ass.info, ass.errT) +// case ass @ ast.LocalVarAssign(lhs, _) if !isCrucialStmt(ass, crucialNodesWithStmtInfo) => // TODO ake: is this valid`? +// total += 1 +// removed += 1 +// ast.LocalVarDeclStmt(ast.LocalVarDecl(lhs.name, lhs.typ)())(ass.pos, ass.info, ass.errT) + case label@ast.Label(name, invs) => + val newInvs = invs filter (isCrucialExp(_, crucialNodesWithExpInfo)) + total += 1 + invs.size + removed += (invs.size - newInvs.size) + ast.Label(name, newInvs)(label.pos, label.info, label.errT) case s: Stmt if !isCrucialStmt(s, crucialNodesWithStmtInfo) => total += 1 removed += 1 From 60af44e496ce7b424225379d75b4545f3f2ee6b2 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 9 Jul 2025 11:02:08 +0200 Subject: [PATCH 153/474] add infeasibility node for inhale false --- src/main/scala/rules/Executor.scala | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 5c255dcf8..529d73435 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -511,6 +511,10 @@ object executor extends ExecutionRules { case _ => produce(s, freshSnap, a, InhaleFailed(inhale), v, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit))((s1, v1) => { v1.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterInhale) + if(Verifier.config.enableAssumptionAnalysis() && a.isInstanceOf[ast.FalseLit]) { + val (_, node) = v1.decider.checkAndGetInfeasibilityNode(False, Verifier.config.checkTimeout(), AssumptionType.Explicit) + v1.decider.pcs.setCurrentInfeasibilityNode(node) + } Q(s1, v1)}) } From b62682b5341bcc291018bc135113ef5dfd534f52 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 10 Jul 2025 16:18:19 +0200 Subject: [PATCH 154/474] cleanup unnecessary labels --- src/main/scala/decider/Decider.scala | 8 -- src/main/scala/decider/PathConditions.scala | 22 ++-- .../NonQuantifiedPropertyInterpreter.scala | 6 +- src/main/scala/rules/Consumer.scala | 11 +- src/main/scala/rules/Evaluator.scala | 120 ++++++++---------- src/main/scala/rules/Joiner.scala | 9 +- .../rules/MoreCompleteExhaleSupporter.scala | 18 +-- .../scala/rules/QuantifiedChunkSupport.scala | 38 +++--- src/main/scala/rules/StateConsolidator.scala | 6 +- src/main/scala/state/State.scala | 6 +- 10 files changed, 98 insertions(+), 146 deletions(-) diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index a6614d916..e3c4a35b5 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -64,7 +64,6 @@ trait Decider { def registerChunk[CH <: GeneralChunk](buildChunk: (Term => CH), perm: Term, analysisInfo: AnalysisInfo, isExhale: Boolean): CH def registerDerivedChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: (Term => CH), perm: Term, analysisInfo: AnalysisInfo, isExhale: Boolean, createLabel: Boolean=true): CH def wrapWithAssumptionAnalysisLabel(term: Term, sourceChunks: Iterable[Chunk] = Set.empty, sourceTerms: Iterable[Term] = Set.empty): Term - def wrapPermissionWithAssumptionAnalysisLabel(perm: Term, sourceChunks: Iterable[Chunk]): Term def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp], assumptionType: AssumptionType): Unit def assume(t: Term, debugExp: Option[DebugExp], assumptionType: AssumptionType): Unit @@ -347,13 +346,6 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => labelNode.map(n => Implies(n.term, term)).getOrElse(term) } - def wrapPermissionWithAssumptionAnalysisLabel(perm: Term, sourceChunks: Iterable[Chunk]): Term = { - if(!Verifier.config.enableAssumptionAnalysis()) return perm - - val labelNode = getOrCreateAnalysisLabelNode(sourceChunks, Set.empty) - labelNode.map(n => Ite(n.term, perm, NoPerm)).getOrElse(perm) - } - def addDebugExp(e: DebugExp): Unit = { if (debugMode) { pathConditions.addDebugExp(e) diff --git a/src/main/scala/decider/PathConditions.scala b/src/main/scala/decider/PathConditions.scala index 86337efb4..e6c67221f 100644 --- a/src/main/scala/decider/PathConditions.scala +++ b/src/main/scala/decider/PathConditions.scala @@ -46,8 +46,7 @@ trait RecordedPathConditions { triggers: Seq[Trigger], name: String, isGlobal: Boolean, - ignore: Term /* TODO: Hack, implement properly */, - v: Verifier) + ignore: Term /* TODO: Hack, implement properly */) : (Seq[Term], Seq[Quantification]) def quantifiedExp(quantifier: Quantifier, @@ -334,8 +333,7 @@ private trait LayeredPathConditionStackLike { triggers: Seq[Trigger], name: String, isGlobal: Boolean, - ignore: Term, - v: Verifier) + ignore: Term) : (Seq[Term], Seq[Quantification]) = { var globals = Vector.empty[Term] @@ -344,8 +342,8 @@ private trait LayeredPathConditionStackLike { val ignores = ignore.topLevelConjuncts for (layer <- layers) { - val actualBranchCondition = layer.branchCondition.map(a => v.decider.wrapWithAssumptionAnalysisLabel(a, Set.empty, Set(a))).getOrElse(True) - val relevantNonGlobals = (layer.nonGlobalAssumptions -- ignores).map(a => v.decider.wrapWithAssumptionAnalysisLabel(a, Set.empty, Set(a))) + val actualBranchCondition = layer.branchCondition.getOrElse(True) + val relevantNonGlobals = layer.nonGlobalAssumptions -- ignores val (trueNonGlobals, additionalGlobals) = if (!actualBranchCondition.existsDefined{ case t if qvars.contains(t) => }) { // The branch condition is independent of all quantified variables // Any assumptions that are also independent of all quantified variables can be treated as global assumptions. @@ -355,7 +353,7 @@ private trait LayeredPathConditionStackLike { (relevantNonGlobals, Seq()) } - globals ++= layer.globalAssumptions.map(a => v.decider.wrapWithAssumptionAnalysisLabel(a, Set.empty, Set(a))) ++ additionalGlobals + globals ++= layer.globalAssumptions ++ additionalGlobals nonGlobals :+= Quantification( @@ -435,11 +433,10 @@ private class DefaultRecordedPathConditions(from: Stack[PathConditionStackLayer] triggers: Seq[Trigger], name: String, isGlobal: Boolean, - ignore: Term, - v: Verifier) + ignore: Term) : (Seq[Term], Seq[Quantification]) = { - quantified(from, quantifier, qvars, triggers, name, isGlobal, ignore, v) + quantified(from, quantifier, qvars, triggers, name, isGlobal, ignore) } def quantifiedExp(quantifier: Quantifier, @@ -607,11 +604,10 @@ private[decider] class LayeredPathConditionStack triggers: Seq[Trigger], name: String, isGlobal: Boolean, - ignore: Term, - v: Verifier) + ignore: Term) : (Seq[Term], Seq[Quantification]) = { - quantified(layers, quantifier, qvars, triggers, name, isGlobal, ignore, v) + quantified(layers, quantifier, qvars, triggers, name, isGlobal, ignore) } def quantifiedExp(quantifier: Quantifier, diff --git a/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala b/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala index 2ed2b3fd8..37621ea33 100644 --- a/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala +++ b/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala @@ -121,8 +121,7 @@ class NonQuantifiedPropertyInterpreter(heap: Iterable[Chunk], verifier: Verifier info: Info): (Term, Option[ast.Exp]) = { val conditionTerm = buildPathCondition(condition, info)._1 if (verifier.decider.check(conditionTerm, Verifier.config.checkTimeout())) { - val (term, exp) = buildPathCondition(thenDo, info) - (verifier.decider.wrapWithAssumptionAnalysisLabel(term, Set.empty, Set(conditionTerm)), exp) + buildPathCondition(thenDo, info) } else { buildPathCondition(otherwise, info) } @@ -156,8 +155,7 @@ class NonQuantifiedPropertyInterpreter(heap: Iterable[Chunk], verifier: Verifier // check that only distinct tuples are handled // TODO: Is it possible to get this behavior without having to check every tuple? if (!info.pm.values.exists(chunk eq _)) { - val (resTerm, resExp) = builder(chunk) - Some((verifier.decider.wrapWithAssumptionAnalysisLabel(resTerm, Set(chunk)), resExp)) + Some(builder(chunk)) } else { None } diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index 92b0eeef5..445308906 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -584,20 +584,15 @@ object consumer extends ConsumptionRules { case Seq(entry) => // One branch is dead (entry.s, entry.data) case Seq(entry1, entry2) => // Both branches are alive - // TODO ake: precision? - val branchConditions1 = entry1.pathConditions.branchConditions - val labelledBranchConditions1 = v.decider.wrapWithAssumptionAnalysisLabel(And(branchConditions1), Set.empty, branchConditions1) - val branchConditions2 = entry2.pathConditions.branchConditions - val labelledBranchConditions2 = v.decider.wrapWithAssumptionAnalysisLabel(And(branchConditions2), Set.empty, branchConditions2) val mergedData = ( State.mergeHeap( - entry1.data._1, labelledBranchConditions1, Option.when(withExp)(BigAnd(entry1.pathConditions.branchConditionExps.map(_._2.get))), - entry2.data._1, labelledBranchConditions2, Option.when(withExp)(BigAnd(entry2.pathConditions.branchConditionExps.map(_._2.get))), + entry1.data._1, And(entry1.pathConditions.branchConditions), Option.when(withExp)(BigAnd(entry1.pathConditions.branchConditionExps.map(_._2.get))), + entry2.data._1, And(entry2.pathConditions.branchConditions), Option.when(withExp)(BigAnd(entry2.pathConditions.branchConditionExps.map(_._2.get))), AnalysisInfo(v.decider, v.decider.assumptionAnalyzer, StringAnalysisSourceInfo("conditional join", e0.pos), AssumptionType.Implicit) // TODO ake ), // Assume that entry1.pcs is inverse of entry2.pcs (entry1.data._2, entry2.data._2) match { - case (Some(t1), Some(t2)) if returnSnap => Some(Ite(labelledBranchConditions1, t1, t2)) + case (Some(t1), Some(t2)) if returnSnap => Some(Ite(And(entry1.pathConditions.branchConditions), t1, t2)) case (None, None) if !returnSnap => None case (_, _) => sys.error(s"Unexpected join data entries: $entries") }, diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index cbd63397c..528ad7aeb 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -187,8 +187,7 @@ object evaluator extends EvaluationRules { case _: ast.WildcardPerm => val (tVar, tConstraints, eVar) = v.decider.freshARP() - val eConstraints = Some(ast.LocalVar("wildcardConstraints", ast.Bool)(e.pos, e.info, e.errT)) - val constraintExp = Option.when(withExp)(DebugExp.createInstance(None, eConstraints, eConstraints, Some(tConstraints), isInternal_ = true, children = InsertionOrderedSet())) + val constraintExp = Option.when(withExp)(DebugExp.createInstance(s"${eVar.get.toString} > none", true)) v.decider.assumeDefinition(tConstraints, constraintExp, AssumptionType.Implicit) /* TODO: Only record wildcards in State.constrainableARPs that are used in exhale * position. Currently, wildcards used in inhale position (only) may not be removed @@ -204,7 +203,7 @@ object evaluator extends EvaluationRules { val s1 = s.copy(functionRecorder = s.functionRecorder.recordConstrainedVar(tVar, tConstraints)) .setConstrainable(Seq(tVar), true) - Q(s1, tVar, eVar.map(eV => ast.LocalVarWithVersion(eV.name, eV.typ)(e.pos, e.info, e.errT)), v) + Q(s1, tVar, eVar, v) case fa: ast.FieldAccess if s.qpFields.contains(fa.field) => eval(s, fa.rcv, pve, v)((s1, tRcvr, eRcvr, v1) => { @@ -239,10 +238,9 @@ object evaluator extends EvaluationRules { Q(s3, fvfLookup, newFa, v1) } else { val toAssert = IsPositive(totalPermissions.replace(`?r`, tRcvr)) - val toAssertExp = Option.when(withExp)(perms.IsPositive(ast.CurrentPerm(fa)())(fa.pos, fa.info, fa.errT)) v1.decider.assert(toAssert) { case false => - createFailure(pve dueTo InsufficientPermission(fa), v1, s1, toAssert, toAssertExp) + createFailure(pve dueTo InsufficientPermission(fa), v1, s1, toAssert, Option.when(withExp)(perms.IsPositive(ast.CurrentPerm(fa)())())) case true => val fvfLookup = Lookup(fa.field.name, fvfDef.sm, tRcvr) val fr1 = s1.functionRecorder.recordSnapshot(fa, v1.decider.pcs.branchConditions, fvfLookup).recordFvfAndDomain(fvfDef) @@ -264,7 +262,7 @@ object evaluator extends EvaluationRules { } val (permCheck, permCheckExp) = if (s1.triggerExp) { - (True, Option.when(withExp)(TrueLit()(fa.pos, fa.info, fa.errT))) + (True, Option.when(withExp)(TrueLit()())) } else { val permVal = relevantChunks.head.perm val totalPermissions = permVal.replace(relevantChunks.head.quantifiedVars, Seq(tRcvr)) @@ -297,7 +295,7 @@ object evaluator extends EvaluationRules { } val (permCheck, permCheckExp) = if (s2.triggerExp) { - (True, Option.when(withExp)(TrueLit()(fa.pos, fa.info, fa.errT))) + (True, Option.when(withExp)(TrueLit()())) } else { val totalPermissions = PermLookup(fa.field.name, pmDef1.pm, tRcvr) (IsPositive(totalPermissions), Option.when(withExp)(ast.PermGtCmp(ast.CurrentPerm(fa)(fa.pos, fa.info, fa.errT), ast.NoPerm()())(fa.pos, fa.info, fa.errT))) @@ -592,7 +590,7 @@ object evaluator extends EvaluationRules { val currentPermAmount = PermLookup(field.name, pmDef.pm, args.head) v1.decider.prover.comment(s"perm($resacc) ~~> assume upper permission bound") val (debugHeapName, debugLabel) = v1.getDebugOldLabel(s2, resacc.pos, Some(h)) - val exp = Option.when(withExp)(ast.PermLeCmp(ast.DebugLabelledOld(ast.CurrentPerm(resacc)(), debugLabel)(), ast.FullPerm()())(resacc.pos, resacc.info, resacc.errT)) + val exp = Option.when(withExp)(ast.PermLeCmp(ast.DebugLabelledOld(ast.CurrentPerm(resacc)(), debugLabel)(), ast.FullPerm()())()) v1.decider.assume(PermAtMost(currentPermAmount, FullPerm), exp, exp.map(s2.substituteVarsInExp(_)), AssumptionType.Internal) val s3 = if (Verifier.config.enableDebugging()) s2.copy(oldHeaps = s2.oldHeaps + (debugHeapName -> h)) else s2 (s3, currentPermAmount) @@ -944,7 +942,7 @@ object evaluator extends EvaluationRules { val preExp = Option.when(withExp)({ DebugExp.createInstance(Some(s"precondition of ${func.name}(${eArgsNew.get.mkString(", ")}) holds"), None, None, InsertionOrderedSet.empty) }) - v3.decider.assume(preFApp, preExp, assumptionType) + v3.decider.assume(preFApp, preExp, assumptionType=assumptionType) val funcAnn = func.info.getUniqueInfo[AnnotationInfo] val tFApp = funcAnn match { case Some(a) if a.values.contains("opaque") => @@ -985,9 +983,8 @@ object evaluator extends EvaluationRules { if (s.cycles(predicate) < Verifier.config.recursivePredicateUnfoldings()) { v.decider.startDebugSubExp() evals(s, eArgs, _ => pve, v)((s1, tArgs, eArgsNew, v1) => - eval(s1, ePerm.getOrElse(ast.FullPerm()(u.pos, u.info, u.errT)), pve, v1)((s2, tPerm, ePermNew, v2) => - // TODO: Replace with permissionSupporter.assertNotNegative - v2.decider.assert(IsPositive(tPerm)) { + eval(s1, ePerm.getOrElse(ast.FullPerm()()), pve, v1)((s2, tPerm, ePermNew, v2) => + v2.decider.assert(IsPositive(tPerm)) { // TODO: Replace with permissionSupporter.assertNotNegative case true => joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s2, v2)((s3, v3, QB) => { val s4 = s3.incCycleCounter(predicate) @@ -1040,8 +1037,7 @@ object evaluator extends EvaluationRules { Q(s12, r12._1, r12._2, v7)}) case false => v2.decider.finishDebugSubExp(s"unfolded(${predicate.name})") - createFailure(pve dueTo NonPositivePermission(ePerm.get), v2, s2, IsPositive(tPerm), ePermNew.map(p => ast.PermGtCmp(p, ast.NoPerm()())(p.pos, p.info, p.errT)))}) - ) + createFailure(pve dueTo NonPositivePermission(ePerm.get), v2, s2, IsPositive(tPerm), ePermNew.map(p => ast.PermGtCmp(p, ast.NoPerm()())(p.pos, p.info, p.errT)))})) } else { val unknownValue = v.decider.appliedFresh("recunf", v.symbolConverter.toSort(eIn.typ), s.relevantQuantifiedVariables.map(_._1)) Q(s, unknownValue, Option.when(withExp)(ast.LocalVarWithVersion("unknownValue", eIn.typ)(eIn.pos, eIn.info, eIn.errT)), v) @@ -1073,35 +1069,35 @@ object evaluator extends EvaluationRules { if (s1.triggerExp) { Q(s1, SeqAt(t0, t1), eNew, v1) } else { - val idxGe0 = AtLeast(t1, IntLiteral(0)) - val idxGe0Exp = Option.when(withExp)(ast.GeCmp(e1, ast.IntLit(0)())(si.pos, si.info, si.errT)) - val idxGe0ExpNew = Option.when(withExp)(ast.GeCmp(esNew.get(1), ast.IntLit(0)())(si.pos, si.info, si.errT)) - val idxLtLength = Less(t1, SeqLength(t0)) - val idxLtLengthExp = Option.when(withExp)(ast.LtCmp(e1, ast.SeqLength(e0)())(si.pos, si.info, si.errT)) - val idxLtLengthExpNew = esNew.map(es => ast.LtCmp(es(1), ast.SeqLength(es.head)())(si.pos, si.info, si.errT)) - v1.decider.assert(idxGe0) { + v1.decider.assert(AtLeast(t1, IntLiteral(0))) { case true => - v1.decider.assert(idxLtLength) { + v1.decider.assert(Less(t1, SeqLength(t0))) { case true => Q(s1, SeqAt(t0, t1), eNew, v1) case false => - val failure = createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, idxLtLength, idxLtLengthExp) + val assertExp2 = Option.when(withExp)(ast.LtCmp(e1, ast.SeqLength(e0)())(e1.pos, e1.info, e1.errT)) + val failure = createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, Less(t1, SeqLength(t0)), assertExp2) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { - v1.decider.assume(idxLtLength, idxLtLengthExp, idxLtLengthExpNew, AssumptionType.Explicit) // TODO ake: assumption type + val assertExp2 = Option.when(withExp)(ast.LeCmp(e1, ast.SeqLength(e0)())()) + val assertExp2New = esNew.map(es => ast.LeCmp(es(1), ast.SeqLength(es.head)())()) + v1.decider.assume(Less(t1, SeqLength(t0)), assertExp2, assertExp2New, assumptionType = AssumptionType.Explicit) failure combine Q(s1, SeqAt(t0, t1), eNew, v1) } else failure} case false => - - val failure1 = createFailure(pve dueTo SeqIndexNegative(e0, e1), v1, s1, idxGe0, idxGe0ExpNew) + val assertExp1 = Option.when(withExp)(ast.GeCmp(e1, ast.IntLit(0)())(e1.pos, e1.info, e1.errT)) + val assertExp1New = Option.when(withExp)(ast.GeCmp(esNew.get(1), ast.IntLit(0)())(e1.pos, e1.info, e1.errT)) + val failure1 = createFailure(pve dueTo SeqIndexNegative(e0, e1), v1, s1, AtLeast(t1, IntLiteral(0)), assertExp1New) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { - v1.decider.assume(idxGe0, idxGe0Exp, idxGe0ExpNew, AssumptionType.Explicit) - v1.decider.assert(idxLtLength) { + v1.decider.assume(AtLeast(t1, IntLiteral(0)), assertExp1, assertExp1New, AssumptionType.Explicit) + val assertExp2 = Option.when(withExp)(ast.LtCmp(e1, ast.SeqLength(e0)())(e1.pos, e1.info, e1.errT)) + val assertExp2New = Option.when(withExp)(ast.LtCmp(esNew.get(1), ast.SeqLength(esNew.get(0))())(e1.pos, e1.info, e1.errT)) + v1.decider.assert(Less(t1, SeqLength(t0))) { case true => failure1 combine Q(s1, SeqAt(t0, t1), eNew, v1) case false => - val failure2 = failure1 combine createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, idxLtLength, idxLtLengthExpNew) + val failure2 = failure1 combine createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, Less(t1, SeqLength(t0)), assertExp2New) if (v1.reportFurtherErrors()) { - v1.decider.assume(idxLtLength, idxLtLengthExp, idxLtLengthExpNew, AssumptionType.Explicit) + v1.decider.assume(Less(t1, SeqLength(t0)), assertExp2, assertExp2New, AssumptionType.Explicit) failure2 combine Q(s1, SeqAt(t0, t1), eNew, v1) } else failure2} } else failure1}}}) @@ -1124,34 +1120,35 @@ object evaluator extends EvaluationRules { if (s1.triggerExp) { Q(s1, SeqUpdate(t0, t1, t2), eNew, v1) } else { - val idxGe0 = AtLeast(t1, IntLiteral(0)) - val idxGe0Exp = Option.when(withExp)(ast.GeCmp(e1, ast.IntLit(0)())(seqUp.pos, seqUp.info, seqUp.errT)) - val idxGe0ExpNew = Option.when(withExp)(ast.GeCmp(esNew.get(1), ast.IntLit(0)())(seqUp.pos, seqUp.info, seqUp.errT)) - val idxLtLength = Less(t1, SeqLength(t0)) - val idxLtLengthExp = Option.when(withExp)(ast.LtCmp(e1, ast.SeqLength(e0)())(seqUp.pos, seqUp.info, seqUp.errT)) - val idxLtLengthExpNew = Option.when(withExp)(ast.LtCmp(esNew.get(1), ast.SeqLength(esNew.get(0))())(seqUp.pos, seqUp.info, seqUp.errT)) - v1.decider.assert(idxGe0) { + val assertExp = Option.when(withExp)(ast.GeCmp(e1, ast.IntLit(0)())(e1.pos, e1.info, e1.errT)) + val assertExpNew = Option.when(withExp)(ast.GeCmp(esNew.get(1), ast.IntLit(0)())(e1.pos, e1.info, e1.errT)) + v1.decider.assert(AtLeast(t1, IntLiteral(0))) { case true => - v1.decider.assert(idxLtLength) { + val assertExp2New = Option.when(withExp)(ast.LtCmp(esNew.get(1), ast.SeqLength(esNew.get(0))())(e1.pos, e1.info, e1.errT)) + v1.decider.assert(Less(t1, SeqLength(t0))) { case true => Q(s1, SeqUpdate(t0, t1, t2), eNew, v1) case false => - val failure = createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, idxLtLength, idxLtLengthExpNew) + val failure = createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, Less(t1, SeqLength(t0)), assertExp2New) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { - v1.decider.assume(idxLtLength, idxLtLengthExp, idxLtLengthExpNew, AssumptionType.Internal) + val assertExp3 = Option.when(withExp)(ast.LeCmp(e1, ast.SeqLength(e0)())()) + val assertExp3New = Option.when(withExp)(ast.LeCmp(esNew.get(1), ast.SeqLength(esNew.get(0))())()) + v1.decider.assume(Less(t1, SeqLength(t0)), assertExp3, assertExp3New, AssumptionType.Explicit) failure combine Q(s1, SeqUpdate(t0, t1, t2), eNew, v1)} else failure} case false => - val failure1 = createFailure(pve dueTo SeqIndexNegative(e0, e1), v1, s1, idxGe0, idxGe0ExpNew) + val failure1 = createFailure(pve dueTo SeqIndexNegative(e0, e1), v1, s1, AtLeast(t1, IntLiteral(0)), assertExpNew) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { - v1.decider.assume(idxGe0, idxGe0Exp, idxGe0ExpNew, AssumptionType.Explicit) - v1.decider.assert(idxLtLength) { + v1.decider.assume(AtLeast(t1, IntLiteral(0)), assertExp, assertExpNew, AssumptionType.Explicit) + val assertExp2 = Option.when(withExp)(ast.LtCmp(e1, ast.SeqLength(e0)())(e1.pos, e1.info, e1.errT)) + val assertExp2New = Option.when(withExp)(ast.LtCmp(esNew.get(1), ast.SeqLength(esNew.get(0))())(e1.pos, e1.info, e1.errT)) + v1.decider.assert(Less(t1, SeqLength(t0))) { case true => failure1 combine Q(s1, SeqUpdate(t0, t1, t2), eNew, v1) case false => - val failure2 = failure1 combine createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, idxLtLength, idxLtLengthExpNew) + val failure2 = failure1 combine createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, Less(t1, SeqLength(t0)), assertExp2New) if (v1.reportFurtherErrors()) { - v1.decider.assume(idxLtLength, idxLtLengthExp, idxLtLengthExpNew, AssumptionType.Explicit) + v1.decider.assume(Less(t1, SeqLength(t0)), assertExp2, assertExp2New, AssumptionType.Explicit) failure2 combine Q(s1, SeqUpdate(t0, t1, t2), eNew, v1) } else failure2} } else failure1}}}) @@ -1263,11 +1260,11 @@ object evaluator extends EvaluationRules { Q(s1, MapLookup(baseT, keyT), esNew.map(es => ast.MapLookup(es(0), es(1))(e.pos, e.info, e.errT)), v1) case (s1, Seq(baseT, keyT), esNew, v1) => val eNew = esNew.map(es => ast.MapLookup(es(0), es(1))(e.pos, e.info, e.errT)) - val assertExp = Option.when(withExp)(ast.MapContains(key, base)(ml.pos, ml.info, ml.errT)) - val assertExpNew = Option.when(withExp)(ast.MapContains(esNew.get(1), esNew.get(0))(ml.pos, ml.info, ml.errT)) v1.decider.assert(SetIn(keyT, MapDomain(baseT))) { case true => Q(s1, MapLookup(baseT, keyT), eNew, v1) case false => + val assertExp = Option.when(withExp)(ast.MapContains(key, base)(ml.pos, ml.info, ml.errT)) + val assertExpNew = Option.when(withExp)(ast.MapContains(esNew.get(1), esNew.get(0))(ml.pos, ml.info, ml.errT)) val failure1 = createFailure(pve dueTo MapKeyNotContained(base, key), v1, s1, SetIn(keyT, MapDomain(baseT)), assertExpNew) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { v1.decider.assume(SetIn(keyT, MapDomain(baseT)), assertExp, assertExpNew, AssumptionType.Explicit) @@ -1355,16 +1352,10 @@ object evaluator extends EvaluationRules { var finalState = s3 val es2AndTriggerResult = evals(s3, es2, _ => pve, v2)((s4, ts2, es2New, v3) => { evalTriggers(s4, optTriggers.getOrElse(Nil), pve, v3)((s5, tTriggers, _) => { // TODO: v4 isn't forward - problem? - val pcs = v3.decider.pcs.after(preMark) val (auxGlobals, auxNonGlobalQuants) = - pcs.quantified(quant, tVars, tTriggers, s"$name-aux", isGlobal = false, bc, v3) -// val auxGlobals = v3.decider.assumptionAnalyzer.createLabelledConditional(v3.decider, pcs.branchConditions ++ pcs.assumptions, auxGlobalsTmp) -// val auxNonGlobalQuants: Seq[Quantification] = auxNonGlobalQuantsTmp.map(q => { -// val newBody = v3.decider.assumptionAnalyzer.createLabelledConditional(v3.decider, pcs.branchConditions ++ pcs.assumptions, q.body) -// q.copy(body=newBody) -// }) + v3.decider.pcs.after(preMark).quantified(quant, tVars, tTriggers, s"$name-aux", isGlobal = false, bc) val auxExps = - Option.when(withExp)(pcs.quantifiedExp(quant, varPairs map (_._2.get), tVars, optTriggers.getOrElse(Nil), tTriggers, s"$name-aux", isGlobal = false, bc)) + Option.when(withExp)(v3.decider.pcs.after(preMark).quantifiedExp(quant, varPairs map (_._2.get), tVars, optTriggers.getOrElse(Nil), tTriggers, s"$name-aux", isGlobal = false, bc)) val additionalPossibleTriggers: Map[ast.Exp, Term] = if (s.recordPossibleTriggers) s5.possibleTriggers else Map() es2AndTriggerTerms = Some((ts2, es2New, tTriggers, (auxGlobals, auxNonGlobalQuants), auxExps, additionalPossibleTriggers)) @@ -1522,12 +1513,13 @@ object evaluator extends EvaluationRules { v: Verifier) (Q: (State, Term, Verifier) => VerificationResult) : VerificationResult = { - val (notZeroExp, notZeroExpNew) = if (withExp) { - (Some(ast.NeCmp(eDivisor, ast.IntLit(0)())(eDivisor.pos, eDivisor.info, eDivisor.errT)), Some(ast.NeCmp(eDivisorNew.get, ast.IntLit(0)())(eDivisor.pos, eDivisor.info, eDivisor.errT))) - } else { (None, None) } + v.decider.assert(tDivisor !== tZero){ case true => Q(s, t, v) case false => + val (notZeroExp, notZeroExpNew) = if (withExp) { + (Some(ast.NeCmp(eDivisor, ast.IntLit(0)())(eDivisor.pos, eDivisor.info, eDivisor.errT)), Some(ast.NeCmp(eDivisorNew.get, ast.IntLit(0)())(eDivisor.pos, eDivisor.info, eDivisor.errT))) + } else { (None, None) } val failure = createFailure(pve dueTo DivisionByZero(eDivisor), v, s, tDivisor !== tZero, notZeroExpNew) if (s.retryLevel == 0 && v.reportFurtherErrors()) { v.decider.assume(tDivisor !== tZero, notZeroExp, notZeroExpNew, AssumptionType.Explicit) @@ -1661,7 +1653,7 @@ object evaluator extends EvaluationRules { val r = evals(s, remainingTriggerExpressions, _ => pve, v)((_, remainingTriggerTerms, _, v1) => { optRemainingTriggerTerms = Some(remainingTriggerTerms) - pcDelta = v1.decider.pcs.after(preMark).assumptions map (t => v1.decider.wrapWithAssumptionAnalysisLabel(t, Set.empty, Set(t))) //decider.π -- πPre + pcDelta = v1.decider.pcs.after(preMark).assumptions //decider.π -- πPre pcDeltaExp = v1.decider.pcs.after(preMark).assumptionExps Success()}) @@ -1707,14 +1699,10 @@ object evaluator extends EvaluationRules { val joinTerm = App(joinSymbol, joinFunctionArgs) val joinExp = joinFunctionArgsExp.map(jfa => ast.FuncApp(joinFunctionName, jfa)(ast.NoPosition, ast.NoInfo, joinType, ast.NoTrafos)) - val joinDefEqs: Seq[(Term, Option[ast.Exp], Option[ast.Exp])] = entries map (entry =>{ - // TODO ake: precision? - val branchConditions = entry.pathConditions.branchConditions - val labelledBranchCondition = v.decider.wrapWithAssumptionAnalysisLabel(And(branchConditions), Set.empty, branchConditions) - (Implies(labelledBranchCondition, BuiltinEquals(joinTerm, entry.data._1)), + val joinDefEqs: Seq[(Term, Option[ast.Exp], Option[ast.Exp])] = entries map (entry => + (Implies(And(entry.pathConditions.branchConditions), BuiltinEquals(joinTerm, entry.data._1)), Option.when(withExp)(ast.Implies(BigAnd(entry.pathConditions.branchConditionExps.map(bc => bc._1)), ast.EqCmp(joinExp.get, entry.data._2.get)())()), - Option.when(withExp)(ast.Implies(BigAnd(entry.pathConditions.branchConditionExps.map(bc => bc._2.get)), ast.EqCmp(joinExp.get, entry.data._2.get)())())) - }) + Option.when(withExp)(ast.Implies(BigAnd(entry.pathConditions.branchConditionExps.map(bc => bc._2.get)), ast.EqCmp(joinExp.get, entry.data._2.get)())()))) var sJoined = entries.tail.foldLeft(entries.head.s)((sAcc, entry) => sAcc.merge(entry.s)) diff --git a/src/main/scala/rules/Joiner.scala b/src/main/scala/rules/Joiner.scala index 04dbd5cd5..d8abd2483 100644 --- a/src/main/scala/rules/Joiner.scala +++ b/src/main/scala/rules/Joiner.scala @@ -99,15 +99,12 @@ object joiner extends JoiningRules { var feasibleBranchesExpNew: Option[List[ast.Exp]] = Option.when(withExp)(Nil) entries foreach (entry => { - val (pcs, pcsDependencies) = entry.pathConditions.conditionalizedWithAnalysis + val pcs = entry.pathConditions.conditionalized val pcsExp = Option.when(withExp)(entry.pathConditions.conditionalizedExp) val comment = "Joined path conditions" v.decider.prover.comment(comment) - val pcsLabelled = pcs map (pcsEntry => v.decider.wrapWithAssumptionAnalysisLabel(pcsEntry, Set.empty, pcsDependencies)) - v.decider.assume(pcsLabelled, pcsExp, comment, enforceAssumption = false, assumptionType=AssumptionType.Internal) - val branchConditions = entry.pathConditions.branchConditions - val branchConditionsLabelled = v.decider.wrapWithAssumptionAnalysisLabel(And(branchConditions), Set.empty, branchConditions) - feasibleBranches = branchConditionsLabelled :: feasibleBranches + v.decider.assume(pcs, Option.when(withExp)(DebugExp.createInstance(comment, InsertionOrderedSet(pcsExp.get))), enforceAssumption = false, AssumptionType.Internal) + feasibleBranches = And(entry.pathConditions.branchConditions) :: feasibleBranches feasibleBranchesExp = feasibleBranchesExp.map(fbe => BigAnd(entry.pathConditions.branchConditionExps.map(_._1)) :: fbe) feasibleBranchesExpNew = feasibleBranchesExpNew.map(fbe => BigAnd(entry.pathConditions.branchConditionExps.map(_._2.get)) :: fbe) }) diff --git a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala index aa386fafe..7d530bc04 100644 --- a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala +++ b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala @@ -105,7 +105,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { And(ch.args.zip(args).map { case (t1, t2) => t1 === t2 }) summarisingSnapshotDefinitions :+= - v.decider.wrapWithAssumptionAnalysisLabel(Implies(And(argumentEqualities, IsPositive(ch.perm)), `?s` === ch.snap), Set(ch)) + Implies(And(argumentEqualities, IsPositive(ch.perm)), `?s` === ch.snap) }) val taggedSummarisingSnapshot = @@ -217,15 +217,13 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { createFailure(ve, v, s, False, "branch is dead") } } else { - summarise(s, relevantChunks, resource, args, argsExp, None, v)((s1, snap, permSum, permSumExp, v1) => { - val assertExp = permSumExp.map(e => IsPositive(e)(e.pos, e.info, e.errT)) + summarise(s, relevantChunks, resource, args, argsExp, None, v)((s1, snap, permSum, permSumExp, v1) => v.decider.assert(IsPositive(permSum), assumptionType) { case true => Q(s1, snap, v1) case false => - createFailure(ve, v, s1, IsPositive(permSum), assertExp) - } - }) + createFailure(ve, v, s1, IsPositive(permSum), permSumExp.map(IsPositive(_)())) + }) } } @@ -308,11 +306,10 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { } if (relevantChunks.isEmpty) { - val assertExp = permsExp.map(pe => ast.EqCmp(pe, ast.NoPerm()())(pe.pos, pe.info, pe.errT)) // if no permission is exhaled, return none v.decider.assert(perms === NoPerm, assumptionType) { case true => Q(s, h, None, Seq.empty, v) - case false => createFailure(ve, v, s, perms === NoPerm, assertExp) + case false => createFailure(ve, v, s, perms === NoPerm, permsExp.map(pe => ast.EqCmp(pe, ast.NoPerm()())(pe.pos, pe.info, pe.errT))) } } else { if (!terms.utils.consumeExactRead(perms, s.constrainableARPs)) { @@ -424,12 +421,11 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { if (!moreNeeded) { Q(s0, newHeap, None, consumedChunks, v) } else { - val assertExp = pNeededExp.map(pn => ast.EqCmp(pn, ast.NoPerm()())(pn.pos, pn.info, pn.errT)) v.decider.assert(pNeeded === NoPerm, assumptionType) { case true => Q(s0, newHeap, None, consumedChunks, v) case false => - createFailure(ve, v, s0, pNeeded === NoPerm, assertExp) + createFailure(ve, v, s0, pNeeded === NoPerm, pNeededExp.map(pn => ast.EqCmp(pn, ast.NoPerm()())(pn.pos, pn.info, pn.errT))) } } } @@ -519,7 +515,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { } case false => v.decider.finishDebugSubExp(s"consume permissions for ${resource.toString()}") - createFailure(ve, v, s, totalPermTaken !== NoPerm, totalPermTakenExp.map(tpt => ast.NeCmp(tpt, ast.NoPerm()())(tpt.pos, tpt.info, tpt.errT))) + createFailure(ve, v, s, totalPermTaken !== NoPerm, totalPermTakenExp.map(tpt => ast.NeCmp(tpt, ast.NoPerm()())())) } } diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 81cb29a2e..9f00362b3 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -551,18 +551,17 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { Forall( codomainQVar, - v.decider.wrapWithAssumptionAnalysisLabel(Implies(effectiveCondition, BuiltinEquals(lookupSummary, lookupChunk)), Set(chunk)), + Implies(effectiveCondition, BuiltinEquals(lookupSummary, lookupChunk)), if (Verifier.config.disableISCTriggers()) Nil else Seq(Trigger(lookupSummary), Trigger(lookupChunk)), s"qp.fvfValDef${v.counter(this).next()}", isGlobal = relevantQvars.isEmpty) }) val resourceAndValueDefinitions = if (s.heapDependentTriggers.contains(field)){ - val chunkTriggers = relevantChunks map (chunk => v.decider.wrapWithAssumptionAnalysisLabel(FieldTrigger(field.name, chunk.snapshotMap, codomainQVar), Set(chunk))) val resourceTriggerDefinition = Forall( codomainQVar, - And(chunkTriggers), + And(relevantChunks map (chunk => FieldTrigger(field.name, chunk.snapshotMap, codomainQVar))), Trigger(Lookup(field.name, sm, codomainQVar)), s"qp.fvfResTrgDef${v.counter(this).next()}", isGlobal = relevantQvars.isEmpty) @@ -639,9 +638,9 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { transformedOptSmDomainDefinitionCondition.getOrElse(True), /* Alternatively: qvarInDomainOfSummarisingSm */ IsPositive(chunk.perm).replace(snapToCodomainTermsSubstitution)) - val term = v.decider.wrapWithAssumptionAnalysisLabel(Implies(effectiveCondition, And(snapshotNotUnit, BuiltinEquals(lookupSummary, lookupChunk))), Set(chunk)) Forall( - qvar, term, + qvar, + Implies(effectiveCondition, And(snapshotNotUnit, BuiltinEquals(lookupSummary, lookupChunk))), if (Verifier.config.disableISCTriggers()) Nil else Seq(Trigger(lookupSummary), Trigger(lookupChunk)), s"qp.psmValDef${v.counter(this).next()}", isGlobal = relevantQvars.isEmpty) @@ -652,12 +651,10 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { case r => r } val resourceAndValueDefinitions = if (s.heapDependentTriggers.contains(resourceIdentifier)){ - - val chunkTriggers = relevantChunks map (chunk => v.decider.wrapWithAssumptionAnalysisLabel(ResourceTriggerFunction(resource, chunk.snapshotMap, Seq(qvar), s.program), Set(chunk))) val resourceTriggerDefinition = Forall( qvar, - And(chunkTriggers), + And(relevantChunks map (chunk => ResourceTriggerFunction(resource, chunk.snapshotMap, Seq(qvar), s.program))), Trigger(ResourceLookup(resource, sm, Seq(qvar), s.program)), s"qp.psmResTrgDef${v.counter(this).next()}", isGlobal = relevantQvars.isEmpty) @@ -693,11 +690,10 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val permSummary = ResourcePermissionLookup(resource, pm, codomainQVars, s.program) - val chunkPerms = relevantChunks map (chunk => chunk.perm) val valueDefinitions = Forall( codomainQVars, - permSummary === BigPermSum(chunkPerms), + permSummary === BigPermSum(relevantChunks map (_.perm)), Trigger(permSummary), s"qp.resPrmSumDef${v.counter(this).next()}", isGlobal = true) @@ -711,11 +707,12 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { // TODO: Quantify over snapshot if resource is predicate. // Also check other places where a similar quantifier is constructed. - val chunkTriggerDefs = relevantChunks map (chunk => v.decider.wrapWithAssumptionAnalysisLabel(ResourceTriggerFunction(resource, chunk.snapshotMap, codomainQVars, s.program), Set(chunk))) val resourceTriggerDefinition = Forall( codomainQVars, - And(resourceTriggerFunction +: chunkTriggerDefs), + And(resourceTriggerFunction +: + relevantChunks.map(chunk => + ResourceTriggerFunction(resource, chunk.snapshotMap, codomainQVars, s.program))), Trigger(ResourcePermissionLookup(resource, pm, codomainQVars, s.program)), s"qp.resTrgDef${v.counter(this).next()}", isGlobal = true) @@ -836,9 +833,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { quantifiedChunkSupporter.summarise( s, relevantChunks, codomainQVars, resource, optSmDomainDefinitionCondition, v) val smDef = SnapshotMapDefinition(resource, sm, valueDefs, optDomainDefinition.toSeq) - - val chunkPerms = relevantChunks map (chunk => chunk.perm) - val totalPermissions = BigPermSum(chunkPerms) + val totalPermissions = BigPermSum(relevantChunks.map(_.perm)) if (Verifier.config.disableValueMapCaching()) { (smDef, s.smCache) @@ -1046,9 +1041,9 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { } val comment = "Check receiver injectivity" v.decider.prover.comment(comment) - val debugExp = Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)) - v.decider.assume(FunctionPreconditionTransformer.transform(receiverInjectivityCheck, s.program), debugExp, AssumptionType.Internal) - v.decider.assert(receiverInjectivityCheck, timeout=Verifier.config.checkTimeout.toOption) { + v.decider.assume(FunctionPreconditionTransformer.transform(receiverInjectivityCheck, s.program), + Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), AssumptionType.Internal) + v.decider.assert(receiverInjectivityCheck) { case true => val ax = inverseFunctions.axiomInversesOfInvertibles val inv = inverseFunctions.copy(axiomInversesOfInvertibles = Forall(ax.vars, ax.body, effectiveTriggers)) @@ -1155,7 +1150,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val interpreter = new NonQuantifiedPropertyInterpreter(h1.values, v) val resourceDescription = Resources.resourceDescriptions(ch.resourceID) val pcs = interpreter.buildPathConditionsForChunk(ch, resourceDescription.instanceProperties(s.mayAssumeUpperBounds)) - pcs.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), AssumptionType.PathCondition)) + pcs.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), assumptionType)) val resourceIdentifier = resource match { case wand: ast.MagicWand => MagicWandIdentifier(wand, s.program) @@ -1304,9 +1299,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { qidPrefix = qid, program = s.program) v.decider.prover.comment("Check receiver injectivity") - val debugExp = Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)) - v.decider.assume(FunctionPreconditionTransformer.transform(receiverInjectivityCheck, s.program), debugExp, AssumptionType.Internal) - v.decider.assert(receiverInjectivityCheck, assumptionType, Verifier.config.checkTimeout.toOption) { + v.decider.assume(FunctionPreconditionTransformer.transform(receiverInjectivityCheck, s.program), Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), AssumptionType.Internal) + v.decider.assert(receiverInjectivityCheck, assumptionType) { case true => val qvarsToInvOfLoc = inverseFunctions.qvarsToInversesOf(formalQVars) val condOfInvOfLoc = tCond.replace(qvarsToInvOfLoc) diff --git a/src/main/scala/rules/StateConsolidator.scala b/src/main/scala/rules/StateConsolidator.scala index 805e19244..6f3d7b960 100644 --- a/src/main/scala/rules/StateConsolidator.scala +++ b/src/main/scala/rules/StateConsolidator.scala @@ -101,16 +101,12 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol mergedChunks.filter(_.isInstanceOf[BasicChunk]) foreach { case ch: BasicChunk => val resource = Resources.resourceDescriptions(ch.resourceID) val pathCond = interpreter.buildPathConditionsForChunk(ch, resource.instanceProperties(s.mayAssumeUpperBounds)) - v.decider.assumptionAnalyzer.disableTransitiveEdges() - pathCond.foreach(p => v.decider.assume(v.decider.wrapWithAssumptionAnalysisLabel(p._1, Set(ch)), Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), AssumptionType.Internal)) - v.decider.assumptionAnalyzer.enableTransitiveEdges() + pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), AssumptionType.Internal)) } Resources.resourceDescriptions foreach { case (id, desc) => val pathCond = interpreter.buildPathConditionsForResource(id, desc.delayedProperties(s.mayAssumeUpperBounds)) - v.decider.assumptionAnalyzer.disableTransitiveEdges() pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), AssumptionType.Internal)) - v.decider.assumptionAnalyzer.enableTransitiveEdges() } v.symbExLog.closeScope(sepIdentifier) diff --git a/src/main/scala/state/State.scala b/src/main/scala/state/State.scala index 5dc2d234e..a0a32284d 100644 --- a/src/main/scala/state/State.scala +++ b/src/main/scala/state/State.scala @@ -369,11 +369,11 @@ object State { val smDomainNeeded3 = smDomainNeeded1 || smDomainNeeded2 - val conditions1 = analysisInfo.decider.wrapWithAssumptionAnalysisLabel(And(pc1.branchConditions), Set.empty, pc1.branchConditions) + val conditions1 = And(pc1.branchConditions) val withExp = Verifier.config.enableDebugging() val conditions1Exp = if (withExp) Some(BigAnd(pc1.branchConditionExps.map(_._2.get))) else None - val conditions2 = analysisInfo.decider.wrapWithAssumptionAnalysisLabel(And(pc2.branchConditions), Set.empty, pc2.branchConditions) - val conditions2Exp = if (withExp) Some(BigAnd(pc2.branchConditionExps.map(_._2.get))) else None + val conditions2 = And(pc2.branchConditions) + val conditions2Exp = if (withExp) Some(BigAnd(pc2.branchConditionExps.map(_._2.get))) else None val mergeStore = (g1: Store, g2: Store) => { Store(mergeMaps(g1.values, (conditions1, conditions1Exp), g2.values, (conditions2, conditions2Exp)) From e40b848d5253e94106eed27012a04e06db79ced5 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 10 Jul 2025 17:30:10 +0200 Subject: [PATCH 155/474] revert consumed chunk changes --- src/main/scala/rules/ChunkSupporter.scala | 32 ++--- src/main/scala/rules/Consumer.scala | 109 +++++++++--------- src/main/scala/rules/Evaluator.scala | 12 +- src/main/scala/rules/Executor.scala | 24 ++-- src/main/scala/rules/MagicWandSupporter.scala | 8 +- .../rules/MoreCompleteExhaleSupporter.scala | 29 +++-- src/main/scala/rules/PredicateSupporter.scala | 9 +- .../scala/rules/QuantifiedChunkSupport.scala | 48 ++++---- .../scala/supporters/MethodSupporter.scala | 7 +- .../functions/FunctionVerificationUnit.scala | 3 +- 10 files changed, 138 insertions(+), 143 deletions(-) diff --git a/src/main/scala/rules/ChunkSupporter.scala b/src/main/scala/rules/ChunkSupporter.scala index 5e5088b8e..af75929e8 100644 --- a/src/main/scala/rules/ChunkSupporter.scala +++ b/src/main/scala/rules/ChunkSupporter.scala @@ -36,7 +36,7 @@ trait ChunkSupportRules extends SymbolicExecutionRules { v: Verifier, description: String, assumptionType: AssumptionType=AssumptionType.Implicit) - (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) + (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult def produce(s: State, h: Heap, ch: NonQuantifiedChunk, v: Verifier) @@ -81,12 +81,12 @@ object chunkSupporter extends ChunkSupportRules { v: Verifier, description: String, assumptionType: AssumptionType=AssumptionType.Implicit) - (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) + (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { - consume2(s, h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, assumptionType)((s2, h2, optSnap, consumedChunks, v2) => + consume2(s, h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, assumptionType)((s2, h2, optSnap, v2) => optSnap match { case Some(snap) => - Q(s2, h2, Some(snap.convert(sorts.Snap)), consumedChunks, v2) + Q(s2, h2, Some(snap.convert(sorts.Snap)), v2) case None if returnSnap => /* Not having consumed anything could mean that we are in an infeasible * branch, or that the permission amount to consume was zero. @@ -97,8 +97,8 @@ object chunkSupporter extends ChunkSupportRules { */ val fresh = v2.decider.fresh(sorts.Snap, Option.when(withExp)(PUnknown())) val s3 = s2.copy(functionRecorder = s2.functionRecorder.recordFreshSnapshot(fresh.applicable)) - Q(s3, h2, Some(fresh), consumedChunks, v2) - case None => Q(s2, h2, None, consumedChunks, v2) + Q(s3, h2, Some(fresh), v2) + case None => Q(s2, h2, None, v2) }) } @@ -113,22 +113,23 @@ object chunkSupporter extends ChunkSupportRules { ve: VerificationError, v: Verifier, assumptionType: AssumptionType) - (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) + (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { + val id = ChunkIdentifier(resource, s.program) if (s.exhaleExt) { val failure = createFailure(ve, v, s, "chunk consume in package") magicWandSupporter.transfer(s, perms, permsExp, failure, Seq(), v)(consumeGreedy(_, _, id, args, _, _, _, assumptionType))((s1, optCh, v1) => - if (returnSnap){ // TODO ake: check that optCh is indeed the consumed chunks - Q(s1, h, optCh.flatMap(ch => Some(ch.snap)), optCh, v1) + if (returnSnap){ + Q(s1, h, optCh.flatMap(ch => Some(ch.snap)), v1) } else { - Q(s1, h, None, optCh, v1) + Q(s1, h, None, v1) }) } else { - executionFlowController.tryOrFail3[Heap, Option[Term], Iterable[Chunk]](s.copy(h = h), v)((s1, v1, QS) => + executionFlowController.tryOrFail2[Heap, Option[Term]](s.copy(h = h), v)((s1, v1, QS) => if (s1.moreCompleteExhale) { - moreCompleteExhaleSupporter.consumeComplete(s1, s1.h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v1, assumptionType)((s2, h2, snap2, consumedChunks, v2) => { - QS(s2.copy(h = s.h), h2, snap2, consumedChunks, v2) + moreCompleteExhaleSupporter.consumeComplete(s1, s1.h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v1, assumptionType)((s2, h2, snap2, v2) => { + QS(s2.copy(h = s.h), h2, snap2, v2) }) } else { consumeGreedy(s1, s1.h, id, args, perms, permsExp, v1, assumptionType) match { @@ -142,10 +143,10 @@ object chunkSupporter extends ChunkSupportRules { } case _ => None } - QS(s2.copy(h = s.h), h2, snap, optCh2.toList, v1) + QS(s2.copy(h = s.h), h2, snap, v1) case (_, s2, h2, _) if v1.decider.checkSmoke(isAssert=true, assumptionType) => if(Verifier.config.enableAssumptionAnalysis()) - QS(s2.copy(h = s.h), h2, None, List.empty, v1) + QS(s2.copy(h = s.h), h2, None, v1) else Success() // TODO: Mark branch as dead? case _ => @@ -165,6 +166,7 @@ object chunkSupporter extends ChunkSupportRules { v: Verifier, assumptionType: AssumptionType) : (ConsumptionResult, State, Heap, Option[NonQuantifiedChunk]) = { + val consumeExact = terms.utils.consumeExactRead(perms, s.constrainableARPs) def assumeProperties(chunk: NonQuantifiedChunk, heap: Heap): Unit = { diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index 445308906..bf581f394 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -36,13 +36,13 @@ trait ConsumptionRules extends SymbolicExecutionRules { * @param v The verifier to use. * @param assumptionType The assumption type used for assumption analysis and proof coverage * @param Q The continuation to invoke if the consumption succeeded, with the following - * arguments: state (1st argument) and verifier (4th argument) resulting from the - * consumption, a heap snapshot (2bd argument )representing the values of the - * consumed partial heap, and the chunks (3rd argument) that were consumed + * arguments: state (1st argument) and verifier (3rd argument) resulting from the + * consumption, and a heap snapshot (2bd argument )representing the values of the + * consumed partial heap. * @return The result of the continuation. */ def consume(s: State, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, assumptionType: AssumptionType=AssumptionType.Implicit) - (Q: (State, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) + (Q: (State, Option[Term], Verifier) => VerificationResult) : VerificationResult /** Subsequently consumes the assertions `as` (from head to tail), starting in state `s`. @@ -67,7 +67,7 @@ trait ConsumptionRules extends SymbolicExecutionRules { pvef: ast.Exp => PartialVerificationError, v: Verifier, assumptionType: AssumptionType=AssumptionType.Implicit) - (Q: (State, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) + (Q: (State, Option[Term], Verifier) => VerificationResult) : VerificationResult } @@ -81,13 +81,13 @@ object consumer extends ConsumptionRules { /** @inheritdoc */ def consume(s: State, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, assumptionType: AssumptionType=AssumptionType.Implicit) - (Q: (State, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) + (Q: (State, Option[Term], Verifier) => VerificationResult) : VerificationResult = { - consumeR(s, s.h, a.whenExhaling, returnSnap, pve, v, assumptionType)((s1, h1, snap, consumedChunks, v1) => { + consumeR(s, s.h, a.whenExhaling, returnSnap, pve, v, assumptionType)((s1, h1, snap, v1) => { val s2 = s1.copy(h = h1, partiallyConsumedHeap = s.partiallyConsumedHeap) - Q(s2, snap, consumedChunks, v1)}) + Q(s2, snap, v1)}) } /** @inheritdoc */ @@ -97,7 +97,7 @@ object consumer extends ConsumptionRules { pvef: ast.Exp => PartialVerificationError, v: Verifier, assumptionType: AssumptionType=AssumptionType.Implicit) - (Q: (State, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) + (Q: (State, Option[Term], Verifier) => VerificationResult) : VerificationResult = { val allTlcs = mutable.ListBuffer[ast.Exp]() @@ -111,10 +111,10 @@ object consumer extends ConsumptionRules { allPves ++= pves }) - consumeTlcs(s, s.h, allTlcs.result(), returnSnap, allPves.result(), v, assumptionType)((s1, h1, snap1, chunks, v1) => { + consumeTlcs(s, s.h, allTlcs.result(), returnSnap, allPves.result(), v, assumptionType)((s1, h1, snap1, v1) => { val s2 = s1.copy(h = h1, partiallyConsumedHeap = s.partiallyConsumedHeap) - Q(s2, snap1, chunks, v1) + Q(s2, snap1, v1) }) } @@ -125,11 +125,11 @@ object consumer extends ConsumptionRules { pves: Seq[PartialVerificationError], v: Verifier, assumptionType: AssumptionType) - (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) + (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { if (tlcs.isEmpty) - Q(s, h, if (returnSnap) Some(Unit) else None, Seq.empty, v) + Q(s, h, if (returnSnap) Some(Unit) else None, v) else { val a = tlcs.head val pve = pves.head @@ -137,12 +137,12 @@ object consumer extends ConsumptionRules { if (tlcs.tail.isEmpty) wrappedConsumeTlc(s, h, a, returnSnap, pve, v, assumptionType)(Q) else - wrappedConsumeTlc(s, h, a, returnSnap, pve, v, assumptionType)((s1, h1, snap1, consumedChunksHead, v1) => { - consumeTlcs(s1, h1, tlcs.tail, returnSnap, pves.tail, v1, assumptionType)((s2, h2, snap2, consumedChunksTail, v2) => + wrappedConsumeTlc(s, h, a, returnSnap, pve, v, assumptionType)((s1, h1, snap1, v1) => { + consumeTlcs(s1, h1, tlcs.tail, returnSnap, pves.tail, v1, assumptionType)((s2, h2, snap2, v2) => (snap1, snap2) match { - case (Some(sn1), Some(sn2)) if returnSnap => Q(s2, h2, Some(Combine(sn1, sn2)), consumedChunksHead ++ consumedChunksTail, v2) - case (None, None) if !returnSnap => Q(s2, h2, None, consumedChunksHead ++ consumedChunksTail, v2) + case (Some(sn1), Some(sn2)) if returnSnap => Q(s2, h2, Some(Combine(sn1, sn2)), v2) + case (None, None) if !returnSnap => Q(s2, h2, None, v2) case (_, _) => sys.error(s"Consume returned unexpected snapshot: ${(returnSnap, (snap1, snap2))}") }) }) @@ -150,7 +150,7 @@ object consumer extends ConsumptionRules { } private def consumeR(s: State, h: Heap, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, assumptionType: AssumptionType=AssumptionType.Implicit) - (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) + (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { val tlcs = a.topLevelConjuncts @@ -170,7 +170,7 @@ object consumer extends ConsumptionRules { pve: PartialVerificationError, v: Verifier, assumptionType: AssumptionType) - (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) + (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { /* tryOrFail effects the "main" heap s.h, so we temporarily set the consume-heap h to be the @@ -178,7 +178,7 @@ object consumer extends ConsumptionRules { * consume. */ val sInit = s.copy(h = h) - executionFlowController.tryOrFail3[Heap, Option[Term], Iterable[Chunk]](sInit, v)((s0, v1, QS) => { + executionFlowController.tryOrFail2[Heap, Option[Term]](sInit, v)((s0, v1, QS) => { val h0 = s0.h /* h0 is h, but potentially consolidated */ val s1 = s0.copy(h = s.h) /* s1 is s, but the retrying flag might be set */ @@ -186,15 +186,15 @@ object consumer extends ConsumptionRules { val sourceInfo = ExpAnalysisSourceInfo(a) v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo) - consumeTlc(s1, h0, a, returnSnap, pve, v1, assumptionType)((s2, h2, snap2, consumedChunks, v2) => { + consumeTlc(s1, h0, a, returnSnap, pve, v1, assumptionType)((s2, h2, snap2, v2) => { v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) v2.symbExLog.closeScope(sepIdentifier) - QS(s2, h2, snap2, consumedChunks, v2)}) + QS(s2, h2, snap2, v2)}) })(Q) } private def consumeTlc(s: State, h: Heap, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, assumptionType: AssumptionType) - (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) + (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { /* ATTENTION: Expressions such as `perm(...)` must be evaluated in-place, @@ -222,13 +222,13 @@ object consumer extends ConsumptionRules { evaluator.eval(s, e0, pve, v)((s1, t0, e0New, v1) => branch(s1, t0, (e0, e0New), v1)( - (s2, v2) => consumeR(s2, h, a0, returnSnap, pve, v2, assumptionType)((s3, h1, t1, consumedChunks, v3) => { + (s2, v2) => consumeR(s2, h, a0, returnSnap, pve, v2, assumptionType)((s3, h1, t1, v3) => { v3.symbExLog.closeScope(uidImplies) - Q(s3, h1, t1, consumedChunks, v3) + Q(s3, h1, t1, v3) }), (s2, v2) => { v2.symbExLog.closeScope(uidImplies) - Q(s2, h, if (returnSnap) Some(Unit) else None, Seq.empty, v2) + Q(s2, h, if (returnSnap) Some(Unit) else None, v2) })) case ite @ ast.CondExp(e0, a1, a2) if !a.isPure && s.moreJoins.id >= JoinMode.Impure.id => @@ -242,13 +242,13 @@ object consumer extends ConsumptionRules { eval(s, e0, pve, v)((s1, t0, e0New, v1) => branch(s1, t0, (e0, e0New), v1)( - (s2, v2) => consumeR(s2, h, a1, returnSnap, pve, v2, assumptionType)((s3, h1, t1, consumedChunks, v3) => { + (s2, v2) => consumeR(s2, h, a1, returnSnap, pve, v2, assumptionType)((s3, h1, t1, v3) => { v3.symbExLog.closeScope(uidCondExp) - Q(s3, h1, t1, consumedChunks, v3) + Q(s3, h1, t1, v3) }), - (s2, v2) => consumeR(s2, h, a2, returnSnap, pve, v2, assumptionType)((s3, h1, t1, consumedChunks, v3) => { + (s2, v2) => consumeR(s2, h, a2, returnSnap, pve, v2, assumptionType)((s3, h1, t1, v3) => { v3.symbExLog.closeScope(uidCondExp) - Q(s3, h1, t1, consumedChunks, v3) + Q(s3, h1, t1, v3) }))) /* TODO: Initial handling of QPs is identical/very similar in consumer @@ -290,8 +290,8 @@ object consumer extends ConsumptionRules { notInjectiveReason = QPAssertionNotInjective(acc.loc), insufficientPermissionReason = InsufficientPermission(acc.loc), v1, - assumptionType)((s2, h2, snap, consumedChunks, v2) => Q(s2.copy(constrainableARPs = s.constrainableARPs), h2, snap, consumedChunks, v2)) - case (s1, _, _, _, _, None, v1) => Q(s1, h, if (returnSnap) Some(Unit) else None, Seq.empty, v1) + assumptionType)((s2, h2, snap, v2) => Q(s2.copy(constrainableARPs = s.constrainableARPs), h2, snap, v2)) + case (s1, _, _, _, _, None, v1) => Q(s1, h, if (returnSnap) Some(Unit) else None, v1) } case QuantifiedPermissionAssertion(forall, cond, acc: ast.PredicateAccessPredicate) => @@ -337,8 +337,8 @@ object consumer extends ConsumptionRules { notInjectiveReason = QPAssertionNotInjective(acc.loc), insufficientPermissionReason = InsufficientPermission(acc.loc), v1, - assumptionType)((s2, h2, snap, consumedChunks, v2) => Q(s2.copy(constrainableARPs = s.constrainableARPs), h2, snap, consumedChunks, v2)) - case (s1, _, _, _, _, None, v1) => Q(s1, h, if (returnSnap) Some(Unit) else None, Seq.empty, v1) + assumptionType)((s2, h2, snap, v2) => Q(s2.copy(constrainableARPs = s.constrainableARPs), h2, snap, v2)) + case (s1, _, _, _, _, None, v1) => Q(s1, h, if (returnSnap) Some(Unit) else None, v1) } case QuantifiedPermissionAssertion(forall, cond, wand: ast.MagicWand) => @@ -380,8 +380,8 @@ object consumer extends ConsumptionRules { notInjectiveReason = sys.error("Quantified wand not injective"), /*ReceiverNotInjective(...)*/ insufficientPermissionReason = MagicWandChunkNotFound(wand), /*InsufficientPermission(...)*/ v1, - assumptionType)((s2, h2, snap, consumedChunks, v2) => Q(s2.copy(constrainableARPs = s.constrainableARPs), h2, snap, consumedChunks, v2)) - case (s1, _, _, _, _, None, v1) => Q(s1.copy(constrainableARPs = s.constrainableARPs), h, if (returnSnap) Some(Unit) else None, Seq.empty, v1) + assumptionType)((s2, h2, snap, v2) => Q(s2.copy(constrainableARPs = s.constrainableARPs), h2, snap, v2)) + case (s1, _, _, _, _, None, v1) => Q(s1.copy(constrainableARPs = s.constrainableARPs), h, if (returnSnap) Some(Unit) else None, v1) } case accPred@ast.AccessPredicate(loc @ ast.FieldAccess(eRcvr, field), ePerm) @@ -422,10 +422,10 @@ object consumer extends ConsumptionRules { pve, v2, assumptionType - )((s3, h3, snap, consumedChunks, v3) => { + )((s3, h3, snap, v3) => { val s4 = s3.copy(constrainableARPs = s1.constrainableARPs, partiallyConsumedHeap = Some(h3)) - Q(s4, h3, snap, consumedChunks, v3)})})) + Q(s4, h3, snap, v3)})})) case ast.AccessPredicate(loc @ ast.PredicateAccess(eArgs, predname), ePerm) if s.qpPredicates.contains(s.program.findPredicate(predname)) => @@ -468,10 +468,10 @@ object consumer extends ConsumptionRules { pve, v2, assumptionType - )((s3, h3, snap, consumedChunks, v3) => { + )((s3, h3, snap, v3) => { val s4 = s3.copy(constrainableARPs = s1.constrainableARPs, partiallyConsumedHeap = Some(h3)) - Q(s4, h3, snap, consumedChunks, v3)})})) + Q(s4, h3, snap, v3)})})) case let: ast.Let if !let.isPure => letSupporter.handle[ast.Exp](s, let, pve, v)((s1, g1, body, v1) => { @@ -490,10 +490,10 @@ object consumer extends ConsumptionRules { val lossExp = permNew.map(p => ast.PermMul(p, s3.permissionScalingFactorExp.get)(p.pos, p.info, p.errT)) val ve = pve dueTo InsufficientPermission(locacc) val description = s"consume ${a.pos}: $a" - chunkSupporter.consume(s3, h, resource, tArgs, eArgs, loss, lossExp, returnSnap, ve, v3, description, assumptionType)((s4, h1, snap1, consumedChunks, v4) => { + chunkSupporter.consume(s3, h, resource, tArgs, eArgs, loss, lossExp, returnSnap, ve, v3, description, assumptionType)((s4, h1, snap1, v4) => { val s5 = s4.copy(partiallyConsumedHeap = Some(h1), constrainableARPs = s.constrainableARPs) - Q(s5, h1, snap1, consumedChunks, v4)})}))) + Q(s5, h1, snap1, v4)})}))) case _: ast.InhaleExhaleExp => createFailure(viper.silicon.utils.consistency.createUnexpectedInhaleExhaleExpressionError(a), v, s, "valid AST") @@ -535,10 +535,10 @@ object consumer extends ConsumptionRules { pve, v1, assumptionType - )((s3, h3, snap, consumedChunks, v3) => { + )((s3, h3, snap, v3) => { val s4 = s3.copy(constrainableARPs = s1.constrainableARPs, partiallyConsumedHeap = Some(h3)) - Q(s4, h3, snap, consumedChunks, v3)})}) + Q(s4, h3, snap, v3)})}) case wand: ast.MagicWand => magicWandSupporter.evaluateWandArguments(s, wand, pve, v)((s1, tArgs, eArgs, v1) => { @@ -549,7 +549,7 @@ object consumer extends ConsumptionRules { case _ => evalAndAssert(s, a, returnSnap, pve, v, assumptionType)((s1, t, v1) => { - Q(s1, h, t, Seq.empty, v1) + Q(s1, h, t, v1) }) } consumed @@ -559,25 +559,25 @@ object consumer extends ConsumptionRules { returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, assumptionType: AssumptionType) - (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) + (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { eval(s, e0, pve, v)((s1, t0, e0New, v1) => - joiner.join[(Heap, Option[Term], Iterable[Chunk]), (Heap, Option[Term], Iterable[Chunk])](s1, v1, resetState = false)((s1, v1, QB) => { + joiner.join[(Heap, Option[Term]), (Heap, Option[Term])](s1, v1, resetState = false)((s1, v1, QB) => { branch(s1.copy(parallelizeBranches = false), t0, (e0, e0New), v1)( (s2, v2) => - consumeR(s2.copy(parallelizeBranches = s1.parallelizeBranches), h, a1, returnSnap, pve, v2, assumptionType)((s3, h1, t1, consumedChunks, v3) => { + consumeR(s2.copy(parallelizeBranches = s1.parallelizeBranches), h, a1, returnSnap, pve, v2, assumptionType)((s3, h1, t1, v3) => { v3.symbExLog.closeScope(scopeUid) - QB(s3, (h1, t1, consumedChunks), v3) + QB(s3, (h1, t1), v3) }), (s2, v2) => a2 match { - case Some(a2) => consumeR(s2.copy(parallelizeBranches = s1.parallelizeBranches), h, a2, returnSnap, pve, v2, assumptionType)((s3, h1, t1, consumedChunks, v3) => { + case Some(a2) => consumeR(s2.copy(parallelizeBranches = s1.parallelizeBranches), h, a2, returnSnap, pve, v2, assumptionType)((s3, h1, t1, v3) => { v3.symbExLog.closeScope(scopeUid) - QB(s3, (h1, t1, consumedChunks), v3) + QB(s3, (h1, t1), v3) }) case None => v2.symbExLog.closeScope(scopeUid) - QB(s2.copy(parallelizeBranches = s1.parallelizeBranches), (h, if (returnSnap) Some(Unit) else None, Seq.empty), v2) + QB(s2.copy(parallelizeBranches = s1.parallelizeBranches), (h, if (returnSnap) Some(Unit) else None), v2) }) })(entries => { val s2 = entries match { @@ -595,8 +595,7 @@ object consumer extends ConsumptionRules { case (Some(t1), Some(t2)) if returnSnap => Some(Ite(And(entry1.pathConditions.branchConditions), t1, t2)) case (None, None) if !returnSnap => None case (_, _) => sys.error(s"Unexpected join data entries: $entries") - }, - entry1.data._3 ++ entry2.data._3 + } ) (entry1.pathConditionAwareMergeWithoutConsolidation(entry2, v1), mergedData) case _ => @@ -604,7 +603,7 @@ object consumer extends ConsumptionRules { } s2 })((s4, data, v4) => { - Q(s4, data._1, data._2, data._3, v4) + Q(s4, data._1, data._2, v4) }) ) } diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 528ad7aeb..0588f7eb2 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -936,7 +936,7 @@ object evaluator extends EvaluationRules { moreJoins = JoinMode.Off, assertReadAccessOnly = if (Verifier.config.respectFunctionPrePermAmounts()) s2.assertReadAccessOnly /* should currently always be false */ else true) - consumes(s3, pres, true, _ => pvePre, v2)((s4, snap, consumedChunks, v3) => { // TODO ake: add edges from consumedChunks + consumes(s3, pres, true, _ => pvePre, v2)((s4, snap, v3) => { val snap1 = snap.get.convert(sorts.Snap) val preFApp = App(functionSupporter.preconditionVersion(v3.symbolConverter.toFunction(func)), snap1 :: tArgs) val preExp = Option.when(withExp)({ @@ -975,7 +975,7 @@ object evaluator extends EvaluationRules { })(join(func.typ, s"joined_${func.name}", joinFunctionArgs, Option.when(withExp)(eArgs), v1))((s6, r, v4) => Q(s6, r._1, r._2, v4))}) - case u @ ast.Unfolding( + case ast.Unfolding( acc @ ast.PredicateAccessPredicate(pa @ ast.PredicateAccess(eArgs, predicateName), ePerm), eIn) => @@ -998,7 +998,7 @@ object evaluator extends EvaluationRules { // val c4 = c3.decCycleCounter(predicate) // eval(σ1, eIn, pve, c4)((tIn, c5) => // QB(tIn, c5))}) - consume(s4, acc, true, pve, v3)((s5, snap, consumedChunks, v4) => { + consume(s4, acc, true, pve, v3)((s5, snap, v4) => { val fr6 = s5.functionRecorder.recordSnapshot(pa, v4.decider.pcs.branchConditions, snap.get) .changeDepthBy(+1) @@ -1020,7 +1020,7 @@ object evaluator extends EvaluationRules { val argsPairs: List[(Term, Option[ast.Exp])] = if (withExp) tArgs zip eArgsNew.get.map(Some(_)) else tArgs zip Seq.fill(tArgs.size)(None) val insg = s7.g + Store(predicate.formalArgs map (_.localVar) zip argsPairs) val s7a = s7.copy(g = insg).setConstrainable(s7.constrainableARPs, false) - produce(s7a, toSf(snap.get), body, pve, v4)((s8, v5) => { // TODO ake: add edges from consumedChunks + produce(s7a, toSf(snap.get), body, pve, v4)((s8, v5) => { val s9 = s8.copy(g = s7.g, functionRecorder = s8.functionRecorder.changeDepthBy(-1), recordVisited = s3.recordVisited, @@ -1052,7 +1052,7 @@ object evaluator extends EvaluationRules { => Q(s4, r4._1, r4._2, v4)) case ast.Asserting(eAss, eIn) => - consume(s, eAss, false, pve, v)((s2, _, consumedChunks, v2) => { // TODO ake: what to do with chunks here? + consume(s, eAss, false, pve, v)((s2, _, v2) => { val s3 = s2.copy(g = s.g, h = s.h) eval(s3, eIn, pve, v2)(Q) }) @@ -1063,7 +1063,7 @@ object evaluator extends EvaluationRules { Q(s1, t, e0New.map(e0p => ast.SeqContains(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) /* Note the reversed order of the arguments! */ - case si @ ast.SeqIndex(e0, e1) => + case ast.SeqIndex(e0, e1) => evals2(s, Seq(e0, e1), Nil, _ => pve, v)({case (s1, Seq(t0, t1), esNew, v1) => val eNew = esNew.map(es => ast.SeqIndex(es.head, es(1))(e.pos, e.info, e.errT)) if (s1.triggerExp) { diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 529d73435..f72df6c3a 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -69,7 +69,7 @@ object executor extends ExecutionRules { val condEdgeRecord = new ConditionalEdgeRecord(ce.condition, s, v.decider.pcs) val sepIdentifier = v.symbExLog.openScope(condEdgeRecord) val s1 = handleOutEdge(s, edge, v) - eval(s1, ce.condition, IfFailed(ce.condition), v)((s2, tCond, condNew, v1) => { + eval(s1, ce.condition, IfFailed(ce.condition), v)((s2, tCond, condNew, v1) => /* Using branch(...) here ensures that the edge condition is recorded * as a branch condition on the pathcondition stack. */ @@ -82,7 +82,7 @@ object executor extends ExecutionRules { (_, v3) => { v3.symbExLog.closeScope(sepIdentifier) Success() - })}) + })) case ue: cfg.UnconditionalEdge[ast.Stmt, ast.Exp] => val s1 = handleOutEdge(s, edge, v) @@ -271,7 +271,7 @@ object executor extends ExecutionRules { })}) combine executionFlowController.locally(s, v)((s0, v0) => { v0.decider.prover.comment("Loop head block: Establish invariant") - consumes(s0, invs, false, LoopInvariantNotEstablished, v0)((sLeftover, _, consumedChunks, v1) => { // TODO ake: add edges from consumedChunks + consumes(s0, invs, false, LoopInvariantNotEstablished, v0)((sLeftover, _, v1) => { v1.decider.prover.comment("Loop head block: Execute statements of loop head block (in invariant state)") phase1data.foldLeft(Success(): VerificationResult) { case (result, _) if !result.continueVerification => result @@ -306,7 +306,7 @@ object executor extends ExecutionRules { * attempting to re-establish the invariant. */ v.decider.prover.comment("Loop head block: Re-establish invariant") - consumes(s, invs, false, e => LoopInvariantNotPreserved(e), v)((_, _, _, _) => + consumes(s, invs, false, e => LoopInvariantNotPreserved(e), v)((_, _, _) => Success()) } } @@ -420,7 +420,7 @@ object executor extends ExecutionRules { ) v2.decider.analysisSourceInfoStack.removeForcedSource() result match { - case (Complete(), s3, remainingChunks, consumedChunks) => // TODO ake: what to do with consumedChunks? + case (Complete(), s3, remainingChunks) => val h3 = Heap(remainingChunks ++ otherChunks) val (sm, smValueDef) = quantifiedChunkSupporter.singletonSnapshotMap(s3, field, Seq(tRcvr), tRhs, v2) v1.decider.prover.comment("Definitional axioms for singleton-FVF's value") @@ -439,7 +439,7 @@ object executor extends ExecutionRules { val (debugHeapName, _) = v.getDebugOldLabel(s4, fa.pos) val s5 = if (withExp) s4.copy(oldHeaps = s4.oldHeaps + (debugHeapName -> magicWandSupporter.getEvalHeap(s4))) else s4 Q(s5, v2) - case (Incomplete(_, _), s3, _, _) => + case (Incomplete(_, _), s3, _) => createFailure(pve dueTo InsufficientPermission(fa), v2, s3, "sufficient permission")}})) case ass @ ast.FieldAssign(fa @ ast.FieldAccess(eRcvr, field), rhs) => @@ -452,7 +452,7 @@ object executor extends ExecutionRules { val description = s"consume ${ass.pos}: $ass" val lhsSourceInfo = TransitivityAnalysisSourceInfo(v2.decider.analysisSourceInfoStack.getFullSourceInfo, ExpAnalysisSourceInfo(fa)) v2.decider.analysisSourceInfoStack.setForcedSource(lhsSourceInfo) // splitting lhs and rhs to make permission flow analysis more precise - chunkSupporter.consume(s2, s2.h, resource, Seq(tRcvr), eRcvrNew.map(Seq(_)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), false, ve, v2, description)((s3, h3, _, consumedChunks, v3) => { + chunkSupporter.consume(s2, s2.h, resource, Seq(tRcvr), eRcvrNew.map(Seq(_)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), false, ve, v2, description)((s3, h3, _, v3) => { v2.decider.analysisSourceInfoStack.removeForcedSource() val (tSnap, _) = ssaifyRhs(tRhs, rhs, rhsNew, field.name, field.typ, v3, s3, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit)) val id = BasicChunkIdentifier(field.name) @@ -521,7 +521,7 @@ object executor extends ExecutionRules { case exhale @ ast.Exhale(a) => val pve = ExhaleFailed(exhale) - consume(s, a, false, pve, v, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit))((s1, _, consumedChunks, v1) => + consume(s, a, false, pve, v, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit))((s1, _, v1) => Q(s1, v1)) case assert @ ast.Assert(a: ast.FalseLit) if !s.isInPackage => @@ -535,7 +535,7 @@ object executor extends ExecutionRules { case assert @ ast.Assert(a) if Verifier.config.disableSubsumption() => val r = - consume(s, a, false, AssertFailed(assert), v, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit))((_, _, _, _) => + consume(s, a, false, AssertFailed(assert), v, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit))((_, _, _) => Success()) r combine Q(s, v) @@ -551,11 +551,11 @@ object executor extends ExecutionRules { * hUsed (reserveHeaps.head) instead of consuming them. hUsed is later discarded and replaced * by s.h. By copying hUsed to s.h the contained permissions remain available inside the wand. */ - consume(s, a, false, pve, v, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit))((s2, _, _, v1) => { + consume(s, a, false, pve, v, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit))((s2, _, v1) => { Q(s2.copy(h = s2.reserveHeaps.head), v1) }) } else - consume(s, a, false, pve, v, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit))((s1, _, _, v1) => { + consume(s, a, false, pve, v, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit))((s1, _, v1) => { val s2 = s1.copy(h = s.h, reserveHeaps = s.reserveHeaps) Q(s2, v1)}) @@ -618,7 +618,7 @@ object executor extends ExecutionRules { tArgs zip Seq.fill(tArgs.size)(None) val s2 = s1.copy(g = Store(fargs.zip(argsWithExp)), recordVisited = true) - consumes(s2, meth.pres, false, _ => pvePre, v1)((s3, _, consumedChunks, v2) => { + consumes(s2, meth.pres, false, _ => pvePre, v1)((s3, _, v2) => { v2.symbExLog.closeScope(preCondId) val postCondLog = new CommentRecord("Postcondition", s3, v2.decider.pcs) val postCondId = v2.symbExLog.openScope(postCondLog) diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 674125215..18841a498 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -332,7 +332,6 @@ object magicWandSupporter extends SymbolicExecutionRules { val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(formalVars, formalVarExps, wand, args, Option.when(withExp)(bodyVars), FullPerm, Option.when(withExp)(ast.FullPerm()()), sm, s.program, v4, assumptionType, isExhale=false) val conservedPcs = s5.conservedPcs.head :+ v4.decider.pcs.after(preMark).definitionsOnly - // TODO ake: conditionalizedWithAnalysis? appendToResults(s5, ch, v4.decider.pcs.after(preMark), (conservedPcs.flatMap(_.conditionalized), Option.when(withExp)(conservedPcs.flatMap(_.conditionalizedExp))), v4) Success() }) @@ -340,7 +339,6 @@ object magicWandSupporter extends SymbolicExecutionRules { this.createChunk(s4, wand, wandSnapshot, pve, v3, assumptionType)((s5, ch, v4) => { val conservedPcs = s5.conservedPcs.head :+ v4.decider.pcs.after(preMark).definitionsOnly // Partition path conditions into a set which include the freshSnapRoot and those which do not - // TODO conditionalizedWithAnalysis? val (pcsWithFreshSnapRoot, pcsWithoutFreshSnapRoot) = conservedPcs.flatMap(pcs => pcs.conditionalized).partition(_.contains(freshSnapRoot)) val pcsWithoutExp = Option.when(withExp)(filterDebugExpsWithoutSnapshot(conservedPcs.flatMap(pcs => pcs.conditionalizedExp), freshSnapRoot)) // For all path conditions which include the freshSnapRoot, add those as part of the definition of the MWSF in the same forall quantifier @@ -410,7 +408,7 @@ object magicWandSupporter extends SymbolicExecutionRules { consume( proofScriptState.copy(oldHeaps = s2.oldHeaps, reserveCfgs = proofScriptState.reserveCfgs.tail), wand.right, true, pve, proofScriptVerifier - )((s3, snapRhs, consumedChunks, v3) => { // TODO ake: what to do with consumedChunks? + )((s3, snapRhs, v3) => { createWandChunkAndRecordResults(s3.copy(exhaleExt = false, oldHeaps = s.oldHeaps), freshSnapRoot, snapRhs.get, v3, assumptionType) }) @@ -471,9 +469,9 @@ object magicWandSupporter extends SymbolicExecutionRules { (Q: (State, Verifier) => VerificationResult) : VerificationResult = { // Consume the magic wand instance "A --* B". - consume(s, wand, true, pve, v)((s1, snapWand, consumedChunksWand, v1) => { + consume(s, wand, true, pve, v)((s1, snapWand, v1) => { // Consume the wand's LHS "A". - consume(s1, wand.left, true, pve, v1)((s2, snapLhs, consumedChunksLeft, v2) => { + consume(s1, wand.left, true, pve, v1)((s2, snapLhs, v2) => { /* It is assumed that snap and MagicWandSnapshot.abstractLhs are structurally the same. * Equating the two snapshots is sound iff a wand is applied only once. * The old solution in this case did use this assumption: diff --git a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala index 7d530bc04..11620af62 100644 --- a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala +++ b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala @@ -238,7 +238,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { ve: VerificationError, v: Verifier, assumptionType: AssumptionType=AssumptionType.Implicit) - (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) + (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { if (!s.assertReadAccessOnly) @@ -258,7 +258,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { ve: VerificationError, v: Verifier, assumptionType: AssumptionType) - (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) + (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { val id = ChunkIdentifier(resource, s.program) @@ -268,7 +268,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { summarise(s, relevantChunks, resource, args, argsExp, None, v)((s1, snap, permSum, permSumExp, v1) => v.decider.assert(Implies(IsPositive(perm), IsPositive(permSum)), assumptionType) { case true => - Q(s1, h, Some(snap), relevantChunks, v1) + Q(s1, h, Some(snap), v1) case false => createFailure(ve, v, s1, IsPositive(permSum), permSumExp.map(IsPositive(_)())) }) @@ -276,7 +276,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { val (s1, permSum, permSumExp) = permSummariseOnly(s, relevantChunks, resource, args, argsExp) v.decider.assert(Implies(IsPositive(perm), IsPositive(permSum)), assumptionType) { case true => - Q(s1, h, None, relevantChunks, v) + Q(s1, h, None, v) case false => createFailure(ve, v, s1, IsPositive(permSum), permSumExp.map(IsPositive(_)())) } @@ -294,7 +294,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { ve: VerificationError, v: Verifier, assumptionType: AssumptionType) - (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) + (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { val id = ChunkIdentifier(resource, s.program) @@ -308,13 +308,13 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { if (relevantChunks.isEmpty) { // if no permission is exhaled, return none v.decider.assert(perms === NoPerm, assumptionType) { - case true => Q(s, h, None, Seq.empty, v) + case true => Q(s, h, None, v) case false => createFailure(ve, v, s, perms === NoPerm, permsExp.map(pe => ast.EqCmp(pe, ast.NoPerm()())(pe.pos, pe.info, pe.errT))) } } else { if (!terms.utils.consumeExactRead(perms, s.constrainableARPs)) { actualConsumeCompleteConstrainable(s, relevantChunks, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, assumptionType)((s1, updatedChunks, optSnap, v2) => { - Q(s1, Heap(updatedChunks ++ otherChunks), optSnap, updatedChunks, v2) // TODO ake: updatedChunks != consumedChunks + Q(s1, Heap(updatedChunks ++ otherChunks), optSnap, v2) }) } else { var pNeeded = perms @@ -322,7 +322,6 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { var pSum: Term = NoPerm var pSumExp: Option[ast.Exp] = permsExp.map(pe => ast.NoPerm()(pe.pos, pe.info, pe.errT)) val newChunks = ListBuffer[NonQuantifiedChunk]() - val consumedChunks = ListBuffer[NonQuantifiedChunk]() var moreNeeded = true val definiteAlias = chunkSupporter.findChunk[NonQuantifiedChunk](relevantChunks, id, args, v).filter(c => @@ -375,7 +374,6 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { if (!v.decider.check(IsNonPositive(newChunk.perm), Verifier.config.splitTimeout(), AssumptionType.Internal)) { newChunks.append(newChunk) } - consumedChunks.append(ch) moreNeeded = !v.decider.check(pNeeded === NoPerm, Verifier.config.splitTimeout(), AssumptionType.Internal) } else { @@ -406,24 +404,23 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { Ite(IsPositive(perms), snap.convert(sorts.Snap), Unit) }) if (!moreNeeded) { - Q(s1, newHeap, condSnap, consumedChunks, v1) + Q(s1, newHeap, condSnap, v1) } else { - val assertExp = pNeededExp.map(pn => ast.EqCmp(pn, ast.NoPerm()())(pn.pos, pn.info, pn.errT)) v1.decider.assert(pNeeded === NoPerm, assumptionType) { case true => - Q(s1, newHeap, condSnap, consumedChunks, v1) + Q(s1, newHeap, condSnap, v1) case false => - createFailure(ve, v1, s1, pNeeded === NoPerm, assertExp) + createFailure(ve, v1, s1, pNeeded === NoPerm, pNeededExp.map(pn => ast.EqCmp(pn, ast.NoPerm()())(pn.pos, pn.info, pn.errT))) } } }) } else { if (!moreNeeded) { - Q(s0, newHeap, None, consumedChunks, v) + Q(s0, newHeap, None, v) } else { v.decider.assert(pNeeded === NoPerm, assumptionType) { case true => - Q(s0, newHeap, None, consumedChunks, v) + Q(s0, newHeap, None, v) case false => createFailure(ve, v, s0, pNeeded === NoPerm, pNeededExp.map(pn => ast.EqCmp(pn, ast.NoPerm()())(pn.pos, pn.info, pn.errT))) } @@ -509,7 +506,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { v.decider.assume(perms === totalPermTaken, Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp)), AssumptionType.Internal) if (returnSnap) { summarise(s1, relevantChunks.toSeq, resource, args, argsExp, None, v)((s2, snap, _, _, v1) => - Q(s2, updatedChunks, Some(snap), v1)) // TODO ake: updatedChunks are the new chunks, not the old consumed ones! + Q(s2, updatedChunks, Some(snap), v1)) } else { Q(s1, updatedChunks, None, v) } diff --git a/src/main/scala/rules/PredicateSupporter.scala b/src/main/scala/rules/PredicateSupporter.scala index 357ac8e28..51ec9c2bd 100644 --- a/src/main/scala/rules/PredicateSupporter.scala +++ b/src/main/scala/rules/PredicateSupporter.scala @@ -75,7 +75,7 @@ object predicateSupporter extends PredicateSupportRules { val s1 = s.copy(g = gIns, smDomainNeeded = true) .scalePermissionFactor(tPerm, ePerm) - consume(s1, body, true, pve, v)((s1a, snap, consumedChunks, v1) => { // TODO ake: add edges from consumedChunks + consume(s1, body, true, pve, v)((s1a, snap, v1) => { if (!Verifier.config.disableFunctionUnfoldTrigger()) { val predTrigger = App(s1a.predicateData(predicate).triggerFunction, snap.get.convert(terms.sorts.Snap) +: tArgs) @@ -142,6 +142,7 @@ object predicateSupporter extends PredicateSupportRules { assumptionType: AssumptionType = AssumptionType.Rewrite) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { + val tArgsWithE = if (withExp) tArgs zip eArgs.get.map(Some(_)) else @@ -165,10 +166,10 @@ object predicateSupporter extends PredicateSupportRules { None, pve, v - )((s2, h2, snap, consumedChunks, v1) => { + )((s2, h2, snap, v1) => { val s3 = s2.copy(g = gIns, h = h2) .setConstrainable(constrainableWildcards, false) - produce(s3, toSf(snap.get), body, pve, v1, assumptionType)((s4, v2) => { // TODO ake: add edge from consumedChunks to new assumptions + produce(s3, toSf(snap.get), body, pve, v1, assumptionType)((s4, v2) => { v2.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterUnfold) if (!Verifier.config.disableFunctionUnfoldTrigger()) { val predicateTrigger = @@ -185,7 +186,7 @@ object predicateSupporter extends PredicateSupportRules { } else { val ve = pve dueTo InsufficientPermission(pa) val description = s"consume ${pa.pos}: $pa" - chunkSupporter.consume(s1, s1.h, predicate, tArgs, eArgs, s1.permissionScalingFactor, s1.permissionScalingFactorExp, true, ve, v, description)((s2, h1, snap, consumedChunks, v1) => { // TODO ake: add edges + chunkSupporter.consume(s1, s1.h, predicate, tArgs, eArgs, s1.permissionScalingFactor, s1.permissionScalingFactorExp, true, ve, v, description)((s2, h1, snap, v1) => { val s3 = s2.copy(g = gIns, h = h1) .setConstrainable(constrainableWildcards, false) produce(s3, toSf(snap.get), body, pve, v1, assumptionType)((s4, v2) => { diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 9f00362b3..1b3254cb2 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -31,7 +31,7 @@ import viper.silver.reporter.InternalWarningMessage import viper.silver.verifier.reasons.{InsufficientPermission, MagicWandChunkNotFound} import viper.silver.verifier.{ErrorReason, PartialVerificationError} -import scala.collection.immutable.{ArraySeq, Set} +import scala.collection.immutable.ArraySeq import scala.reflect.ClassTag case class InverseFunctions(condition: Term, @@ -1204,7 +1204,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { insufficientPermissionReason: => ErrorReason, v: Verifier, assumptionType: AssumptionType=AssumptionType.Implicit) - (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) + (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { val (inverseFunctions, imagesOfFormalQVars) = @@ -1340,7 +1340,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val (relevantChunks, otherChunks) = quantifiedChunkSupporter.splitHeap[QuantifiedBasicChunk]( heap, ChunkIdentifier(resource, s.program)) - val (result, s3, remainingChunks, consumedChunks) = + val (result, s3, remainingChunks) = quantifiedChunkSupporter.removePermissions( s2, relevantChunks, @@ -1397,13 +1397,13 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val h2 = Heap(remainingChunks ++ otherChunks) val s4 = s3.copy(smCache = smCache2, constrainableARPs = s.constrainableARPs) - (result, s4, h2, Some(consumedChunk)) // TODO ake: or consumedChunks? + (result, s4, h2, Some(consumedChunk)) })((s4, optCh, v3) => optCh match { - case Some(ch) if returnSnap => Q(s4, s4.h, Some(ch.snapshotMap.convert(sorts.Snap)), optCh, v3) + case Some(ch) if returnSnap => Q(s4, s4.h, Some(ch.snapshotMap.convert(sorts.Snap)), v3) case None if returnSnap => - Q(s4, s4.h, Some(freshSnap(sorts.Snap, v3)), optCh, v3) - case _ => Q(s4, s4.h, None, None, v3) + Q(s4, s4.h, Some(freshSnap(sorts.Snap, v3)), v3) + case _ => Q(s4, s4.h, None, v3) } ) } else { @@ -1425,7 +1425,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { assumptionType ) permissionRemovalResult match { - case (Complete(), s2, remainingChunks, consumedChunks) => + case (Complete(), s2, remainingChunks) => val h3 = Heap(remainingChunks ++ otherChunks) if (returnSnap) { val optSmDomainDefinitionCondition2 = @@ -1439,11 +1439,11 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { partiallyConsumedHeap = Some(h3), constrainableARPs = s.constrainableARPs, smCache = smCache2) - Q(s3, h3, Some(smDef2.sm.convert(sorts.Snap)), consumedChunks, v) + Q(s3, h3, Some(smDef2.sm.convert(sorts.Snap)), v) } else { - Q(s2, h3, None, consumedChunks, v) + Q(s2, h3, None, v) } - case (Incomplete(_, _), s2, _, _) => + case (Incomplete(_, _), s2, _) => createFailure(pve dueTo insufficientPermissionReason, v, s2, "QP consume")} } case false => @@ -1466,7 +1466,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { pve: PartialVerificationError, v: Verifier, assumptionType: AssumptionType=AssumptionType.Implicit) - (Q: (State, Heap, Option[Term], Iterable[Chunk], Verifier) => VerificationResult) + (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { val resource = resourceAccess.res(s.program) @@ -1489,7 +1489,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { magicWandSupporter.transfer(s, permissions, permissionsExp, failure, Seq(), v)((s1, h1, rPerm, rPermExp, v1) => { val (relevantChunks, otherChunks) = quantifiedChunkSupporter.splitHeap[QuantifiedBasicChunk](h1, chunkIdentifier) - val (result, s2, remainingChunks, consumedChunks) = quantifiedChunkSupporter.removePermissions( + val (result, s2, remainingChunks) = quantifiedChunkSupporter.removePermissions( s1, relevantChunks, codomainQVars, @@ -1524,15 +1524,15 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { codomainQVars, codomainQVarsExp, resource, arguments, argumentsExp, permsTaken, permsTakenExp, smDef1.sm, s.program, v1, assumptionType, isExhale=true) val s3 = s2.copy(functionRecorder = s2.functionRecorder.recordFvfAndDomain(smDef1), smCache = smCache1) - (result, s3, h2, Some(consumedChunk)) // TODO ake: or consumedChunks? + (result, s3, h2, Some(consumedChunk)) })((s4, optCh, v2) => optCh match { case Some(ch) if returnSnap => val snap = ResourceLookup(resource, ch.snapshotMap, arguments, s4.program).convert(sorts.Snap) - Q(s4, s4.h, Some(snap), optCh.toList, v2) + Q(s4, s4.h, Some(snap), v2) case None if returnSnap => - Q(s4, s4.h, Some(freshSnap(sorts.Snap, v2)), optCh.toList, v2) - case _ => Q(s4, s4.h, None, optCh.toList, v2) + Q(s4, s4.h, Some(freshSnap(sorts.Snap, v2)), v2) + case _ => Q(s4, s4.h, None, v2) } ) } else { @@ -1555,7 +1555,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { assumptionType ) result match { - case (Complete(), s1, remainingChunks, consumedChunks) => + case (Complete(), s1, remainingChunks) => val h1 = Heap(remainingChunks ++ otherChunks) if (returnSnap) { val (smDef1, smCache1) = @@ -1570,11 +1570,11 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val s2 = s1.copy(functionRecorder = s1.functionRecorder.recordFvfAndDomain(smDef1), smCache = smCache1) val snap = ResourceLookup(resource, smDef1.sm, arguments, s2.program).convert(sorts.Snap) - Q(s2, h1, Some(snap), consumedChunks, v) + Q(s2, h1, Some(snap), v) } else { - Q(s1, h1, None, consumedChunks, v) + Q(s1, h1, None, v) } - case (Incomplete(_, _), _, _, _) => + case (Incomplete(_, _), _, _) => resourceAccess match { case locAcc: ast.LocationAccess => createFailure(pve dueTo InsufficientPermission(locAcc), v, s, "single QP consume") case wand: ast.MagicWand => createFailure(pve dueTo MagicWandChunkNotFound(wand), v, s, "single QP consume") @@ -1633,7 +1633,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { chunkOrderHeuristic: Seq[QuantifiedBasicChunk] => Seq[QuantifiedBasicChunk], v: Verifier, assumptionType: AssumptionType=AssumptionType.Implicit) - : (ConsumptionResult, State, Seq[QuantifiedBasicChunk], Seq[QuantifiedBasicChunk]) = { + : (ConsumptionResult, State, Seq[QuantifiedBasicChunk]) = { val rmPermRecord = new CommentRecord("removePermissions", s, v.decider.pcs) val sepIdentifier = v.symbExLog.openScope(rmPermRecord) @@ -1650,7 +1650,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val constrainPermissions = !consumeExactRead(perms, s.constrainableARPs) if (s.assertReadAccessOnly) { val result = assertReadPermission(s, candidates, codomainQVars, condition, perms, permsExp, v, assumptionType) - return (result, s, relevantChunks, Seq.empty) + return (result, s, relevantChunks) } @@ -1760,7 +1760,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.prover.comment("Done removing quantified permissions") v.symbExLog.closeScope(sepIdentifier) - (success, s.copy(functionRecorder = currentFunctionRecorder), remainingChunks, relevantChunks filterNot (remainingChunks.contains(_))) + (success, s.copy(functionRecorder = currentFunctionRecorder), remainingChunks) } private def createPermissionConstraintAndDepletedCheck(codomainQVars: Seq[Var], /* rs := r_1, ..., r_m */ diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index a16ac4b9b..99a4275b3 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -113,10 +113,9 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif Success()})}) && { executionFlowController.locally(s2a, v2)((s3, v3) => { - exec(s3, body, v3)((s4, v4) =>{ - consumes(s4, posts, false, postViolated, v4, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Postcondition))((_, _, _, _) => { - Success() - })})}) } )})}) + exec(s3, body, v3)((s4, v4) => + consumes(s4, posts, false, postViolated, v4, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Postcondition))((_, _, _) => + Success()))}) } )})}) result.assumptionAnalyzer = v.decider.assumptionAnalyzer v.decider.resetProverOptions() diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index 8033b0cc1..5c725573a 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -148,7 +148,6 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver def verify(sInit: State, function: ast.Function): Seq[VerificationResult] = { val comment = ("-" * 10) + " FUNCTION " + function.name + ("-" * 10) - logger.debug(s"\n\n$comment\n") decider.prover.comment(comment) @@ -272,7 +271,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver Some(DebugExp.createInstance(e, eNew)) } else { None } decider.assume(BuiltinEquals(data.formalResult, tBody), debugExp, AssumptionType.Implicit) - consumes(s2, posts, false, postconditionViolated, v, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Postcondition))((s3, _, _, _) => { + consumes(s2, posts, false, postconditionViolated, v, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Postcondition))((s3, _, _) => { recorders :+= s3.functionRecorder Success()})})})} From 1dc89886463a2fc01db14c2ac2087b4f2513f0c3 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 10 Jul 2025 18:15:09 +0200 Subject: [PATCH 156/474] annotate test examples with expected assumption type --- .../dependencyAnalysisTests/all/aliasing.vpr | 34 ++++---- .../dependencyAnalysisTests/all/branches.vpr | 64 +++++++-------- .../dependencyAnalysisTests/all/divBy0.vpr | 24 +++--- .../dependencyAnalysisTests/all/gaussian.vpr | 46 +++++------ .../all/imprecision.vpr | 68 +++++++++------- .../all/infeasible.vpr | 32 ++++---- .../dependencyAnalysisTests/all/list.vpr | 6 +- .../all/method-sum.vpr | 26 +++---- .../all/presentation-examples.vpr | 18 ++--- .../all/quantified-perm.vpr | 28 +++---- .../dependencyAnalysisTests/all/sum-loops.vpr | 62 +++++++-------- .../dependencyAnalysisTests/all/tuples.vpr | 18 ++--- .../dependencyAnalysisTests/all/wands.vpr | 12 +-- .../dependencyAnalysisTests/new/meeting.vpr | 78 +++++++++++-------- .../unitTests/branches.vpr | 40 +++++----- .../unitTests/loops.vpr | 56 ++++++------- .../unitTests/magicWands.vpr | 20 ++--- .../unitTests/method-call.vpr | 38 ++++----- .../unitTests/misc.vpr | 12 +-- .../unitTests/permissions.vpr | 66 ++++++++-------- .../unitTests/predicates.vpr | 26 +++---- .../unitTests/quantifiedPermissions.vpr | 64 +++++++-------- src/test/scala/AssumptionAnalysisTests.scala | 13 +--- 23 files changed, 436 insertions(+), 415 deletions(-) diff --git a/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr b/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr index 1d864ae6a..557c2d7b9 100644 --- a/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr @@ -2,44 +2,44 @@ field f: Int method maybeAlias(a: Ref, b: Ref, c: Bool, n: Int) - requires @dependency()(acc(a.f, 1/2)) - requires @dependency()(acc(b.f, 1/2)) - requires @dependency()(c ==> a == b) + requires @dependency("Explicit")(acc(a.f, 1/2)) + requires @dependency("Explicit")(acc(b.f, 1/2)) + requires @dependency("Explicit")(c ==> a == b) requires a.f > 0 && n > 0 && b.f >= 0 requires a.f < 100 requires !c ==> a.f < b.f { - if(@dependency()(c)){ - @testAssertion() + if(@dependency("PathCondition")(c)){ + @testAssertion("Implicit") a.f := n + 1 } } method aliasing1(x: Ref, n: Int) - requires @dependency()(acc(x.f)) - requires @irrelevant()(n > 0) - requires @irrelevant()(x.f > n) + requires @dependency("Explicit")(acc(x.f)) + requires @irrelevant("Explicit")(n > 0) + requires @irrelevant("Explicit")(x.f > n) { - @dependency() + @dependency("Implicit") x.f := n + 1 var y: Ref - @dependency() + @dependency("Implicit") y := x - @testAssertion() + @testAssertion("Explicit") assert y.f > n } method aliasing2(x: Ref, y: Ref, n: Int) - requires @dependency()(acc(x.f, 1/2)) - requires @dependency()(acc(y.f, 1/2)) - requires @irrelevant()(n > 0) - requires @irrelevant()(x.f > n) + requires @dependency("Explicit")(acc(x.f, 1/2)) + requires @dependency("Explicit")(acc(y.f, 1/2)) + requires @irrelevant("Explicit")(n > 0) + requires @irrelevant("Explicit")(x.f > n) { - if(@dependency()(x == y)){ - @testAssertion() + if(@dependency("PathCondition")(x == y)){ + @testAssertion("Implicit") x.f := n + 1 } } diff --git a/src/test/resources/dependencyAnalysisTests/all/branches.vpr b/src/test/resources/dependencyAnalysisTests/all/branches.vpr index bc0818fe1..d54078c99 100644 --- a/src/test/resources/dependencyAnalysisTests/all/branches.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/branches.vpr @@ -2,27 +2,27 @@ method branches1(a: Int, b: Int) { var n:Int, c: Bool - @dependency() + @dependency("Explicit") assume 0 < a - @dependency() + @dependency("Explicit") assume b > 4 - @irrelevant() + @irrelevant("Explicit") assume a < 100 - @irrelevant() + @irrelevant("Explicit") assume b < 50 - @irrelevant() + @irrelevant("Explicit") assume c ==> a > 5 if(c){ - @dependency() + @dependency("Implicit") n := a + 1 }else{ - @dependency() + @dependency("Implicit") n := b + 1 } - @testAssertion() + @testAssertion("Explicit") assert n > 1 } @@ -30,23 +30,23 @@ method branches2(a: Int, b: Int) { var n:Int, c: Bool - @dependency() + @dependency("Explicit") assume 0 < a - @irrelevant() + @irrelevant("Explicit") assume a < 100 - @irrelevant() + @irrelevant("Explicit") assume a < b - @irrelevant() + @irrelevant("Explicit") assume b < 50 var x: Int if(a >= n){ - @irrelevant() + @irrelevant("Implicit") x := a + b }else{ - @dependency() + @dependency("Implicit") x := n + 1 - @testAssertion() + @testAssertion("Explicit") assert x > 1 } } @@ -55,13 +55,13 @@ method branches3(a: Int, b: Int) { var n:Int, c: Bool - @irrelevant() + @irrelevant("Explicit") assume 0 < a - @irrelevant() + @irrelevant("Explicit") assume a < 100 - @dependency() + @dependency("Explicit") assume 0 < b - @irrelevant() + @irrelevant("Explicit") assume b < 50 if(c){ @@ -72,14 +72,14 @@ method branches3(a: Int, b: Int) var x: Int if(a >= n){ - @dependency() + @dependency("Implicit") x := a + b }else{ - @dependency() + @dependency("Implicit") x := n + 1 } - @testAssertion() + @testAssertion("Explicit") assert x > n } @@ -88,33 +88,33 @@ method nestedBranches1(a: Int, b: Int) { var n:Int, c: Bool - @irrelevant() + @irrelevant("Explicit") assume 0 < a - @irrelevant() + @irrelevant("Explicit") assume a < 100 - @dependency() + @dependency("Explicit") assume 0 < b - @irrelevant() + @irrelevant("Explicit") assume b < 50 - @irrelevant() + @irrelevant("Explicit") assume c ==> a > 5 if(c){ if(a > b){ - @dependency() + @dependency("Implicit") n := a - b }else{ - @dependency() + @dependency("Implicit") n := a + b } - @dependency() + @dependency("Implicit") n := n - 1 }else{ - @dependency() + @dependency("Implicit") n := a + b } - @testAssertion() + @testAssertion("Explicit") assert n <= a + b assert c ==> n < a + b } diff --git a/src/test/resources/dependencyAnalysisTests/all/divBy0.vpr b/src/test/resources/dependencyAnalysisTests/all/divBy0.vpr index 5fdfe2ab5..ce768e38f 100644 --- a/src/test/resources/dependencyAnalysisTests/all/divBy0.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/divBy0.vpr @@ -15,7 +15,7 @@ method divBy0() var y: Int @dependency("Explicit") assume y > 0 - @testAssertion() + @testAssertion("Implicit") x := x/y } @@ -23,39 +23,39 @@ method basic() { var x: Int var y: Int - @irrelevant() + @irrelevant("Explicit") assume x > 0 - @dependency() + @dependency("Explicit") assume y > 0 - @dependency() + @dependency("Explicit") assume y < x @dependency("Implicit") x := x/y - @testAssertion() + @testAssertion("Explicit") assert x >= 1 } method sumClient(x: Int, y: Int) { - @dependency() + @dependency("Explicit") assume x >= 0 - @dependency() + @dependency("Explicit") assume y >= 0 - @dependency() + @dependency("Explicit") assume x < y var n: Int - @dependency() + @dependency("Implicit") n := sum(x, y) // the following stmt reports dependency on n := sum(x, y) because (x < y && n == x + y && n > 100 ==> y != 0) // although you could also prove it via (0 <= x && x < y ==> y != 0) var n2: Int - @dependency() + @dependency("Implicit") n2 := sum(x/y, y) - @dependency() + @dependency("Implicit") n := n + n2 - @testAssertion() + @testAssertion("Explicit") assert n >= x + 2*y } \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/gaussian.vpr b/src/test/resources/dependencyAnalysisTests/all/gaussian.vpr index f4499684e..b85ae08f9 100644 --- a/src/test/resources/dependencyAnalysisTests/all/gaussian.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/gaussian.vpr @@ -2,48 +2,48 @@ field f: Int method gaussianSimple(n: Int) returns (res: Int) requires 0 <= n - requires @irrelevant()(n <= 5) + requires @irrelevant("Explicit")(n <= 5) { res := 0 var i: Int := 0 - while(@dependency()(i <= n)) - invariant @dependency()(i <= (n + 1)) - invariant @irrelevant()(i <= 6) - invariant @dependency()(res == (i - 1) * i / 2) + while(@dependency("PathCondition")(i <= n)) + invariant @dependency("LoopInvariant")(i <= (n + 1)) + invariant @irrelevant("LoopInvariant")(i <= 6) + invariant @dependency("LoopInvariant")(res == (i - 1) * i / 2) { - @dependency() + @dependency("Implicit") res := res + i - @dependency() + @dependency("Implicit") i := i + 1 } - @testAssertion() + @testAssertion("Explicit") assert res == n * (n + 1) / 2 } method gaussianPerm(a: Ref, p: Perm) returns (res: Int) - requires @dependency()(none < p) && p < write - requires @dependency()(acc(a.f, p)) + requires @dependency("Explicit")(none < p) && p < write + requires @dependency("Explicit")(acc(a.f, p)) requires 0 <= a.f requires a.f <= 5 ensures acc(a.f, p) { res := 0 var i: Int := 0 - while(@dependency()(i <= a.f)) - invariant @dependency()(acc(a.f, p)) + while(@dependency("PathCondition")(i <= a.f)) + invariant @dependency("LoopInvariant")(acc(a.f, p)) invariant 0 <= a.f && a.f <= 5 - invariant @dependency()(i <= (a.f + 1)) + invariant @dependency("LoopInvariant")(i <= (a.f + 1)) invariant i <= 6 invariant @dependency("LoopInvariant")(res == (i - 1) * i / 2) { @dependency("Implicit") res := res + i - @dependency() + @dependency("Implicit") i := i + 1 } - @testAssertion() + @testAssertion("Explicit") assert res == a.f * (a.f + 1) / 2 } @@ -57,25 +57,25 @@ method gaussianPred(n: Int) returns (res: Int) { res := 0 var i: Int - @dependency() + @dependency("Implicit") i := 0 - @dependency() + @dependency("Implicit") fold gaussianEq(res, i) while(i <= n) invariant i <= (n + 1) invariant i <= 6 - invariant @dependency()(gaussianEq(res, i)) + invariant @dependency("LoopInvariant")(gaussianEq(res, i)) { - @dependency() + @dependency("Implicit") unfold gaussianEq(res, i) - @dependency() + @dependency("Implicit") res := res + i - @dependency() + @dependency("Implicit") i := i + 1 - @dependency() + @dependency("Implicit") fold gaussianEq(res, i) } assert i == n+1 - @testAssertion() + @testAssertion("Explicit") assert gaussianEq(res, n+1) } diff --git a/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr b/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr index 1c183ea31..b7d45b79f 100644 --- a/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr @@ -2,70 +2,84 @@ field f: Int method permTest(a: Ref, b: Ref, n: Int) - requires @dependency()(acc(a.f)) - requires @irrelevant()(acc(b.f)) && @irrelevant()(b.f > 0) + requires @dependency("Explicit")(acc(a.f)) + requires @irrelevant("Explicit")(acc(b.f)) && @irrelevant("Explicit")(b.f > 0) { - @dependency() + @dependency("Explicit") assume n > 0 - @irrelevant() + @irrelevant("Implicit") a.f := b.f + 2 - @dependency() + @dependency("Implicit") a.f := n - @testAssertion() + @testAssertion("Explicit") assert a.f >= 0 } method quantifiedPerm2(xs: Seq[Ref], ys: Seq[Ref]) - requires @dependency()(|xs| > 5) - requires @irrelevant()(|ys| > 3) + requires @dependency("Explicit")(|xs| > 5) + requires @irrelevant("Explicit")(|ys| > 3) { - @dependency() + @dependency("Explicit") inhale (forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0)) - //@irrelevant() // TODO ake + //@irrelevant("Explicit") // TODO ake inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard) && y.f > 0) - @testAssertion() + @testAssertion("Explicit") assert xs[0].f > 0 } method quantifiedPerm3(xs: Seq[Ref], ys: Seq[Ref]) - requires @dependency()(|xs| > 5) - requires @irrelevant()(|ys| > 3) + requires @dependency("Explicit")(|xs| > 5) + requires @irrelevant("Explicit")(|ys| > 3) { - @dependency() + @dependency("Explicit") inhale (forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0)) - // @irrelevant() // TODO ake + // @irrelevant("Explicit") // TODO ake inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard)) - @testAssertion() + @testAssertion("Explicit") assert xs[0].f > 0 } method quantifiedPerm4(xs: Seq[Ref], ys: Seq[Ref]) - requires @dependency()(|xs| > 5) - requires @irrelevant()(|ys| > 3) + requires @dependency("Explicit")(|xs| > 5) + requires @irrelevant("Explicit")(|ys| > 3) { - @dependency() + @dependency("Explicit") inhale (forall x: Ref :: x in xs ==> acc(x.f) && x.f > 0) - @irrelevant() + @irrelevant("Explicit") inhale forall y: Ref :: y in ys ==> acc(y.f) - @testAssertion() + @testAssertion("Explicit") assert xs[0].f > 0 } method quantifiedPerm5(xs: Seq[Ref], ys: Seq[Ref]) - requires @dependency()(|xs| > 5) - requires @irrelevant()(|ys| > 3) + requires @dependency("Explicit")(|xs| > 5) + requires @irrelevant("Explicit")(|ys| > 3) { - @dependency() + @dependency("Explicit") inhale forall x: Ref :: x in xs ==> acc(x.f) - @irrelevant() + @irrelevant("Explicit") inhale forall y: Ref :: y in ys ==> acc(y.f) - @irrelevant() + @irrelevant("Implicit") xs[0].f := ys[0].f + 2 - @testAssertion() + @testAssertion("Implicit") xs[0].f := 2 +} + +method nonUniqueUnsatCore(x: Ref) + requires x != null ==> acc(x.f) +{ + var a: Int + if(x == null){ + @irrelevant("Explicit") + inhale a >= 0 + } + @dependency("Explicit") + inhale a >= 0 + @testAssertion("Explicit") + assert a >= 0 } \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/infeasible.vpr b/src/test/resources/dependencyAnalysisTests/all/infeasible.vpr index 1f323118a..11e66680d 100644 --- a/src/test/resources/dependencyAnalysisTests/all/infeasible.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/infeasible.vpr @@ -1,49 +1,49 @@ field f: Int method infeasibleBranch1(a: Int, b: Int) - requires @dependency()(a > 0) + requires @dependency("Explicit")(a > 0) { - if(@dependency()(a < 0)){ - @testAssertion() + if(@dependency("PathCondition")(a < 0)){ + @testAssertion("Explicit") assert false } } method infeasibleBranch2(a: Int, b: Int) - requires @dependency()(a > 0) + requires @dependency("Explicit")(a > 0) { var res: Int - if(@dependency()(a < 0)){ - @irrelevant() + if(@dependency("PathCondition")(a < 0)){ + @irrelevant("Explicit") assume res < 0 }else{ - @dependency() + @dependency("Explicit") assume res > 0 } - @testAssertion() + @testAssertion("Explicit") assert res > 0 } method infeasibleBranchPerm(a: Ref, b: Ref) - requires @dependency()(acc(a.f, 1/2)) - requires @dependency()(a.f > 0) + requires @dependency("Explicit")(acc(a.f, 1/2)) + requires @dependency("Explicit")(a.f > 0) { - if(@dependency()(a.f < 0)){ - @testAssertion() + if(@dependency("PathCondition")(a.f < 0)){ + @testAssertion("Implicit") a.f := a.f + 1 } } method infeasibleBranchNoPerm(a: Int, b: Ref) - requires @dependency()(a > 0) + requires @dependency("Explicit")(a > 0) { var res: Int - if(@dependency()(a < 0)){ - @irrelevant() + if(@dependency("PathCondition")(a < 0)){ + @irrelevant("Implicit") res := b.f + 1 - @testAssertion() + @testAssertion("Explicit") assert false } } \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/list.vpr b/src/test/resources/dependencyAnalysisTests/all/list.vpr index 9e6566c19..24b9911d4 100644 --- a/src/test/resources/dependencyAnalysisTests/all/list.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/list.vpr @@ -23,7 +23,7 @@ method appendList1(this: Ref, e: Int) @irrelevant() assume 0 <= this.elem && this.elem < 100 - if (@irrelevant()(this.next == null)) { // TODO ake: node does not exist? + if (@irrelevant()(this.next == null)) { var n: Ref @dependency() @@ -53,7 +53,7 @@ method appendList2(this: Ref, e: Int) @irrelevant() assume 0 <= this.elem && this.elem < 100 - if (this.next == null) { // TODO ake: node does not exist + if (@dependency()(this.next == null)) { var n: Ref @irrelevant() @@ -84,7 +84,7 @@ method appendListFull(this: Ref, e: Int) @irrelevant() assume 0 <= this.elem && this.elem < 100 - if (this.next == null) { + if (@dependency()(this.next == null)) { var n: Ref @dependency() diff --git a/src/test/resources/dependencyAnalysisTests/all/method-sum.vpr b/src/test/resources/dependencyAnalysisTests/all/method-sum.vpr index e9c22ad06..90f7e38b3 100644 --- a/src/test/resources/dependencyAnalysisTests/all/method-sum.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/method-sum.vpr @@ -12,40 +12,40 @@ method sum(x: Int, y: Int) returns(res: Int) method sumClient(x: Int, y: Int) { - @dependency() + @dependency("Explicit") assume x >= 0 - @dependency() + @dependency("Explicit") assume y >= 0 - @dependency() + @dependency("Explicit") assume x < y - @dependency() + @dependency("Implicit") var n: Int := sum(x, y) // the following stmt reports dependency on n := sum(x, y) because (x < y && n == x + y && n > 100 ==> y != 0) // although you could also prove it via (0 <= x && x < y ==> y != 0) - @dependency() + @dependency("Implicit") var n2: Int := sum(x/y, y) - @dependency() + @dependency("Implicit") n := n + n2 - @testAssertion() + @testAssertion("Explicit") assert n >= x + 2*y } method sumClient2(x: Int, y: Int) { - @irrelevant() + @irrelevant("Explicit") assume x >= 0 - @dependency() + @dependency("Explicit") assume y >= 0 - @irrelevant() + @irrelevant("Explicit") assume x < y - @irrelevant() + @irrelevant("Implicit") var n: Int := sum(x, x) assert n >= 100 - @dependency() + @dependency("Implicit") var n2: Int := sum(y, y) - @testAssertion() + @testAssertion("Explicit") assert n2 == 2*y } \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/presentation-examples.vpr b/src/test/resources/dependencyAnalysisTests/all/presentation-examples.vpr index 8cdaba977..9b2294f48 100644 --- a/src/test/resources/dependencyAnalysisTests/all/presentation-examples.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/presentation-examples.vpr @@ -4,24 +4,24 @@ field g: Int method foo(x: Ref, y: Ref) { - @dependency() + @dependency("Explicit") inhale acc(x.f) - @dependency() + @dependency("Explicit") inhale acc(y.f) - @irrelevant() + @irrelevant("Explicit") inhale acc(y.g) - @dependency() + @dependency("Explicit") assume 0 < x.f - @irrelevant() + @irrelevant("Explicit") assume x.f < 100 - @irrelevant() + @irrelevant("Explicit") assume y.f < 100 - @irrelevant() + @irrelevant("Explicit") assume 0 <= y.f - @dependency() + @dependency("Explicit") assume x.f < y.f - @testAssertion() + @testAssertion("Explicit") assert x.f / y.f <= 1 } \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/quantified-perm.vpr b/src/test/resources/dependencyAnalysisTests/all/quantified-perm.vpr index 625570444..4a7289d5b 100644 --- a/src/test/resources/dependencyAnalysisTests/all/quantified-perm.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/quantified-perm.vpr @@ -5,41 +5,41 @@ field second : Ref method quantifiedWritePerm(nodes: Set[Ref], x: Ref) - requires @irrelevant()(forall n:Ref :: { n.first } n in nodes ==> + requires @irrelevant("Explicit")(forall n:Ref :: { n.first } n in nodes ==> acc(n.first) && (n.first != null ==> n.first in nodes)) - requires @dependency()(forall n:Ref :: { n.second } n in nodes ==> + requires @dependency("Explicit")(forall n:Ref :: { n.second } n in nodes ==> acc(n.second) && (n.second != null ==> n.second in nodes)) - requires @dependency()(x in nodes) + requires @dependency("Explicit")(x in nodes) { var y : Ref - if(@dependency()(x.second != null)) { - @dependency() + if(@dependency("PathCondition")(x.second != null)) { + @dependency("Implicit") y := x.second // permissions covered by preconditions - @dependency() + @dependency("Implicit") y.second := y - @testAssertion() + @testAssertion("Explicit") assert x.second.second == x.second } } method quantifiedSum(nodes: Set[Ref], x: Ref) - requires @dependency()(forall n:Ref :: { n.first } n in nodes ==> + requires @dependency("Explicit")(forall n:Ref :: { n.first } n in nodes ==> acc(n.first) && (n.first != null ==> n.first in nodes)) - requires @dependency()(forall n:Ref :: { n.f } n in nodes ==> + requires @dependency("Explicit")(forall n:Ref :: { n.f } n in nodes ==> acc(n.f) && 0 <= n.f && n.f <= 100) - requires @dependency()(x in nodes) + requires @dependency("Explicit")(x in nodes) { var a: Int - @dependency() + @dependency("Implicit") a := x.f - if(@dependency()(x.first != null)) { - @dependency() + if(@dependency("PathCondition")(x.first != null)) { + @dependency("Implicit") a := a + x.first.f } - @testAssertion() + @testAssertion("Explicit") assert a >= 0 } diff --git a/src/test/resources/dependencyAnalysisTests/all/sum-loops.vpr b/src/test/resources/dependencyAnalysisTests/all/sum-loops.vpr index 809bc691e..f48e96ad8 100644 --- a/src/test/resources/dependencyAnalysisTests/all/sum-loops.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/sum-loops.vpr @@ -1,76 +1,76 @@ field f: Int method sum(n: Int) returns (res: Int) - requires @dependency()(0 <= n) + requires @dependency("Explicit")(0 <= n) ensures res == n * (n + 1) / 2 { - @dependency() + @dependency("Implicit") res := 0 var i: Int - @dependency() + @dependency("Implicit") i := 0; - while(@dependency()(i <= n)) - invariant @dependency()(i <= (n + 1)) - invariant @dependency()(res == (i - 1) * i / 2) + while(@dependency("PathCondition")(i <= n)) + invariant @dependency("LoopInvariant")(i <= (n + 1)) + invariant @dependency("LoopInvariant")(res == (i - 1) * i / 2) { - @dependency() + @dependency("Implicit") res := res + i - @dependency() + @dependency("Implicit") i := i + 1 } - @testAssertion() + @testAssertion("Explicit") assert res == n * (n + 1) / 2 } method sumPerm1(n: Int, res: Ref) - requires @dependency()(acc(res.f)) - requires @dependency()(0 <= n) + requires @dependency("Explicit")(acc(res.f)) + requires @dependency("Explicit")(0 <= n) ensures acc(res.f) ensures res.f == n * (n + 1) / 2 { - @dependency() + @dependency("Implicit") res.f := 0 var i: Int - @dependency() + @dependency("Implicit") i := 0; - while(@dependency()(i <= n)) - invariant @dependency()(acc(res.f)) - invariant @dependency()(i <= (n + 1)) - invariant @dependency()(res.f == (i - 1) * i / 2) + while(@dependency("PathCondition")(i <= n)) + invariant @dependency("LoopInvariant")(acc(res.f)) + invariant @dependency("LoopInvariant")(i <= (n + 1)) + invariant @dependency("LoopInvariant")(res.f == (i - 1) * i / 2) { - @dependency() + @dependency("Implicit") res.f := res.f + i - @dependency() + @dependency("Implicit") i := i + 1 } - @testAssertion() + @testAssertion("Explicit") assert res.f == n * (n + 1) / 2 } method sumPerm2(n: Int, res: Ref) - requires @dependency()(acc(res.f)) - requires @irrelevant()(0 <= n) + requires @dependency("Explicit")(acc(res.f)) + requires @irrelevant("Explicit")(0 <= n) ensures acc(res.f) { - @irrelevant() + @irrelevant("Implicit") res.f := 0 var i: Int - @irrelevant() + @irrelevant("Implicit") i := 0; - while(@irrelevant()(i <= n)) - invariant @dependency()(acc(res.f)) - invariant @irrelevant()(i <= (n + 1)) - invariant @irrelevant()(res.f == (i - 1) * i / 2) + while(@irrelevant("PathCondition")(i <= n)) + invariant @dependency("LoopInvariant")(acc(res.f)) + invariant @irrelevant("LoopInvariant")(i <= (n + 1)) + invariant @irrelevant("LoopInvariant")(res.f == (i - 1) * i / 2) { - @irrelevant() + @irrelevant("Implicit") res.f := res.f + i - @irrelevant() + @irrelevant("Implicit") i := i + 1 } - @testAssertion() // only check permission flow + @testAssertion("Implicit") // only check permission flow res.f := 5 } \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/tuples.vpr b/src/test/resources/dependencyAnalysisTests/all/tuples.vpr index 441262f77..3e5aa27e2 100644 --- a/src/test/resources/dependencyAnalysisTests/all/tuples.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/tuples.vpr @@ -6,27 +6,27 @@ predicate tuple(this: Ref) { } method setTuple(this: Ref, l: Int, r: Int) - requires @dependency()(tuple(this)) + requires @dependency("Explicit")(tuple(this)) ensures tuple(this) { - @dependency() + @dependency("Implicit") unfold tuple(this) - @irrelevant() + @irrelevant("Implicit") this.left := l - @irrelevant() + @irrelevant("Implicit") this.right := r - @testAssertion() + @testAssertion("Implicit") fold tuple(this) } method addTuple(this: Ref) returns (sum: Int) - requires @dependency()(acc(tuple(this), 1/2)) + requires @dependency("Explicit")(acc(tuple(this), 1/2)) ensures acc(tuple(this), 1/2) { - @dependency() + @dependency("Implicit") unfold acc(tuple(this), 1/2) - @irrelevant() + @irrelevant("Implicit") sum := this.left + this.right - @testAssertion() + @testAssertion("Implicit") fold acc(tuple(this), 1/2) } \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/wands.vpr b/src/test/resources/dependencyAnalysisTests/all/wands.vpr index 46efcca63..387440037 100644 --- a/src/test/resources/dependencyAnalysisTests/all/wands.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/wands.vpr @@ -16,20 +16,20 @@ function elems(start: Ref) : Seq[Int] } method appendit_wand1(l1 : Ref, l2: Ref) - requires @dependency()(list(l1)) - requires @dependency()(list(l2)) + requires @dependency("Explicit")(list(l1)) + requires @dependency("Explicit")(list(l2)) requires l2 != null ensures list(l1) { - @dependency() + @dependency("Implicit") unfold list(l1) if(l1.next == null) { // easy case - @dependency() + @dependency("Implicit") l1.next := l2 - @testAssertion() + @testAssertion("Implicit") fold list(l1) } else { - @irrelevant() + @irrelevant("Explicit") assume false } } diff --git a/src/test/resources/dependencyAnalysisTests/new/meeting.vpr b/src/test/resources/dependencyAnalysisTests/new/meeting.vpr index a1ccd0b67..d3acf611f 100644 --- a/src/test/resources/dependencyAnalysisTests/new/meeting.vpr +++ b/src/test/resources/dependencyAnalysisTests/new/meeting.vpr @@ -2,40 +2,36 @@ field f: Int method infeasible1(x: Ref) - requires @dependency()(acc(x.f, 1/2)) // missing dependency iff infeasible branches are skipped - requires @dependency()(x.f > 0) // missing dependency iff infeasible branches are skipped + requires acc(x.f, 1/2) + requires x.f > 0 { - if(@dependency()(x.f < 0)){ // missing dependency iff infeasible branches are skipped - @testAssertion() - x.f := x.f + 1 // // missing node iff infeasible branches are skipped + if(x.f < 0){ + x.f := x.f + 1 // missing node iff infeasible branches are skipped + assert x.f > 0 } } method infeasible2(x: Ref) - requires @irrelevant()(x != null ==> acc(x.f)) // unexpected dependency iff infeasible branches are always executed + requires x != null ==> acc(x.f) // unexpected dependency { - if(@irrelevant()(x == null)){ // unexpected dependency iff infeasible branches are always executed + var a: Int + if(x == null){ // unexpected dependency var a: Int - @dependency() a := 0 - @testAssertion() assert a >= 0 - } + } } method infeasible3(x: Ref) { - if(@irrelevant()(x != null)){ // unexpected dependency iff infeasible branches are always executed - @irrelevant() + if(x != null){ // unexpected dependency iff infeasible branches are always executed inhale acc(x.f) } - if(@irrelevant()(x == null)){ // unexpected dependency iff infeasible branches are always executed + if(x == null){ // unexpected dependency iff infeasible branches are always executed var a: Int - @dependency() a := 0 - @testAssertion() assert a >= 0 } } @@ -60,26 +56,44 @@ method currentPerm(x: Ref) assert perm(x.f) > 1/4 } -method noAlias(a: Ref, b: Ref, c: Ref) - requires @dependency()(acc(a.f)) - requires @dependency()(acc(b.f, 1/2)) - requires @irrelevant()(acc(c.f, 1/2)) + +method automatedTest() { - @testAssertion() - assert a != b + var a: Int + a := 0 + a := 1 + a := 0 + assert a == 0 } +field elem: Int +field next: Ref + +predicate list(this: Ref) { + acc(this.elem) && acc(this.next) && + (this.next != null ==> list(this.next)) +} -method permTest(a: Ref, b: Ref, n: Int) - requires @dependency()(acc(a.f)) - requires @irrelevant()(acc(b.f)) && @irrelevant()(b.f > 0) +method appendListFull(this: Ref, e: Int) + requires list(this) + requires 0 <= e && e < 100 + ensures list(this) { - @dependency() - assume n > 0 - @irrelevant() - a.f := b.f + 2 - @dependency() - a.f := n - @testAssertion() - assert a.f >= 0 + unfold list(this) + assume 0 <= this.elem && this.elem < 100 + + if (this.next == null) { + var n: Ref + + n := new(elem, next) + n.elem := e + n.next := null + this.next := n + fold list(n) + } else { + appendListFull(this.next, e) + } + + fold list(this) } + diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/branches.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/branches.vpr index e92ca3175..ef40a5744 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/branches.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/branches.vpr @@ -2,72 +2,72 @@ method branch1(){ var x: Int, y: Int - if(@dependency()(x > 0)){ - @dependency() + if(@dependency("PathCondition")(x > 0)){ + @dependency("Implicit") y := x + 1 }else{ - @dependency() + @dependency("Implicit") y := -x + 1 } - @testAssertion() + @testAssertion("Explicit") assert y > 0 } method branch2(){ var x: Int, y: Int - if(@irrelevant()(x > 0)){ - @dependency() + if(@irrelevant("PathCondition")(x > 0)){ + @dependency("Explicit") assume y > 0 }else{ - @dependency() + @dependency("Explicit") assume y >= 10 } - @testAssertion() + @testAssertion("Explicit") assert y > 0 } method branch3(){ var x: Int, y: Int - if(@dependency()(x > 0)){ - @dependency() + if(@dependency("PathCondition")(x > 0)){ + @dependency("Implicit") y := x + 1 }else{ - @irrelevant() + @irrelevant("Explicit") assume y >= 10 } - @testAssertion() + @testAssertion("Explicit") assert x > 0 ==> y > 0 } method branch4(){ var x: Int, y: Int - if(@dependency()(x > 0)){ - @irrelevant() + if(@dependency("PathCondition")(x > 0)){ + @irrelevant("Implicit") y := x + 1 }else{ - @dependency() + @dependency("Implicit") y := 10 - x - @testAssertion() + @testAssertion("Explicit") assert y > 0 } } method branch5(){ var x: Int, y: Int - @dependency() + @dependency("Explicit") assume y > 0 - if(@irrelevant()(x > 0)){ - @irrelevant() + if(@irrelevant("PathCondition")(x > 0)){ + @irrelevant("Implicit") y := x + 1 }else{ - @testAssertion() + @testAssertion("Explicit") assert y >= 0 } } \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/loops.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/loops.vpr index d98d416d6..8e26615b5 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/loops.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/loops.vpr @@ -5,16 +5,16 @@ method loop1(){ res := 0 i := 10 while(i > 0) - invariant @irrelevant()(i <= 10) - invariant @irrelevant()(i >= 0) - invariant @dependency()(res >= 0) + invariant @irrelevant("LoopInvariant")(i <= 10) + invariant @irrelevant("LoopInvariant")(i >= 0) + invariant @dependency("LoopInvariant")(res >= 0) { - @dependency() + @dependency("Implicit") res := res + i i := i - 1 } - @testAssertion() + @testAssertion("Explicit") assert res >= 0 } @@ -23,16 +23,16 @@ method loop2(){ var res: Int res := 0 i := 10 - while(@dependency()(i > 0)) - invariant @irrelevant()(i <= 10) - invariant @irrelevant()(i >= 0) - invariant @irrelevant()(res >= 0) + while(@dependency("PathCondition")(i > 0)) + invariant @irrelevant("LoopInvariant")(i <= 10) + invariant @irrelevant("LoopInvariant")(i >= 0) + invariant @irrelevant("LoopInvariant")(res >= 0) { - @irrelevant() + @irrelevant("Implicit") res := res + i - @dependency() + @dependency("Implicit") i := i - 1 - @testAssertion() + @testAssertion("Explicit") assert i >= 0 } } @@ -42,16 +42,16 @@ method loop3(){ var res: Int res := 0 i := 10 - while(@dependency()(i > 0)) - invariant @irrelevant()(i <= 10) - invariant @irrelevant()(i >= 0) - invariant @dependency()(res >= 0) + while(@dependency("PathCondition")(i > 0)) + invariant @irrelevant("LoopInvariant")(i <= 10) + invariant @irrelevant("LoopInvariant")(i >= 0) + invariant @dependency("LoopInvariant")(res >= 0) { - @dependency() + @dependency("Implicit") res := res + i - @irrelevant() + @irrelevant("Implicit") i := i - 1 - @testAssertion() + @testAssertion("Explicit") assert res > 0 } } @@ -59,22 +59,22 @@ method loop3(){ method loop4(){ var i: Int var res: Int - @dependency() + @dependency("Implicit") res := 0 - @irrelevant() + @irrelevant("Implicit") i := 10 - while(@irrelevant()(i > 0)) - invariant @irrelevant()(i <= 10) - invariant @irrelevant()(i >= 0) - invariant @dependency()(res >= 0) + while(@irrelevant("PathCondition")(i > 0)) + invariant @irrelevant("LoopInvariant")(i <= 10) + invariant @irrelevant("LoopInvariant")(i >= 0) + invariant @dependency("LoopInvariant")(res >= 0) { res := res - 1 - @irrelevant() + @irrelevant("Implicit") i := i - 1 - @dependency() + @dependency("Explicit") assume res > 0 } - @testAssertion() + @testAssertion("Explicit") assert res >= 0 } \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr index a1199444c..8357d8246 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr @@ -12,36 +12,36 @@ method basicApply() { var x: Int var y: Int - @dependency() + @dependency("Explicit") inhale x > 0 - @dependency() + @dependency("Explicit") inhale x > 0 --* y > 0 - @dependency() + @dependency("Implicit") apply x > 0 --* y > 0 - @testAssertion() + @testAssertion("Explicit") assert y > 0 } method basicPackage(l: Ref) - requires @dependency()(list(l)) + requires @dependency("Explicit")(list(l)) ensures list(l) { - @dependency() + @dependency("Implicit") unfold list(l) var tmp : Ref - @dependency() + @dependency("Implicit") tmp := l.next - @dependency() + @dependency("Implicit") package list(tmp) --* list(l) { fold list(l) // TODO ake } - @dependency() + @dependency("Implicit") apply list(tmp) --* list(l) - @testAssertion() + @testAssertion("Explicit") assert list(l) } \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/method-call.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/method-call.vpr index 97f59110b..d0d370311 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/method-call.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/method-call.vpr @@ -9,65 +9,65 @@ method sum(x: Int, y: Int) returns(res: Int) method call1(){ var x: Int, y: Int - @dependency() + @dependency("Explicit") assume x > 10 - @dependency() + @dependency("Explicit") assume y > 0 - @testAssertion() + @testAssertion("Implicit") x := sum(x, y) } method call2(){ var x: Int, y: Int, z: Int - @dependency() + @dependency("Explicit") assume x > 10 - @dependency() + @dependency("Explicit") assume y > 0 - @dependency() + @dependency("Implicit") z := sum(x, y) - @testAssertion() + @testAssertion("Explicit") assert z == x + y } method call3(x: Int, y: Int) { - @dependency() + @dependency("Explicit") assume x > 0 - @dependency() + @dependency("Explicit") assume y > 0 var n: Int, n2: Int - @dependency() + @dependency("Implicit") n := sum(x, y) - @dependency() + @dependency("Implicit") n2 := sum(x, y) - @dependency() + @dependency("Implicit") n := n + n2 - @testAssertion() + @testAssertion("Explicit") assert n == 2*x + 2*y } method call4(x: Int, y: Int, z: Int) { - @dependency() + @dependency("Explicit") assume x > 0 - @dependency() + @dependency("Explicit") assume y > 0 - @irrelevant() + @irrelevant("Explicit") assume z > 0 var n: Int, n2: Int - @dependency() + @dependency("Implicit") n := sum(x, y) - @irrelevant() + @irrelevant("Implicit") n2 := sum(x, z) - @testAssertion() + @testAssertion("Explicit") assert n == x + y } diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/misc.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/misc.vpr index 0eeb61124..07140d26f 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/misc.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/misc.vpr @@ -1,20 +1,20 @@ field f: Int method divBy0(a: Ref, n: Int) - requires @dependency()(n > 0) - requires @dependency()(acc(a.f)) - requires @irrelevant()(a.f > 0) + requires @dependency("Explicit")(n > 0) + requires @dependency("Explicit")(acc(a.f)) + requires @irrelevant("Explicit")(a.f > 0) { var res: Int - @testAssertion() + @testAssertion("Implicit") res := a.f / n } method assumeFalse() { var a: Int - @dependency() + @dependency("Explicit") assume false - @testAssertion() + @testAssertion("Explicit") assert a == 2 } \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr index 12e8c96d9..36bc58c75 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr @@ -3,104 +3,104 @@ field f: Int method perm1(){ var x: Ref - @dependency() + @dependency("Implicit") x := new(f) - @testAssertion() + @testAssertion("Explicit") inhale x.f > 0 } method perm2(){ var x: Ref - @dependency() + @dependency("Explicit") inhale acc(x.f, 1/2) - @testAssertion() + @testAssertion("Explicit") inhale x.f > 0 } method perm3(){ var x: Ref - @dependency() + @dependency("Explicit") inhale acc(x.f) - @testAssertion() + @testAssertion("Implicit") x.f := x.f + 1 } method perm4(){ var x: Ref - @dependency() + @dependency("Explicit") inhale acc(x.f) - @dependency() + @dependency("Explicit") inhale x.f > 0 - @dependency() + @dependency("Implicit") x.f := x.f + 1 - @testAssertion() + @testAssertion("Explicit") assert x.f > 1 } method perm5(){ var x: Ref - @dependency() + @dependency("Explicit") inhale acc(x.f) - @irrelevant() + @irrelevant("Implicit") x.f := x.f + 1 - @testAssertion() + @testAssertion("Explicit") exhale acc(x.f) } method perm6(){ var x: Ref - @dependency() + @dependency("Explicit") inhale acc(x.f) - @dependency() + @dependency("Explicit") inhale x.f > 0 - @dependency() + @dependency("Implicit") x.f := x.f + 1 - // @irrelevant() // TODO ake + // @irrelevant("Implicit") // TODO ake exhale acc(x.f, 1/2) - @testAssertion() + @testAssertion("Explicit") assert x.f > 1 } method permAmount1(x: Ref, p: Perm) requires p > none - requires @dependency()(acc(x.f, p)) + requires @dependency("Explicit")(acc(x.f, p)) { - @dependency() + @dependency("Explicit") assume p > 1/2 - @testAssertion() + @testAssertion("Explicit") exhale acc(x.f, 1/2) } method permAmount2(x: Ref, p: Perm) requires p > none - requires @dependency()(acc(x.f, p)) + requires @dependency("Explicit")(acc(x.f, p)) { - @dependency() + @dependency("Explicit") inhale x.f > 0 - @dependency() + @dependency("Explicit") assume p > 1/2 exhale acc(x.f, 1/2) - @testAssertion() + @testAssertion("Explicit") assert x.f > 0 } method noAlias(a: Ref, b: Ref, c: Ref) - requires @dependency()(acc(a.f)) - requires @dependency()(acc(b.f, 1/2)) - requires @irrelevant()(acc(c.f, 1/2)) + requires @dependency("Explicit")(acc(a.f)) + requires @dependency("Explicit")(acc(b.f, 1/2)) + requires @irrelevant("Explicit")(acc(c.f, 1/2)) { - @testAssertion() + @testAssertion("Explicit") assert a != b } method exhaleInhale(a: Ref) - requires @dependency()(acc(a.f)) + requires @dependency("Explicit")(acc(a.f)) { - @dependency() + @dependency("Implicit") exhale acc(a.f, 1/2) - @dependency() + @dependency("Explicit") inhale acc(a.f, 1/2) - @testAssertion() + @testAssertion("Explicit") assert perm(a.f) == 1/1 } \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/predicates.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/predicates.vpr index 5a7bdc4a9..ada05d864 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/predicates.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/predicates.vpr @@ -9,44 +9,44 @@ predicate greater5(n: Int){ } method foldP(n: Int) - requires @dependency()(n > 10) + requires @dependency("Explicit")(n > 10) { var x: Int := 1 - @testAssertion() + @testAssertion("Implicit") fold greater0(x + n) } method unfoldP(n: Int) - requires @dependency()(greater0(n)) + requires @dependency("Explicit")(greater0(n)) { var x: Int := 1 - @dependency() + @dependency("Implicit") unfold greater0(n) - @dependency() + @dependency("Implicit") x := n + x - @testAssertion() + @testAssertion("Explicit") assert x > 1 } method unfoldFoldP(a: Int, b: Int) - requires @dependency()(greater0(a)) && @dependency()(greater5(b)) + requires @dependency("Explicit")(greater0(a)) && @dependency("Explicit")(greater5(b)) ensures greater5(a + b) { - @dependency() + @dependency("Implicit") unfold greater0(a) - @dependency() + @dependency("Implicit") unfold greater5(b) - @testAssertion() + @testAssertion("Implicit") fold greater5(a + b) } method callWithPredicate(a: Int, b: Int) - requires @dependency()(greater0(a)) && @dependency()(greater5(b)) + requires @dependency("Explicit")(greater0(a)) && @dependency("Explicit")(greater5(b)) ensures greater5(a + b) { - @dependency() + @dependency("Implicit") unfoldFoldP(a, b) - @testAssertion() + @testAssertion("Explicit") assert greater5(a + b) } \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr index 829981be2..bf51d8be7 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr @@ -1,102 +1,102 @@ field f: Int method quantifiedPerm1(xs: Seq[Ref]) { - assume @dependency()(|xs| > 5) - inhale @dependency()(forall x: Ref :: x in xs ==> acc(x.f)) + assume @dependency("Explicit")(|xs| > 5) + inhale @dependency("Explicit")(forall x: Ref :: x in xs ==> acc(x.f)) @testAssertion() xs[0].f := 10 } method quantifiedPerm2(xs: Seq[Ref]) { - assume @dependency()(|xs| > 5) - inhale @dependency()(forall x: Ref :: x in xs ==> acc(x.f)) + assume @dependency("Explicit")(|xs| > 5) + inhale @dependency("Explicit")(forall x: Ref :: x in xs ==> acc(x.f)) @testAssertion() xs[0].f := xs[1].f + xs[4].f } method quantifiedPerm3(xs: Seq[Ref], y: Ref) { - assume @dependency()(|xs| > 5) - inhale @dependency()(forall x: Ref :: x in xs ==> acc(x.f)) - inhale @dependency()(acc(y.f, wildcard)) + assume @dependency("Explicit")(|xs| > 5) + inhale @dependency("Explicit")(forall x: Ref :: x in xs ==> acc(x.f)) + inhale @dependency("Explicit")(acc(y.f, wildcard)) - @testAssertion() + @testAssertion("Explicit") assert xs[0] != y } method quantifiedExhalePartiallyTest(xs: Seq[Ref]) { var res: Int assume |xs| > 5 - inhale @dependency()(forall x: Ref :: x in xs ==> acc(x.f)) + inhale @dependency("Explicit")(forall x: Ref :: x in xs ==> acc(x.f)) - @testAssertion() + @testAssertion("Explicit") exhale forall x: Ref :: x in xs ==> acc(x.f, 1/2) } method quantifiedExhalePartially(xs: Seq[Ref]) { var res: Int - assume @dependency()(|xs| > 5) - inhale @dependency()(forall x: Ref :: x in xs ==> acc(x.f)) + assume @dependency("Explicit")(|xs| > 5) + inhale @dependency("Explicit")(forall x: Ref :: x in xs ==> acc(x.f)) exhale forall x: Ref :: x in xs ==> acc(x.f, 1/2) - @testAssertion() + @testAssertion("Implicit") res := xs[1].f + 1 } method quantifiedExhaleFully(xs: Seq[Ref]) { - assume @irrelevant()(|xs| > 5) - inhale @dependency()(forall x: Ref :: x in xs ==> acc(x.f)) + assume @irrelevant("Explicit")(|xs| > 5) + inhale @dependency("Explicit")(forall x: Ref :: x in xs ==> acc(x.f)) - @testAssertion() + @testAssertion("Explicit") exhale forall x: Ref :: x in xs ==> acc(x.f) } method quantifiedPermWrite1(xs: Seq[Ref]) { - @dependency() + @dependency("Explicit") assume |xs| > 5 - @dependency() + @dependency("Explicit") inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) - @testAssertion() + @testAssertion("Implicit") xs[0].f := 0 } method quantifiedPermWrite2(xs: Seq[Ref]) { - @dependency() + @dependency("Explicit") assume |xs| > 5 - @dependency() + @dependency("Explicit") inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) - @dependency() + @dependency("Implicit") xs[0].f := 0 - @testAssertion() + @testAssertion("Explicit") assert xs[0].f == 0 } method quantifiedPermWrite3(xs: Seq[Ref]) { - @dependency() + @dependency("Explicit") assume |xs| > 5 - @dependency() + @dependency("Explicit") inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) - @irrelevant() + @irrelevant("Implicit") xs[0].f := 0 - @testAssertion() + @testAssertion("Explicit") assert xs[0] != xs[1] ==> xs[1].f > 0 } method quantifiedPermWrite4(xs: Seq[Ref], ys: Seq[Ref]) - requires @dependency()(|xs| > 5) - requires @irrelevant()(|ys| > 3) + requires @dependency("Explicit")(|xs| > 5) + requires @irrelevant("Explicit")(|ys| > 3) { - @dependency() + @dependency("Explicit") inhale forall x: Ref :: x in xs ==> acc(x.f) - @irrelevant() + @irrelevant("Explicit") inhale forall y: Ref :: y in ys ==> acc(y.f) - @testAssertion() + @testAssertion("Implicit") xs[0].f := 2 } \ No newline at end of file diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index a131f9f8c..1bf81e762 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -20,16 +20,9 @@ class AssumptionAnalysisTests extends AnyFunSuite { val ignores: Seq[String] = Seq() val testDirectories: Seq[String] = Seq( // "dependencyAnalysisTests", + "dependencyAnalysisTests/all", "dependencyAnalysisTests/unitTests", -// "dependencyAnalysisTests/quick", -// "examples/binary-search", - // "examples/graph-copy", - // "examples/graph-marking", -// "examples/max_array", -// "examples/quickselect", -// "examples/longest-common-prefix", -// "examples/tree-delete-min", -// "fromSilver" +// "dependencyAnalysisTests/quick" ) val irrelevantKeyword = "irrelevant" @@ -93,7 +86,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { val program: Program = tests.loadProgram(filePrefix, fileName, frontend) val result = frontend.verifier.verify(program) if(result.isInstanceOf[verifier.Failure]) { - cancel("Program does not verify. Skip test.") + cancel(f"Program does not verify. Skip test.\n$result") return } From 7f37ee20fb37ce8e724e9ee6ed1dd9e546f78ece Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 10 Jul 2025 18:49:28 +0200 Subject: [PATCH 157/474] fix tests --- .../dependencyAnalysisTests/all/exhale.vpr | 24 +++++++ .../all/imprecision.vpr | 4 +- .../dependencyAnalysisTests/all/list.vpr | 66 +++++++++---------- .../unitTests/permissions.vpr | 2 +- .../unitTests/quantifiedPermissions.vpr | 2 +- src/test/scala/AssumptionAnalysisTests.scala | 14 ++-- 6 files changed, 68 insertions(+), 44 deletions(-) create mode 100644 src/test/resources/dependencyAnalysisTests/all/exhale.vpr diff --git a/src/test/resources/dependencyAnalysisTests/all/exhale.vpr b/src/test/resources/dependencyAnalysisTests/all/exhale.vpr new file mode 100644 index 000000000..31e1eddd5 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/all/exhale.vpr @@ -0,0 +1,24 @@ +field f: Int + + +method exhaleDependency(x: Ref) + requires @dependency("Explicit")(acc(x.f)) +{ + @dependency("Implicit") + exhale acc(x.f, 1/2) + @dependency("Explicit") + inhale acc(x.f, 1/2) + @testAssertion("Implicit") + x.f := 0 +} + +method exhaleIrrelevant(x: Ref) + requires acc(x.f, 1/2) // TODO ake: imprecise? @irrelevant("Explicit")(acc(x.f, 1/2)) +{ + // @irrelevant("Implicit") // TODO ake: imprecise? + exhale acc(x.f, 1/4) + @dependency("Explicit") + inhale acc(x.f, 1/2) + @testAssertion("Implicit") + inhale x.f > 0 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr b/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr index b7d45b79f..6b4d8deff 100644 --- a/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr @@ -64,7 +64,7 @@ method quantifiedPerm5(xs: Seq[Ref], ys: Seq[Ref]) @irrelevant("Explicit") inhale forall y: Ref :: y in ys ==> acc(y.f) - @irrelevant("Implicit") + // @irrelevant("Implicit") // TODO ake: imprecise xs[0].f := ys[0].f + 2 @testAssertion("Implicit") xs[0].f := 2 @@ -75,7 +75,7 @@ method nonUniqueUnsatCore(x: Ref) { var a: Int if(x == null){ - @irrelevant("Explicit") + // @irrelevant("Explicit") // TODO ake: imprecise inhale a >= 0 } @dependency("Explicit") diff --git a/src/test/resources/dependencyAnalysisTests/all/list.vpr b/src/test/resources/dependencyAnalysisTests/all/list.vpr index 24b9911d4..8819d6ae3 100644 --- a/src/test/resources/dependencyAnalysisTests/all/list.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/list.vpr @@ -14,27 +14,27 @@ function listLength(l:Ref) : Int } method appendList1(this: Ref, e: Int) - requires @irrelevant()(list(this)) - requires @irrelevant()(0 <= e && e < 100) + requires list(this) // TODO ake: imprecise @irrelevant("Explicit")(list(this)) + requires @irrelevant("Explicit")(0 <= e && e < 100) ensures list(this) { - @irrelevant() + // @irrelevant("Implicit") TODO ake: imprecise unfold list(this) - @irrelevant() + @irrelevant("Explicit") assume 0 <= this.elem && this.elem < 100 - if (@irrelevant()(this.next == null)) { + if (this.next == null) { // TODO ake: imprecise @irrelevant("PathCondition")(this.next == null) var n: Ref - @dependency() + @dependency("Implicit") n := new(elem, next) - @irrelevant() + @irrelevant("Implicit") n.elem := e - @dependency() + @dependency("Implicit") n.next := null - @irrelevant() + @irrelevant("Implicit") this.next := n - @testAssertion() + @testAssertion("Implicit") fold list(n) } else { appendList1(this.next, e) @@ -44,30 +44,30 @@ method appendList1(this: Ref, e: Int) } method appendList2(this: Ref, e: Int) - requires @dependency()(list(this)) - requires @dependency()(0 <= e && e < 100) + requires @dependency("Explicit")(list(this)) + requires @dependency("Explicit")(0 <= e && e < 100) ensures list(this) { - @dependency() + @dependency("Implicit") unfold list(this) - @irrelevant() + @irrelevant("Explicit") assume 0 <= this.elem && this.elem < 100 - if (@dependency()(this.next == null)) { + if (@dependency("PathCondition")(this.next == null)) { var n: Ref - @irrelevant() + @irrelevant("Implicit") n := new(elem, next) - @irrelevant() + @irrelevant("Implicit") n.elem := e - @irrelevant() + @irrelevant("Implicit") n.next := null - @irrelevant() + @irrelevant("Implicit") this.next := n - @irrelevant() + @irrelevant("Implicit") fold list(n) } else { - @testAssertion() + @testAssertion("Implicit") appendList2(this.next, e) } @@ -75,33 +75,33 @@ method appendList2(this: Ref, e: Int) } method appendListFull(this: Ref, e: Int) - requires @dependency()(list(this)) - requires @dependency()(0 <= e && e < 100) + requires @dependency("Explicit")(list(this)) + requires @dependency("Explicit")(0 <= e && e < 100) ensures list(this) { - @dependency() + @dependency("Implicit") unfold list(this) - @irrelevant() + @irrelevant("Explicit") assume 0 <= this.elem && this.elem < 100 - if (@dependency()(this.next == null)) { + if (@dependency("PathCondition")(this.next == null)) { var n: Ref - @dependency() + @dependency("Implicit") n := new(elem, next) - @irrelevant() + @irrelevant("Implicit") n.elem := e - @dependency() + @dependency("Implicit") n.next := null - @dependency() + @dependency("Implicit") this.next := n - @dependency() + @dependency("Implicit") fold list(n) } else { - @dependency() + @dependency("Implicit") appendListFull(this.next, e) } - @testAssertion() + @testAssertion("Implicit") fold list(this) } diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr index 36bc58c75..048f513ba 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr @@ -88,7 +88,7 @@ method permAmount2(x: Ref, p: Perm) method noAlias(a: Ref, b: Ref, c: Ref) requires @dependency("Explicit")(acc(a.f)) requires @dependency("Explicit")(acc(b.f, 1/2)) - requires @irrelevant("Explicit")(acc(c.f, 1/2)) + requires acc(c.f, 1/2) // TODO ake: imprecise @irrelevant("Explicit")() { @testAssertion("Explicit") assert a != b diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr index bf51d8be7..e8988a636 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr @@ -81,7 +81,7 @@ method quantifiedPermWrite3(xs: Seq[Ref]) { @dependency("Explicit") inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) - @irrelevant("Implicit") + // @irrelevant("Implicit") // TODO ake: imprecise xs[0].f := 0 @testAssertion("Explicit") diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index 1bf81e762..458dc6811 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -16,7 +16,7 @@ import scala.jdk.CollectionConverters.IterableHasAsScala class AssumptionAnalysisTests extends AnyFunSuite { - val CHECK_PRECISION = false + val CHECK_PRECISION = true val ignores: Seq[String] = Seq() val testDirectories: Seq[String] = Seq( // "dependencyAnalysisTests", @@ -34,9 +34,9 @@ class AssumptionAnalysisTests extends AnyFunSuite { testDirectories foreach createTests - // test("dependencyAnalysisTests/all" + "/" + "misc"){ - // executeTest("examples/max_array/", "max-array-standard", frontend) - // } +// test("custom test"){ +// executeTest("dependencyAnalysisTests/quick/", "test", frontend) +// } def createTests(dirName: String): Unit = { val path = Paths.get(getClass.getClassLoader.getResource(dirName).toURI) @@ -313,7 +313,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { private def extractTestAssertionNodesFromGraph(graph: AssumptionAnalysisGraph): Seq[AssumptionAnalysisNode] = { graph.nodes.filter(node => (node.getNodeType.equals("Assertion") || node.getNodeType.equals("Exhale") || node.getNodeType.equals("Check")) && - node.sourceInfo.toString.contains("@" + testAssertionKeyword + "()") + node.sourceInfo.toString.contains("@" + testAssertionKeyword + "(") ).toSeq } @@ -321,7 +321,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { graph.nodes.filter(node => { (node.getNodeType.equals("Assumption") || node.getNodeType.equals("Inhale") || node.getNodeType.equals("Infeasible")) && !node.assumptionType.equals(AssumptionType.Internal) && - node.sourceInfo.toString.contains("@" + dependencyKeyword + "()") + node.sourceInfo.toString.contains("@" + dependencyKeyword + "(") } ).toSeq } @@ -330,7 +330,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { graph.nodes.filter(node => { (node.getNodeType.equals("Assumption") || node.getNodeType.equals("Inhale") || node.getNodeType.equals("Infeasible")) && !node.assumptionType.equals(AssumptionType.Internal) && - node.sourceInfo.toString.contains("@" + irrelevantKeyword + "()") + node.sourceInfo.toString.contains("@" + irrelevantKeyword + "(") } ).toSeq } From 548a8b5c36786bc9489e1f861007e9daace979a6 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 11 Jul 2025 11:47:59 +0200 Subject: [PATCH 158/474] remove "reassume analysis labels" workaround --- .../AssumptionAnalyzer.scala | 13 ------- src/main/scala/decider/Decider.scala | 22 ++---------- src/main/scala/decider/PathConditions.scala | 26 +++++++++++++- src/main/scala/rules/MagicWandSupporter.scala | 15 +++++--- .../functions/FunctionVerificationUnit.scala | 10 +++--- .../unitTests/magicWands.vpr | 34 ++++++++++++++++++- 6 files changed, 76 insertions(+), 44 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 439dfd344..08c4c7d37 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -56,7 +56,6 @@ trait AssumptionAnalyzer { def createLabelNode(labelTerm: Term, sourceChunks: Iterable[Chunk], sourceTerms: Iterable[Term]): Option[LabelNode] = None def registerInhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: (Term => CH), perm: Term, labelNode: Option[LabelNode], analysisInfo: AnalysisInfo, isExhale: Boolean): CH = buildChunk(perm) def registerExhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: (Term => CH), perm: Term, analysisInfo: AnalysisInfo): CH = buildChunk(perm) - def getReassumeLabelNodes: Iterable[LabelNode] = Set.empty def computeProofCoverage(): Unit = {} } @@ -308,18 +307,6 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { chunk } - private def createReassumeLabelNode(oldLabelNode: LabelNode): LabelNode = { - val newLabelNode = LabelNode(oldLabelNode.term) - // do not add to originalLabelNodes! - addNode(newLabelNode) - assumptionGraph.addEdges(Set(oldLabelNode.id), newLabelNode.id) - newLabelNode - } - - override def getReassumeLabelNodes: Iterable[LabelNode] = { // TODO ake: work with scopes! - originalLabelNodes map createReassumeLabelNode - } - override def computeProofCoverage(): Unit = { val explicitAssertionNodes = assumptionGraph.getExplicitAssertionNodes val explicitAssertionNodeIds = explicitAssertionNodes map (_.id) diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index e3c4a35b5..a502b7485 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -143,7 +143,6 @@ 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 - private var _isReassumeAnalysisLabelsRequired: Boolean = false var assumptionAnalyzer: AssumptionAnalyzer = new NoAssumptionAnalyzer() var analysisSourceInfoStack: AnalysisSourceInfoStack = AnalysisSourceInfoStack() @@ -180,7 +179,6 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => pathConditions = other while (prover.pushPopScopeDepth > 1){ prover.pop() - _isReassumeAnalysisLabelsRequired = true } // TODO: Change interface to make the cast unnecessary? val layeredStack = other.asInstanceOf[LayeredPathConditionStack] @@ -282,7 +280,6 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => //val commentRecord = new CommentRecord("pop", null, null) //val sepIdentifier = symbExLog.openScope(commentRecord) _prover.pop() - _isReassumeAnalysisLabelsRequired = true pathConditions.popScope() //symbExLog.closeScope(sepIdentifier) } @@ -340,7 +337,8 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } def wrapWithAssumptionAnalysisLabel(term: Term, sourceChunks: Iterable[Chunk] = Set.empty, sourceTerms: Iterable[Term] = Set.empty): Term = { - if(!Verifier.config.enableAssumptionAnalysis()) return term + if(!Verifier.config.enableAssumptionAnalysis() || term.equals(True) || sourceChunks.size + sourceTerms.size == 0) + return term val labelNode = getOrCreateAnalysisLabelNode(sourceChunks, sourceTerms) labelNode.map(n => Implies(n.term, term)).getOrElse(term) @@ -473,14 +471,10 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } def assumeLabel(term: Term, assumptionLabel: String): Unit = { - // do not add to pathConditions! + pathConditions.addAnalysisLabel(term) prover.assume(term, assumptionLabel) } - def reassumeAssumptionAnalysisLabels(): Unit = { - assumptionAnalyzer.getReassumeLabelNodes foreach (node => decider.assumeLabel(node.term, AssumptionAnalyzer.createAssumptionLabel(Some(node.id)))) - } - /* Asserting facts */ def checkSmoke(isAssert: Boolean = false, assumptionType: AssumptionType=AssumptionType.Implicit): Boolean = { @@ -489,11 +483,6 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => (AssumptionAnalyzer.createAssertionLabel(nodeId), nodeId) }else{ ("", None) } - if(_isReassumeAnalysisLabelsRequired) { - reassumeAssumptionAnalysisLabels() - _isReassumeAnalysisLabelsRequired = false - } - val timeout = if (isAssert) Verifier.config.assertTimeout.toOption else Verifier.config.checkTimeout.toOption val result = prover.check(timeout, label) == Unsat if(result) { @@ -571,11 +560,6 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => val assertRecord = new ProverAssertRecord(t, timeout) val sepIdentifier = symbExLog.openScope(assertRecord) - if(_isReassumeAnalysisLabelsRequired) { - reassumeAssumptionAnalysisLabels() - _isReassumeAnalysisLabelsRequired = false - } - val result = prover.assert(t, timeout, label) if(result) diff --git a/src/main/scala/decider/PathConditions.scala b/src/main/scala/decider/PathConditions.scala index e6c67221f..9ded3ac83 100644 --- a/src/main/scala/decider/PathConditions.scala +++ b/src/main/scala/decider/PathConditions.scala @@ -31,6 +31,7 @@ trait RecordedPathConditions { def definingAssumptions: InsertionOrderedSet[Term] def definingAssumptionExps: InsertionOrderedSet[DebugExp] def declarations: InsertionOrderedSet[Decl] + def analysisLabels: InsertionOrderedSet[Term] def definitionsOnly: RecordedPathConditions @@ -71,6 +72,7 @@ trait PathConditionStack extends RecordedPathConditions { def popUntilMark(mark: Mark): Unit def setCurrentInfeasibilityNode(node: Option[Int]): Unit def getCurrentInfeasibilityNode: Option[Int] + def addAnalysisLabel(assumption: Term): Unit def startDebugSubExp(): Unit def finishDebugSubExp(description : String): Unit @@ -104,6 +106,7 @@ private class PathConditionStackLayer private var _globalDefiningAssumptionDebugExps: InsertionOrderedSet[DebugExp] = InsertionOrderedSet.empty private var _nonGlobalDefiningAssumptionDebugExps: InsertionOrderedSet[DebugExp] = InsertionOrderedSet.empty private var _declarations: InsertionOrderedSet[Decl] = InsertionOrderedSet.empty + private var _analysisLabels: InsertionOrderedSet[Term] = InsertionOrderedSet.empty def branchCondition: Option[Term] = _branchCondition def branchConditionExp: Option[(ast.Exp, Option[ast.Exp])] = _branchConditionExp @@ -120,6 +123,7 @@ private class PathConditionStackLayer def nonGlobalDefiningAssumptionDebugExps: InsertionOrderedSet[DebugExp] = _nonGlobalDefiningAssumptionDebugExps def nonGlobalAssumptionDebugExps: InsertionOrderedSet[DebugExp] = _nonGlobalAssumptionDebugExps ++ debugExpStack.flatten def declarations: InsertionOrderedSet[Decl] = _declarations + def analysisLabels: InsertionOrderedSet[Term] = _analysisLabels def assumptions: InsertionOrderedSet[Term] = globalAssumptions ++ nonGlobalAssumptions def assumptionDebugExps: InsertionOrderedSet[DebugExp] = globalAssumptionDebugExps ++ nonGlobalAssumptionDebugExps @@ -138,6 +142,7 @@ private class PathConditionStackLayer result._globalDefiningAssumptionDebugExps = _globalDefiningAssumptionDebugExps result._nonGlobalAssumptionDebugExps = _nonGlobalDefiningAssumptionDebugExps result._nonGlobalDefiningAssumptionDebugExps = _nonGlobalDefiningAssumptionDebugExps + result._analysisLabels = _analysisLabels result } @@ -170,6 +175,15 @@ private class PathConditionStackLayer _nonGlobalAssumptions += assumption } + def addAnalysisLabel(assumption: Term): Unit = { + assert( + !assumption.isInstanceOf[And], + s"Unexpectedly found a conjunction (should have been split): $assumption") + _globalAssumptions += assumption // labels are always global + _globalDefiningAssumptions += assumption + _analysisLabels += assumption + } + def addNonGlobalDebugExp(debugExp : DebugExp): Unit = { _nonGlobalAssumptionDebugExps += debugExp } @@ -272,7 +286,10 @@ private trait LayeredPathConditionStackLike { conditionalizedWithAnalysis(layers)._1 } - // TODO ake: precision + protected def analysisLabels(layers: Stack[PathConditionStackLayer]): InsertionOrderedSet[Term] = + InsertionOrderedSet(layers.flatMap(_.analysisLabels)) + + // TODO ake: remove? protected def conditionalizedWithAnalysis(layers: Stack[PathConditionStackLayer]): (Seq[Term], Seq[Term]) = { var unconditionalTerms = Vector.empty[Term] var conditionalTerms = Vector.empty[Term] @@ -417,6 +434,7 @@ private class DefaultRecordedPathConditions(from: Stack[PathConditionStackLayer] val definingAssumptions: InsertionOrderedSet[Term] = definingAssumptions(from) val definingAssumptionExps: InsertionOrderedSet[DebugExp] = definingAssumptionExps(from) val declarations: InsertionOrderedSet[Decl] = declarations(from) + val analysisLabels: InsertionOrderedSet[Term] = analysisLabels(from) def contains(assumption: Term): Boolean = contains(from, assumption) @@ -522,6 +540,10 @@ private[decider] class LayeredPathConditionStack layers.head.add(declaration) } + def addAnalysisLabel(assumption: Term): Unit = { + layers.head.addAnalysisLabel(assumption) + } + def pushScope(): Unit = { val scopeMark = pushLayer() scopeMarks = scopeMark :: scopeMarks @@ -589,6 +611,8 @@ private[decider] class LayeredPathConditionStack def declarations: InsertionOrderedSet[Decl] = InsertionOrderedSet(layers.flatMap(_.declarations)) // Note: Performance? + def analysisLabels: InsertionOrderedSet[Term] = InsertionOrderedSet(layers.flatMap(_.analysisLabels)) + def definingAssumptions: InsertionOrderedSet[Term] = InsertionOrderedSet(layers.flatMap(_.globalDefiningAssumptions) ++ layers.flatMap(_.nonGlobalDefiningAssumptions)) // Note: Performance? diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 18841a498..6cd70d7c6 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -359,7 +359,9 @@ object magicWandSupporter extends SymbolicExecutionRules { } } + var analysisLabels = InsertionOrderedSet[Term]().empty val tempResult = executionFlowController.locally(sEmp, v)((s1, v1) => { + val prePackageMark = v.decider.pcs.mark() /* A snapshot (binary tree) will be constructed using First/Second datatypes, * that preserves the original root. The leafs of this tree will later appear * in the snapshot of the RHS at the appropriate places. Thus equating @@ -401,15 +403,15 @@ object magicWandSupporter extends SymbolicExecutionRules { // The proof script should transform the current state such that we can consume the wand's RHS. val prevSourceInfo = v2.decider.analysisSourceInfoStack.getAnalysisSourceInfos v2.decider.analysisSourceInfoStack.setAnalysisSourceInfo(List.empty) - executor.exec(s2, proofScriptCfg, v2)((proofScriptState, proofScriptVerifier) => { // TODO ake: propagate assumption type! + executor.exec(s2, proofScriptCfg, v2)((proofScriptState, proofScriptVerifier) => { + v2.decider.analysisSourceInfoStack.setAnalysisSourceInfo(prevSourceInfo) // Consume the wand's RHS and produce a snapshot which records all the values of variables on the RHS. // This part indirectly calls the methods `this.transfer` and `this.consumeFromMultipleHeaps`. - v2.decider.analysisSourceInfoStack.setAnalysisSourceInfo(prevSourceInfo) consume( proofScriptState.copy(oldHeaps = s2.oldHeaps, reserveCfgs = proofScriptState.reserveCfgs.tail), wand.right, true, pve, proofScriptVerifier )((s3, snapRhs, v3) => { - + analysisLabels = v.decider.pcs.after(prePackageMark).analysisLabels createWandChunkAndRecordResults(s3.copy(exhaleExt = false, oldHeaps = s.oldHeaps), freshSnapRoot, snapRhs.get, v3, assumptionType) }) }) @@ -424,6 +426,9 @@ object magicWandSupporter extends SymbolicExecutionRules { createWandChunkAndRecordResults(s1, freshSnap(sorts.Snap, v), freshSnap(sorts.Snap, v), v, assumptionType) } + // some of the analysis labels, introduced while verifying the package statement, might be needed later on -> reassume them + analysisLabels foreach (l => v.decider.assume(v.decider.wrapWithAssumptionAnalysisLabel(l, Set.empty, Set(l)), None, AssumptionType.Internal)) + val currentAnalysisSourceInfoStack = v.decider.analysisSourceInfoStack recordedBranches.foldLeft(tempResult)((prevRes, recordedState) => { prevRes && { @@ -439,10 +444,10 @@ object magicWandSupporter extends SymbolicExecutionRules { val exp = viper.silicon.utils.ast.BigAnd(branchConditionsExp.map(_._1)) val expNew = Option.when(withExp)(viper.silicon.utils.ast.BigAnd(branchConditionsExp.map(_._2.get))) // Set the branch conditions - v1.decider.setCurrentBranchCondition(And(branchConditions), (exp, expNew)) + v1.decider.setCurrentBranchCondition(And(branchConditions map (t => v1.decider.wrapWithAssumptionAnalysisLabel(t, Set.empty, Set(t)))), (exp, expNew)) // Recreate all path conditions in the Z3 proof script that we recorded for that branch - v1.decider.assume(conservedPcs._1, conservedPcs._2, AssumptionType.Internal) + v1.decider.assume(conservedPcs._1 map (t => v1.decider.wrapWithAssumptionAnalysisLabel(t, Set.empty, Set(t))), conservedPcs._2, AssumptionType.Internal) // Execute the continuation Q Q(s2, magicWandChunk, v1) diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index 5c725573a..77bc7f0e4 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -225,8 +225,8 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver val preMark = decider.setPathConditionMark() produces(s0, toSf(`?s`), pres, ContractNotWellformed, v, AssumptionType.Explicit)((s1, _) => { val relevantPathConditionStack = decider.pcs.after(preMark) - phase1Data :+= Phase1Data(s1, relevantPathConditionStack.branchConditions map (t => v.decider.wrapWithAssumptionAnalysisLabel(t, Set.empty, Set(t))), relevantPathConditionStack.branchConditionExps, - relevantPathConditionStack.assumptions map (t => v.decider.wrapWithAssumptionAnalysisLabel(t, Set.empty, Set(t))), Option.when(evaluator.withExp)(relevantPathConditionStack.assumptionExps)) + phase1Data :+= Phase1Data(s1, relevantPathConditionStack.branchConditions, relevantPathConditionStack.branchConditionExps, + relevantPathConditionStack.assumptions, Option.when(evaluator.withExp)(relevantPathConditionStack.assumptionExps)) // The postcondition must be produced with a fresh snapshot (different from `?s`) because // the postcondition's snapshot structure is most likely different than that of the // precondition @@ -259,10 +259,10 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver case (fatalResult: FatalResult, _) => fatalResult case (intermediateResult, Phase1Data(sPre, bcsPre, bcsPreExp, pcsPre, pcsPreExp)) => intermediateResult && executionFlowController.locally(sPre, v)((s1, _) => { - val labelledBcsPre = v.decider.wrapWithAssumptionAnalysisLabel(And(bcsPre), Set.empty, bcsPre) + val labelledBcsPre = And(bcsPre map (t => v.decider.wrapWithAssumptionAnalysisLabel(t, Set.empty, Set(t)))) decider.setCurrentBranchCondition(labelledBcsPre, (BigAnd(bcsPreExp.map(_._1)), Option.when(wExp)(BigAnd(bcsPreExp.map(_._2.get))))) - // TODO ake: pcsPreExp are missing position infos sometimes (e.g. Snapshots) - decider.assume(pcsPre, pcsPreExp, s"precondition of ${function.name}", enforceAssumption=false, assumptionType=annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit)) + val labelledPcsPre = pcsPre map (t => v.decider.wrapWithAssumptionAnalysisLabel(t, Set.empty, Set(t))) + decider.assume(labelledPcsPre, pcsPreExp, s"precondition of ${function.name}", enforceAssumption=false, assumptionType=annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit)) v.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) eval(s1, body, FunctionNotWellformed(function), v)((s2, tBody, bodyNew, _) => { val debugExp = if (wExp) { diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr index 8357d8246..57c437468 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr @@ -36,7 +36,8 @@ method basicPackage(l: Ref) @dependency("Implicit") package list(tmp) --* list(l) { - fold list(l) // TODO ake + @dependency() + fold list(l) } @dependency("Implicit") @@ -44,4 +45,35 @@ method basicPackage(l: Ref) @testAssertion("Explicit") assert list(l) +} + + +predicate greater0(a: Int){ + a > 0 +} + +method advancedPackage(a: Int, b: Int) + { + + @dependency("Implicit") + package (greater0(a) && greater0(b)) --* greater0(a + b) + { + @dependency("Implicit") + unfold greater0(a) + @dependency("Implicit") + unfold greater0(b) + @dependency("Implicit") + fold greater0(a + b) + } + + @dependency("Explicit") + inhale greater0(a) + @dependency("Explicit") + inhale greater0(b) + + @dependency("Implicit") + apply (greater0(a) && greater0(b)) --* greater0(a + b) + + @testAssertion("Explicit") + assert greater0(a + b) } \ No newline at end of file From 7b62557fa3c829b5eb9eeb8e9d5117af34ceae50 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 11 Jul 2025 11:57:55 +0200 Subject: [PATCH 159/474] add quantified magic wand test --- .../unitTests/magicWands.vpr | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr index 57c437468..ea055d118 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr @@ -76,4 +76,17 @@ method advancedPackage(a: Int, b: Int) @testAssertion("Explicit") assert greater0(a + b) +} + +method quantifiedWand(xs: Seq[Int], b: Int) + requires @dependency("Explicit")(|xs| > 2) + requires @dependency("Explicit")(forall x: Int :: x in xs ==> (greater0(x) --* greater0(x + b))) + { + @dependency("Explicit") + inhale greater0(xs[0]) + @dependency("Implicit") + apply greater0(xs[0]) --* greater0(xs[0] + b) + + @testAssertion("Explicit") + assert greater0(xs[0] + b) } \ No newline at end of file From abc56b1878b8fdf154ed5a7b45abf320e0aa59f6 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 11 Jul 2025 12:06:32 +0200 Subject: [PATCH 160/474] minor fix --- src/main/scala/verifier/DefaultMainVerifier.scala | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index f67ec0e7a..1de306896 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -319,10 +319,12 @@ class DefaultMainVerifier(config: Config, assumptionAnalyzers.foreach(_.assumptionGraph.addTransitiveEdges()) assumptionAnalyzers foreach (_.exportGraph()) assumptionAnalyzers foreach (_.computeProofCoverage()) - val joinedGraph = AssumptionAnalyzer.joinGraphs(assumptionAnalyzers.map(_.assumptionGraph).toSet) - joinedGraph.exportGraph("graphExports/joinedGraphs") - if(reporter.isInstanceOf[DependencyAnalysisReporter]) { - reporter.asInstanceOf[DependencyAnalysisReporter].assumptionAnalyzers = assumptionAnalyzers + if(Verifier.config.assumptionAnalysisExportPath.isDefined) + AssumptionAnalyzer.joinGraphs(assumptionAnalyzers.map(_.assumptionGraph).toSet).exportGraph("graphExports/joinedGraphs") + reporter match { + case analysisReporter: DependencyAnalysisReporter => + analysisReporter.assumptionAnalyzers = assumptionAnalyzers + case _ => } } From b346d138eed13f521ed5d867cf0c541c289c92f2 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 11 Jul 2025 15:44:24 +0200 Subject: [PATCH 161/474] cleanup --- src/main/scala/decider/Decider.scala | 28 +++---- src/main/scala/decider/PathConditions.scala | 11 +-- src/main/scala/decider/ProverStdIO.scala | 14 ++-- src/main/scala/decider/Z3ProverStdIO.scala | 7 +- src/main/scala/interfaces/state/Chunks.scala | 4 +- src/main/scala/rules/Brancher.scala | 24 +++--- src/main/scala/rules/ChunkSupporter.scala | 4 +- src/main/scala/rules/Evaluator.scala | 2 +- .../scala/rules/ExecutionFlowController.scala | 7 -- src/main/scala/rules/Executor.scala | 79 +++++++++---------- src/main/scala/rules/HavocSupporter.scala | 4 +- .../rules/MoreCompleteExhaleSupporter.scala | 3 +- .../scala/rules/PermissionSupporter.scala | 5 +- src/main/scala/rules/PredicateSupporter.scala | 2 +- src/main/scala/rules/Producer.scala | 4 +- .../scala/rules/QuantifiedChunkSupport.scala | 2 +- src/main/scala/rules/StateConsolidator.scala | 54 ++++++------- src/main/scala/state/Chunks.scala | 2 +- .../scala/supporters/SnapshotSupporter.scala | 2 +- .../functions/FunctionVerificationUnit.scala | 2 +- .../scala/verifier/DefaultMainVerifier.scala | 4 +- 21 files changed, 118 insertions(+), 146 deletions(-) diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index a502b7485..33d79c681 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -80,7 +80,7 @@ trait Decider { * 1. It passes State and Operations to the continuation * 2. The implementation reacts to a failing assertion by e.g. a state consolidation */ - def assert(t: Term, assumptionType: AssumptionType=AssumptionType.Implicit, timeout: Option[Int] = Verifier.config.assertTimeout.toOption)(Q: Boolean => VerificationResult): VerificationResult + def assert(t: Term, assumptionType: AssumptionType=AssumptionType.Implicit, timeout: Option[Int] = None)(Q: Boolean => VerificationResult): VerificationResult def fresh(id: String, sort: Sort, ptype: Option[PType]): Var def fresh(id: String, argSorts: Seq[Sort], resultSort: Sort): Function @@ -183,7 +183,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => // TODO: Change interface to make the cast unnecessary? val layeredStack = other.asInstanceOf[LayeredPathConditionStack] layeredStack.layers.reverse.foreach(l => { - l.assumptions foreach prover.assume // TODO ake: labels? + l.assumptions foreach prover.assume prover.push(timeout = Verifier.config.pushTimeout.toOption) }) } @@ -381,13 +381,10 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => (t, AssumptionAnalyzer.createAssumptionLabel(assumptionId)) } - if (filteredAssumptions.nonEmpty){ - assumeWithoutSmokeChecks(filteredAssumptionsWithLabels, isDefinition=isDefinition) - } + if (filteredAssumptions.nonEmpty) assumeWithoutSmokeChecks(filteredAssumptionsWithLabels, isDefinition=isDefinition) } def assume(assumptions: Seq[Term], debugExps: Option[Seq[DebugExp]], assumptionType: AssumptionType): Unit = { - val assumptionsWithLabels = assumptions map (t => { val assumptionId = assumptionAnalyzer.addAssumption(t, analysisSourceInfoStack.getFullSourceInfo, assumptionType) (t, AssumptionAnalyzer.createAssumptionLabel(assumptionId)) @@ -424,7 +421,6 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } def assume(terms: Iterable[Term], debugExp: Option[DebugExp], enforceAssumption: Boolean, assumptionType: AssumptionType): Unit = { - val filteredTerms = if (enforceAssumption) terms else terms filterNot isKnownToBeTrue @@ -542,18 +538,12 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => (result, assertNode) } - private def isKnownToBeTrue(t: Term) = { - t match { - case True => - true - // case eq: BuiltinEquals => eq.p0 == eq.p1 /* WARNING: Blocking trivial equalities might hinder axiom triggering. */ - case _ if pcs.assumptions contains t => - true - case q: Quantification if q.body == True => - true - case _ => - false - } + private def isKnownToBeTrue(t: Term) = t match { + case True => true + // case eq: BuiltinEquals => eq.p0 == eq.p1 /* WARNING: Blocking trivial equalities might hinder axiom triggering. */ + case _ if pcs.assumptions contains t => true + case q: Quantification if q.body == True => true + case _ => false } private def proverAssert(t: Term, timeout: Option[Int], label: String) = { diff --git a/src/main/scala/decider/PathConditions.scala b/src/main/scala/decider/PathConditions.scala index 9ded3ac83..07abaef6a 100644 --- a/src/main/scala/decider/PathConditions.scala +++ b/src/main/scala/decider/PathConditions.scala @@ -110,10 +110,6 @@ private class PathConditionStackLayer def branchCondition: Option[Term] = _branchCondition def branchConditionExp: Option[(ast.Exp, Option[ast.Exp])] = _branchConditionExp - def infeasibilityNodeId: Option[Int] = _infeasibilityNodeId - def setInfeasibilityNodeId(id: Option[Int]): Unit = { - _infeasibilityNodeId = id - } def globalAssumptions: InsertionOrderedSet[Term] = _globalAssumptions def globalDefiningAssumptions: InsertionOrderedSet[Term] = _globalDefiningAssumptions def nonGlobalDefiningAssumptions: InsertionOrderedSet[Term] = _nonGlobalDefiningAssumptions @@ -125,6 +121,11 @@ private class PathConditionStackLayer def declarations: InsertionOrderedSet[Decl] = _declarations def analysisLabels: InsertionOrderedSet[Term] = _analysisLabels + def infeasibilityNodeId: Option[Int] = _infeasibilityNodeId + def setInfeasibilityNodeId(id: Option[Int]): Unit = { + _infeasibilityNodeId = id + } + def assumptions: InsertionOrderedSet[Term] = globalAssumptions ++ nonGlobalAssumptions def assumptionDebugExps: InsertionOrderedSet[DebugExp] = globalAssumptionDebugExps ++ nonGlobalAssumptionDebugExps @@ -343,7 +344,7 @@ private trait LayeredPathConditionStackLike { unconditionalTerms ++ conditionalTerms } - + protected def quantified(layers: Stack[PathConditionStackLayer], quantifier: Quantifier, qvars: Seq[Var], diff --git a/src/main/scala/decider/ProverStdIO.scala b/src/main/scala/decider/ProverStdIO.scala index d068cf347..e0d8707a0 100644 --- a/src/main/scala/decider/ProverStdIO.scala +++ b/src/main/scala/decider/ProverStdIO.scala @@ -221,8 +221,7 @@ abstract class ProverStdIO(uniqueId: String, // private val quantificationLogger = bookkeeper.logfiles("quantification-problems") def assume(term: Term): Unit = { - assume(term, "prover_" + proverLabelId) - proverLabelId += 1 + assume(term, nextProverLabel()) } def assume(term: Term, label: String): Unit = { @@ -243,11 +242,11 @@ abstract class ProverStdIO(uniqueId: String, assume(termConverter.convert(term), finalLabel) } -def nextProverLabel(): String = { - val label = "prover_" + proverLabelId - proverLabelId += 1 - label -} + def nextProverLabel(): String = { + val label = "prover_" + proverLabelId + proverLabelId += 1 + label + } def assume(term: String, label: String): Unit = { // bookkeeper.assumptionCounter += 1 @@ -395,7 +394,6 @@ def nextProverLabel(): String = { if(result == Unsat && Verifier.config.enableAssumptionAnalysis()) lastUnsatCore_ = extractUnsatCore() - result } diff --git a/src/main/scala/decider/Z3ProverStdIO.scala b/src/main/scala/decider/Z3ProverStdIO.scala index c5101c217..cfd2f9122 100644 --- a/src/main/scala/decider/Z3ProverStdIO.scala +++ b/src/main/scala/decider/Z3ProverStdIO.scala @@ -6,13 +6,12 @@ package viper.silicon.decider -import viper.silicon.common.config.Version +import java.nio.file.{Path, Paths} import viper.silicon.state.IdentifierFactory import viper.silicon.verifier.Verifier -import viper.silver.reporter.Reporter import viper.silver.verifier.{DefaultDependency => SilDefaultDependency} - -import java.nio.file.{Path, Paths} +import viper.silver.reporter.Reporter +import viper.silicon.common.config.Version object Z3ProverStdIO { diff --git a/src/main/scala/interfaces/state/Chunks.scala b/src/main/scala/interfaces/state/Chunks.scala index 69c6c393e..00161d7a7 100644 --- a/src/main/scala/interfaces/state/Chunks.scala +++ b/src/main/scala/interfaces/state/Chunks.scala @@ -6,9 +6,9 @@ package viper.silicon.interfaces.state -import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType, PermissionInhaleNode} +import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType} import viper.silicon.resources.ResourceID -import viper.silicon.state.terms.{PermMinus, Term, Var} +import viper.silicon.state.terms.{Term, Var} import viper.silver.ast trait Chunk { diff --git a/src/main/scala/rules/Brancher.scala b/src/main/scala/rules/Brancher.scala index 08f5ac98d..b9457947e 100644 --- a/src/main/scala/rules/Brancher.scala +++ b/src/main/scala/rules/Brancher.scala @@ -6,9 +6,7 @@ package viper.silicon.rules -import viper.silicon.assumptionAnalysis.{AnalysisSourceInfo, AssumptionType, ExpAnalysisSourceInfo} -import viper.silicon.common.collections.immutable.InsertionOrderedSet - +import viper.silicon.assumptionAnalysis.{ExpAnalysisSourceInfo} import java.util.concurrent._ import viper.silicon.common.concurrency._ import viper.silicon.decider.PathConditionStack @@ -54,16 +52,17 @@ object brancher extends BranchingRules { * (2) the branch condition contains a quantified variable */ val skipPathFeasibilityCheck = ( - fromShortCircuitingAnd - || ( s.quantifiedVariables.nonEmpty + fromShortCircuitingAnd + || ( s.quantifiedVariables.nonEmpty && s.quantifiedVariables.map(_._1).exists(condition.freeVariables.contains)) ) val sourceInfo = ExpAnalysisSourceInfo(conditionExp._1) v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo) /* True if the then-branch is to be explored */ - val executeThenBranch = (skipPathFeasibilityCheck - || !v.decider.check(negatedCondition, Verifier.config.checkTimeout())) + val executeThenBranch = ( + skipPathFeasibilityCheck + || !v.decider.check(negatedCondition, Verifier.config.checkTimeout())) val thenInfeasibilityNode: Option[Int] = if(Verifier.config.enableAssumptionAnalysis() && !executeThenBranch) { val (_, node) = v.decider.checkAndGetInfeasibilityNode(negatedCondition, Verifier.config.checkTimeout()) @@ -71,9 +70,10 @@ object brancher extends BranchingRules { } else None /* False if the then-branch is to be explored */ - val executeElseBranch = (!executeThenBranch /* Assumes that ast least one branch is feasible */ - || skipPathFeasibilityCheck - || !v.decider.check(condition, Verifier.config.checkTimeout())) + val executeElseBranch = ( + !executeThenBranch /* Assumes that ast least one branch is feasible */ + || skipPathFeasibilityCheck + || !v.decider.check(condition, Verifier.config.checkTimeout())) val elseInfeasibilityNode: Option[Int] = if(Verifier.config.enableAssumptionAnalysis() && !executeElseBranch) { val (_, node) = v.decider.checkAndGetInfeasibilityNode(condition, Verifier.config.checkTimeout()) @@ -212,7 +212,7 @@ object brancher extends BranchingRules { val thenRes = if (executeThenBranch || Verifier.config.enableAssumptionAnalysis()) { v.symbExLog.markReachable(uidBranchPoint) v.decider.analysisSourceInfoStack.setAnalysisSourceInfo(currentAnalysisSourceInfos) - val res = executionFlowController.locally(s, v)((s1, v1) => { + executionFlowController.locally(s, v)((s1, v1) => { v1.decider.prover.comment(s"[then-branch: $cnt | $condition]") val sourceInfo = ExpAnalysisSourceInfo(conditionExp._1) v1.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo) @@ -222,7 +222,6 @@ object brancher extends BranchingRules { fThen(v1.stateConsolidator(s1).consolidateOptionally(s1, v1), v1) }) - res } else { Unreachable() } @@ -231,7 +230,6 @@ object brancher extends BranchingRules { v.reporter.report(BranchFailureMessage("silicon", s.currentMember.get.asInstanceOf[ast.Member with Serializable], condenseToViperResult(Seq(thenRes)).asInstanceOf[Failure])) } - thenRes }.combine({ diff --git a/src/main/scala/rules/ChunkSupporter.scala b/src/main/scala/rules/ChunkSupporter.scala index af75929e8..20b624f7f 100644 --- a/src/main/scala/rules/ChunkSupporter.scala +++ b/src/main/scala/rules/ChunkSupporter.scala @@ -83,6 +83,7 @@ object chunkSupporter extends ChunkSupportRules { assumptionType: AssumptionType=AssumptionType.Implicit) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { + consume2(s, h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, assumptionType)((s2, h2, optSnap, v2) => optSnap match { case Some(snap) => @@ -179,8 +180,7 @@ object chunkSupporter extends ChunkSupportRules { findChunk[NonQuantifiedChunk](h.values, id, args, v) match { case Some(ch) => if (s.assertReadAccessOnly) { - val termToCheck = Implies(IsPositive(perms), IsPositive(ch.perm)) - if (v.decider.check(termToCheck, Verifier.config.assertTimeout.getOrElse(0), assumptionType)) { + if (v.decider.check(Implies(IsPositive(perms), IsPositive(ch.perm)), Verifier.config.assertTimeout.getOrElse(0), assumptionType)) { (Complete(), s, h, Some(ch)) } else { (Incomplete(perms, permsExp), s, h, None) diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 0588f7eb2..8967b060a 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -1114,7 +1114,7 @@ object evaluator extends EvaluationRules { case ast.RangeSeq(e0, e1) => evalBinOp(s, e0, e1, SeqRanged, pve, v)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.RangeSeq(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) - case seqUp @ ast.SeqUpdate(e0, e1, e2) => + case ast.SeqUpdate(e0, e1, e2) => evals2(s, Seq(e0, e1, e2), Nil, _ => pve, v)({ case (s1, Seq(t0, t1, t2), esNew, v1) => val eNew = esNew.map(es => ast.SeqUpdate(es.head, es(1), es(2))(e.pos, e.info, e.errT)) if (s1.triggerExp) { diff --git a/src/main/scala/rules/ExecutionFlowController.scala b/src/main/scala/rules/ExecutionFlowController.scala index 58b8ca051..8cc287d0f 100644 --- a/src/main/scala/rules/ExecutionFlowController.scala +++ b/src/main/scala/rules/ExecutionFlowController.scala @@ -172,11 +172,4 @@ object executionFlowController extends ExecutionFlowRules { : VerificationResult = tryOrFailWithResult[(R1, R2)](s, v)((s1, v1, QS) => action(s1, v1, (s2, r21, r22, v2) => QS(s2, (r21, r22), v2)))((s2, r, v2) => Q(s2, r._1, r._2, v2)) - - def tryOrFail3[R1, R2, R3](s: State, v: Verifier) - (action: (State, Verifier, (State, R1, R2, R3, Verifier) => VerificationResult) => VerificationResult) - (Q: (State, R1, R2, R3, Verifier) => VerificationResult) - : VerificationResult = - - tryOrFailWithResult[(R1, R2, R3)](s, v)((s1, v1, QS) => action(s1, v1, (s2, r21, r22, r23, v2) => QS(s2, (r21, r22, r23), v2)))((s2, r, v2) => Q(s2, r._1, r._2, r._3, v2)) } diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index f72df6c3a..87a3914bf 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -408,9 +408,9 @@ object executor extends ExecutionRules { s2p, relevantChunks, Seq(`?r`), - Option.when(withExp)(Seq(ast.LocalVarDecl(`?r`.id.name, ast.Ref)(eRcvr.pos, eRcvr.info, eRcvr.errT))), + Option.when(withExp)(Seq(ast.LocalVarDecl(`?r`.id.name, ast.Ref)())), `?r` === tRcvr, - eRcvrNew.map(r => ast.EqCmp(ast.LocalVar(`?r`.id.name, ast.Ref)(), r)(eRcvr.pos, eRcvr.info, eRcvr.errT)), + eRcvrNew.map(r => ast.EqCmp(ast.LocalVar(`?r`.id.name, ast.Ref)(), r)()), Some(Seq(tRcvr)), field, FullPerm, @@ -445,31 +445,30 @@ object executor extends ExecutionRules { case ass @ ast.FieldAssign(fa @ ast.FieldAccess(eRcvr, field), rhs) => assert(!s.exhaleExt) val pve = AssignmentFailed(ass) - eval(s, eRcvr, pve, v)((s1, tRcvr, eRcvrNew, v1) =>{ - eval(s1, rhs, pve, v1)((s2, tRhs, rhsNew, v2) => { - val resource = fa.res(s.program) - val ve = pve dueTo InsufficientPermission(fa) - val description = s"consume ${ass.pos}: $ass" - val lhsSourceInfo = TransitivityAnalysisSourceInfo(v2.decider.analysisSourceInfoStack.getFullSourceInfo, ExpAnalysisSourceInfo(fa)) + eval(s, eRcvr, pve, v)((s1, tRcvr, eRcvrNew, v1) => + eval(s1, rhs, pve, v1)((s2, tRhs, rhsNew, v2) => { + val resource = fa.res(s.program) + val ve = pve dueTo InsufficientPermission(fa) + val description = s"consume ${ass.pos}: $ass" + val lhsSourceInfo = TransitivityAnalysisSourceInfo(v2.decider.analysisSourceInfoStack.getFullSourceInfo, ExpAnalysisSourceInfo(fa)) + v2.decider.analysisSourceInfoStack.setForcedSource(lhsSourceInfo) // splitting lhs and rhs to make permission flow analysis more precise + chunkSupporter.consume(s2, s2.h, resource, Seq(tRcvr), eRcvrNew.map(Seq(_)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), false, ve, v2, description)((s3, h3, _, v3) => { + v2.decider.analysisSourceInfoStack.removeForcedSource() + val (tSnap, _) = ssaifyRhs(tRhs, rhs, rhsNew, field.name, field.typ, v3, s3, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit)) + val id = BasicChunkIdentifier(field.name) v2.decider.analysisSourceInfoStack.setForcedSource(lhsSourceInfo) // splitting lhs and rhs to make permission flow analysis more precise - chunkSupporter.consume(s2, s2.h, resource, Seq(tRcvr), eRcvrNew.map(Seq(_)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), false, ve, v2, description)((s3, h3, _, v3) => { - v2.decider.analysisSourceInfoStack.removeForcedSource() - val (tSnap, _) = ssaifyRhs(tRhs, rhs, rhsNew, field.name, field.typ, v3, s3, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit)) - val id = BasicChunkIdentifier(field.name) - v2.decider.analysisSourceInfoStack.setForcedSource(lhsSourceInfo) // splitting lhs and rhs to make permission flow analysis more precise - val newChunk = BasicChunk.apply(FieldID, id, Seq(tRcvr), eRcvrNew.map(Seq(_)), tSnap, rhsNew, FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), - v3.decider.getAnalysisInfo(AssumptionType.Internal)) - v2.decider.analysisSourceInfoStack.removeForcedSource() - chunkSupporter.produce(s3, h3, newChunk, v3)((s4, h4, v4) => { - val s5 = s4.copy(h = h4) - val (debugHeapName, _) = v4.getDebugOldLabel(s5, fa.pos) - val s6 = if (withExp) s5.copy(oldHeaps = s5.oldHeaps + (debugHeapName -> magicWandSupporter.getEvalHeap(s5))) else s5 - v4.decider.assumptionAnalyzer.addCustomTransitiveDependency(lhsSourceInfo, v4.decider.analysisSourceInfoStack.getFullSourceInfo) - Q(s6, v4) - }) + val newChunk = BasicChunk.apply(FieldID, id, Seq(tRcvr), eRcvrNew.map(Seq(_)), tSnap, rhsNew, FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), + v3.decider.getAnalysisInfo(AssumptionType.Internal)) + v2.decider.analysisSourceInfoStack.removeForcedSource() + chunkSupporter.produce(s3, h3, newChunk, v3)((s4, h4, v4) => { + val s5 = s4.copy(h = h4) + val (debugHeapName, _) = v4.getDebugOldLabel(s5, fa.pos) + val s6 = if (withExp) s5.copy(oldHeaps = s5.oldHeaps + (debugHeapName -> magicWandSupporter.getEvalHeap(s5))) else s5 + v4.decider.assumptionAnalyzer.addCustomTransitiveDependency(lhsSourceInfo, v4.decider.analysisSourceInfoStack.getFullSourceInfo) + Q(s6, v4) }) }) - } + }) ) case stmt@ast.NewStmt(x, fields) => @@ -491,9 +490,7 @@ object executor extends ExecutionRules { quantifiedChunkSupporter.createSingletonQuantifiedChunk(Seq(`?r`), Option.when(withExp)(Seq(ast.LocalVarDecl("r", ast.Ref)(stmt.pos, stmt.info, stmt.errT))), field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), p, pExp, sm, s.program, v, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit), isExhale=false) } else { - val newChunk = BasicChunk(FieldID, BasicChunkIdentifier(field.name), Seq(tRcvr), Option.when(withExp)(Seq(x)), snap, snapExp, p, pExp, - v.decider.getAnalysisInfo(annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit))) - newChunk + BasicChunk(FieldID, BasicChunkIdentifier(field.name), Seq(tRcvr), Option.when(withExp)(Seq(x)), snap, snapExp, p, pExp, v.decider.getAnalysisInfo(annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit))) } }) val ts = viper.silicon.state.utils.computeReferenceDisjointnesses(s, tRcvr) @@ -503,21 +500,19 @@ object executor extends ExecutionRules { v.decider.assume(ts, Option.when(withExp)(DebugExp.createInstance(Some("Reference Disjointness"), esNew, esNew, InsertionOrderedSet.empty)), enforceAssumption = false, assumptionType=AssumptionType.Implicit) Q(s2, v) - case inhale @ ast.Inhale(a) => - a match { - case _: ast.FalseLit if !Verifier.config.enableAssumptionAnalysis() => - /* We're done */ - Success() - case _ => - produce(s, freshSnap, a, InhaleFailed(inhale), v, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit))((s1, v1) => { - v1.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterInhale) - if(Verifier.config.enableAssumptionAnalysis() && a.isInstanceOf[ast.FalseLit]) { - val (_, node) = v1.decider.checkAndGetInfeasibilityNode(False, Verifier.config.checkTimeout(), AssumptionType.Explicit) - v1.decider.pcs.setCurrentInfeasibilityNode(node) - } - Q(s1, v1)}) - } - + case inhale @ ast.Inhale(a) => a match { + case _: ast.FalseLit if !Verifier.config.enableAssumptionAnalysis() => + /* We're done */ + Success() + case _ => + produce(s, freshSnap, a, InhaleFailed(inhale), v, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit))((s1, v1) => { + v1.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterInhale) + if(Verifier.config.enableAssumptionAnalysis() && a.isInstanceOf[ast.FalseLit]) { + val (_, node) = v1.decider.checkAndGetInfeasibilityNode(False, Verifier.config.checkTimeout(), AssumptionType.Explicit) + v1.decider.pcs.setCurrentInfeasibilityNode(node) + } + Q(s1, v1)}) + } case exhale @ ast.Exhale(a) => val pve = ExhaleFailed(exhale) diff --git a/src/main/scala/rules/HavocSupporter.scala b/src/main/scala/rules/HavocSupporter.scala index de92a9545..d5d00afc9 100644 --- a/src/main/scala/rules/HavocSupporter.scala +++ b/src/main/scala/rules/HavocSupporter.scala @@ -45,6 +45,7 @@ object havocSupporter extends SymbolicExecutionRules { assumptionType: AssumptionType) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { + val pve = QuasihavocFailed(havoc) // If there is no havoc condition, use True as the condition @@ -85,6 +86,7 @@ object havocSupporter extends SymbolicExecutionRules { assumptionType: AssumptionType) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { + val pve = HavocallFailed(havocall) val ast.Quasihavocall(vars, lhs, eRsc) = havocall val qid = resourceName(s, eRsc) @@ -131,7 +133,7 @@ object havocSupporter extends SymbolicExecutionRules { val comment = "QP receiver injectivity check is well-defined" val injectivityDebugExp = Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)) v.decider.assume(FunctionPreconditionTransformer.transform(receiverInjectivityCheck, s.program), injectivityDebugExp, AssumptionType.Internal) - v.decider.assert(receiverInjectivityCheck, timeout=Verifier.config.assertTimeout.toOption) { + v.decider.assert(receiverInjectivityCheck) { case false => createFailure(pve dueTo notInjectiveReason, v, s1, receiverInjectivityCheck, "QP receiver injective") case true => // Generate the inverse axioms diff --git a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala index 11620af62..75e24b011 100644 --- a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala +++ b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala @@ -244,14 +244,13 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { if (!s.assertReadAccessOnly) actualConsumeComplete(s, h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, assumptionType)(Q) else - summariseHeapAndAssertReadAccess(s, h, resource, perms, permsExp, args, argsExp, returnSnap, ve, v, assumptionType)(Q) + summariseHeapAndAssertReadAccess(s, h, resource, perms, args, argsExp, returnSnap, ve, v, assumptionType)(Q) } private def summariseHeapAndAssertReadAccess(s: State, h: Heap, resource: ast.Resource, perm: Term, - permExp: Option[ast.Exp], args: Seq[Term], argsExp: Option[Seq[ast.Exp]], returnSnap: Boolean, diff --git a/src/main/scala/rules/PermissionSupporter.scala b/src/main/scala/rules/PermissionSupporter.scala index e9d46d5a3..58c386fe9 100644 --- a/src/main/scala/rules/PermissionSupporter.scala +++ b/src/main/scala/rules/PermissionSupporter.scala @@ -23,10 +23,10 @@ object permissionSupporter extends SymbolicExecutionRules { case k: Var if s.constrainableARPs.contains(k) => Q(s, v) case _ => - val assertExp = ePermNew.map(ep => perms.IsNonNegative(ep)(ep.pos, ep.info, ep.errT)) v.decider.assert(perms.IsNonNegative(tPerm)) { case true => Q(s, v) case false => + val assertExp = ePermNew.map(ep => perms.IsNonNegative(ep)(ep.pos, ep.info, ep.errT)) createFailure(pve dueTo NegativePermission(ePerm), v, s, perms.IsNonNegative(tPerm), assertExp) } } @@ -40,10 +40,9 @@ object permissionSupporter extends SymbolicExecutionRules { case k: Var if s.constrainableARPs.contains(k) => Q(s, v) case _ => - val assertExp = Option.when(withExp)(perms.IsPositive(ePerm)(ePerm.pos, ePerm.info, ePerm.errT)) v.decider.assert(perms.IsPositive(tPerm)) { case true => Q(s, v) - case false => createFailure(pve dueTo NonPositivePermission(ePerm), v, s, perms.IsPositive(tPerm), assertExp) + case false => createFailure(pve dueTo NonPositivePermission(ePerm), v, s, perms.IsPositive(tPerm), Option.when(withExp)(perms.IsPositive(ePerm)())) } } } diff --git a/src/main/scala/rules/PredicateSupporter.scala b/src/main/scala/rules/PredicateSupporter.scala index 51ec9c2bd..b6f6423b7 100644 --- a/src/main/scala/rules/PredicateSupporter.scala +++ b/src/main/scala/rules/PredicateSupporter.scala @@ -89,7 +89,7 @@ object predicateSupporter extends PredicateSupportRules { val (sm, smValueDef) = quantifiedChunkSupporter.singletonSnapshotMap(s2, predicate, tArgs, predSnap, v1) v1.decider.prover.comment("Definitional axioms for singleton-SM's value") - val debugExp = Option.when(withExp)(DebugExp.createInstance("Definitional axioms for singleton-SM's value", isInternal_ = true)) + val debugExp = Option.when(withExp)(DebugExp.createInstance("Definitional axioms for singleton-SM's value", true)) v1.decider.assumeDefinition(smValueDef, debugExp, assumptionType) val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk( diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index 6251acc0a..cf2c09c8d 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -249,7 +249,7 @@ object producer extends ProductionRules { QB(s3, null, v3) }), (s2, v2) => { - v2.decider.assume(sf(sorts.Snap, v2) === Unit, Option.when(withExp)(DebugExp.createInstance("Empty snapshot", isInternal_ = true)), AssumptionType.Internal) + v2.decider.assume(sf(sorts.Snap, v2) === Unit, Option.when(withExp)(DebugExp.createInstance("Empty snapshot", true)), AssumptionType.Internal) /* TODO: Avoid creating a fresh var (by invoking) `sf` that is not used * otherwise. In order words, only make this assumption if `sf` has * already been used, e.g. in a snapshot equality such as `s0 == (s1, s2)`. @@ -571,7 +571,7 @@ object producer extends ProductionRules { /* Any regular expressions, i.e. boolean and arithmetic. */ case _ => v.decider.assume(sf(sorts.Snap, v) === Unit, - Option.when(withExp)(DebugExp.createInstance("Empty snapshot", isInternal_ = true)), AssumptionType.Internal) /* TODO: See comment for case ast.Implies above */ + Option.when(withExp)(DebugExp.createInstance("Empty snapshot", true)), AssumptionType.Internal) /* TODO: See comment for case ast.Implies above */ eval(s, a, pve, v)((s1, t, aNew, v1) => { v1.decider.assume(t, Option.when(withExp)(a), aNew, assumptionType) Q(s1, v1)}) diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 1b3254cb2..b8904c4fc 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -734,7 +734,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { : (PermMapDefinition, PmCache) = { v.decider.analysisSourceInfoStack.setForcedSource("summarizing heap") v.decider.assumptionAnalyzer.disableTransitiveEdges() - val res = Verifier.config.mapCache(s.pmCache.get(resource, relevantChunks)) match { // TODO ake: do not get from cache when analysis is enabled + val res = Verifier.config.mapCache(s.pmCache.get(resource, relevantChunks)) match { case Some(pmDef) => v.decider.assume(pmDef.valueDefinitions, Option.when(withExp)(DebugExp.createInstance("value definitions", isInternal_ = true)), enforceAssumption = false, assumptionType=AssumptionType.Internal) (pmDef, s.pmCache) diff --git a/src/main/scala/rules/StateConsolidator.scala b/src/main/scala/rules/StateConsolidator.scala index 6f3d7b960..6b99f8353 100644 --- a/src/main/scala/rules/StateConsolidator.scala +++ b/src/main/scala/rules/StateConsolidator.scala @@ -83,7 +83,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol val (_functionRecorder, _mergedChunks, _newChunks, snapEqs) = singleMerge(functionRecorder, destChunks, newChunks, s.functionRecorderQuantifiedVariables().map(_._1), v) - snapEqs foreach (t => v.decider.assume(t, Option.when(withExp)(DebugExp.createInstance("Snapshot Equations", isInternal_ = true)), AssumptionType.Internal)) + snapEqs foreach (t => v.decider.assume(t, Option.when(withExp)(DebugExp.createInstance("Snapshot Equations", true)), AssumptionType.Internal)) functionRecorder = _functionRecorder mergedChunks = _mergedChunks @@ -191,7 +191,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol private def findMatchingChunk(chunks: Iterable[Chunk], chunk: Chunk, v: Verifier): Option[Chunk] = { chunk match { case chunk: BasicChunk => - chunkSupporter.findChunk[BasicChunk](chunks, chunk.id, chunk.args, v) // TODO ake: add edge from check chunk equality + chunkSupporter.findChunk[BasicChunk](chunks, chunk.id, chunk.args, v) // TODO ake: add edge from check chunk equality? case chunk: QuantifiedChunk => quantifiedChunkSupporter.findChunk(chunks, chunk, v) case _ => None } @@ -206,32 +206,30 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol (fRec, ch, v.decider.wrapWithAssumptionAnalysisLabel(snapEq, Set(chunk1, chunk2)))}) } - private def mergeChunks1(fr1: FunctionRecorder, chunk1: Chunk, chunk2: Chunk, qvars: Seq[Var], v: Verifier): Option[(FunctionRecorder, Chunk, Term)] = { - (chunk1, chunk2) match { - case (BasicChunk(rid1, id1, args1, args1Exp, snap1, snap1Exp, perm1, perm1Exp), BasicChunk(_, _, _, _, snap2, _, perm2, perm2Exp)) => - val (fr2, combinedSnap, snapEq) = combineSnapshots(fr1, snap1, snap2, perm1, perm2, qvars, v) - val newExp = perm1Exp.map(p1 => ast.PermAdd(p1, perm2Exp.get)()) - Some(fr2, BasicChunk(rid1, id1, args1, args1Exp, combinedSnap, snap1Exp, PermPlus(perm1, perm2), newExp, v.decider.getAnalysisInfo(AssumptionType.Internal)), snapEq) - case (l@QuantifiedFieldChunk(id1, fvf1, condition1, condition1Exp, perm1, perm1Exp, invs1, singletonRcvr1, singletonRcvr1Exp, hints1), - r@QuantifiedFieldChunk(_, fvf2, _, _, perm2, perm2Exp, _, _, _, hints2)) => - assert(l.quantifiedVars == Seq(`?r`)) - assert(r.quantifiedVars == Seq(`?r`)) - // We need to use l.perm/r.perm here instead of perm1 and perm2 since the permission amount might be dependent on the condition/domain - val (fr2, combinedSnap, snapEq) = quantifiedChunkSupporter.combineFieldSnapshotMaps(fr1, id1.name, fvf1, fvf2, l.perm, r.perm, v) - val permSum = PermPlus(perm1, perm2) - val permSumExp = perm1Exp.map(p1 => ast.PermAdd(p1, perm2Exp.get)()) - val bestHints = if (hints1.nonEmpty) hints1 else hints2 - Some(fr2, QuantifiedFieldChunk(id1, combinedSnap, condition1, condition1Exp, permSum, permSumExp, invs1, singletonRcvr1, singletonRcvr1Exp, bestHints, v.decider.getAnalysisInfo(AssumptionType.Internal)), snapEq) - case (l@QuantifiedPredicateChunk(id1, qVars1, qVars1Exp, psf1, _, _, perm1, perm1Exp, _, _, _, _), - r@QuantifiedPredicateChunk(_, qVars2, qVars2Exp, psf2, condition2, condition2Exp, perm2, perm2Exp, invs2, singletonArgs2, singletonArgs2Exp, hints2)) => - val (fr2, combinedSnap, snapEq) = quantifiedChunkSupporter.combinePredicateSnapshotMaps(fr1, id1.name, qVars2, psf1, psf2, l.perm.replace(qVars1, qVars2), r.perm, v) - - val permSum = PermPlus(perm1.replace(qVars1, qVars2), perm2) - val permSumExp = perm1Exp.map(p1 => ast.PermAdd(p1.replace(qVars1Exp.get.zip(qVars2Exp.get).toMap), perm2Exp.get)()) - Some(fr2, QuantifiedPredicateChunk(id1, qVars2, qVars2Exp, combinedSnap, condition2, condition2Exp, permSum, permSumExp, invs2, singletonArgs2, singletonArgs2Exp, hints2, v.decider.getAnalysisInfo(AssumptionType.Internal)), snapEq) - case _ => - None - } + private def mergeChunks1(fr1: FunctionRecorder, chunk1: Chunk, chunk2: Chunk, qvars: Seq[Var], v: Verifier): Option[(FunctionRecorder, Chunk, Term)] = (chunk1, chunk2) match { + case (BasicChunk(rid1, id1, args1, args1Exp, snap1, snap1Exp, perm1, perm1Exp), BasicChunk(_, _, _, _, snap2, _, perm2, perm2Exp)) => + val (fr2, combinedSnap, snapEq) = combineSnapshots(fr1, snap1, snap2, perm1, perm2, qvars, v) + + Some(fr2, BasicChunk(rid1, id1, args1, args1Exp, combinedSnap, snap1Exp, PermPlus(perm1, perm2), perm1Exp.map(p1 => ast.PermAdd(p1, perm2Exp.get)()), v.decider.getAnalysisInfo(AssumptionType.Internal)), snapEq) + case (l@QuantifiedFieldChunk(id1, fvf1, condition1, condition1Exp, perm1, perm1Exp, invs1, singletonRcvr1, singletonRcvr1Exp, hints1), + r@QuantifiedFieldChunk(_, fvf2, _, _, perm2, perm2Exp, _, _, _, hints2)) => + assert(l.quantifiedVars == Seq(`?r`)) + assert(r.quantifiedVars == Seq(`?r`)) + // We need to use l.perm/r.perm here instead of perm1 and perm2 since the permission amount might be dependent on the condition/domain + val (fr2, combinedSnap, snapEq) = quantifiedChunkSupporter.combineFieldSnapshotMaps(fr1, id1.name, fvf1, fvf2, l.perm, r.perm, v) + val permSum = PermPlus(perm1, perm2) + val permSumExp = perm1Exp.map(p1 => ast.PermAdd(p1, perm2Exp.get)()) + val bestHints = if (hints1.nonEmpty) hints1 else hints2 + Some(fr2, QuantifiedFieldChunk(id1, combinedSnap, condition1, condition1Exp, permSum, permSumExp, invs1, singletonRcvr1, singletonRcvr1Exp, bestHints, v.decider.getAnalysisInfo(AssumptionType.Internal)), snapEq) + case (l@QuantifiedPredicateChunk(id1, qVars1, qVars1Exp, psf1, _, _, perm1, perm1Exp, _, _, _, _), + r@QuantifiedPredicateChunk(_, qVars2, qVars2Exp, psf2, condition2, condition2Exp, perm2, perm2Exp, invs2, singletonArgs2, singletonArgs2Exp, hints2)) => + val (fr2, combinedSnap, snapEq) = quantifiedChunkSupporter.combinePredicateSnapshotMaps(fr1, id1.name, qVars2, psf1, psf2, l.perm.replace(qVars1, qVars2), r.perm, v) + + val permSum = PermPlus(perm1.replace(qVars1, qVars2), perm2) + val permSumExp = perm1Exp.map(p1 => ast.PermAdd(p1.replace(qVars1Exp.get.zip(qVars2Exp.get).toMap), perm2Exp.get)()) + Some(fr2, QuantifiedPredicateChunk(id1, qVars2, qVars2Exp, combinedSnap, condition2, condition2Exp, permSum, permSumExp, invs2, singletonArgs2, singletonArgs2Exp, hints2, v.decider.getAnalysisInfo(AssumptionType.Internal)), snapEq) + case _ => + None } /** Merge the snapshots of two chunks that denote the same location, i.e. whose ids and arguments diff --git a/src/main/scala/state/Chunks.scala b/src/main/scala/state/Chunks.scala index 70a021d6d..bdc344229 100644 --- a/src/main/scala/state/Chunks.scala +++ b/src/main/scala/state/Chunks.scala @@ -6,7 +6,7 @@ package viper.silicon.state -import viper.silicon.assumptionAnalysis.{AnalysisInfo, PermissionInhaleNode} +import viper.silicon.assumptionAnalysis.AnalysisInfo import viper.silicon.interfaces.state._ import viper.silicon.resources._ import viper.silicon.rules.InverseFunctions diff --git a/src/main/scala/supporters/SnapshotSupporter.scala b/src/main/scala/supporters/SnapshotSupporter.scala index 22bb1dba7..4e3b71297 100644 --- a/src/main/scala/supporters/SnapshotSupporter.scala +++ b/src/main/scala/supporters/SnapshotSupporter.scala @@ -164,7 +164,7 @@ class DefaultSnapshotSupporter(symbolConverter: SymbolConverter) extends Snapsho (snap0, snap1, snap === Combine(snap0, snap1)) } - v.decider.assume(snapshotEq, Option.when(Verifier.config.enableDebugging())(DebugExp.createInstance("Snapshot", isInternal_ = true)), assumptionType) + v.decider.assume(snapshotEq, Option.when(Verifier.config.enableDebugging())(DebugExp.createInstance("Snapshot", true)), assumptionType) (snap0, snap1) } diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index 77bc7f0e4..bffbbb374 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -46,9 +46,9 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver extends FunctionVerificationUnit[Sort, Decl, Term] with StatefulComponent { + import producer._ import consumer._ import evaluator._ - import producer._ @unused private var program: ast.Program = _ /*private*/ var functionData: Map[ast.Function, FunctionData] = Map.empty diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index 1de306896..c039b4208 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -59,7 +59,7 @@ class DefaultMainVerifier(config: Config, Verifier.config = config - override val debugMode: Boolean = config.enableDebugging() + override val debugMode = config.enableDebugging() private val uniqueIdCounter = new Counter(1) def nextUniqueVerifierId(): String = f"${uniqueIdCounter.next()}%02d" @@ -288,7 +288,7 @@ class DefaultMainVerifier(config: Config, _verificationPoolManager.queueVerificationTask(v => { val startTime = System.currentTimeMillis() - val results = v.cfgSupporter.verify(s, cfg) // TODO ake: assumption analysis + val results = v.cfgSupporter.verify(s, cfg) .flatMap(extractAllVerificationResults) val elapsed = System.currentTimeMillis() - startTime From 62894a5456d4ad0d72635e5f7d5e8e9cf486c452 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 12 Jul 2025 08:12:02 +0200 Subject: [PATCH 162/474] cleanup --- .../AssumptionAnalysisGraph.scala | 4 --- .../AssumptionAnalyzer.scala | 13 +------- src/main/scala/decider/Decider.scala | 31 ++++++++----------- src/main/scala/decider/PathConditions.scala | 20 +++--------- .../scala/interfaces/decider/Prover.scala | 1 - src/main/scala/rules/StateConsolidator.scala | 2 +- 6 files changed, 20 insertions(+), 51 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index ce1eb2aa5..e3a4d6cc5 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -233,10 +233,6 @@ case class PermissionExhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSo override def getNodeString: String = "exhale " + chunk.toString } -//case class PermissionAssertNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean) extends GeneralAssertionNode with ChunkAnalysisInfo { -// override def getNodeString: String = "assert " + term + " for chunk " + chunk.toString -//} - case class LabelNode(term: Term) extends GeneralAssumptionNode { val sourceInfo: AnalysisSourceInfo = NoAnalysisSourceInfo() val assumptionType: AssumptionType = AssumptionType.Internal diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 08c4c7d37..e37dcdb9d 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -36,7 +36,6 @@ trait AssumptionAnalyzer { def addNodes(nodes: Iterable[AssumptionAnalysisNode]): Unit def addNode(node: AssumptionAnalysisNode): Unit def addAssumption(assumption: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, description: Option[String] = None): Option[Int] - def addPermissionAssertNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] def addPermissionExhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] def createAssertOrCheckNode(term: Term, assumptionType: AssumptionType, analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] @@ -120,7 +119,7 @@ object AssumptionAnalyzer { val newGraph = new DefaultAssumptionAnalysisGraph assumptionAnalysisGraphs foreach (graph => newGraph.addNodes(graph.nodes)) assumptionAnalysisGraphs foreach (graph => graph.edges foreach {case (s, t) => newGraph.addEdges(s, t)}) - assumptionAnalysisGraphs foreach (graph => graph.transitiveEdges foreach {case (s, t) => newGraph.addEdges(s, t)}) // TODO ake: add transitive edges + assumptionAnalysisGraphs foreach (graph => graph.transitiveEdges foreach {case (s, t) => newGraph.addEdges(s, t)}) val types = Set(AssumptionType.Implicit, AssumptionType.Explicit) val relevantAssumptionNodes = newGraph.nodes filter (node => node.isInstanceOf[GeneralAssumptionNode] && types.contains(node.assumptionType)) newGraph.nodes filter (node => node.isInstanceOf[GeneralAssertionNode] && node.assumptionType.equals(AssumptionType.Postcondition)) foreach {node => @@ -189,15 +188,6 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { Some(node.id) } - // TODO ake: remove - override def addPermissionAssertNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { -// val node = PermissionAssertNode(chunk, permAmount, sourceInfo, assumptionType, isClosed_) -// addNode(node) -// addPermissionDependencies(Set(chunk), Set(), Some(node.id)) -// Some(node.id) - None - } - override def addPermissionExhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { val node = PermissionExhaleNode(chunk, permAmount, sourceInfo, assumptionType, isClosed_) addNode(node) @@ -333,7 +323,6 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { } override def addPermissionExhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None - override def addPermissionAssertNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None override def addDependencyFromExhaleToInhale(inhaledChunk: Chunk, sourceInfo: AnalysisSourceInfo): Unit = {} diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 33d79c681..61ca92bee 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -385,10 +385,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } def assume(assumptions: Seq[Term], debugExps: Option[Seq[DebugExp]], assumptionType: AssumptionType): Unit = { - val assumptionsWithLabels = assumptions map (t => { - val assumptionId = assumptionAnalyzer.addAssumption(t, analysisSourceInfoStack.getFullSourceInfo, assumptionType) - (t, AssumptionAnalyzer.createAssumptionLabel(assumptionId)) - }) + val assumptionsWithLabels = addAssumptionLabels(assumptions, assumptionType) assumeWithoutSmokeChecks(InsertionOrderedSet(assumptionsWithLabels)) if (debugMode) { @@ -396,23 +393,17 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } } - // TODO ake: review this def assume(assumptions: Iterable[Term], debugExps: Option[Iterable[DebugExp]], description: String, enforceAssumption: Boolean, assumptionType: AssumptionType): Unit = { val debugExp = Option.when(debugExps.isDefined)(DebugExp.createInstance(description, InsertionOrderedSet(debugExps.get))) - // TODO ake: put after filtering - val assumptionsWithLabels = assumptions map (t => { - val assumptionIds = assumptionAnalyzer.addAssumption(t, analysisSourceInfoStack.getFullSourceInfo, assumptionType) - (t, AssumptionAnalyzer.createAssumptionLabel(assumptionIds)) - }) - - val filteredTerms = - if (enforceAssumption) assumptionsWithLabels - else assumptionsWithLabels filterNot(t => isKnownToBeTrue(t._1)) + if (enforceAssumption) assumptions + else assumptions filterNot isKnownToBeTrue if(filteredTerms.isEmpty) return + val assumptionsWithLabels = addAssumptionLabels(filteredTerms, assumptionType) + assumeWithoutSmokeChecks(InsertionOrderedSet(assumptionsWithLabels)) if (debugMode && debugExp.isDefined) { @@ -420,6 +411,13 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } } + private def addAssumptionLabels(filteredTerms: Iterable[Term], assumptionType: AssumptionType) = { + filteredTerms map (t => { + val assumptionIds = assumptionAnalyzer.addAssumption(t, analysisSourceInfoStack.getFullSourceInfo, assumptionType) + (t, AssumptionAnalyzer.createAssumptionLabel(assumptionIds)) + }) + } + def assume(terms: Iterable[Term], debugExp: Option[DebugExp], enforceAssumption: Boolean, assumptionType: AssumptionType): Unit = { val filteredTerms = if (enforceAssumption) terms @@ -430,10 +428,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => if (debugMode) { addDebugExp(debugExp.get.withTerm(And(filteredTerms))) } - val termsWithLabel = filteredTerms map (t => { - val assumptionId = assumptionAnalyzer.addAssumption(t, analysisSourceInfoStack.getFullSourceInfo, assumptionType) - (t, AssumptionAnalyzer.createAssumptionLabel(assumptionId)) - }) + val termsWithLabel = addAssumptionLabels(filteredTerms, assumptionType) assumeWithoutSmokeChecks(InsertionOrderedSet(termsWithLabel)) } diff --git a/src/main/scala/decider/PathConditions.scala b/src/main/scala/decider/PathConditions.scala index 07abaef6a..e155541a9 100644 --- a/src/main/scala/decider/PathConditions.scala +++ b/src/main/scala/decider/PathConditions.scala @@ -38,7 +38,6 @@ trait RecordedPathConditions { def contains(assumption: Term): Boolean def conditionalized: Seq[Term] - def conditionalizedWithAnalysis: (Seq[Term], Seq[Term]) // TODO ake: remove and integrated labels into conditionalized def conditionalizedExp: Seq[DebugExp] @@ -280,22 +279,17 @@ private trait LayeredPathConditionStackLike { protected def declarations(layers: Stack[PathConditionStackLayer]): InsertionOrderedSet[Decl] = InsertionOrderedSet(layers.flatMap(_.declarations)) // Note: Performance? + protected def analysisLabels(layers: Stack[PathConditionStackLayer]): InsertionOrderedSet[Term] = + InsertionOrderedSet(layers.flatMap(_.analysisLabels)) + protected def contains(layers: Stack[PathConditionStackLayer], assumption: Term): Boolean = layers exists (_.contains(assumption)) - protected def conditionalized(layers: Stack[PathConditionStackLayer]): Seq[Term] = { - conditionalizedWithAnalysis(layers)._1 - } - - protected def analysisLabels(layers: Stack[PathConditionStackLayer]): InsertionOrderedSet[Term] = - InsertionOrderedSet(layers.flatMap(_.analysisLabels)) - // TODO ake: remove? - protected def conditionalizedWithAnalysis(layers: Stack[PathConditionStackLayer]): (Seq[Term], Seq[Term]) = { + protected def conditionalized(layers: Stack[PathConditionStackLayer]): Seq[Term] = { var unconditionalTerms = Vector.empty[Term] var conditionalTerms = Vector.empty[Term] var implicationLHS: Term = True - var originalTerms = Vector.empty[Term] for (layer <- layers.reverseIterator) { unconditionalTerms ++= layer.globalAssumptions @@ -303,15 +297,13 @@ private trait LayeredPathConditionStackLike { layer.branchCondition match { case Some(condition) => implicationLHS = And(implicationLHS, condition) - originalTerms = originalTerms :+ condition case None => } conditionalTerms :+= Implies(implicationLHS, And(layer.nonGlobalAssumptions)) - originalTerms = originalTerms ++ layer.nonGlobalAssumptions ++ layer.globalAssumptions } - (unconditionalTerms ++ conditionalTerms, originalTerms) + unconditionalTerms ++ conditionalTerms } protected def conditionalizedExp(layers: Stack[PathConditionStackLayer]): Seq[DebugExp] = { @@ -440,7 +432,6 @@ private class DefaultRecordedPathConditions(from: Stack[PathConditionStackLayer] def contains(assumption: Term): Boolean = contains(from, assumption) val conditionalized: Seq[Term] = conditionalized(from) - val conditionalizedWithAnalysis: (Seq[Term], Seq[Term]) = conditionalizedWithAnalysis(from) lazy val conditionalizedExp: Seq[DebugExp] = conditionalizedExp(from) def definitionsOnly(): RecordedPathConditions = { @@ -620,7 +611,6 @@ private[decider] class LayeredPathConditionStack def contains(assumption: Term): Boolean = allAssumptions.contains(assumption) def conditionalized: Seq[Term] = conditionalized(layers) - override def conditionalizedWithAnalysis: (Seq[Term], Seq[Term]) = conditionalizedWithAnalysis(layers) def conditionalizedExp: Seq[DebugExp] = conditionalizedExp(layers) diff --git a/src/main/scala/interfaces/decider/Prover.scala b/src/main/scala/interfaces/decider/Prover.scala index 4e926e3da..36904103c 100644 --- a/src/main/scala/interfaces/decider/Prover.scala +++ b/src/main/scala/interfaces/decider/Prover.scala @@ -38,7 +38,6 @@ trait ProverLike { terms foreach assume } - // TODO ake: review def assumeAxiomsWithAnalysisInfo(axioms: InsertionOrderedSet[(Term, AnalysisSourceInfo)], description: String): Unit = { if (debugMode) preambleAssumptions :+= new DebugAxiom(description, axioms.map(_._1)) diff --git a/src/main/scala/rules/StateConsolidator.scala b/src/main/scala/rules/StateConsolidator.scala index 6b99f8353..54a3b43f2 100644 --- a/src/main/scala/rules/StateConsolidator.scala +++ b/src/main/scala/rules/StateConsolidator.scala @@ -191,7 +191,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol private def findMatchingChunk(chunks: Iterable[Chunk], chunk: Chunk, v: Verifier): Option[Chunk] = { chunk match { case chunk: BasicChunk => - chunkSupporter.findChunk[BasicChunk](chunks, chunk.id, chunk.args, v) // TODO ake: add edge from check chunk equality? + chunkSupporter.findChunk[BasicChunk](chunks, chunk.id, chunk.args, v) case chunk: QuantifiedChunk => quantifiedChunkSupporter.findChunk(chunks, chunk, v) case _ => None } From bb99685c5bdc48ddc388ac72342cd6fbd114aeb4 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 12 Jul 2025 10:15:14 +0200 Subject: [PATCH 163/474] fix imprecision for qp and wildcards --- src/main/scala/interfaces/state/Chunks.scala | 2 +- .../scala/rules/QuantifiedChunkSupport.scala | 6 +- .../all/imprecision-fixed.vpr | 70 ++++++++++ .../all/imprecision.vpr | 124 +++++++++--------- .../dependencyAnalysisTests/new/meeting.vpr | 47 +++---- .../dependencyAnalysisTests/notes.txt | 48 +++++++ .../unitTests/permissions.vpr | 6 + 7 files changed, 211 insertions(+), 92 deletions(-) create mode 100644 src/test/resources/dependencyAnalysisTests/all/imprecision-fixed.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/notes.txt diff --git a/src/main/scala/interfaces/state/Chunks.scala b/src/main/scala/interfaces/state/Chunks.scala index 00161d7a7..9d4845b58 100644 --- a/src/main/scala/interfaces/state/Chunks.scala +++ b/src/main/scala/interfaces/state/Chunks.scala @@ -38,7 +38,7 @@ object GeneralChunk { def permMinus(chunk: GeneralChunk, newPerm: Term, newPermExp: Option[ast.Exp], analysisInfo: AnalysisInfo): GeneralChunk = { val newChunk = analysisInfo.decider.registerDerivedChunk[GeneralChunk](Set(chunk), {finalPerm => chunk.permMinus(finalPerm, newPermExp)}, - newPerm, analysisInfo.withAssumptionType(AssumptionType.Implicit), isExhale=false, createLabel=false) + newPerm, analysisInfo.withAssumptionType(AssumptionType.Internal), isExhale=false, createLabel=false) val exhaledChunk = analysisInfo.decider.registerDerivedChunk[GeneralChunk](Set(chunk), {finalPerm => chunk.withPerm(finalPerm, newPermExp)}, newPerm, analysisInfo, isExhale=true, createLabel=false) diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index b8904c4fc..b0e2c2ba0 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -1076,7 +1076,9 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { triggers = effectiveTriggers, qidPrefix = qid ) - v.decider.assume(pcsForChunk, pcsForChunkExp, pcsForChunkExp, assumptionType) + v.decider.assumptionAnalyzer.disableTransitiveEdges() // TODO ake: issue #01 + v.decider.assume(pcsForChunk, pcsForChunkExp, pcsForChunkExp, AssumptionType.Internal) + v.decider.assumptionAnalyzer.enableTransitiveEdges() }) val (fr1, h1) = v.stateConsolidator(s).merge(s.functionRecorder, s, s.h, Heap(Seq(ch)), v) @@ -1713,7 +1715,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { if (constrainPermissions) { v.decider.prover.comment(s"Constrain original permissions $perms") - v.decider.assume(permissionConstraint, permissionConstraintExp, permissionConstraintExp, AssumptionType.Internal) + v.decider.assume(permissionConstraint, permissionConstraintExp, permissionConstraintExp, assumptionType) remainingChunks = remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.getAnalysisInfo(assumptionType)) } else { diff --git a/src/test/resources/dependencyAnalysisTests/all/imprecision-fixed.vpr b/src/test/resources/dependencyAnalysisTests/all/imprecision-fixed.vpr new file mode 100644 index 000000000..d0a9db2a9 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/all/imprecision-fixed.vpr @@ -0,0 +1,70 @@ +field f: Int + +method permTest(a: Ref, b: Ref, n: Int) + requires @dependency("Explicit")(acc(a.f)) + requires @irrelevant("Explicit")(acc(b.f)) && @irrelevant("Explicit")(b.f > 0) +{ + @dependency("Explicit") + assume n > 0 + @irrelevant("Implicit") // fixed imprecision (field assign) + a.f := b.f + 2 + @dependency("Implicit") + a.f := n + @testAssertion("Explicit") + assert a.f >= 0 +} + + +method quantifiedPerm2(xs: Seq[Ref], ys: Seq[Ref]) + requires @dependency("Explicit")(|xs| > 5) + requires @irrelevant("Explicit")(|ys| > 3) +{ + @dependency("Explicit") + inhale (forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0)) + @irrelevant("Explicit") // fixed imprecision (wildcards) + inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard) && y.f > 0) + + @testAssertion("Explicit") + assert xs[0].f > 0 +} + +method quantifiedPerm3(xs: Seq[Ref], ys: Seq[Ref]) + requires @dependency("Explicit")(|xs| > 5) + requires @irrelevant("Explicit")(|ys| > 3) +{ + @dependency("Explicit") + inhale (forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0)) + @irrelevant("Explicit") // fixed imprecision (wildcards) + inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard)) + + @testAssertion("Explicit") + assert xs[0].f > 0 +} + +method quantifiedPerm4(xs: Seq[Ref], ys: Seq[Ref]) + requires @dependency("Explicit")(|xs| > 5) + requires @irrelevant("Explicit")(|ys| > 3) +{ + @dependency("Explicit") + inhale (forall x: Ref :: x in xs ==> acc(x.f) && x.f > 0) + @irrelevant("Explicit") // fixed imprecision (heap summary) + inhale forall y: Ref :: y in ys ==> acc(y.f) + + @testAssertion("Explicit") + assert xs[0].f > 0 +} + +method quantifiedPerm5(xs: Seq[Ref], ys: Seq[Ref]) + requires @dependency("Explicit")(|xs| > 5) + requires @irrelevant("Explicit")(|ys| > 3) +{ + @dependency("Explicit") + inhale forall x: Ref :: x in xs ==> acc(x.f) + @irrelevant("Explicit") + inhale forall y: Ref :: y in ys ==> acc(y.f) + + @irrelevant("Implicit") // fixed imprecision (field assign with qp) + xs[0].f := ys[0].f + 2 + @testAssertion("Implicit") + xs[0].f := 2 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr b/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr index 6b4d8deff..834cb4fe2 100644 --- a/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr @@ -1,85 +1,83 @@ field f: Int - -method permTest(a: Ref, b: Ref, n: Int) - requires @dependency("Explicit")(acc(a.f)) - requires @irrelevant("Explicit")(acc(b.f)) && @irrelevant("Explicit")(b.f > 0) +// When executing the branch, the 1st inhale is reported as a dependency, the 2nd inhale is ignored. +// If the branch is not executed, the 2nd inhale is reported as a dependency +// In reality, the 2nd inhale is all we need for the assertion. +method nonUniqueUnsatCore(x: Ref) + requires x != null ==> acc(x.f) { + var a: Int + if(x == null){ + // @irrelevant("Explicit") // imprecise + inhale a >= 0 + } + @dependency("Explicit") - assume n > 0 - @irrelevant("Implicit") - a.f := b.f + 2 - @dependency("Implicit") - a.f := n + inhale a >= 0 + @testAssertion("Explicit") - assert a.f >= 0 + assert a >= 0 } +method unprovableInfeasibility(){ + var a: Int -method quantifiedPerm2(xs: Seq[Ref], ys: Seq[Ref]) - requires @dependency("Explicit")(|xs| > 5) - requires @irrelevant("Explicit")(|ys| > 3) -{ - @dependency("Explicit") - inhale (forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0)) - //@irrelevant("Explicit") // TODO ake - inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard) && y.f > 0) + if (exists x: Int, y: Int, z: Int :: x > 0 && y > 0 && z > 0 && x*x*x + y*y*y == z*z*z) { + // complicated condition that is equivalent to false, but can’t be proven by solver + // effectively unreachable code - @testAssertion("Explicit") - assert xs[0].f > 0 + // @irrelevant("Explicit") + assume a == 0 // assumption 1 + } else { + @dependency("Explicit") + assume a == 0 // assumption 2 + } + + @testAssertion("Explicit") + assert a == 0 } -method quantifiedPerm3(xs: Seq[Ref], ys: Seq[Ref]) - requires @dependency("Explicit")(|xs| > 5) - requires @irrelevant("Explicit")(|ys| > 3) +// Implication causes branching. +// If x == null is assumed, only the assignment is reported as a dependency +// If x != null, the branch is infeasible, but we executed it anyways. The implication and branch condition are reported as dependencies. +method infeasible2(x: Ref) + requires x != null ==> acc(x.f) // unexpected dependency { - @dependency("Explicit") - inhale (forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0)) - // @irrelevant("Explicit") // TODO ake - inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard)) + if(x == null){ // unexpected dependency + var a: Int - @testAssertion("Explicit") - assert xs[0].f > 0 + @dependency("Implicit") + a := 0 + + @testAssertion("Explicit") + assert a >= 0 + } } -method quantifiedPerm4(xs: Seq[Ref], ys: Seq[Ref]) - requires @dependency("Explicit")(|xs| > 5) - requires @irrelevant("Explicit")(|ys| > 3) -{ - @dependency("Explicit") - inhale (forall x: Ref :: x in xs ==> acc(x.f) && x.f > 0) - @irrelevant("Explicit") - inhale forall y: Ref :: y in ys ==> acc(y.f) +method exhaleImprecision(){ + var x: Ref - @testAssertion("Explicit") - assert xs[0].f > 0 -} + @dependency("Explicit") + inhale acc(x.f) -method quantifiedPerm5(xs: Seq[Ref], ys: Seq[Ref]) - requires @dependency("Explicit")(|xs| > 5) - requires @irrelevant("Explicit")(|ys| > 3) -{ - @dependency("Explicit") - inhale forall x: Ref :: x in xs ==> acc(x.f) - @irrelevant("Explicit") - inhale forall y: Ref :: y in ys ==> acc(y.f) - - // @irrelevant("Implicit") // TODO ake: imprecise - xs[0].f := ys[0].f + 2 - @testAssertion("Implicit") - xs[0].f := 2 + @dependency("Explicit") + inhale x.f > 0 + + @dependency("Implicit") + x.f := x.f + 1 + + @irrelevant("Implicit") // TODO ake: unexpected dependency + exhale acc(x.f, 1/2) + + @testAssertion("Explicit") + assert x.f > 1 } -method nonUniqueUnsatCore(x: Ref) - requires x != null ==> acc(x.f) +method noAlias(a: Ref, b: Ref, c: Ref) + requires @dependency("Explicit")(acc(a.f)) + requires @dependency("Explicit")(acc(b.f, 1/2)) + requires @irrelevant("Explicit")(acc(c.f, 1/2)) // TODO ake: unexpected dependency { - var a: Int - if(x == null){ - // @irrelevant("Explicit") // TODO ake: imprecise - inhale a >= 0 - } - @dependency("Explicit") - inhale a >= 0 @testAssertion("Explicit") - assert a >= 0 + assert a != b } \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/new/meeting.vpr b/src/test/resources/dependencyAnalysisTests/new/meeting.vpr index d3acf611f..08dc0dcd6 100644 --- a/src/test/resources/dependencyAnalysisTests/new/meeting.vpr +++ b/src/test/resources/dependencyAnalysisTests/new/meeting.vpr @@ -1,5 +1,26 @@ field f: Int +method exhaleInhale(a: Ref) + requires acc(a.f) +{ + + exhale acc(a.f, 1/2) + + inhale acc(a.f, 1/2) + + assert perm(a.f) == write +} + +method exhaleImprecision(){ + var x: Ref + + inhale acc(x.f) + inhale x.f > 0 + + exhale acc(x.f, 1/2) + + assert x.f > 0 +} method infeasible1(x: Ref) requires acc(x.f, 1/2) @@ -23,32 +44,6 @@ method infeasible2(x: Ref) } } -method infeasible3(x: Ref) -{ - if(x != null){ // unexpected dependency iff infeasible branches are always executed - inhale acc(x.f) - } - - if(x == null){ // unexpected dependency iff infeasible branches are always executed - var a: Int - a := 0 - assert a >= 0 - } -} - -method foo(a: Int, b:Int, x: Ref) - requires b >= 0 - ensures a >= 0 -{ - if(b < 0){ - x.f := a - }else{ - inhale a > 10 - } - - assert a > 0 -} - method currentPerm(x: Ref) requires acc(x.f, 1/2) diff --git a/src/test/resources/dependencyAnalysisTests/notes.txt b/src/test/resources/dependencyAnalysisTests/notes.txt new file mode 100644 index 000000000..6d7e25d10 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/notes.txt @@ -0,0 +1,48 @@ +field f: Int + +// ---------------------------------------------------------------- +// issue #01: IMPRECISION cause by WILDCARDS + +method quantifiedPerm2(xs: Seq[Ref], ys: Seq[Ref]) + requires @dependency("Explicit")(|xs| > 5) + requires @irrelevant("Explicit")(|ys| > 3) +{ + @dependency("Explicit") + inhale (forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0)) // (1) + @irrelevant("Explicit") // ERROR: unexpected dependency + inhale forall y: Ref :: y in ys ==> acc(y.f, wildcard) // (2) + + @testAssertion("Explicit") + assert xs[0].f > 0 // (3) +} + +// (1) k7 = wildcard +// (2) assume k14 > Z assume RdVar(k14) (k14=wildcard) +// (3) assert Z < (xs[0] in xs? k7: Z) + (xs[0] in ys? k14: Z) + + +// ---------------------------------------------------------------- +// issue #02 - IMPRECISION caused by write to quantified resource + + +method quantifiedPerm5(xs: Seq[Ref], ys: Seq[Ref]) + requires @dependency("Explicit")(|xs| > 5) + requires @irrelevant("Explicit")(|ys| > 3) +{ + @dependency("Explicit") + inhale forall x: Ref :: x in xs ==> acc(x.f) + @irrelevant("Explicit") + inhale forall y: Ref :: y in ys ==> acc(y.f) + + @irrelevant("Implicit") + xs[0].f := ys[0].f + 2 // (1) + @testAssertion("Implicit") + xs[0].f := 2 // (2) +} + +// (2) imprecisely depends on (1) +// issue: inhaling remaining permission of lhs was labelled as implicit +// solution: +// - inhale of remaining permissions / full permission after assignments are labelled internal +// - exhale is implicit +// - lhs and rhs are split such that transitive edges only go from lhs to rhs diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr index 048f513ba..dc75da21e 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr @@ -1,5 +1,11 @@ field f: Int +method currentPerm(x: Ref) + requires @dependency("Explicit")(acc(x.f, 1/2)) +{ + @testAssertion("Explicit") + assert perm(x.f) > 1/4 +} method perm1(){ var x: Ref From 74beef8dfd486ca8e021d22617f2297f15b37133 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 12 Jul 2025 11:00:53 +0200 Subject: [PATCH 164/474] fix imprecision for state consolidation --- .../NonQuantifiedPropertyInterpreter.scala | 12 ++++++++---- src/main/scala/rules/StateConsolidator.scala | 6 +++++- .../all/imprecision-fixed.vpr | 15 +++++++++++++++ .../dependencyAnalysisTests/all/imprecision.vpr | 8 -------- .../unitTests/permissions.vpr | 2 +- 5 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala b/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala index 37621ea33..6a63bd8e7 100644 --- a/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala +++ b/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala @@ -147,15 +147,19 @@ class NonQuantifiedPropertyInterpreter(heap: Iterable[Chunk], verifier: Verifier body: PropertyExpression[kinds.Boolean], info: Info) : (Term, Option[ast.Exp]) = { - val builder: GeneralChunk => (Term, Option[ast.Exp]) = chunkVariables match { - case c +: Seq() => chunk => buildPathCondition(body, info.addMapping(c, chunk)) - case c +: tail => chunk => buildForEach(chunks, tail, body, info.addMapping(c, chunk)) + val builder: GeneralChunk => (Term, Option[ast.Exp]) = {chunk => + val res = chunkVariables match { + case c +: Seq() => buildPathCondition(body, info.addMapping(c, chunk)) + case c +: tail => buildForEach(chunks, tail, body, info.addMapping(c, chunk)) + } + (verifier.decider.wrapWithAssumptionAnalysisLabel(res._1, Set(chunk)), res._2) } val conds = chunks.flatMap { chunk => // check that only distinct tuples are handled // TODO: Is it possible to get this behavior without having to check every tuple? if (!info.pm.values.exists(chunk eq _)) { - Some(builder(chunk)) + val res = builder(chunk) + Some((verifier.decider.wrapWithAssumptionAnalysisLabel(res._1, Set(chunk)), res._2)) } else { None } diff --git a/src/main/scala/rules/StateConsolidator.scala b/src/main/scala/rules/StateConsolidator.scala index 54a3b43f2..2b7377bfd 100644 --- a/src/main/scala/rules/StateConsolidator.scala +++ b/src/main/scala/rules/StateConsolidator.scala @@ -101,12 +101,16 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol mergedChunks.filter(_.isInstanceOf[BasicChunk]) foreach { case ch: BasicChunk => val resource = Resources.resourceDescriptions(ch.resourceID) val pathCond = interpreter.buildPathConditionsForChunk(ch, resource.instanceProperties(s.mayAssumeUpperBounds)) - pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), AssumptionType.Internal)) + v.decider.assumptionAnalyzer.disableTransitiveEdges() + pathCond.foreach(p => v.decider.assume(v.decider.wrapWithAssumptionAnalysisLabel(p._1, Set(ch)), Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), AssumptionType.Internal)) + v.decider.assumptionAnalyzer.enableTransitiveEdges() } Resources.resourceDescriptions foreach { case (id, desc) => val pathCond = interpreter.buildPathConditionsForResource(id, desc.delayedProperties(s.mayAssumeUpperBounds)) + v.decider.assumptionAnalyzer.disableTransitiveEdges() pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), AssumptionType.Internal)) + v.decider.assumptionAnalyzer.enableTransitiveEdges() } v.symbExLog.closeScope(sepIdentifier) diff --git a/src/test/resources/dependencyAnalysisTests/all/imprecision-fixed.vpr b/src/test/resources/dependencyAnalysisTests/all/imprecision-fixed.vpr index d0a9db2a9..b6552eabd 100644 --- a/src/test/resources/dependencyAnalysisTests/all/imprecision-fixed.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/imprecision-fixed.vpr @@ -67,4 +67,19 @@ method quantifiedPerm5(xs: Seq[Ref], ys: Seq[Ref]) xs[0].f := ys[0].f + 2 @testAssertion("Implicit") xs[0].f := 2 +} + +// Imprecision due to state consolidation triggered by assertion +// state consolidation infers pair-wise non-aliasing of all 3 resources in one assumption. +// This assumption depends on all preconditions. +// The assertion depends on this assumption. +// solution: wrap with analysis labels (see viper.silicon.resources.NonQuantifiedPropertyInterpreter.buildForEach) +// and disable transitive edges (see viper.silicon.rules.DefaultStateConsolidator.consolidate) +method noAlias(a: Ref, b: Ref, c: Ref) + requires @dependency("Explicit")(acc(a.f)) + requires @dependency("Explicit")(acc(b.f, 1/2)) + requires @irrelevant("Explicit")(acc(c.f, 1/2)) +{ + @testAssertion("Explicit") + assert a != b } \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr b/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr index 834cb4fe2..4c1b2746a 100644 --- a/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr @@ -73,11 +73,3 @@ method exhaleImprecision(){ assert x.f > 1 } -method noAlias(a: Ref, b: Ref, c: Ref) - requires @dependency("Explicit")(acc(a.f)) - requires @dependency("Explicit")(acc(b.f, 1/2)) - requires @irrelevant("Explicit")(acc(c.f, 1/2)) // TODO ake: unexpected dependency -{ - @testAssertion("Explicit") - assert a != b -} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr index dc75da21e..ec1775182 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr @@ -94,7 +94,7 @@ method permAmount2(x: Ref, p: Perm) method noAlias(a: Ref, b: Ref, c: Ref) requires @dependency("Explicit")(acc(a.f)) requires @dependency("Explicit")(acc(b.f, 1/2)) - requires acc(c.f, 1/2) // TODO ake: imprecise @irrelevant("Explicit")() + requires @irrelevant("Explicit")(acc(c.f, 1/2)) { @testAssertion("Explicit") assert a != b From f6b07e9a5aa1654a5f2b878255b89828266ac5d8 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 12 Jul 2025 11:29:16 +0200 Subject: [PATCH 165/474] fix wildcards --- src/main/scala/rules/QuantifiedChunkSupport.scala | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index b0e2c2ba0..8cb2b3254 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -1076,9 +1076,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { triggers = effectiveTriggers, qidPrefix = qid ) - v.decider.assumptionAnalyzer.disableTransitiveEdges() // TODO ake: issue #01 - v.decider.assume(pcsForChunk, pcsForChunkExp, pcsForChunkExp, AssumptionType.Internal) - v.decider.assumptionAnalyzer.enableTransitiveEdges() + v.decider.assume(pcsForChunk, pcsForChunkExp, pcsForChunkExp, AssumptionType.Internal) // TODO ake: issue #01 }) val (fr1, h1) = v.stateConsolidator(s).merge(s.functionRecorder, s, s.h, Heap(Seq(ch)), v) From 7e66190e9989f414d10676df39591d35226543f7 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 12 Jul 2025 11:29:35 +0200 Subject: [PATCH 166/474] update tests --- .../dependencyAnalysisTests/all/exhale.vpr | 14 +++++ .../all/imprecision-fixed.vpr | 38 ++++---------- .../all/imprecision.vpr | 51 +++++++++++++++++++ .../dependencyAnalysisTests/notes.txt | 1 + .../unitTests/permissions.vpr | 17 +------ .../unitTests/quantifiedPermissions.vpr | 15 +++++- 6 files changed, 93 insertions(+), 43 deletions(-) diff --git a/src/test/resources/dependencyAnalysisTests/all/exhale.vpr b/src/test/resources/dependencyAnalysisTests/all/exhale.vpr index 31e1eddd5..d148eee19 100644 --- a/src/test/resources/dependencyAnalysisTests/all/exhale.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/exhale.vpr @@ -21,4 +21,18 @@ method exhaleIrrelevant(x: Ref) inhale acc(x.f, 1/2) @testAssertion("Implicit") inhale x.f > 0 +} + +method exhale2(){ + var x: Ref + @dependency("Explicit") + inhale acc(x.f) + @dependency("Explicit") + inhale x.f > 0 + @dependency("Implicit") + x.f := x.f + 1 + // @irrelevant("Implicit") // TODO ake + exhale acc(x.f, 1/2) + @testAssertion("Explicit") + assert x.f > 1 } \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/imprecision-fixed.vpr b/src/test/resources/dependencyAnalysisTests/all/imprecision-fixed.vpr index b6552eabd..15a3f225b 100644 --- a/src/test/resources/dependencyAnalysisTests/all/imprecision-fixed.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/imprecision-fixed.vpr @@ -15,32 +15,6 @@ method permTest(a: Ref, b: Ref, n: Int) } -method quantifiedPerm2(xs: Seq[Ref], ys: Seq[Ref]) - requires @dependency("Explicit")(|xs| > 5) - requires @irrelevant("Explicit")(|ys| > 3) -{ - @dependency("Explicit") - inhale (forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0)) - @irrelevant("Explicit") // fixed imprecision (wildcards) - inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard) && y.f > 0) - - @testAssertion("Explicit") - assert xs[0].f > 0 -} - -method quantifiedPerm3(xs: Seq[Ref], ys: Seq[Ref]) - requires @dependency("Explicit")(|xs| > 5) - requires @irrelevant("Explicit")(|ys| > 3) -{ - @dependency("Explicit") - inhale (forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0)) - @irrelevant("Explicit") // fixed imprecision (wildcards) - inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard)) - - @testAssertion("Explicit") - assert xs[0].f > 0 -} - method quantifiedPerm4(xs: Seq[Ref], ys: Seq[Ref]) requires @dependency("Explicit")(|xs| > 5) requires @irrelevant("Explicit")(|ys| > 3) @@ -54,6 +28,15 @@ method quantifiedPerm4(xs: Seq[Ref], ys: Seq[Ref]) assert xs[0].f > 0 } +// issue #02 - imprecision due to field assign with qp +// issue: access to a quantified field depended on all writes to any location covered by this qp resource +// expl: when exhaling permission to a single location of a qp resource (e.g. for field assign), +// permissions for other locations are inhaled. The inhale of full permission after the field assign +// depends on this inhale. +// solution: +// - inhale of remaining permissions / full permission after assignments are labelled internal +// - exhale is implicit +// - lhs and rhs are split such that transitive edges only go from lhs to rhs method quantifiedPerm5(xs: Seq[Ref], ys: Seq[Ref]) requires @dependency("Explicit")(|xs| > 5) requires @irrelevant("Explicit")(|ys| > 3) @@ -69,6 +52,7 @@ method quantifiedPerm5(xs: Seq[Ref], ys: Seq[Ref]) xs[0].f := 2 } +// issue #03 // Imprecision due to state consolidation triggered by assertion // state consolidation infers pair-wise non-aliasing of all 3 resources in one assumption. // This assumption depends on all preconditions. @@ -78,7 +62,7 @@ method quantifiedPerm5(xs: Seq[Ref], ys: Seq[Ref]) method noAlias(a: Ref, b: Ref, c: Ref) requires @dependency("Explicit")(acc(a.f)) requires @dependency("Explicit")(acc(b.f, 1/2)) - requires @irrelevant("Explicit")(acc(c.f, 1/2)) + requires @irrelevant("Explicit")(acc(c.f, 1/2)) // fixed imprecision (state consolidation) { @testAssertion("Explicit") assert a != b diff --git a/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr b/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr index 4c1b2746a..2231428ec 100644 --- a/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr @@ -73,3 +73,54 @@ method exhaleImprecision(){ assert x.f > 1 } +method quantifiedPerm2(xs: Seq[Ref], ys: Seq[Ref]) + requires @dependency("Explicit")(|xs| > 5) + requires @irrelevant("Explicit")(|ys| > 3) +{ + @dependency("Explicit") + inhale (forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0)) + @irrelevant("Explicit") // this is precise (as opposed to quantifiedPerm3 + inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard)) + + @testAssertion("Explicit") + assert xs[0].f > 0 +} + +// issue #01 - imprecision due to wildcards +// when accessing a qp resource, we check Z < (x in xs? …) + (y in ys? …) the unsat core contains dependencies to all qp perm amounts (e.g. wildcard) +method quantifiedPerm3(xs: Seq[Ref], ys: Seq[Ref]) + requires @dependency("Explicit")(|xs| > 5) + requires @irrelevant("Explicit")(|ys| > 3) +{ + @dependency("Explicit") + inhale (forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0)) + @irrelevant("Explicit") // TODO ake: unexpected dependency (wildcards) + inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard) && y.f > 0) + + @testAssertion("Explicit") + assert xs[0].f > 0 +} + +// issue #01 - imprecision due to perm amounts +// when accessing a qp resource, we check Z < (x in xs? …) + (y in ys? …) the unsat core contains dependencies to all qp perm amounts (e.g. wildcard) +method quantifiedPerm4(xs: Seq[Ref], ys: Seq[Ref]) + requires @dependency("Explicit")(|xs| > 5) + requires @irrelevant("Explicit")(|ys| > 3) +{ + var p1: Perm + @dependency("Explicit") + assume none < p1 && p1 < 1/2 + + var p2: Perm + @irrelevant("Explicit") + assume none < p2 && p2 < 1/2 // TODO ake: unexpected dependency + + @dependency("Explicit") + inhale (forall x: Ref :: x in xs ==> (acc(x.f, p1) && x.f > 0)) + + @irrelevant("Explicit") // fixed imprecision (wildcards) + inhale forall y: Ref :: y in ys ==> (acc(y.f, p2) && y.f > 0) + + @testAssertion("Explicit") + assert xs[0].f > 0 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/notes.txt b/src/test/resources/dependencyAnalysisTests/notes.txt index 6d7e25d10..d6805dbc6 100644 --- a/src/test/resources/dependencyAnalysisTests/notes.txt +++ b/src/test/resources/dependencyAnalysisTests/notes.txt @@ -20,6 +20,7 @@ method quantifiedPerm2(xs: Seq[Ref], ys: Seq[Ref]) // (2) assume k14 > Z assume RdVar(k14) (k14=wildcard) // (3) assert Z < (xs[0] in xs? k7: Z) + (xs[0] in ys? k14: Z) +// when accessing a qp resource, we check Z < (x in xs? …) + (y in ys? …) the unsat core contains dependencies to all qp perm amounts (e.g. wildcard) // ---------------------------------------------------------------- // issue #02 - IMPRECISION caused by write to quantified resource diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr index ec1775182..21d884fbe 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr @@ -53,20 +53,6 @@ method perm5(){ exhale acc(x.f) } -method perm6(){ - var x: Ref - @dependency("Explicit") - inhale acc(x.f) - @dependency("Explicit") - inhale x.f > 0 - @dependency("Implicit") - x.f := x.f + 1 - // @irrelevant("Implicit") // TODO ake - exhale acc(x.f, 1/2) - @testAssertion("Explicit") - assert x.f > 1 -} - method permAmount1(x: Ref, p: Perm) requires p > none requires @dependency("Explicit")(acc(x.f, p)) @@ -109,4 +95,5 @@ method exhaleInhale(a: Ref) inhale acc(a.f, 1/2) @testAssertion("Explicit") assert perm(a.f) == 1/1 -} \ No newline at end of file +} + diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr index e8988a636..4358dba09 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr @@ -81,7 +81,7 @@ method quantifiedPermWrite3(xs: Seq[Ref]) { @dependency("Explicit") inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) - // @irrelevant("Implicit") // TODO ake: imprecise + @irrelevant("Implicit") xs[0].f := 0 @testAssertion("Explicit") @@ -99,4 +99,17 @@ method quantifiedPermWrite4(xs: Seq[Ref], ys: Seq[Ref]) @testAssertion("Implicit") xs[0].f := 2 +} + +method quantifiedPermWildcard(xs: Seq[Ref], ys: Seq[Ref]) + requires @dependency("Explicit")(|xs| > 5) + requires @irrelevant("Explicit")(|ys| > 3) +{ + @dependency("Explicit") + inhale (forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0)) + @irrelevant("Explicit") + inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard)) + + @testAssertion("Explicit") + assert xs[0].f > 0 } \ No newline at end of file From 7921cb03740beb22b30f7966a0e356d7bdad4469 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 14 Jul 2025 17:04:07 +0200 Subject: [PATCH 167/474] merge graph and minor fixes --- .../AssumptionAnalysisGraph.scala | 46 +++++++++++++++++-- .../AssumptionAnalyzer.scala | 17 +++++++ src/main/scala/decider/Decider.scala | 2 +- src/main/scala/rules/Executor.scala | 1 - .../functions/FunctionVerificationUnit.scala | 1 + .../scala/verifier/DefaultMainVerifier.scala | 1 + .../all/imprecision.vpr | 6 +-- src/test/scala/AssumptionAnalysisTests.scala | 6 +-- 8 files changed, 68 insertions(+), 12 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index e3a4d6cc5..93a2b8333 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -2,7 +2,7 @@ package viper.silicon.assumptionAnalysis import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType import viper.silicon.interfaces.state.Chunk -import viper.silicon.state.terms.{False, Term} +import viper.silicon.state.terms.{False, Term, True} import viper.silver.ast.Position import java.io.{File, PrintWriter} @@ -83,7 +83,7 @@ trait AssumptionAnalysisGraph { .groupBy(_.asInstanceOf[ChunkAnalysisInfo].getChunk) } - private def getNodesPerTransitivitySourceInfo: Map[String, mutable.Seq[AssumptionAnalysisNode]] = { + def getNodesPerTransitivitySourceInfo: Map[String, mutable.Seq[AssumptionAnalysisNode]] = { nodes.groupBy(_.sourceInfo.getSourceForTransitiveEdges.toString) } @@ -154,6 +154,43 @@ trait AssumptionAnalysisGraph { nodes foreach (n => writer.println(getNodeExportString(n).replace("\n", " "))) writer.close() } + + def mergeNodesBySource(): AssumptionAnalysisGraph = { + def keepNode(n: AssumptionAnalysisNode): Boolean = n.isClosed || n.isInstanceOf[InfeasibilityNode] + val mergedGraph = new DefaultAssumptionAnalysisGraph + val nodeMap = mutable.HashMap[Int, Int]() + nodes.filter(keepNode).foreach{n => + nodeMap.put(n.id, n.id) + mergedGraph.addNode(n) + } + + val nodesBySource = nodes.filter(!keepNode(_)) + .groupBy(n => (n.sourceInfo.getSourceForTransitiveEdges.toString, n.sourceInfo.getTopLevelSource.toString, n.assumptionType)) + nodesBySource foreach {case ((_, _, assumptionType), nodes) => + val assumptionNodes = nodes.filter(_.isInstanceOf[GeneralAssumptionNode]) + if(assumptionNodes.nonEmpty) { + val newNode = SimpleAssumptionNode(True, None, assumptionNodes.head.sourceInfo, assumptionType, isClosed = true) + assumptionNodes foreach (n => nodeMap.put(n.id, newNode.id)) + mergedGraph.addNode(newNode) + } + } + + nodesBySource foreach {case ((_, _, assumptionType), nodes) => + val assertionNodes = nodes.filter(_.isInstanceOf[GeneralAssertionNode]) + if(assertionNodes.nonEmpty){ + val newNode = SimpleAssertionNode(True, assumptionType, assertionNodes.head.sourceInfo, isClosed=true) + assertionNodes foreach (n => nodeMap.put(n.id, newNode.id)) + mergedGraph.addNode(newNode) + } + } + + (edges ++ transitiveEdges) foreach {case (source, targets) => + val newSource = nodeMap(source) + mergedGraph.addEdges(newSource, targets.map(nodeMap(_))) + } + + mergedGraph + } } @@ -171,8 +208,9 @@ class DefaultAssumptionAnalysisGraph extends AssumptionAnalysisGraph { override def addEdges(source: Int, targets: Iterable[Int]): Unit = { val oldTargets = edges.getOrElse(source, Set.empty) - if(targets.nonEmpty) - edges.update(source, oldTargets ++ targets) + val newTargets = targets.filter(_ != source) + if(newTargets.nonEmpty) + edges.update(source, oldTargets ++ newTargets) } override def addEdges(sources: Iterable[Int], target: Int): Unit = { diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index e37dcdb9d..99922c726 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -57,6 +57,23 @@ trait AssumptionAnalyzer { def registerExhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: (Term => CH), perm: Term, analysisInfo: AnalysisInfo): CH = buildChunk(perm) def computeProofCoverage(): Unit = {} + + def exportMergedGraph(): Unit = { + if(Verifier.config.assumptionAnalysisExportPath.isEmpty) return + + val mergedGraph = assumptionGraph.mergeNodesBySource() + + val foldername: Option[String] = getMember map { + case ast.Method(name, _, _, _, _, _) => name + case ast.Function(name, _, _, _, _, _) => name + case ast.Domain(name, _, _, _, _) => name + case contracted: ast.Contracted => contracted.toString() + case location: ast.Location => location.pos.toString + case member: ast.ExtensionMember => member.pos.toString + } + mergedGraph.exportGraph(Verifier.config.assumptionAnalysisExportPath() + "/" + foldername.getOrElse("latestExport") + "_merged") + } + } object AssumptionAnalyzer { diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 61ca92bee..cc8237728 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -527,7 +527,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => val result = asserted || proverAssert(t, timeout, AssumptionAnalyzer.createAssertionLabel(assertNode map (_.id))) - if(result || !isCheck) assertNode foreach assumptionAnalyzer.addNode + if(result) assertNode foreach assumptionAnalyzer.addNode symbExLog.closeScope(sepIdentifier) (result, assertNode) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 87a3914bf..06b0f4666 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -279,7 +279,6 @@ object executor extends ExecutionRules { val s2 = s1.copy(invariantContexts = sLeftover.h +: s1.invariantContexts) intermediateResult combine executionFlowController.locally(s2, v1)((s3, v2) => { v2.decider.declareAndRecordAsFreshFunctions(ff1 -- v2.decider.freshFunctions) /* [BRANCH-PARALLELISATION] */ - // TODO ake: pcs.assumptionExps without exps do not have a source, but setting the source here will result in all invariants having the same source v2.decider.assume(pcs.assumptions map (t => v.decider.wrapWithAssumptionAnalysisLabel(t, Set.empty, Set(t))), Some(pcs.assumptionExps), "Loop invariant", enforceAssumption=false, assumptionType=AssumptionType.LoopInvariant) v2.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) if (v2.decider.checkSmoke() && !Verifier.config.enableAssumptionAnalysis()) diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index bffbbb374..da8e04525 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -254,6 +254,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver var recorders: Seq[FunctionRecorder] = Vector.empty val wExp = evaluator.withExp val annotatedAssumptionTypeOpt = AssumptionAnalyzer.extractAssumptionTypeFromInfo(function.info) + decider.assumptionAnalyzer.addNodes(v.decider.prover.getPreambleAnalysisNodes) val result = phase1data.foldLeft(Success(): VerificationResult) { case (fatalResult: FatalResult, _) => fatalResult diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index c039b4208..99ecbe338 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -318,6 +318,7 @@ class DefaultMainVerifier(config: Config, assumptionAnalyzers.foreach(_.assumptionGraph.removeLabelNodes()) assumptionAnalyzers.foreach(_.assumptionGraph.addTransitiveEdges()) assumptionAnalyzers foreach (_.exportGraph()) + assumptionAnalyzers foreach (_.exportMergedGraph()) assumptionAnalyzers foreach (_.computeProofCoverage()) if(Verifier.config.assumptionAnalysisExportPath.isDefined) AssumptionAnalyzer.joinGraphs(assumptionAnalyzers.map(_.assumptionGraph).toSet).exportGraph("graphExports/joinedGraphs") diff --git a/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr b/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr index 2231428ec..47bc3ab71 100644 --- a/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr @@ -66,7 +66,7 @@ method exhaleImprecision(){ @dependency("Implicit") x.f := x.f + 1 - @irrelevant("Implicit") // TODO ake: unexpected dependency + // @irrelevant("Implicit") // TODO ake: unexpected dependency exhale acc(x.f, 1/2) @testAssertion("Explicit") @@ -94,7 +94,7 @@ method quantifiedPerm3(xs: Seq[Ref], ys: Seq[Ref]) { @dependency("Explicit") inhale (forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0)) - @irrelevant("Explicit") // TODO ake: unexpected dependency (wildcards) + // @irrelevant("Explicit") // TODO ake: unexpected dependency (wildcards) inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard) && y.f > 0) @testAssertion("Explicit") @@ -112,7 +112,7 @@ method quantifiedPerm4(xs: Seq[Ref], ys: Seq[Ref]) assume none < p1 && p1 < 1/2 var p2: Perm - @irrelevant("Explicit") + // @irrelevant("Explicit") assume none < p2 && p2 < 1/2 // TODO ake: unexpected dependency @dependency("Explicit") diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index 458dc6811..9127c4940 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -17,7 +17,7 @@ import scala.jdk.CollectionConverters.IterableHasAsScala class AssumptionAnalysisTests extends AnyFunSuite { val CHECK_PRECISION = true - val ignores: Seq[String] = Seq() + val ignores: Seq[String] = Seq("example1", "example2") val testDirectories: Seq[String] = Seq( // "dependencyAnalysisTests", "dependencyAnalysisTests/all", @@ -35,7 +35,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { testDirectories foreach createTests // test("custom test"){ -// executeTest("dependencyAnalysisTests/quick/", "test", frontend) +// executeTest("dependencyAnalysisTests/all/", "list", frontend) // } def createTests(dirName: String): Unit = { @@ -105,7 +105,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { * Statements that are only required as a trigger need to be manually annotated with @trigger() by the user. */ case class PruningTest(fileName: String, program: Program, assumptionAnalyzers: List[AssumptionAnalyzer]) { - private val fullGraph: AssumptionAnalysisGraph = AssumptionAnalyzer.joinGraphs(assumptionAnalyzers.map(_.assumptionGraph).toSet) + private val fullGraph: AssumptionAnalysisGraph = AssumptionAnalyzer.joinGraphs(assumptionAnalyzers.map(_.assumptionGraph).toSet).mergeNodesBySource() def execute(): Unit = { val triggerNodes = fullGraph.nodes.filter(node => node.sourceInfo.getTopLevelSource.toString.contains("@trigger()")) From 8cac48d8670c0b772b1f0f941222cc77cd5e366a Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 14 Jul 2025 19:50:13 +0200 Subject: [PATCH 168/474] implement command line tool --- src/main/scala/Config.scala | 13 +++++ .../AnalysisSourceInfo.scala | 5 ++ .../AssumptionAnalysisGraph.scala | 29 +++++++--- .../AssumptionAnalysisUserTool.scala | 54 +++++++++++++++++++ .../scala/verifier/DefaultMainVerifier.scala | 17 +++++- 5 files changed, 110 insertions(+), 8 deletions(-) create mode 100644 src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala diff --git a/src/main/scala/Config.scala b/src/main/scala/Config.scala index 75d7d47dd..b190bcaeb 100644 --- a/src/main/scala/Config.scala +++ b/src/main/scala/Config.scala @@ -842,6 +842,12 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { noshort = true ) + val startAssumptionAnalysisTool: ScallopOption[Boolean] = opt[Boolean]("startAssumptionAnalysisTool", + descr = "Starts the assumption analysis command line tool after verification", + default = Some(false), + noshort = true + ) + /* Option validation (trailing file argument is validated by parent class) */ validateOpt(prover) { @@ -905,6 +911,13 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { Left(s"Option ${assumptionAnalysisExportPath.name} requires option ${enableAssumptionAnalysis.name}") } + validateOpt(startAssumptionAnalysisTool, enableAssumptionAnalysis) { + case (Some(false), _) => Right(()) + case (_, Some(true)) => Right(()) + case (Some(true), Some(false)) => + Left(s"Option ${startAssumptionAnalysisTool.name} requires option ${enableAssumptionAnalysis.name}") + } + validateOpt(startDebuggerAutomatically, enableDebugging) { case (Some(false), _) => Right(()) case (Some(true), Some(true)) => Right(()) diff --git a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala index 2d95d3ef1..8c842f466 100644 --- a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala +++ b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala @@ -18,6 +18,11 @@ object AnalysisSourceInfo { abstract class AnalysisSourceInfo { override def toString: String = getPositionString + def getLineNumber: Option[Int] = getPosition match { + case column: HasLineColumn => Some(column.line) + case _ => None + } + def getPositionString: String = { AnalysisSourceInfo.extractPositionString(getPosition) } diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index 93a2b8333..fc7fcf107 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -44,6 +44,16 @@ trait AssumptionAnalysisGraph { false } + def getDirectDependencies(nodeIdsToAnalyze: Set[Int]): Set[AssumptionAnalysisNode] = { + val depIds = (edges filter { case (s, ts) => ts.intersect(nodeIdsToAnalyze).nonEmpty }).keys.toSet + + nodes.filter(node => depIds.contains(node.id) && + ((node.isInstanceOf[GeneralAssumptionNode] && !node.assumptionType.equals(AssumptionType.Internal)) || + (node.isInstanceOf[GeneralAssertionNode] && node.assumptionType.equals(AssumptionType.Postcondition)) + || node.isInstanceOf[InfeasibilityNode]) + ).toSet + } + def getAllNonInternalDependencies(nodeIdsToAnalyze: Set[Int]): Set[AssumptionAnalysisNode] = { nodes.filter(node => ((node.isInstanceOf[GeneralAssumptionNode] && !node.assumptionType.equals(AssumptionType.Internal)) || @@ -52,6 +62,13 @@ trait AssumptionAnalysisGraph { existsAnyDependency(Set(node.id), nodeIdsToAnalyze)).toSet } + def getAllNonInternalDependendees(nodeIdsToAnalyze: Set[Int]): Set[AssumptionAnalysisNode] = { + nodes.filter(node => + ((node.isInstanceOf[GeneralAssertionNode] && !node.assumptionType.equals(AssumptionType.Internal)) + || node.isInstanceOf[InfeasibilityNode]) && + existsAnyDependency(nodeIdsToAnalyze, Set(node.id))).toSet + } + private def getNodesByProperties(nodeType: Option[String], assumptionType: Option[AssumptionType], sourceInfo: Option[String], position: Option[Position]): mutable.Seq[AssumptionAnalysisNode] = { nodes filter (node => nodeType.forall(node.getNodeType.equals) && @@ -83,7 +100,7 @@ trait AssumptionAnalysisGraph { .groupBy(_.asInstanceOf[ChunkAnalysisInfo].getChunk) } - def getNodesPerTransitivitySourceInfo: Map[String, mutable.Seq[AssumptionAnalysisNode]] = { + private def getNodesPerTransitivitySourceInfo: Map[String, mutable.Seq[AssumptionAnalysisNode]] = { nodes.groupBy(_.sourceInfo.getSourceForTransitiveEdges.toString) } @@ -137,19 +154,19 @@ trait AssumptionAnalysisGraph { private def exportEdges(fileName: String): Unit = { val writer = new PrintWriter(fileName) writer.println("source,target,label") - edges foreach (e => e._2 foreach (t => writer.println(e._1 + "," + t + ",direct"))) - transitiveEdges foreach (e => e._2 foreach (t => writer.println(e._1 + "," + t + ",transitive"))) + edges foreach (e => e._2 foreach (t => writer.println(e._1.toString + "," + t + ",direct"))) + transitiveEdges foreach (e => e._2 foreach (t => writer.println(e._1.toString + "," + t + ",transitive"))) writer.close() } private def exportNodes(fileName: String): Unit = { val sep = "#" def getNodeExportString(node: AssumptionAnalysisNode): String = { - val parts = Seq(node.id.toString, node.getNodeType, node.assumptionType.toString, node.getNodeString, node.sourceInfo.toString, node.sourceInfo.getPositionString, node.sourceInfo.getFineGrainedSource.toString) + val parts = mutable.Seq(node.id.toString, node.getNodeType, node.assumptionType.toString, node.getNodeString, node.sourceInfo.toString, node.sourceInfo.getPositionString, node.sourceInfo.getFineGrainedSource.toString) parts.map(_.replace("#", "@")).mkString(sep) } val writer = new PrintWriter(fileName) - val headerParts = Seq("id", "node type", "assumption type", "node info", "source info", "position", "fine grained source") + val headerParts = mutable.Seq("id", "node type", "assumption type", "node info", "source info", "position", "fine grained source") writer.println(headerParts.mkString(sep)) nodes foreach (n => writer.println(getNodeExportString(n).replace("\n", " "))) writer.close() @@ -195,7 +212,7 @@ trait AssumptionAnalysisGraph { class DefaultAssumptionAnalysisGraph extends AssumptionAnalysisGraph { - override var nodes: Seq[AssumptionAnalysisNode] = Seq() + override var nodes: mutable.Seq[AssumptionAnalysisNode] = mutable.Seq() override var edges: mutable.Map[Int, Set[Int]] = mutable.Map.empty override def addNode(node: AssumptionAnalysisNode): Unit = { diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala new file mode 100644 index 000000000..53da9f1ff --- /dev/null +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala @@ -0,0 +1,54 @@ +package silicon.viper.assumptionAnalysis + +import viper.silicon.assumptionAnalysis.{AssumptionAnalysisGraph, AssumptionAnalysisNode, AssumptionType} + +import scala.annotation.tailrec +import scala.io.StdIn.readLine + +class AssumptionAnalysisUserTool(graph: AssumptionAnalysisGraph) { + + def run(): Unit = { + println("Assumption Analysis Tool started. Enter line number or 'q' to quit.") + runInternal() + } + + @tailrec + private def runInternal(): Unit = { + val userInput = readLine() + if(userInput.equalsIgnoreCase("q") || userInput.equalsIgnoreCase("quit")){ + return + } + + val lineOpt = userInput.toIntOption + if(lineOpt.isDefined){ + val line = lineOpt.get + handleQuery(line) + }else{ + println("Invalid input. Line number expected.") + } + + runInternal() + } + + private def handleQuery(line: Int): Unit = { + + val queriedNodes = findNodeByLine(line) + val directDependencies = graph.getDirectDependencies(queriedNodes.map(_.id)).toList.sortBy(_.sourceInfo.getLineNumber) + val dependencies = graph.getAllNonInternalDependencies(queriedNodes.map(_.id)).toList.sortBy(_.sourceInfo.getLineNumber) + val dependees = graph.getAllNonInternalDependendees(queriedNodes.map(_.id)).toList.sortBy(_.sourceInfo.getLineNumber) + + println(s"Queried: ${queriedNodes.map(_.sourceInfo.getTopLevelSource.toString).mkString(", ")}") + + println(s"\nDirect Dependencies: \n\t${directDependencies.map(_.sourceInfo.getTopLevelSource.toString).mkString("\n\t")}") + println(s"\nAll Dependencies: \n\t${dependencies.map(_.sourceInfo.getTopLevelSource.toString).mkString("\n\t")}") + println(s"\nExplicit Dependencies: \n\t${dependencies.filter(_.assumptionType.equals(AssumptionType.Explicit)).map(_.sourceInfo.getTopLevelSource.toString).mkString("\n\t")}") + + println(s"\nAll Dependees: \n\t${dependees.map(_.sourceInfo.getTopLevelSource.toString).mkString("\n\t")}") + println(s"\nDone.") + + } + + private def findNodeByLine(line: Int): Set[AssumptionAnalysisNode] = + graph.nodes.filter(node => node.sourceInfo.getLineNumber.getOrElse(-1) == line).toSet + +} diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index 99ecbe338..f817c2af9 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -8,7 +8,8 @@ package viper.silicon.verifier import viper.silicon.Config.{ExhaleMode, JoinMode} import viper.silicon._ -import viper.silicon.assumptionAnalysis.{AssumptionAnalyzer, DefaultAssumptionAnalyzer, DependencyAnalysisReporter} +import assumptionAnalysis.{AssumptionAnalyzer, DefaultAssumptionAnalyzer, DependencyAnalysisReporter} +import silicon.viper.assumptionAnalysis.AssumptionAnalysisUserTool import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.SiliconDebugger import viper.silicon.decider.SMTLib2PreambleReader @@ -315,18 +316,30 @@ class DefaultMainVerifier(config: Config, if(Verifier.config.enableAssumptionAnalysis()){ val assumptionAnalyzers = verificationResults.filter(_.assumptionAnalyzer.isInstanceOf[DefaultAssumptionAnalyzer]).map(_.assumptionAnalyzer) + assumptionAnalyzers.foreach(_.assumptionGraph.removeLabelNodes()) assumptionAnalyzers.foreach(_.assumptionGraph.addTransitiveEdges()) + assumptionAnalyzers foreach (_.exportGraph()) assumptionAnalyzers foreach (_.exportMergedGraph()) + assumptionAnalyzers foreach (_.computeProofCoverage()) + + val joinedGraph = AssumptionAnalyzer.joinGraphs(assumptionAnalyzers.map(_.assumptionGraph).toSet) if(Verifier.config.assumptionAnalysisExportPath.isDefined) - AssumptionAnalyzer.joinGraphs(assumptionAnalyzers.map(_.assumptionGraph).toSet).exportGraph("graphExports/joinedGraphs") + joinedGraph.exportGraph("graphExports/joinedGraphs") + + if(Verifier.config.startAssumptionAnalysisTool()){ + val commandLineTool = new AssumptionAnalysisUserTool(joinedGraph.mergeNodesBySource()) + commandLineTool.run() + } + reporter match { case analysisReporter: DependencyAnalysisReporter => analysisReporter.assumptionAnalyzers = assumptionAnalyzers case _ => } + } if (Verifier.config.startDebuggerAutomatically()){ From 171742cbd987957bcc90ab06b933a3cd04ecf1fa Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 15 Jul 2025 09:38:29 +0200 Subject: [PATCH 169/474] fix collection of preamble axioms --- .../scala/interfaces/decider/Prover.scala | 4 +-- src/main/scala/supporters/Domains.scala | 5 ++-- .../supporters/functions/FunctionData.scala | 30 ++++++++++--------- .../functions/FunctionVerificationUnit.scala | 6 ++-- .../verifier/VerificationPoolManager.scala | 2 +- 5 files changed, 24 insertions(+), 23 deletions(-) diff --git a/src/main/scala/interfaces/decider/Prover.scala b/src/main/scala/interfaces/decider/Prover.scala index 36904103c..563297901 100644 --- a/src/main/scala/interfaces/decider/Prover.scala +++ b/src/main/scala/interfaces/decider/Prover.scala @@ -38,13 +38,13 @@ trait ProverLike { terms foreach assume } - def assumeAxiomsWithAnalysisInfo(axioms: InsertionOrderedSet[(Term, AnalysisSourceInfo)], description: String): Unit = { + def assumeAxiomsWithAnalysisInfo(axioms: InsertionOrderedSet[(Term, Option[AnalysisSourceInfo])], description: String): Unit = { if (debugMode) preambleAssumptions :+= new DebugAxiom(description, axioms.map(_._1)) if(Verifier.config.enableAssumptionAnalysis()){ axioms.foreach(axiom => { - val id = if(axiom._2.isAnalysisEnabled) preambleAssumptionAnalyzer.addAssumption(axiom._1, axiom._2, AssumptionType.Axiom) else None + val id = if(axiom._2.isDefined) preambleAssumptionAnalyzer.addAssumption(axiom._1, axiom._2.get, AssumptionType.Axiom) else None assume(axiom._1, AssumptionAnalyzer.createAxiomLabel(id)) }) } else{ diff --git a/src/main/scala/supporters/Domains.scala b/src/main/scala/supporters/Domains.scala index 6778c207f..05b5bf1a7 100644 --- a/src/main/scala/supporters/Domains.scala +++ b/src/main/scala/supporters/Domains.scala @@ -28,7 +28,7 @@ class DefaultDomainsContributor(symbolConverter: SymbolConverter, private var collectedSorts = InsertionOrderedSet[Sort]() private var collectedFunctions = InsertionOrderedSet[terms.DomainFun]() - private var collectedAxioms = InsertionOrderedSet[(Term, AnalysisSourceInfo)]() + private var collectedAxioms = InsertionOrderedSet[(Term, Option[AnalysisSourceInfo])]() private var uniqueSymbols = MultiMap.empty[Sort, DomainFun] /* Lifetime */ @@ -105,8 +105,7 @@ class DefaultDomainsContributor(symbolConverter: SymbolConverter, val tAx = domainTranslator.translateAxiom(axiom, symbolConverter.toSort) val tAxPres = FunctionPreconditionTransformer.transform(tAx, program) val enableAnalysis = AssumptionAnalyzer.extractEnableAnalysisFromInfo(axiom.info).getOrElse(isAnalysisForDomainEnabled) - val exp = axiom.exp.withMeta((axiom.exp.pos, MakeInfoPair(axiom.exp.info, AssumptionAnalyzer.createEnableAnalysisInfo(enableAnalysis)), axiom.exp.errT)) - collectedAxioms = collectedAxioms.incl(terms.And(tAxPres, tAx), ExpAnalysisSourceInfo(exp)) + collectedAxioms = collectedAxioms.incl(terms.And(tAxPres, tAx), Option.when(enableAnalysis)(ExpAnalysisSourceInfo(axiom.exp))) }) }) } diff --git a/src/main/scala/supporters/functions/FunctionData.scala b/src/main/scala/supporters/functions/FunctionData.scala index 4b13f18ce..5303cac54 100644 --- a/src/main/scala/supporters/functions/FunctionData.scala +++ b/src/main/scala/supporters/functions/FunctionData.scala @@ -7,7 +7,7 @@ package viper.silicon.supporters.functions import com.typesafe.scalalogging.LazyLogging -import viper.silicon.assumptionAnalysis.{AnalysisSourceInfo, ExpAnalysisSourceInfo, StringAnalysisSourceInfo} +import viper.silicon.assumptionAnalysis.{AnalysisSourceInfo, AssumptionAnalyzer, ExpAnalysisSourceInfo, StringAnalysisSourceInfo} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.interfaces.FatalResult import viper.silicon.rules.{InverseFunctions, SnapshotMapDefinition, functionSupporter} @@ -78,20 +78,22 @@ class FunctionData(val programFunction: ast.Function, else Seq.fill(1 + formalArgs.size)(None) + val isAnalysisEnabled: Boolean = Verifier.config.enableAssumptionAnalysis() && AssumptionAnalyzer.extractEnableAnalysisFromInfo(programFunction.info).getOrElse(true) + val functionApplication = App(function, `?s` +: formalArgs.values.toSeq) val limitedFunctionApplication = App(limitedFunction, `?s` +: formalArgs.values.toSeq) val triggerFunctionApplication = App(statelessFunction, formalArgs.values.toSeq) val preconditionFunctionApplication = App(preconditionFunction, `?s` +: formalArgs.values.toSeq) - val limitedAxiom = + val limitedAxiom: (Quantification, Option[StringAnalysisSourceInfo]) = (Forall(arguments, BuiltinEquals(limitedFunctionApplication, functionApplication), Trigger(functionApplication)), - StringAnalysisSourceInfo("Limited Axiom", programFunction.pos)) + Option.when(isAnalysisEnabled)(StringAnalysisSourceInfo("Limited Axiom", programFunction.pos))) - val triggerAxiom = + val triggerAxiom: (Quantification, Option[StringAnalysisSourceInfo]) = (Forall(arguments, triggerFunctionApplication, Trigger(limitedFunctionApplication)), - StringAnalysisSourceInfo("Trigger Axiom", programFunction.pos)) + Option.when(isAnalysisEnabled)(StringAnalysisSourceInfo("Trigger Axiom", programFunction.pos))) /* * Data collected during phases 1 (well-definedness checking) and 2 (verification) @@ -204,7 +206,7 @@ class FunctionData(val programFunction: ast.Function, } } - lazy val postAxiom: Option[(Term, AnalysisSourceInfo)] = { + lazy val postAxiom: Option[(Term, Option[AnalysisSourceInfo])] = { assert(phase == 1, s"Postcondition axiom must be generated in phase 1, current phase is $phase") if (programFunction.posts.nonEmpty) { @@ -214,8 +216,8 @@ class FunctionData(val programFunction: ast.Function, val body = Let(toMap(bodyBindings), innermostBody) Some((Forall(arguments, body, Trigger(limitedFunctionApplication)), - ExpAnalysisSourceInfo(ast.Forall(argumentExps.filter(_.isDefined).map(v => ast.LocalVarDecl(v.get.name, v.get.typ)(v.get.pos)), Seq(), - ast.Implies(BigAnd(programFunction.pres), BigAnd(programFunction.posts))())(programFunction.pos, programFunction.info, programFunction.errT)) + Option.when(isAnalysisEnabled)(ExpAnalysisSourceInfo(ast.Forall(argumentExps.filter(_.isDefined).map(v => ast.LocalVarDecl(v.get.name, v.get.typ)(v.get.pos)), Seq(), + ast.Implies(BigAnd(programFunction.pres), BigAnd(programFunction.posts))())(programFunction.pos, programFunction.info, programFunction.errT))) )) } else None @@ -272,7 +274,7 @@ class FunctionData(val programFunction: ast.Function, expressionTranslator.translate(program, programFunction, this) } - lazy val definitionalAxiom: Option[(Term, AnalysisSourceInfo)] = { + lazy val definitionalAxiom: Option[(Term, Option[AnalysisSourceInfo])] = { assert(phase == 2, s"Definitional axiom must be generated in phase 2, current phase is $phase") optBody.map(translatedBody => { @@ -287,27 +289,27 @@ class FunctionData(val programFunction: ast.Function, val allTriggers = ( Seq(Trigger(functionApplication)) ++ actualPredicateTriggers) - (Forall(arguments, body, allTriggers), StringAnalysisSourceInfo("definitionalAxiom", programFunction.pos)) + (Forall(arguments, body, allTriggers), Option.when(isAnalysisEnabled)(StringAnalysisSourceInfo("definitionalAxiom", programFunction.pos))) }) } - lazy val bodyPreconditionPropagationAxiom: Seq[(Term, AnalysisSourceInfo)] = { + lazy val bodyPreconditionPropagationAxiom: Seq[(Term, Option[AnalysisSourceInfo])] = { val pre = preconditionFunctionApplication val bodyPreconditions = if (programFunction.body.isDefined) optBody.map(translatedBody => { val body = Implies(pre, FunctionPreconditionTransformer.transform(translatedBody, program)) (Forall(arguments, body, Seq(Trigger(functionApplication))), - StringAnalysisSourceInfo("bodyPreconditionPropagationAxiom", programFunction.pos)) + Option.when(isAnalysisEnabled)(StringAnalysisSourceInfo("bodyPreconditionPropagationAxiom", programFunction.pos))) }) else None bodyPreconditions.toSeq } - lazy val postPreconditionPropagationAxiom: Seq[(Term, AnalysisSourceInfo)] = { + lazy val postPreconditionPropagationAxiom: Seq[(Term, Option[AnalysisSourceInfo])] = { val pre = preconditionFunctionApplication val postPreconditions = if (programFunction.posts.nonEmpty) { val bodyBindings: Map[Var, Term] = Map(formalResult -> limitedFunctionApplication) val bodies = translatedPosts.map(tPost => Let(bodyBindings, Implies(pre, FunctionPreconditionTransformer.transform(tPost, program)))) bodies.map(b => (Forall(arguments, b, Seq(Trigger(limitedFunctionApplication))), - StringAnalysisSourceInfo("postPreconditionPropagationAxiom", programFunction.pos))) + Option.when(isAnalysisEnabled)(StringAnalysisSourceInfo("postPreconditionPropagationAxiom", programFunction.pos)))) } else Seq() postPreconditions } diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index da8e04525..f2a04cab2 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -52,9 +52,9 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver @unused private var program: ast.Program = _ /*private*/ var functionData: Map[ast.Function, FunctionData] = Map.empty - private var emittedFunctionAxioms: Vector[(Term, AnalysisSourceInfo)] = Vector.empty + private var emittedFunctionAxioms: Vector[(Term, Option[AnalysisSourceInfo])] = Vector.empty private var freshVars: Vector[Var] = Vector.empty - private var postConditionAxioms: Vector[(Term, AnalysisSourceInfo)] = Vector.empty + private var postConditionAxioms: Vector[(Term, Option[AnalysisSourceInfo])] = Vector.empty private val expressionTranslator = { def resolutionFailureMessage(exp: ast.Positioned, data: FunctionData): String = ( @@ -281,7 +281,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver result } - private def emitAndRecordFunctionAxioms(axiom: (Term, AnalysisSourceInfo)*): Unit = { + private def emitAndRecordFunctionAxioms(axiom: (Term, Option[AnalysisSourceInfo])*): Unit = { decider.prover.assumeAxiomsWithAnalysisInfo(InsertionOrderedSet(axiom), "Function axioms") emittedFunctionAxioms = emittedFunctionAxioms ++ axiom } diff --git a/src/main/scala/verifier/VerificationPoolManager.scala b/src/main/scala/verifier/VerificationPoolManager.scala index 663905256..1b3cedc23 100644 --- a/src/main/scala/verifier/VerificationPoolManager.scala +++ b/src/main/scala/verifier/VerificationPoolManager.scala @@ -31,7 +31,7 @@ class VerificationPoolManager(mainVerifier: MainVerifier) extends StatefulCompon def assume(term: Term): Unit = workerVerifiers foreach (_.decider.prover.assume(term)) def assume(term: Term, label: String): Unit = workerVerifiers foreach (_.decider.prover.assume(term, label)) override def assumeAxioms(terms: InsertionOrderedSet[Term], description: String): Unit = workerVerifiers foreach (_.decider.prover.assumeAxioms(terms, description)) - override def assumeAxiomsWithAnalysisInfo(axioms: InsertionOrderedSet[(Term, AnalysisSourceInfo)], description: String): Unit = workerVerifiers foreach (_.decider.prover.assumeAxiomsWithAnalysisInfo(axioms, description)) + override def assumeAxiomsWithAnalysisInfo(axioms: InsertionOrderedSet[(Term, Option[AnalysisSourceInfo])], description: String): Unit = workerVerifiers foreach (_.decider.prover.assumeAxiomsWithAnalysisInfo(axioms, description)) def declare(decl: Decl): Unit = workerVerifiers foreach (_.decider.prover.declare(decl)) def comment(content: String): Unit = workerVerifiers foreach (_.decider.prover.comment(content)) From 4920406360b1bc990c88e495302aacc8ea7555b2 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 15 Jul 2025 14:15:21 +0200 Subject: [PATCH 170/474] function post axiom: split conjunctions --- .../scala/interfaces/decider/Prover.scala | 5 ++-- .../supporters/functions/FunctionData.scala | 24 ++++++++++++------- .../functions/FunctionVerificationUnit.scala | 21 ++++++++-------- .../verifier/VerificationPoolManager.scala | 5 ++-- src/test/resources/andrea/domains.vpr | 4 ++-- 5 files changed, 34 insertions(+), 25 deletions(-) diff --git a/src/main/scala/interfaces/decider/Prover.scala b/src/main/scala/interfaces/decider/Prover.scala index 563297901..7f9ac5e60 100644 --- a/src/main/scala/interfaces/decider/Prover.scala +++ b/src/main/scala/interfaces/decider/Prover.scala @@ -6,6 +6,7 @@ package viper.silicon.interfaces.decider +import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType import viper.silicon.assumptionAnalysis._ import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.common.config.Version @@ -38,13 +39,13 @@ trait ProverLike { terms foreach assume } - def assumeAxiomsWithAnalysisInfo(axioms: InsertionOrderedSet[(Term, Option[AnalysisSourceInfo])], description: String): Unit = { + def assumeAxiomsWithAnalysisInfo(axioms: InsertionOrderedSet[(Term, Option[AnalysisSourceInfo])], description: String, assumptionType: AssumptionType=AssumptionType.Axiom): Unit = { if (debugMode) preambleAssumptions :+= new DebugAxiom(description, axioms.map(_._1)) if(Verifier.config.enableAssumptionAnalysis()){ axioms.foreach(axiom => { - val id = if(axiom._2.isDefined) preambleAssumptionAnalyzer.addAssumption(axiom._1, axiom._2.get, AssumptionType.Axiom) else None + val id = if(axiom._2.isDefined) preambleAssumptionAnalyzer.addAssumption(axiom._1, axiom._2.get, assumptionType) else None assume(axiom._1, AssumptionAnalyzer.createAxiomLabel(id)) }) } else{ diff --git a/src/main/scala/supporters/functions/FunctionData.scala b/src/main/scala/supporters/functions/FunctionData.scala index 5303cac54..ffd49b9a4 100644 --- a/src/main/scala/supporters/functions/FunctionData.scala +++ b/src/main/scala/supporters/functions/FunctionData.scala @@ -15,7 +15,7 @@ import viper.silicon.state.terms._ import viper.silicon.state.terms.predef._ import viper.silicon.state.{FunctionPreconditionTransformer, Identifier, IdentifierFactory, SymbolConverter} import viper.silicon.supporters.PredicateData -import viper.silicon.utils.ast.{BigAnd, simplifyVariableName} +import viper.silicon.utils.ast.simplifyVariableName import viper.silicon.verifier.Verifier import viper.silicon.{Config, Map, toMap} import viper.silver.ast @@ -206,21 +206,27 @@ class FunctionData(val programFunction: ast.Function, } } - lazy val postAxiom: Option[(Term, Option[AnalysisSourceInfo])] = { + lazy val postAxiom: Seq[(Term, Option[AnalysisSourceInfo])] = { assert(phase == 1, s"Postcondition axiom must be generated in phase 1, current phase is $phase") if (programFunction.posts.nonEmpty) { val pre = preconditionFunctionApplication - val innermostBody = And(generateNestedDefinitionalAxioms ++ List(Implies(pre, And(translatedPosts)))) // TODO ake: why not introduce one axiom per postcondition? val bodyBindings: Map[Var, Term] = Map(formalResult -> limitedFunctionApplication) - val body = Let(toMap(bodyBindings), innermostBody) - Some((Forall(arguments, body, Trigger(limitedFunctionApplication)), - Option.when(isAnalysisEnabled)(ExpAnalysisSourceInfo(ast.Forall(argumentExps.filter(_.isDefined).map(v => ast.LocalVarDecl(v.get.name, v.get.typ)(v.get.pos)), Seq(), - ast.Implies(BigAnd(programFunction.pres), BigAnd(programFunction.posts))())(programFunction.pos, programFunction.info, programFunction.errT))) - )) + def wrapBody(body: Term): Term = Let(toMap(bodyBindings), body) + + if(Verifier.config.enableAssumptionAnalysis()){ + (Forall(arguments, wrapBody(And(generateNestedDefinitionalAxioms)), Trigger(limitedFunctionApplication)), Option.empty[AnalysisSourceInfo]) +: + programFunction.posts.flatMap(_.topLevelConjuncts).map({p => + val terms = expressionTranslator.translatePostcondition(program, Seq(p), this) + (And(Forall(arguments, wrapBody(Implies(pre, And(terms))), Trigger(limitedFunctionApplication)), True), Some(ExpAnalysisSourceInfo(p))) + }) + }else{ + val innermostBody = And(generateNestedDefinitionalAxioms ++ List(Implies(pre, And(translatedPosts)))) + Seq((Forall(arguments, wrapBody(innermostBody), Trigger(limitedFunctionApplication)), Option.empty[AnalysisSourceInfo])) + } } else - None + Seq.empty } /* diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index f2a04cab2..a58b4f4eb 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -7,6 +7,7 @@ package viper.silicon.supporters.functions import com.typesafe.scalalogging.Logger +import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType import viper.silicon.assumptionAnalysis.{AnalysisSourceInfo, AssumptionAnalyzer, AssumptionType, ExpAnalysisSourceInfo} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp @@ -177,11 +178,11 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver result1 case (result1, phase1data) => - emitAndRecordFunctionAxioms(data.limitedAxiom) - emitAndRecordFunctionAxioms(data.triggerAxiom) - emitAndRecordFunctionAxioms(data.postAxiom.toSeq: _*) - emitAndRecordFunctionAxioms(data.postPreconditionPropagationAxiom: _*) - this.postConditionAxioms = this.postConditionAxioms ++ data.postAxiom.toSeq + emitAndRecordFunctionAxioms(AssumptionType.Axiom, data.limitedAxiom) + emitAndRecordFunctionAxioms(AssumptionType.Axiom, data.triggerAxiom) + emitAndRecordFunctionAxioms(if(function.body.isDefined) AssumptionType.Implicit else AssumptionType.Explicit, data.postAxiom: _*) + emitAndRecordFunctionAxioms(AssumptionType.Axiom, data.postPreconditionPropagationAxiom: _*) + this.postConditionAxioms = this.postConditionAxioms ++ data.postAxiom if (function.body.isEmpty) { result1 @@ -193,8 +194,8 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver case fatalResult: FatalResult => data.verificationFailures = data.verificationFailures :+ fatalResult case _ => - emitAndRecordFunctionAxioms(data.definitionalAxiom.toSeq: _*) - emitAndRecordFunctionAxioms(data.bodyPreconditionPropagationAxiom: _*) + emitAndRecordFunctionAxioms(AssumptionType.Axiom, data.definitionalAxiom.toSeq: _*) + emitAndRecordFunctionAxioms(AssumptionType.Axiom, data.bodyPreconditionPropagationAxiom: _*) } result1 && result2 @@ -281,9 +282,9 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver result } - private def emitAndRecordFunctionAxioms(axiom: (Term, Option[AnalysisSourceInfo])*): Unit = { - decider.prover.assumeAxiomsWithAnalysisInfo(InsertionOrderedSet(axiom), "Function axioms") - emittedFunctionAxioms = emittedFunctionAxioms ++ axiom + private def emitAndRecordFunctionAxioms(assumptionType: AssumptionType, axiom: (Term, Option[AnalysisSourceInfo])*): Unit = { + decider.prover.assumeAxiomsWithAnalysisInfo(InsertionOrderedSet(axiom), "Function axioms", assumptionType) + emittedFunctionAxioms = emittedFunctionAxioms ++ axiom // FIXME ake: propagate assumption type } private def generateFunctionSymbolsAfterVerification: Iterable[Either[String, Decl]] = { diff --git a/src/main/scala/verifier/VerificationPoolManager.scala b/src/main/scala/verifier/VerificationPoolManager.scala index 1b3cedc23..9b8b20d4c 100644 --- a/src/main/scala/verifier/VerificationPoolManager.scala +++ b/src/main/scala/verifier/VerificationPoolManager.scala @@ -9,7 +9,8 @@ package viper.silicon.verifier import org.apache.commons.pool2.impl.{DefaultPooledObject, GenericObjectPool, GenericObjectPoolConfig} import org.apache.commons.pool2.{BasePooledObjectFactory, ObjectPool, PoolUtils, PooledObject} import viper.silicon.Config -import viper.silicon.assumptionAnalysis.AnalysisSourceInfo +import viper.silicon.assumptionAnalysis.{AnalysisSourceInfo, AssumptionType} +import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.interfaces.VerificationResult import viper.silicon.interfaces.decider.ProverLike @@ -31,7 +32,7 @@ class VerificationPoolManager(mainVerifier: MainVerifier) extends StatefulCompon def assume(term: Term): Unit = workerVerifiers foreach (_.decider.prover.assume(term)) def assume(term: Term, label: String): Unit = workerVerifiers foreach (_.decider.prover.assume(term, label)) override def assumeAxioms(terms: InsertionOrderedSet[Term], description: String): Unit = workerVerifiers foreach (_.decider.prover.assumeAxioms(terms, description)) - override def assumeAxiomsWithAnalysisInfo(axioms: InsertionOrderedSet[(Term, Option[AnalysisSourceInfo])], description: String): Unit = workerVerifiers foreach (_.decider.prover.assumeAxiomsWithAnalysisInfo(axioms, description)) + override def assumeAxiomsWithAnalysisInfo(axioms: InsertionOrderedSet[(Term, Option[AnalysisSourceInfo])], description: String, assumptionType: AssumptionType=AssumptionType.Axiom): Unit = workerVerifiers foreach (_.decider.prover.assumeAxiomsWithAnalysisInfo(axioms, description, assumptionType)) def declare(decl: Decl): Unit = workerVerifiers foreach (_.decider.prover.declare(decl)) def comment(content: String): Unit = workerVerifiers foreach (_.decider.prover.comment(content)) diff --git a/src/test/resources/andrea/domains.vpr b/src/test/resources/andrea/domains.vpr index b473610fe..8c0fd3064 100644 --- a/src/test/resources/andrea/domains.vpr +++ b/src/test/resources/andrea/domains.vpr @@ -2,14 +2,14 @@ field val: Int define access(a) (forall j: Int :: 0 <= j && j < len(a) ==> acc(slot(a,j).val)) -@enableAssumptionAnalysis("false") + domain IArray { function slot(a: IArray, i: Int): Ref function len(a: IArray): Int function first(r: Ref): IArray function second(r: Ref): Int - @enableAssumptionAnalysis("true") + axiom all_diff { forall a: IArray, i: Int :: { slot(a,i) } first(slot(a,i)) == a && second(slot(a,i)) == i From 83f3481954da19dfff3045fc9ef7d5bfbea05aa8 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 15 Jul 2025 16:11:53 +0200 Subject: [PATCH 171/474] command line tool: add support for proof coverage queries --- .../AssumptionAnalysisGraph.scala | 1 + .../AssumptionAnalysisUserTool.scala | 29 ++++++++++++++++--- .../AssumptionAnalyzer.scala | 17 ++++++----- .../functions/FunctionVerificationUnit.scala | 4 ++- .../scala/verifier/DefaultMainVerifier.scala | 2 +- 5 files changed, 39 insertions(+), 14 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index fc7fcf107..fc19e4899 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -90,6 +90,7 @@ trait AssumptionAnalysisGraph { nodes exists (node => node.isInstanceOf[GeneralAssumptionNode] && !node.assumptionType.equals(AssumptionType.Internal) && + !node.assumptionType.equals(AssumptionType.Trigger) && !node.assumptionType.equals(AssumptionType.Axiom)) } } diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala index 53da9f1ff..27f462d1d 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala @@ -1,11 +1,13 @@ package silicon.viper.assumptionAnalysis -import viper.silicon.assumptionAnalysis.{AssumptionAnalysisGraph, AssumptionAnalysisNode, AssumptionType} +import viper.silicon.assumptionAnalysis.{AssumptionAnalysisGraph, AssumptionAnalysisNode, AssumptionAnalyzer, AssumptionType} import scala.annotation.tailrec import scala.io.StdIn.readLine +import viper.silver.ast +import viper.silver.ast.Method -class AssumptionAnalysisUserTool(graph: AssumptionAnalysisGraph) { +class AssumptionAnalysisUserTool(graph: AssumptionAnalysisGraph, assumptionAnalyzers: Seq[AssumptionAnalyzer]) { def run(): Unit = { println("Assumption Analysis Tool started. Enter line number or 'q' to quit.") @@ -22,7 +24,9 @@ class AssumptionAnalysisUserTool(graph: AssumptionAnalysisGraph) { val lineOpt = userInput.toIntOption if(lineOpt.isDefined){ val line = lineOpt.get - handleQuery(line) + handleDependencyQuery(line) + }else if(userInput.equalsIgnoreCase("coverage") || userInput.equalsIgnoreCase("cov")){ + handleProofCoverageQuery() }else{ println("Invalid input. Line number expected.") } @@ -30,7 +34,24 @@ class AssumptionAnalysisUserTool(graph: AssumptionAnalysisGraph) { runInternal() } - private def handleQuery(line: Int): Unit = { + private def handleProofCoverageQuery(): Unit = { + println("Proof Coverage") + assumptionAnalyzers.filter(aa => aa.getMember.isDefined && aa.getMember.exists { + case meth: Method => meth.body.isDefined + case func: ast.Function => func.body.isDefined + case _ => false + }) + .foreach(aa => { + val (coverage, uncoveredSources) = aa.computeProofCoverage().get + println(s"${aa.getMember.map(_.name).getOrElse("")}") + println(s"coverage: $coverage") + if (!coverage.equals(1.0)) + println(s"uncovered nodes:\n\t${uncoveredSources.mkString("\n\t")}") + println() + }) + } + + private def handleDependencyQuery(line: Int): Unit = { val queriedNodes = findNodeByLine(line) val directDependencies = graph.getDirectDependencies(queriedNodes.map(_.id)).toList.sortBy(_.sourceInfo.getLineNumber) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 99922c726..68f8567b4 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -56,7 +56,7 @@ trait AssumptionAnalyzer { def registerInhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: (Term => CH), perm: Term, labelNode: Option[LabelNode], analysisInfo: AnalysisInfo, isExhale: Boolean): CH = buildChunk(perm) def registerExhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: (Term => CH), perm: Term, analysisInfo: AnalysisInfo): CH = buildChunk(perm) - def computeProofCoverage(): Unit = {} + def computeProofCoverage(): Option[(Double, Seq[String])] = None def exportMergedGraph(): Unit = { if(Verifier.config.assumptionAnalysisExportPath.isEmpty) return @@ -314,17 +314,18 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { chunk } - override def computeProofCoverage(): Unit = { + override def computeProofCoverage(): Option[(Double, Seq[String])] = { val explicitAssertionNodes = assumptionGraph.getExplicitAssertionNodes val explicitAssertionNodeIds = explicitAssertionNodes map (_.id) val nodesPerSourceInfo = assumptionGraph.getNonInternalAssumptionNodesPerSource - val coveredNodes = nodesPerSourceInfo filter { case (_, nodes) => + val uncoveredSources = (nodesPerSourceInfo filter { case (_, nodes) => val nodeIds = (nodes map (_.id)).toSet - // it is either an explicit assertion itself or it has a dependency to an explicit assertion - nodeIds.intersect(explicitAssertionNodeIds).nonEmpty || - assumptionGraph.existsAnyDependency(nodeIds, explicitAssertionNodeIds) - } - proofCoverage = coveredNodes.size.toDouble / nodesPerSourceInfo.size.toDouble + // it is not an explicit assertion itself and has no dependency to an explicit assertion + nodeIds.intersect(explicitAssertionNodeIds).isEmpty && + !assumptionGraph.existsAnyDependency(nodeIds, explicitAssertionNodeIds) + }).keys.toSeq + proofCoverage = 1.0 - (uncoveredSources.size.toDouble / nodesPerSourceInfo.size.toDouble) + Some((proofCoverage, uncoveredSources)) } } diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index a58b4f4eb..5b76e2b74 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -180,7 +180,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver case (result1, phase1data) => emitAndRecordFunctionAxioms(AssumptionType.Axiom, data.limitedAxiom) emitAndRecordFunctionAxioms(AssumptionType.Axiom, data.triggerAxiom) - emitAndRecordFunctionAxioms(if(function.body.isDefined) AssumptionType.Implicit else AssumptionType.Explicit, data.postAxiom: _*) + emitAndRecordFunctionAxioms(AssumptionType.Axiom, data.postAxiom: _*) // FIXME ake: if(function.body.isDefined) AssumptionType.Implicit else AssumptionType.Explicit emitAndRecordFunctionAxioms(AssumptionType.Axiom, data.postPreconditionPropagationAxiom: _*) this.postConditionAxioms = this.postConditionAxioms ++ data.postAxiom @@ -272,7 +272,9 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver val eNew = ast.EqCmp(ast.Result(function.typ)(), bodyNew.get)(function.pos, function.info, function.errT) Some(DebugExp.createInstance(e, eNew)) } else { None } + decider.analysisSourceInfoStack.setForcedSource(ExpAnalysisSourceInfo(body)) decider.assume(BuiltinEquals(data.formalResult, tBody), debugExp, AssumptionType.Implicit) + decider.analysisSourceInfoStack.removeForcedSource() consumes(s2, posts, false, postconditionViolated, v, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Postcondition))((s3, _, _) => { recorders :+= s3.functionRecorder Success()})})})} diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index f817c2af9..116ba174b 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -330,7 +330,7 @@ class DefaultMainVerifier(config: Config, joinedGraph.exportGraph("graphExports/joinedGraphs") if(Verifier.config.startAssumptionAnalysisTool()){ - val commandLineTool = new AssumptionAnalysisUserTool(joinedGraph.mergeNodesBySource()) + val commandLineTool = new AssumptionAnalysisUserTool(joinedGraph.mergeNodesBySource(), assumptionAnalyzers) commandLineTool.run() } From 0f7f6b9c7643f488318dbc7dac8b528533a91d79 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 15 Jul 2025 16:32:49 +0200 Subject: [PATCH 172/474] extend command line tool capabilities --- .../AssumptionAnalysisUserTool.scala | 52 ++++++++++++------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala index 27f462d1d..4bdbf8af6 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala @@ -8,9 +8,14 @@ import viper.silver.ast import viper.silver.ast.Method class AssumptionAnalysisUserTool(graph: AssumptionAnalysisGraph, assumptionAnalyzers: Seq[AssumptionAnalyzer]) { + private val infoString = "Enter " + + "\n\t'dep [line numbers]' to print all dependencies of the given line numbers or" + + "\n\t'cov [member names]' to print proof coverage of given member names or" + + "\n\t'q' to quit" def run(): Unit = { - println("Assumption Analysis Tool started. Enter line number or 'q' to quit.") + println("Assumption Analysis Tool started.") + println(infoString) runInternal() } @@ -20,25 +25,33 @@ class AssumptionAnalysisUserTool(graph: AssumptionAnalysisGraph, assumptionAnaly if(userInput.equalsIgnoreCase("q") || userInput.equalsIgnoreCase("quit")){ return } - - val lineOpt = userInput.toIntOption - if(lineOpt.isDefined){ - val line = lineOpt.get - handleDependencyQuery(line) - }else if(userInput.equalsIgnoreCase("coverage") || userInput.equalsIgnoreCase("cov")){ - handleProofCoverageQuery() + if(userInput.nonEmpty) { + handleUserInput(userInput) }else{ - println("Invalid input. Line number expected.") + println(infoString) } runInternal() } - private def handleProofCoverageQuery(): Unit = { + private def handleUserInput(userInput: String): Unit = { + val inputParts = userInput.split(" ") + if (inputParts(0).equalsIgnoreCase("d") || inputParts(0).equalsIgnoreCase("dep")) { + val lines = inputParts.tail.flatMap(_.toIntOption).toSet + handleDependencyQuery(lines) + } else if (inputParts(0).equalsIgnoreCase("coverage") || inputParts(0).equalsIgnoreCase("cov")) { + handleProofCoverageQuery(inputParts.tail) + } else { + println("Invalid input.") + println(infoString) + } + } + + private def handleProofCoverageQuery(memberNames: Seq[String]): Unit = { println("Proof Coverage") assumptionAnalyzers.filter(aa => aa.getMember.isDefined && aa.getMember.exists { - case meth: Method => meth.body.isDefined - case func: ast.Function => func.body.isDefined + case meth: Method => meth.body.isDefined && (memberNames.isEmpty || memberNames.contains(meth.name)) + case func: ast.Function => func.body.isDefined && (memberNames.isEmpty || memberNames.contains(func.name)) case _ => false }) .foreach(aa => { @@ -51,20 +64,19 @@ class AssumptionAnalysisUserTool(graph: AssumptionAnalysisGraph, assumptionAnaly }) } - private def handleDependencyQuery(line: Int): Unit = { - - val queriedNodes = findNodeByLine(line) + private def handleDependencyQuery(lines: Set[Int]): Unit = { + val queriedNodes = lines flatMap findNodeByLine val directDependencies = graph.getDirectDependencies(queriedNodes.map(_.id)).toList.sortBy(_.sourceInfo.getLineNumber) val dependencies = graph.getAllNonInternalDependencies(queriedNodes.map(_.id)).toList.sortBy(_.sourceInfo.getLineNumber) val dependees = graph.getAllNonInternalDependendees(queriedNodes.map(_.id)).toList.sortBy(_.sourceInfo.getLineNumber) - println(s"Queried: ${queriedNodes.map(_.sourceInfo.getTopLevelSource.toString).mkString(", ")}") + println(s"Queried:\n\t${queriedNodes.map(_.sourceInfo.getTopLevelSource.toString).mkString("\n\t")}") - println(s"\nDirect Dependencies: \n\t${directDependencies.map(_.sourceInfo.getTopLevelSource.toString).mkString("\n\t")}") - println(s"\nAll Dependencies: \n\t${dependencies.map(_.sourceInfo.getTopLevelSource.toString).mkString("\n\t")}") - println(s"\nExplicit Dependencies: \n\t${dependencies.filter(_.assumptionType.equals(AssumptionType.Explicit)).map(_.sourceInfo.getTopLevelSource.toString).mkString("\n\t")}") + println(s"\nDirect Dependencies:\n\t${directDependencies.map(_.sourceInfo.getTopLevelSource.toString).mkString("\n\t")}") + println(s"\nAll Dependencies:\n\t${dependencies.map(_.sourceInfo.getTopLevelSource.toString).mkString("\n\t")}") + println(s"\nExplicit Dependencies:\n\t${dependencies.filter(_.assumptionType.equals(AssumptionType.Explicit)).map(_.sourceInfo.getTopLevelSource.toString).mkString("\n\t")}") - println(s"\nAll Dependees: \n\t${dependees.map(_.sourceInfo.getTopLevelSource.toString).mkString("\n\t")}") + println(s"\nAll Dependees:\n\t${dependees.map(_.sourceInfo.getTopLevelSource.toString).mkString("\n\t")}") println(s"\nDone.") } From 243f3a3abe8ec3e581cf6709ccc8de6858dd5ff4 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 15 Jul 2025 19:38:00 +0200 Subject: [PATCH 173/474] big refactoring, add AssumptionAnalysisInterpreter --- .../AssumptionAnalysisGraph.scala | 260 +++------------ .../AssumptionAnalysisInterpreter.scala | 159 ++++++++++ .../AssumptionAnalysisNode.scala | 73 +++++ .../AssumptionAnalysisUserTool.scala | 32 +- .../AssumptionAnalyzer.scala | 296 ++++++------------ .../DependencyAnalysisReporter.scala | 3 +- src/main/scala/interfaces/Verification.scala | 6 +- .../scala/supporters/MethodSupporter.scala | 7 +- .../PredicateVerificationUnit.scala | 1 - .../functions/FunctionVerificationUnit.scala | 7 +- .../scala/verifier/DefaultMainVerifier.scala | 23 +- src/test/scala/AssumptionAnalysisTests.scala | 99 +++--- 12 files changed, 472 insertions(+), 494 deletions(-) create mode 100644 src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala create mode 100644 src/main/scala/assumptionAnalysis/AssumptionAnalysisNode.scala diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index fc19e4899..de613bcc7 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -1,14 +1,8 @@ package viper.silicon.assumptionAnalysis -import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType -import viper.silicon.interfaces.state.Chunk -import viper.silicon.state.terms.{False, Term, True} -import viper.silver.ast.Position - import java.io.{File, PrintWriter} import java.util.concurrent.atomic.AtomicInteger import scala.collection.mutable -import scala.collection.mutable.Seq object AssumptionAnalysisGraphHelper { @@ -19,94 +13,62 @@ object AssumptionAnalysisGraphHelper { } } -trait AssumptionAnalysisGraph { - var nodes: mutable.Seq[AssumptionAnalysisNode] - var edges: mutable.Map[Int, Set[Int]] - var transitiveEdges: mutable.Map[Int, Set[Int]] = mutable.Map.empty +trait ReadOnlyAssumptionAnalysisGraph { + def getNodes: Seq[AssumptionAnalysisNode] + def getDirectEdges: Map[Int, Set[Int]] + def getTransitiveEdges: Map[Int, Set[Int]] + def getAllEdges: Map[Int, Set[Int]] - def addNode(node: AssumptionAnalysisNode): Unit - def addNodes(nodes: Iterable[AssumptionAnalysisNode]): Unit - def addEdges(source: Int, targets: Iterable[Int]): Unit - def addEdges(sources: Iterable[Int], target: Int): Unit - def addEdges(sources: Iterable[Int], targets: Iterable[Int]): Unit + def existsAnyDependency(sources: Set[Int], targets: Set[Int]): Boolean - def existsAnyDependency(sources: Set[Int], targets: Set[Int]): Boolean = { - var visited: Set[Int] = sources - var queue: List[Int] = sources.toList - while(queue.nonEmpty){ - val curr = queue.head - val newVisits = edges.getOrElse(curr, Set()) ++ transitiveEdges.getOrElse(curr, Set()) - if(newVisits.intersect(targets).nonEmpty) - return true - visited = visited ++ Set(curr) - queue = queue.tail ++ (newVisits filter (!visited.contains(_))) - } - false - } - - def getDirectDependencies(nodeIdsToAnalyze: Set[Int]): Set[AssumptionAnalysisNode] = { - val depIds = (edges filter { case (s, ts) => ts.intersect(nodeIdsToAnalyze).nonEmpty }).keys.toSet - - nodes.filter(node => depIds.contains(node.id) && - ((node.isInstanceOf[GeneralAssumptionNode] && !node.assumptionType.equals(AssumptionType.Internal)) || - (node.isInstanceOf[GeneralAssertionNode] && node.assumptionType.equals(AssumptionType.Postcondition)) - || node.isInstanceOf[InfeasibilityNode]) - ).toSet - } + def exportGraph(dirName: String): Unit +} - def getAllNonInternalDependencies(nodeIdsToAnalyze: Set[Int]): Set[AssumptionAnalysisNode] = { - nodes.filter(node => - ((node.isInstanceOf[GeneralAssumptionNode] && !node.assumptionType.equals(AssumptionType.Internal)) || - (node.isInstanceOf[GeneralAssertionNode] && node.assumptionType.equals(AssumptionType.Postcondition)) - || node.isInstanceOf[InfeasibilityNode]) && - existsAnyDependency(Set(node.id), nodeIdsToAnalyze)).toSet - } +class AssumptionAnalysisGraph extends ReadOnlyAssumptionAnalysisGraph { + var nodes: mutable.Seq[AssumptionAnalysisNode] = mutable.Seq() + private val edges: mutable.Map[Int, Set[Int]] = mutable.Map.empty + private val transitiveEdges: mutable.Map[Int, Set[Int]] = mutable.Map.empty - def getAllNonInternalDependendees(nodeIdsToAnalyze: Set[Int]): Set[AssumptionAnalysisNode] = { - nodes.filter(node => - ((node.isInstanceOf[GeneralAssertionNode] && !node.assumptionType.equals(AssumptionType.Internal)) - || node.isInstanceOf[InfeasibilityNode]) && - existsAnyDependency(nodeIdsToAnalyze, Set(node.id))).toSet - } + def getNodes: Seq[AssumptionAnalysisNode] = nodes.toSeq + def getDirectEdges: Map[Int, Set[Int]] = edges.toMap + def getTransitiveEdges: Map[Int, Set[Int]] = transitiveEdges.toMap + def getAllEdges: Map[Int, Set[Int]] = (edges ++ transitiveEdges).toMap - private def getNodesByProperties(nodeType: Option[String], assumptionType: Option[AssumptionType], sourceInfo: Option[String], position: Option[Position]): mutable.Seq[AssumptionAnalysisNode] = { - nodes filter (node => - nodeType.forall(node.getNodeType.equals) && - assumptionType.forall(node.assumptionType.equals) && - sourceInfo.forall(node.sourceInfo.toString.equals) && - position.forall(node.sourceInfo.getPosition.equals) - ) + def addNode(node: AssumptionAnalysisNode): Unit = { + nodes = nodes :+ node } - def getExplicitAssertionNodes: Set[AssumptionAnalysisNode] = { - (getNodesByProperties(Some("Assertion"), Some(AssumptionType.Explicit), None, None) ++ - getNodesByProperties(Some("Assertion"), Some(AssumptionType.Postcondition), None, None) ++ - getNodesByProperties(Some("Exhale"), Some(AssumptionType.Explicit), None, None) ++ - getNodesByProperties(Some("Exhale"), Some(AssumptionType.Postcondition), None, None)).toSet + def addNodes(nodes: Iterable[AssumptionAnalysisNode]): Unit = { + nodes foreach addNode } - def getNonInternalAssumptionNodesPerSource: Map[String, mutable.Seq[AssumptionAnalysisNode]] = { - getNodesPerSourceInfo filter {case (_, nodes) => - nodes exists (node => - node.isInstanceOf[GeneralAssumptionNode] && - !node.assumptionType.equals(AssumptionType.Internal) && - !node.assumptionType.equals(AssumptionType.Trigger) && - !node.assumptionType.equals(AssumptionType.Axiom)) - } + def addEdges(source: Int, targets: Iterable[Int]): Unit = { + val oldTargets = edges.getOrElse(source, Set.empty) + val newTargets = targets.filter(_ != source) + if(newTargets.nonEmpty) + edges.update(source, oldTargets ++ newTargets) } - - def getNodesPerChunk: Map[Chunk, mutable.Seq[AssumptionAnalysisNode]] = { - nodes.filter (_.isInstanceOf[ChunkAnalysisInfo]) - .groupBy(_.asInstanceOf[ChunkAnalysisInfo].getChunk) + def addEdges(sources: Iterable[Int], target: Int): Unit = { + addEdges(sources, Set(target)) } - private def getNodesPerTransitivitySourceInfo: Map[String, mutable.Seq[AssumptionAnalysisNode]] = { - nodes.groupBy(_.sourceInfo.getSourceForTransitiveEdges.toString) + def addEdges(sources: Iterable[Int], targets: Iterable[Int]): Unit = { + sources foreach (addEdges(_, targets)) } - private def getNodesPerSourceInfo: Map[String, mutable.Seq[AssumptionAnalysisNode]] = { - nodes.groupBy(_.sourceInfo.getTopLevelSource.toString) + def existsAnyDependency(sources: Set[Int], targets: Set[Int]): Boolean = { + var visited: Set[Int] = sources + var queue: List[Int] = sources.toList + while(queue.nonEmpty){ + val curr = queue.head + val newVisits = edges.getOrElse(curr, Set()) ++ transitiveEdges.getOrElse(curr, Set()) + if(newVisits.intersect(targets).nonEmpty) + return true + visited = visited ++ Set(curr) + queue = queue.tail ++ (newVisits filter (!visited.contains(_))) + } + false } private def addTransitiveEdges(source: AssumptionAnalysisNode, targets: Iterable[AssumptionAnalysisNode]): Unit = { @@ -119,6 +81,12 @@ trait AssumptionAnalysisGraph { source foreach (s => addTransitiveEdges(s, targets)) } + // TODO ake: maybe move to AssumptionAnalyzer? + private def getNodesPerTransitivitySourceInfo: Map[String, Seq[AssumptionAnalysisNode]] = { + getNodes.groupBy(_.sourceInfo.getSourceForTransitiveEdges.toString) + } + + // TODO ake: maybe move to AssumptionAnalyzer? def addTransitiveEdges(): Unit = { val nodesPerSourceInfo = getNodesPerTransitivitySourceInfo nodesPerSourceInfo foreach {nodes => @@ -140,6 +108,7 @@ trait AssumptionAnalysisGraph { addEdges(predecessors, successors) } + // TODO ake: maybe move to AssumptionAnalyzer? def removeLabelNodes(): Unit = { nodes filter (_.isInstanceOf[LabelNode]) foreach removeAllEdgesForNode nodes = nodes filter (!_.isInstanceOf[LabelNode]) @@ -172,139 +141,6 @@ trait AssumptionAnalysisGraph { nodes foreach (n => writer.println(getNodeExportString(n).replace("\n", " "))) writer.close() } - - def mergeNodesBySource(): AssumptionAnalysisGraph = { - def keepNode(n: AssumptionAnalysisNode): Boolean = n.isClosed || n.isInstanceOf[InfeasibilityNode] - val mergedGraph = new DefaultAssumptionAnalysisGraph - val nodeMap = mutable.HashMap[Int, Int]() - nodes.filter(keepNode).foreach{n => - nodeMap.put(n.id, n.id) - mergedGraph.addNode(n) - } - - val nodesBySource = nodes.filter(!keepNode(_)) - .groupBy(n => (n.sourceInfo.getSourceForTransitiveEdges.toString, n.sourceInfo.getTopLevelSource.toString, n.assumptionType)) - nodesBySource foreach {case ((_, _, assumptionType), nodes) => - val assumptionNodes = nodes.filter(_.isInstanceOf[GeneralAssumptionNode]) - if(assumptionNodes.nonEmpty) { - val newNode = SimpleAssumptionNode(True, None, assumptionNodes.head.sourceInfo, assumptionType, isClosed = true) - assumptionNodes foreach (n => nodeMap.put(n.id, newNode.id)) - mergedGraph.addNode(newNode) - } - } - - nodesBySource foreach {case ((_, _, assumptionType), nodes) => - val assertionNodes = nodes.filter(_.isInstanceOf[GeneralAssertionNode]) - if(assertionNodes.nonEmpty){ - val newNode = SimpleAssertionNode(True, assumptionType, assertionNodes.head.sourceInfo, isClosed=true) - assertionNodes foreach (n => nodeMap.put(n.id, newNode.id)) - mergedGraph.addNode(newNode) - } - } - - (edges ++ transitiveEdges) foreach {case (source, targets) => - val newSource = nodeMap(source) - mergedGraph.addEdges(newSource, targets.map(nodeMap(_))) - } - - mergedGraph - } } -class DefaultAssumptionAnalysisGraph extends AssumptionAnalysisGraph { - override var nodes: mutable.Seq[AssumptionAnalysisNode] = mutable.Seq() - override var edges: mutable.Map[Int, Set[Int]] = mutable.Map.empty - - override def addNode(node: AssumptionAnalysisNode): Unit = { - nodes = nodes :+ node - } - - override def addNodes(nodes: Iterable[AssumptionAnalysisNode]): Unit = { - nodes foreach addNode - } - - override def addEdges(source: Int, targets: Iterable[Int]): Unit = { - val oldTargets = edges.getOrElse(source, Set.empty) - val newTargets = targets.filter(_ != source) - if(newTargets.nonEmpty) - edges.update(source, oldTargets ++ newTargets) - } - - override def addEdges(sources: Iterable[Int], target: Int): Unit = { - addEdges(sources, Set(target)) - } - - override def addEdges(sources: Iterable[Int], targets: Iterable[Int]): Unit = { - sources foreach (addEdges(_, targets)) - } -} - -trait AssumptionAnalysisNode { - val id: Int = AssumptionAnalysisGraphHelper.nextId() - val sourceInfo: AnalysisSourceInfo - val assumptionType: AssumptionType - val isClosed: Boolean - val term: Term - def getTerm: Term = term - - override def toString: String = id.toString + " | " + getNodeString + " | " + sourceInfo.toString - - def getNodeString: String - def getNodeType: String -} - -trait GeneralAssumptionNode extends AssumptionAnalysisNode { - override def getNodeType: String = "Assumption" -} -trait GeneralAssertionNode extends AssumptionAnalysisNode { - override def getNodeType: String = "Assertion" -} - -trait ChunkAnalysisInfo { - val chunk: Chunk - def getChunk: Chunk = chunk -} - -case class SimpleAssumptionNode(term: Term, description: Option[String], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean) extends GeneralAssumptionNode { - override def getNodeString: String = "assume " + term.toString + description.map(" (" + _ + ")").getOrElse("") -} - -case class SimpleAssertionNode(term: Term, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo, isClosed: Boolean) extends GeneralAssertionNode { - override def getNodeString: String = "assert " + term.toString -} - -case class SimpleCheckNode(term: Term, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo, isClosed: Boolean) extends GeneralAssertionNode { - override def getNodeString: String = "check " + term - override def getNodeType: String = "Check" -} - -case class PermissionInhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean, labelNode: LabelNode) extends GeneralAssumptionNode with ChunkAnalysisInfo { - override def getNodeString: String = "inhale " + chunk.toString - override def getNodeType: String = "Inhale" -} - -case class PermissionExhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean) extends GeneralAssertionNode with ChunkAnalysisInfo { - override def getNodeType: String = "Exhale" - override def getNodeString: String = "exhale " + chunk.toString -} - -case class LabelNode(term: Term) extends GeneralAssumptionNode { - val sourceInfo: AnalysisSourceInfo = NoAnalysisSourceInfo() - val assumptionType: AssumptionType = AssumptionType.Internal - val isClosed: Boolean = true - val description: String = term.toString - override def getNodeType: String = "Label" - override def getNodeString: String = "assume " + description -} - -case class InfeasibilityNode(sourceInfo: AnalysisSourceInfo) extends AssumptionAnalysisNode { - val term: Term = False - val assumptionType: AssumptionType = AssumptionType.Implicit - val isClosed: Boolean = true - val description: String = "False" - - override def getNodeType: String = "Infeasible" - override def getNodeString: String = "infeasible" -} - diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala new file mode 100644 index 000000000..235aee355 --- /dev/null +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala @@ -0,0 +1,159 @@ +package viper.silicon.assumptionAnalysis + +import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType +import viper.silicon.state.terms.True +import viper.silicon.verifier.Verifier +import viper.silver.ast +import viper.silver.ast.Position + +import scala.collection.mutable + +object AssumptionAnalysisInterpreter { + def joinGraphsAndGetInterpreter(assumptionAnalysisInterpreters: Set[AssumptionAnalysisInterpreter]): AssumptionAnalysisInterpreter = { + val newGraph = new AssumptionAnalysisGraph + + assumptionAnalysisInterpreters foreach (interpreter => newGraph.addNodes(interpreter.getGraph.getNodes)) + assumptionAnalysisInterpreters foreach (interpreter => interpreter.getGraph.getAllEdges foreach {case (s, t) => newGraph.addEdges(s, t)}) + + val types = Set(AssumptionType.Implicit, AssumptionType.Explicit) + val relevantAssumptionNodes = newGraph.nodes filter (node => node.isInstanceOf[GeneralAssumptionNode] && types.contains(node.assumptionType)) + + newGraph.nodes filter (node => node.isInstanceOf[GeneralAssertionNode] && node.assumptionType.equals(AssumptionType.Postcondition)) foreach {node => // TODO ake: check if this also works for functions + val nodeSourceInfoString = node.sourceInfo.getTopLevelSource.toString + val assumptionNodesForJoin = relevantAssumptionNodes filter (aNode => aNode.sourceInfo.getFineGrainedSource.toString.equals(nodeSourceInfoString)) + newGraph.addEdges(node.id, assumptionNodesForJoin map (_.id)) + } + + new AssumptionAnalysisInterpreter("joined", newGraph) + } +} + +class AssumptionAnalysisInterpreter(name: String, graph: ReadOnlyAssumptionAnalysisGraph, member: Option[ast.Member]=None) { + private def getGraph: ReadOnlyAssumptionAnalysisGraph = graph + def getName: String = name + def getMember: Option[ast.Member] = member + def getNodes: Set[AssumptionAnalysisNode] = graph.getNodes.toSet + + def getNodesByLine(line: Int): Set[AssumptionAnalysisNode] = + getNodes.filter(node => node.sourceInfo.getLineNumber.isDefined && node.sourceInfo.getLineNumber.get == line) + + def getDirectDependencies(nodeIdsToAnalyze: Set[Int]): Set[AssumptionAnalysisNode] = { + graph.getNodes.filter(node => graph.getDirectEdges.get(node.id).exists(_.intersect(nodeIdsToAnalyze).nonEmpty) && + ((node.isInstanceOf[GeneralAssumptionNode] && !node.assumptionType.equals(AssumptionType.Internal)) || + (node.isInstanceOf[GeneralAssertionNode] && node.assumptionType.equals(AssumptionType.Postcondition)) + || node.isInstanceOf[InfeasibilityNode]) + ).toSet + } + + def getAllNonInternalDependencies(nodeIdsToAnalyze: Set[Int]): Set[AssumptionAnalysisNode] = { + graph.getNodes.filter(node => + ((node.isInstanceOf[GeneralAssumptionNode] && !node.assumptionType.equals(AssumptionType.Internal)) || + (node.isInstanceOf[GeneralAssertionNode] && node.assumptionType.equals(AssumptionType.Postcondition)) + || node.isInstanceOf[InfeasibilityNode]) && + graph.existsAnyDependency(Set(node.id), nodeIdsToAnalyze)).toSet + } + + def getAllExplicitDependencies(nodeIdsToAnalyze: Set[Int]): Set[AssumptionAnalysisNode] = { + getAllNonInternalDependencies(nodeIdsToAnalyze).filter(_.assumptionType.equals(AssumptionType.Explicit)) + } + + def getAllNonInternalDependendents(nodeIdsToAnalyze: Set[Int]): Set[AssumptionAnalysisNode] = { + graph.getNodes.filter(node => + ((node.isInstanceOf[GeneralAssertionNode] && !node.assumptionType.equals(AssumptionType.Internal)) + || node.isInstanceOf[InfeasibilityNode]) && + graph.existsAnyDependency(nodeIdsToAnalyze, Set(node.id))).toSet + } + + private def getNodesByProperties(nodeType: Option[String], assumptionType: Option[AssumptionType], sourceInfo: Option[String], position: Option[Position]): Seq[AssumptionAnalysisNode] = { + graph.getNodes filter (node => + nodeType.forall(node.getNodeType.equals) && + assumptionType.forall(node.assumptionType.equals) && + sourceInfo.forall(node.sourceInfo.toString.equals) && + position.forall(node.sourceInfo.getPosition.equals) + ) + } + + def getExplicitAssertionNodes: Set[AssumptionAnalysisNode] = { + (getNodesByProperties(Some("Assertion"), Some(AssumptionType.Explicit), None, None) ++ + getNodesByProperties(Some("Assertion"), Some(AssumptionType.Postcondition), None, None) ++ + getNodesByProperties(Some("Exhale"), Some(AssumptionType.Explicit), None, None) ++ + getNodesByProperties(Some("Exhale"), Some(AssumptionType.Postcondition), None, None)).toSet + } + + private def getNonInternalAssumptionNodesPerSource: Map[String, Seq[AssumptionAnalysisNode]] = { + getNodesPerSourceInfo filter {case (_, nodes) => + nodes exists (node => + node.isInstanceOf[GeneralAssumptionNode] && + !node.assumptionType.equals(AssumptionType.Internal) && + !node.assumptionType.equals(AssumptionType.Trigger) && + !node.assumptionType.equals(AssumptionType.Axiom)) + } + } + + private def getNodesPerSourceInfo: Map[String, Seq[AssumptionAnalysisNode]] = { + graph.getNodes.groupBy(_.sourceInfo.getTopLevelSource.toString) + } + + def exportGraph(): Unit = { + if(Verifier.config.assumptionAnalysisExportPath.isEmpty) return + graph.exportGraph(Verifier.config.assumptionAnalysisExportPath() + "/" + name) + } + + def exportMergedGraph(): Unit = { + if(Verifier.config.assumptionAnalysisExportPath.isEmpty) return + val mergedGraph = mergeNodesBySource() + mergedGraph.exportGraph(Verifier.config.assumptionAnalysisExportPath() + "/" + name + "_merged") + } + + private def mergeNodesBySource(): AssumptionAnalysisGraph = { + def keepNode(n: AssumptionAnalysisNode): Boolean = n.isClosed || n.isInstanceOf[InfeasibilityNode] + + val mergedGraph = new AssumptionAnalysisGraph + val nodeMap = mutable.HashMap[Int, Int]() + graph.getNodes.filter(keepNode).foreach{n => + nodeMap.put(n.id, n.id) + mergedGraph.addNode(n) + } + + val nodesBySource = graph.getNodes.filter(!keepNode(_)) + .groupBy(n => (n.sourceInfo.getSourceForTransitiveEdges.toString, n.sourceInfo.getTopLevelSource.toString, n.assumptionType)) + nodesBySource foreach {case ((_, _, assumptionType), nodes) => + val assumptionNodes = nodes.filter(_.isInstanceOf[GeneralAssumptionNode]) + if(assumptionNodes.nonEmpty) { + val newNode = SimpleAssumptionNode(True, None, assumptionNodes.head.sourceInfo, assumptionType, isClosed = true) + assumptionNodes foreach (n => nodeMap.put(n.id, newNode.id)) + mergedGraph.addNode(newNode) + } + } + + nodesBySource foreach {case ((_, _, assumptionType), nodes) => + val assertionNodes = nodes.filter(_.isInstanceOf[GeneralAssertionNode]) + if(assertionNodes.nonEmpty){ + val newNode = SimpleAssertionNode(True, assumptionType, assertionNodes.head.sourceInfo, isClosed=true) + assertionNodes foreach (n => nodeMap.put(n.id, newNode.id)) + mergedGraph.addNode(newNode) + } + } + + graph.getAllEdges foreach {case (source, targets) => + val newSource = nodeMap(source) + mergedGraph.addEdges(newSource, targets.map(nodeMap(_))) + } + + mergedGraph + } + + def computeProofCoverage(): Option[(Double, Seq[String])] = { + val explicitAssertionNodes = getExplicitAssertionNodes + val explicitAssertionNodeIds = explicitAssertionNodes map (_.id) + val nodesPerSourceInfo = getNonInternalAssumptionNodesPerSource + val uncoveredSources = (nodesPerSourceInfo filter { case (_, nodes) => + val nodeIds = (nodes map (_.id)).toSet + // it is not an explicit assertion itself and has no dependency to an explicit assertion + nodeIds.intersect(explicitAssertionNodeIds).isEmpty && + !graph.existsAnyDependency(nodeIds, explicitAssertionNodeIds) + }).keys.toSeq + val proofCoverage = 1.0 - (uncoveredSources.size.toDouble / nodesPerSourceInfo.size.toDouble) + Some((proofCoverage, uncoveredSources)) + } +} diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisNode.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisNode.scala new file mode 100644 index 000000000..7c50ea153 --- /dev/null +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisNode.scala @@ -0,0 +1,73 @@ +package viper.silicon.assumptionAnalysis + +import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType +import viper.silicon.interfaces.state.Chunk +import viper.silicon.state.terms.{False, Term} + +trait AssumptionAnalysisNode { + val id: Int = AssumptionAnalysisGraphHelper.nextId() + val sourceInfo: AnalysisSourceInfo + val assumptionType: AssumptionType + val isClosed: Boolean + val term: Term + def getTerm: Term = term + + override def toString: String = id.toString + " | " + getNodeString + " | " + sourceInfo.toString + + def getNodeString: String + def getNodeType: String +} + +trait GeneralAssumptionNode extends AssumptionAnalysisNode { + override def getNodeType: String = "Assumption" +} +trait GeneralAssertionNode extends AssumptionAnalysisNode { + override def getNodeType: String = "Assertion" +} + +trait ChunkAnalysisInfo { + val chunk: Chunk + def getChunk: Chunk = chunk +} + +case class SimpleAssumptionNode(term: Term, description: Option[String], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean) extends GeneralAssumptionNode { + override def getNodeString: String = "assume " + term.toString + description.map(" (" + _ + ")").getOrElse("") +} + +case class SimpleAssertionNode(term: Term, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo, isClosed: Boolean) extends GeneralAssertionNode { + override def getNodeString: String = "assert " + term.toString +} + +case class SimpleCheckNode(term: Term, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo, isClosed: Boolean) extends GeneralAssertionNode { + override def getNodeString: String = "check " + term + override def getNodeType: String = "Check" +} + +case class PermissionInhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean, labelNode: LabelNode) extends GeneralAssumptionNode with ChunkAnalysisInfo { + override def getNodeString: String = "inhale " + chunk.toString + override def getNodeType: String = "Inhale" +} + +case class PermissionExhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean) extends GeneralAssertionNode with ChunkAnalysisInfo { + override def getNodeType: String = "Exhale" + override def getNodeString: String = "exhale " + chunk.toString +} + +case class LabelNode(term: Term) extends GeneralAssumptionNode { + val sourceInfo: AnalysisSourceInfo = NoAnalysisSourceInfo() + val assumptionType: AssumptionType = AssumptionType.Internal + val isClosed: Boolean = true + val description: String = term.toString + override def getNodeType: String = "Label" + override def getNodeString: String = "assume " + description +} + +case class InfeasibilityNode(sourceInfo: AnalysisSourceInfo) extends AssumptionAnalysisNode { + val term: Term = False + val assumptionType: AssumptionType = AssumptionType.Implicit + val isClosed: Boolean = true + val description: String = "False" + + override def getNodeType: String = "Infeasible" + override def getNodeString: String = "infeasible" +} diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala index 4bdbf8af6..260e0eb3e 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala @@ -1,13 +1,13 @@ package silicon.viper.assumptionAnalysis -import viper.silicon.assumptionAnalysis.{AssumptionAnalysisGraph, AssumptionAnalysisNode, AssumptionAnalyzer, AssumptionType} +import viper.silicon.assumptionAnalysis.{AssumptionAnalysisGraph, AssumptionAnalysisInterpreter, AssumptionAnalysisNode, AssumptionAnalyzer, AssumptionType} import scala.annotation.tailrec import scala.io.StdIn.readLine import viper.silver.ast import viper.silver.ast.Method -class AssumptionAnalysisUserTool(graph: AssumptionAnalysisGraph, assumptionAnalyzers: Seq[AssumptionAnalyzer]) { +class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpreter, memberInterpreters: Seq[AssumptionAnalysisInterpreter]) { private val infoString = "Enter " + "\n\t'dep [line numbers]' to print all dependencies of the given line numbers or" + "\n\t'cov [member names]' to print proof coverage of given member names or" + @@ -49,7 +49,7 @@ class AssumptionAnalysisUserTool(graph: AssumptionAnalysisGraph, assumptionAnaly private def handleProofCoverageQuery(memberNames: Seq[String]): Unit = { println("Proof Coverage") - assumptionAnalyzers.filter(aa => aa.getMember.isDefined && aa.getMember.exists { + memberInterpreters.filter(aa => aa.getMember.isDefined && aa.getMember.exists { case meth: Method => meth.body.isDefined && (memberNames.isEmpty || memberNames.contains(meth.name)) case func: ast.Function => func.body.isDefined && (memberNames.isEmpty || memberNames.contains(func.name)) case _ => false @@ -65,23 +65,23 @@ class AssumptionAnalysisUserTool(graph: AssumptionAnalysisGraph, assumptionAnaly } private def handleDependencyQuery(lines: Set[Int]): Unit = { - val queriedNodes = lines flatMap findNodeByLine - val directDependencies = graph.getDirectDependencies(queriedNodes.map(_.id)).toList.sortBy(_.sourceInfo.getLineNumber) - val dependencies = graph.getAllNonInternalDependencies(queriedNodes.map(_.id)).toList.sortBy(_.sourceInfo.getLineNumber) - val dependees = graph.getAllNonInternalDependendees(queriedNodes.map(_.id)).toList.sortBy(_.sourceInfo.getLineNumber) + def getSourceInfoString(nodes: Set[AssumptionAnalysisNode]) = { + nodes.map(_.sourceInfo.getTopLevelSource).toList.sortBy(_.getLineNumber).mkString("\n\t") + } + + val queriedNodes = lines flatMap fullGraphInterpreter.getNodesByLine + val directDependencies = getSourceInfoString(fullGraphInterpreter.getDirectDependencies(queriedNodes.map(_.id))) + val allDependencies = getSourceInfoString(fullGraphInterpreter.getAllNonInternalDependencies(queriedNodes.map(_.id))) + val explicitDependencies = getSourceInfoString(fullGraphInterpreter.getAllExplicitDependencies(queriedNodes.map(_.id))) + val dependents = getSourceInfoString(fullGraphInterpreter.getAllNonInternalDependendents(queriedNodes.map(_.id))) println(s"Queried:\n\t${queriedNodes.map(_.sourceInfo.getTopLevelSource.toString).mkString("\n\t")}") - println(s"\nDirect Dependencies:\n\t${directDependencies.map(_.sourceInfo.getTopLevelSource.toString).mkString("\n\t")}") - println(s"\nAll Dependencies:\n\t${dependencies.map(_.sourceInfo.getTopLevelSource.toString).mkString("\n\t")}") - println(s"\nExplicit Dependencies:\n\t${dependencies.filter(_.assumptionType.equals(AssumptionType.Explicit)).map(_.sourceInfo.getTopLevelSource.toString).mkString("\n\t")}") + println(s"\nDirect Dependencies:\n\t$directDependencies") + println(s"\nAll Dependencies:\n\t$allDependencies") + println(s"\nExplicit Dependencies:\n\t$explicitDependencies") - println(s"\nAll Dependees:\n\t${dependees.map(_.sourceInfo.getTopLevelSource.toString).mkString("\n\t")}") + println(s"\nAll Dependents:\n\t$dependents") println(s"\nDone.") - } - - private def findNodeByLine(line: Int): Set[AssumptionAnalysisNode] = - graph.nodes.filter(node => node.sourceInfo.getLineNumber.getOrElse(-1) == line).toSet - } diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 68f8567b4..32ef86abc 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -3,14 +3,12 @@ package viper.silicon.assumptionAnalysis import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType import viper.silicon.interfaces.state.{Chunk, GeneralChunk} import viper.silicon.state.terms._ -import viper.silicon.verifier.Verifier import viper.silver.ast trait AssumptionAnalyzer { - val assumptionGraph: AssumptionAnalysisGraph = new DefaultAssumptionAnalysisGraph() + protected val assumptionGraph: AssumptionAnalysisGraph = new AssumptionAnalysisGraph() protected var isClosed_ = false - protected var infeasibilityNode: Option[Int] = None def disableTransitiveEdges(): Unit = { isClosed_ = true @@ -20,65 +18,35 @@ trait AssumptionAnalyzer { isClosed_ = false } - def getinfeasibilityNode: Option[Int] = infeasibilityNode - - def setinfeasibilityNode(nodeId: Int): Unit = { - infeasibilityNode = Some(nodeId) - } - - def unsetinfeasibilityNode(): Unit = { - infeasibilityNode = None - } + def getMember: Option[ast.Member] def getNodes: Iterable[AssumptionAnalysisNode] - def getChunkInhaleNode(chunk: Chunk): Option[PermissionInhaleNode] = None + def getChunkInhaleNode(chunk: Chunk): Option[PermissionInhaleNode] def addNodes(nodes: Iterable[AssumptionAnalysisNode]): Unit def addNode(node: AssumptionAnalysisNode): Unit def addAssumption(assumption: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, description: Option[String] = None): Option[Int] + def registerInhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, labelNode: Option[LabelNode], analysisInfo: AnalysisInfo, isExhale: Boolean): CH = buildChunk(perm) + def registerExhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, analysisInfo: AnalysisInfo): CH = buildChunk(perm) def addPermissionExhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] + def createLabelNode(labelTerm: Term, sourceChunks: Iterable[Chunk], sourceTerms: Iterable[Term]): Option[LabelNode] def createAssertOrCheckNode(term: Term, assumptionType: AssumptionType, analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] def addAssertFalseNode(isCheck: Boolean, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo): Option[Int] - def addInfeasibilityNode(isCheck: Boolean, sourceInfo: AnalysisSourceInfo): Option[Int] = None + def addInfeasibilityNode(isCheck: Boolean, sourceInfo: AnalysisSourceInfo): Option[Int] def addDependency(source: Option[Int], dest: Option[Int]): Unit def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit def addPermissionDependencies(sourceChunks: Set[Chunk], sourceTerms: Set[Term], targetChunk: Chunk): Unit - def addDependencyFromExhaleToInhale(inhaledChunk: Chunk, sourceInfo: AnalysisSourceInfo): Unit - def addCustomTransitiveDependency(sourceSourceInfo: AnalysisSourceInfo, targetSourceInfo: AnalysisSourceInfo): Unit = {} - - def getMember: Option[ast.Member] - - def exportGraph(): Unit - - def createLabelNode(labelTerm: Term, sourceChunks: Iterable[Chunk], sourceTerms: Iterable[Term]): Option[LabelNode] = None - def registerInhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: (Term => CH), perm: Term, labelNode: Option[LabelNode], analysisInfo: AnalysisInfo, isExhale: Boolean): CH = buildChunk(perm) - def registerExhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: (Term => CH), perm: Term, analysisInfo: AnalysisInfo): CH = buildChunk(perm) - - def computeProofCoverage(): Option[(Double, Seq[String])] = None - - def exportMergedGraph(): Unit = { - if(Verifier.config.assumptionAnalysisExportPath.isEmpty) return - - val mergedGraph = assumptionGraph.mergeNodesBySource() - - val foldername: Option[String] = getMember map { - case ast.Method(name, _, _, _, _, _) => name - case ast.Function(name, _, _, _, _, _) => name - case ast.Domain(name, _, _, _, _) => name - case contracted: ast.Contracted => contracted.toString() - case location: ast.Location => location.pos.toString - case member: ast.ExtensionMember => member.pos.toString - } - mergedGraph.exportGraph(Verifier.config.assumptionAnalysisExportPath() + "/" + foldername.getOrElse("latestExport") + "_merged") - } + def addCustomTransitiveDependency(sourceSourceInfo: AnalysisSourceInfo, targetSourceInfo: AnalysisSourceInfo): Unit + def finalizeGraph(): Unit + def convertToInterpreter(name: String): Option[AssumptionAnalysisInterpreter] } object AssumptionAnalyzer { - val assumptionTypeAnnotationKey = "assumptionType" - val enableAssumptionAnalysisAnnotationKey = "enableAssumptionAnalysis" + private val assumptionTypeAnnotationKey = "assumptionType" + private val enableAssumptionAnalysisAnnotationKey = "enableAssumptionAnalysis" private def extractAnnotationFromInfo(info: ast.Info, annotationKey: String): Option[Seq[String]] = { info.getAllInfos[ast.AnnotationInfo] @@ -96,15 +64,6 @@ object AssumptionAnalyzer { if(annotation.isDefined && annotation.get.nonEmpty) annotation.get.head.toBooleanOption else None } - def createEnableAnalysisInfo(enableAnalysis: Boolean): ast.AnnotationInfo = - ast.AnnotationInfo(Map((enableAssumptionAnalysisAnnotationKey, Seq(enableAnalysis.toString)))) - - def createAnalysisAnnotationInfo(enableAnalysis: Boolean, assumptionType: AssumptionType): ast.AnnotationInfo = - ast.AnnotationInfo(Map( - (enableAssumptionAnalysisAnnotationKey, Seq(enableAnalysis.toString)), - (assumptionTypeAnnotationKey, Seq(assumptionType.toString)) - )) - def createAssumptionLabel(id: Option[Int]): String = { createLabel("assumption", id) } @@ -131,44 +90,89 @@ object AssumptionAnalyzer { def isAssumptionLabel(label: String): Boolean = label.startsWith("assumption_") def isAxiomLabel(label: String): Boolean = label.startsWith("axiom_") - - def joinGraphs(assumptionAnalysisGraphs: Set[AssumptionAnalysisGraph]): AssumptionAnalysisGraph = { - val newGraph = new DefaultAssumptionAnalysisGraph - assumptionAnalysisGraphs foreach (graph => newGraph.addNodes(graph.nodes)) - assumptionAnalysisGraphs foreach (graph => graph.edges foreach {case (s, t) => newGraph.addEdges(s, t)}) - assumptionAnalysisGraphs foreach (graph => graph.transitiveEdges foreach {case (s, t) => newGraph.addEdges(s, t)}) - val types = Set(AssumptionType.Implicit, AssumptionType.Explicit) - val relevantAssumptionNodes = newGraph.nodes filter (node => node.isInstanceOf[GeneralAssumptionNode] && types.contains(node.assumptionType)) - newGraph.nodes filter (node => node.isInstanceOf[GeneralAssertionNode] && node.assumptionType.equals(AssumptionType.Postcondition)) foreach {node => - val nodeSourceInfoString = node.sourceInfo.getTopLevelSource.toString - val assumptionNodesForJoin = relevantAssumptionNodes filter (aNode => aNode.sourceInfo.getFineGrainedSource.toString.equals(nodeSourceInfoString)) - newGraph.addEdges(node.id, assumptionNodesForJoin map (_.id)) - } - newGraph - } - } class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { - protected var originalLabelNodes: List[LabelNode] = List.empty protected var proofCoverage: Double = 0.0 + override def getMember: Option[ast.Member] = Some(member) + + override def getNodes: Iterable[AssumptionAnalysisNode] = assumptionGraph.nodes + + override def getChunkInhaleNode(chunk: Chunk): Option[PermissionInhaleNode] = { + val inhaleNode = assumptionGraph.nodes + .filter(c => c.isInstanceOf[PermissionInhaleNode] && chunk.equals(c.asInstanceOf[ChunkAnalysisInfo].getChunk)) + .map(_.asInstanceOf[PermissionInhaleNode]) + assert(inhaleNode.size == 1) + inhaleNode.headOption + } + + private def getChunkNodeIds(oldChunks: Set[Chunk]): Set[Int] = { + assumptionGraph.nodes + .filter(c => c.isInstanceOf[PermissionInhaleNode] && oldChunks.contains(c.asInstanceOf[ChunkAnalysisInfo].getChunk)) + .map(_.id).toSet + } + + private def getNodeIdsByTerm(terms: Set[Term]): Set[Int] = { + assumptionGraph.nodes + .filter(t => terms.contains(t.getTerm)) + .map(_.id).toSet + } + + override def addNodes(nodes: Iterable[AssumptionAnalysisNode]): Unit = { assumptionGraph.addNodes(nodes) } - override def getNodes: Iterable[AssumptionAnalysisNode] = assumptionGraph.nodes - override def addNode(node: AssumptionAnalysisNode): Unit = { assumptionGraph.addNode(node) } + // adding assumption nodes override def addAssumption(assumption: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, description: Option[String]): Option[Int] = { val node = SimpleAssumptionNode(assumption, description, analysisSourceInfo, assumptionType, isClosed_) addNode(node) Some(node.id) } + override def registerExhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, analysisInfo: AnalysisInfo): CH = { + val chunk = buildChunk(perm) + val chunkNode = addPermissionExhaleNode(chunk, chunk.perm, analysisInfo.sourceInfo, analysisInfo.assumptionType) + addPermissionDependencies(sourceChunks, Set(), chunkNode) + chunk + } + + override def registerInhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, labelNodeOpt: Option[LabelNode], analysisInfo: AnalysisInfo, isExhale: Boolean): CH = { + val labelNode = labelNodeOpt.get + val chunk = buildChunk(Ite(labelNode.term, perm, NoPerm)) + val chunkNode = addPermissionInhaleNode(chunk, chunk.perm, analysisInfo.sourceInfo, analysisInfo.assumptionType, labelNode) + if(chunkNode.isDefined) + addDependency(chunkNode, Some(labelNode.id)) + addPermissionDependencies(sourceChunks, Set(), chunkNode) + chunk + } + + private def addPermissionInhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, labelNode: LabelNode): Option[Int] = { + val node = PermissionInhaleNode(chunk, permAmount, sourceInfo, assumptionType, isClosed_, labelNode) + addNode(node) + Some(node.id) + } + + override def addPermissionExhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { + val node = PermissionExhaleNode(chunk, permAmount, sourceInfo, assumptionType, isClosed_) + addNode(node) + addPermissionDependencies(Set(chunk), Set(), Some(node.id)) + Some(node.id) + } + + override def createLabelNode(labelTerm: Term, sourceChunks: Iterable[Chunk], sourceTerms: Iterable[Term]): Option[LabelNode] = { + val labelNode = LabelNode(labelTerm) + addNode(labelNode) + assumptionGraph.addEdges(getChunkNodeIds(sourceChunks.toSet) ++ getNodeIdsByTerm(sourceTerms.toSet), labelNode.id) + Some(labelNode) + } + + // adding assertion nodes override def createAssertOrCheckNode(term: Term, assumptionType: AssumptionType, analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] = { if(isCheck) Some(SimpleCheckNode(term, assumptionType, analysisSourceInfo, isClosed_)) @@ -188,6 +192,13 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { Some(node.id) } + + // adding dependencies + override def addDependency(source: Option[Int], dest: Option[Int]): Unit = { + if(source.isDefined && dest.isDefined) + assumptionGraph.addEdges(source.get, Set(dest.get)) + } + override def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit = { val assumptionLabels = dep.replace("(", "").replace(")", "").split(" ") val assumptionIds = assumptionLabels.filter(AssumptionAnalyzer.isAssumptionLabel).map(AssumptionAnalyzer.getIdFromLabel) @@ -199,54 +210,6 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { assumptionGraph.addEdges(axiomIds, assertionIds) } - private def addPermissionInhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, labelNode: LabelNode): Option[Int] = { - val node = PermissionInhaleNode(chunk, permAmount, sourceInfo, assumptionType, isClosed_, labelNode) - addNode(node) - Some(node.id) - } - - override def addPermissionExhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { - val node = PermissionExhaleNode(chunk, permAmount, sourceInfo, assumptionType, isClosed_) - addNode(node) - addPermissionDependencies(Set(chunk), Set(), Some(node.id)) - Some(node.id) - } - - override def getChunkInhaleNode(chunk: Chunk): Option[PermissionInhaleNode] = { - val inhaleNode = assumptionGraph.nodes - .filter(c => c.isInstanceOf[PermissionInhaleNode] && chunk.equals(c.asInstanceOf[ChunkAnalysisInfo].getChunk)) - .map(_.asInstanceOf[PermissionInhaleNode]) - assert(inhaleNode.size == 1) - inhaleNode.headOption - } - - private def getChunkNodeIds(oldChunks: Set[Chunk]): Set[Int] = { - assumptionGraph.nodes - .filter(c => c.isInstanceOf[PermissionInhaleNode] && oldChunks.contains(c.asInstanceOf[ChunkAnalysisInfo].getChunk)) - .map(_.id).toSet - } - - private def getChunkNodeIdsWithSource(oldChunks: Set[Chunk], sourceInfo: AnalysisSourceInfo): Set[Int] = { - assumptionGraph.nodes - .filter(c => c.isInstanceOf[PermissionInhaleNode] && oldChunks.contains(c.asInstanceOf[ChunkAnalysisInfo].getChunk) && c.sourceInfo.equals(sourceInfo)) - .map(_.id).toSet - } - - private def getNodeIdsByTerm(terms: Set[Term]): Set[Int] = { - assumptionGraph.nodes - .filter(t => terms.contains(t.getTerm)) - .map(_.id).toSet - } - - override def addDependencyFromExhaleToInhale(inhaledChunk: Chunk, sourceInfo: AnalysisSourceInfo): Unit = { - val inhaledChunkNodeIds = getChunkNodeIdsWithSource(Set(inhaledChunk), sourceInfo) - assert(inhaledChunkNodeIds.size == 1) - val exhaleNodes = assumptionGraph.nodes - .filter(c => c.isInstanceOf[PermissionExhaleNode] && c.sourceInfo.equals(sourceInfo)) - .map(_.id).toSet - assumptionGraph.addEdges(exhaleNodes, inhaledChunkNodeIds) - } - private def addPermissionDependencies(sourceChunks: Set[Chunk], sourceTerms: Set[Term], newChunkNodeId: Option[Int]): Unit = { if(newChunkNodeId.isEmpty) return @@ -261,95 +224,44 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { addPermissionDependencies(sourceChunks, sourceTerms, newChunkId.headOption) } - override def addDependency(source: Option[Int], dest: Option[Int]): Unit = { - if(source.isDefined && dest.isDefined) - assumptionGraph.addEdges(source.get, Set(dest.get)) - } - override def addCustomTransitiveDependency(sourceSourceInfo: AnalysisSourceInfo, targetSourceInfo: AnalysisSourceInfo): Unit = { val sourceNodes = assumptionGraph.nodes filter (n => n.isInstanceOf[GeneralAssertionNode] && n.sourceInfo.getSourceForTransitiveEdges.equals(sourceSourceInfo.getSourceForTransitiveEdges)) val targetNodes = assumptionGraph.nodes filter (n => n.isInstanceOf[GeneralAssumptionNode] && n.sourceInfo.getSourceForTransitiveEdges.equals(targetSourceInfo.getSourceForTransitiveEdges)) assumptionGraph.addEdges(sourceNodes map (_.id), targetNodes map (_.id)) } - override def getMember: Option[ast.Member] = Some(member) - - override def exportGraph(): Unit = { - if(Verifier.config.assumptionAnalysisExportPath.isEmpty) return - - val foldername: Option[String] = getMember map { - case ast.Method(name, _, _, _, _, _) => name - case ast.Function(name, _, _, _, _, _) => name - case ast.Domain(name, _, _, _, _) => name - case contracted: ast.Contracted => contracted.toString() - case location: ast.Location => location.pos.toString - case member: ast.ExtensionMember => member.pos.toString - } - assumptionGraph.exportGraph(Verifier.config.assumptionAnalysisExportPath() + "/" + foldername.getOrElse("latestExport")) - } - - - override def createLabelNode(labelTerm: Term, sourceChunks: Iterable[Chunk], sourceTerms: Iterable[Term]): Option[LabelNode] = { - val labelNode = LabelNode(labelTerm) - addNode(labelNode) - originalLabelNodes = originalLabelNodes :+ labelNode - assumptionGraph.addEdges(getChunkNodeIds(sourceChunks.toSet) ++ getNodeIdsByTerm(sourceTerms.toSet), labelNode.id) - Some(labelNode) - } - - override def registerExhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: (Term => CH), perm: Term, analysisInfo: AnalysisInfo): CH = { - val chunk = buildChunk(perm) - val chunkNode = addPermissionExhaleNode(chunk, chunk.perm, analysisInfo.sourceInfo, analysisInfo.assumptionType) - addPermissionDependencies(sourceChunks, Set(), chunkNode) - chunk + override def finalizeGraph(): Unit = { + assumptionGraph.removeLabelNodes() + assumptionGraph.addTransitiveEdges() } - override def registerInhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: (Term => CH), perm: Term, labelNodeOpt: Option[LabelNode], analysisInfo: AnalysisInfo, isExhale: Boolean): CH = { - val labelNode = labelNodeOpt.get - val chunk = buildChunk(Ite(labelNode.term, perm, NoPerm)) - val chunkNode = addPermissionInhaleNode(chunk, chunk.perm, analysisInfo.sourceInfo, analysisInfo.assumptionType, labelNode) - if(chunkNode.isDefined) - addDependency(chunkNode, Some(labelNode.id)) - addPermissionDependencies(sourceChunks, Set(), chunkNode) - chunk - } - - override def computeProofCoverage(): Option[(Double, Seq[String])] = { - val explicitAssertionNodes = assumptionGraph.getExplicitAssertionNodes - val explicitAssertionNodeIds = explicitAssertionNodes map (_.id) - val nodesPerSourceInfo = assumptionGraph.getNonInternalAssumptionNodesPerSource - val uncoveredSources = (nodesPerSourceInfo filter { case (_, nodes) => - val nodeIds = (nodes map (_.id)).toSet - // it is not an explicit assertion itself and has no dependency to an explicit assertion - nodeIds.intersect(explicitAssertionNodeIds).isEmpty && - !assumptionGraph.existsAnyDependency(nodeIds, explicitAssertionNodeIds) - }).keys.toSeq - proofCoverage = 1.0 - (uncoveredSources.size.toDouble / nodesPerSourceInfo.size.toDouble) - Some((proofCoverage, uncoveredSources)) - } + override def convertToInterpreter(name: String): Option[AssumptionAnalysisInterpreter] = + Some(new AssumptionAnalysisInterpreter(name, assumptionGraph, getMember)) } class NoAssumptionAnalyzer extends AssumptionAnalyzer { - override def getNodes: Iterable[AssumptionAnalysisNode] = Seq() - override def addNode(node: AssumptionAnalysisNode): Unit = {} - override def addNodes(nodes: Iterable[AssumptionAnalysisNode]): Unit = {} - override def createAssertOrCheckNode(term: Term, assumptionType: AssumptionType, analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] = None + override def getMember: Option[ast.Member] = None - override def addInfeasibilityNode(isCheck: Boolean, sourceInfo: AnalysisSourceInfo): Option[Int] = None - override def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit = { - } + override def getNodes: Iterable[AssumptionAnalysisNode] = Set.empty + override def getChunkInhaleNode(chunk: Chunk): Option[PermissionInhaleNode] = None + override def addNodes(nodes: Iterable[AssumptionAnalysisNode]): Unit = {} + override def addNode(node: AssumptionAnalysisNode): Unit = {} + override def addAssumption(assumption: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, description: Option[String] = None): Option[Int] = None override def addPermissionExhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None + override def createLabelNode(labelTerm: Term, sourceChunks: Iterable[Chunk], sourceTerms: Iterable[Term]): Option[LabelNode] = None - override def addDependencyFromExhaleToInhale(inhaledChunk: Chunk, sourceInfo: AnalysisSourceInfo): Unit = {} + override def createAssertOrCheckNode(term: Term, assumptionType: AssumptionType, analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] = None + override def addAssertFalseNode(isCheck: Boolean, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo): Option[Int] = None + override def addInfeasibilityNode(isCheck: Boolean, sourceInfo: AnalysisSourceInfo): Option[Int] = None - override def addPermissionDependencies(sourceChunks: Set[Chunk], sourceTerms: Set[Term], newChunkNodeId: Chunk): Unit = {} - def addDependency(source: Option[Int], dest: Option[Int]): Unit = {} - override def getMember: Option[ast.Member] = None + override def addDependency(source: Option[Int], dest: Option[Int]): Unit = {} + override def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit = {} + override def addPermissionDependencies(sourceChunks: Set[Chunk], sourceTerms: Set[Term], targetChunk: Chunk): Unit = {} + override def addCustomTransitiveDependency(sourceSourceInfo: AnalysisSourceInfo, targetSourceInfo: AnalysisSourceInfo): Unit = {} - override def addAssumption(term: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, description: Option[String]): Option[Int] = None - override def exportGraph(): Unit = {} + override def finalizeGraph(): Unit = {} - override def addAssertFalseNode(isCheck: Boolean, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo): Option[Int] = None + override def convertToInterpreter(name: String): Option[AssumptionAnalysisInterpreter] = None } \ No newline at end of file diff --git a/src/main/scala/assumptionAnalysis/DependencyAnalysisReporter.scala b/src/main/scala/assumptionAnalysis/DependencyAnalysisReporter.scala index dcfe41dcf..0af7ed3c6 100644 --- a/src/main/scala/assumptionAnalysis/DependencyAnalysisReporter.scala +++ b/src/main/scala/assumptionAnalysis/DependencyAnalysisReporter.scala @@ -1,10 +1,9 @@ package viper.silicon.assumptionAnalysis -import viper.silicon.assumptionAnalysis.AssumptionAnalysisGraph import viper.silver.reporter.{Message, Reporter} case class DependencyAnalysisReporter(name: String = "dependencyAnalysis_reporter", path: String = "report.csv") extends Reporter { - var assumptionAnalyzers: List[AssumptionAnalyzer] = List.empty + var assumptionAnalysisInterpreters: List[AssumptionAnalysisInterpreter] = List.empty override def report(msg: Message): Unit = {} } diff --git a/src/main/scala/interfaces/Verification.scala b/src/main/scala/interfaces/Verification.scala index 4b7ce3f2f..d694f6b78 100644 --- a/src/main/scala/interfaces/Verification.scala +++ b/src/main/scala/interfaces/Verification.scala @@ -6,9 +6,9 @@ package viper.silicon.interfaces -import viper.silicon.assumptionAnalysis.{AssumptionAnalyzer, NoAssumptionAnalyzer} -import viper.silicon.debugger.{DebugAxiom, DebugExp, DebugExpPrintConfiguration} +import viper.silicon.assumptionAnalysis.AssumptionAnalysisInterpreter import viper.silicon.common.collections.immutable.InsertionOrderedSet +import viper.silicon.debugger.{DebugAxiom, DebugExp} import viper.silicon.interfaces.state.Chunk import viper.silicon.reporting._ import viper.silicon.state.terms.{FunctionDecl, MacroDecl, Term} @@ -31,7 +31,7 @@ sealed abstract class VerificationResult { var previous: Vector[VerificationResult] = Vector() //Sets had problems with equality val continueVerification: Boolean = true var isReported: Boolean = false - var assumptionAnalyzer: AssumptionAnalyzer = new NoAssumptionAnalyzer() + var assumptionAnalysisInterpreter: Option[AssumptionAnalysisInterpreter] = None def isFatal: Boolean def &&(other: => VerificationResult): VerificationResult diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index 99a4275b3..3e44a5a9e 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -31,9 +31,9 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif def decider: Decider object methodSupporter extends MethodVerificationUnit with StatefulComponent { + import consumer._ import executor._ import producer._ - import consumer._ private var _units: Seq[ast.Method] = _ @@ -117,7 +117,10 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif consumes(s4, posts, false, postViolated, v4, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Postcondition))((_, _, _) => Success()))}) } )})}) - result.assumptionAnalyzer = v.decider.assumptionAnalyzer + + v.decider.assumptionAnalyzer.finalizeGraph() + result.assumptionAnalysisInterpreter = v.decider.assumptionAnalyzer.convertToInterpreter(method.name) + v.decider.resetProverOptions() symbExLog.closeMemberScope() diff --git a/src/main/scala/supporters/PredicateVerificationUnit.scala b/src/main/scala/supporters/PredicateVerificationUnit.scala index 096e11383..3b1aa2a72 100644 --- a/src/main/scala/supporters/PredicateVerificationUnit.scala +++ b/src/main/scala/supporters/PredicateVerificationUnit.scala @@ -107,7 +107,6 @@ trait DefaultPredicateVerificationUnitProvider extends VerifierComponent { v: Ve } symbExLog.closeMemberScope() - result.assumptionAnalyzer = v.decider.assumptionAnalyzer Seq(result) } diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index 5b76e2b74..6909d154f 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -47,9 +47,9 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver extends FunctionVerificationUnit[Sort, Decl, Term] with StatefulComponent { - import producer._ import consumer._ import evaluator._ + import producer._ @unused private var program: ast.Program = _ /*private*/ var functionData: Map[ast.Function, FunctionData] = Map.empty @@ -160,7 +160,10 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver val res = handleFunction(sInit, function) symbExLog.closeMemberScope() - res.assumptionAnalyzer = v.decider.assumptionAnalyzer + + v.decider.assumptionAnalyzer.finalizeGraph() + res.assumptionAnalysisInterpreter = v.decider.assumptionAnalyzer.convertToInterpreter(function.name) + Seq(res) } diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index 116ba174b..bf4a2d89d 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -6,10 +6,10 @@ package viper.silicon.verifier +import silicon.viper.assumptionAnalysis.AssumptionAnalysisUserTool import viper.silicon.Config.{ExhaleMode, JoinMode} import viper.silicon._ -import assumptionAnalysis.{AssumptionAnalyzer, DefaultAssumptionAnalyzer, DependencyAnalysisReporter} -import silicon.viper.assumptionAnalysis.AssumptionAnalysisUserTool +import viper.silicon.assumptionAnalysis.{AssumptionAnalysisInterpreter, DependencyAnalysisReporter} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.SiliconDebugger import viper.silicon.decider.SMTLib2PreambleReader @@ -315,28 +315,23 @@ class DefaultMainVerifier(config: Config, ++ methodVerificationResults) if(Verifier.config.enableAssumptionAnalysis()){ - val assumptionAnalyzers = verificationResults.filter(_.assumptionAnalyzer.isInstanceOf[DefaultAssumptionAnalyzer]).map(_.assumptionAnalyzer) - - assumptionAnalyzers.foreach(_.assumptionGraph.removeLabelNodes()) - assumptionAnalyzers.foreach(_.assumptionGraph.addTransitiveEdges()) - - assumptionAnalyzers foreach (_.exportGraph()) - assumptionAnalyzers foreach (_.exportMergedGraph()) + val assumptionAnalysisInterpreters = verificationResults.filter(_.assumptionAnalysisInterpreter.isDefined).map(_.assumptionAnalysisInterpreter.get) - assumptionAnalyzers foreach (_.computeProofCoverage()) + assumptionAnalysisInterpreters foreach (_.exportGraph()) + assumptionAnalysisInterpreters foreach (_.exportMergedGraph()) - val joinedGraph = AssumptionAnalyzer.joinGraphs(assumptionAnalyzers.map(_.assumptionGraph).toSet) + val joinedGraphInterpreter = AssumptionAnalysisInterpreter.joinGraphsAndGetInterpreter(assumptionAnalysisInterpreters.toSet) if(Verifier.config.assumptionAnalysisExportPath.isDefined) - joinedGraph.exportGraph("graphExports/joinedGraphs") + joinedGraphInterpreter.exportGraph() if(Verifier.config.startAssumptionAnalysisTool()){ - val commandLineTool = new AssumptionAnalysisUserTool(joinedGraph.mergeNodesBySource(), assumptionAnalyzers) + val commandLineTool = new AssumptionAnalysisUserTool(joinedGraphInterpreter, assumptionAnalysisInterpreters) commandLineTool.run() } reporter match { case analysisReporter: DependencyAnalysisReporter => - analysisReporter.assumptionAnalyzers = assumptionAnalyzers + analysisReporter.assumptionAnalysisInterpreters = assumptionAnalysisInterpreters case _ => } diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index 9127c4940..57c09e432 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -2,9 +2,9 @@ import org.scalatest.funsuite.AnyFunSuite import viper.silicon.SiliconFrontend import viper.silicon.assumptionAnalysis._ +import viper.silver.ast._ import viper.silver.ast.utility.ViperStrategy import viper.silver.ast.utility.rewriter.Traverse -import viper.silver.ast._ import viper.silver.frontend.SilFrontend import viper.silver.verifier.VerificationResult import viper.silver.{ast, verifier} @@ -20,9 +20,10 @@ class AssumptionAnalysisTests extends AnyFunSuite { val ignores: Seq[String] = Seq("example1", "example2") val testDirectories: Seq[String] = Seq( // "dependencyAnalysisTests", - "dependencyAnalysisTests/all", - "dependencyAnalysisTests/unitTests", +// "dependencyAnalysisTests/all", +// "dependencyAnalysisTests/unitTests", // "dependencyAnalysisTests/quick" + "dependencyAnalysisTests/fromSilver" ) val irrelevantKeyword = "irrelevant" @@ -90,11 +91,11 @@ class AssumptionAnalysisTests extends AnyFunSuite { return } - val assumptionAnalyzers = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].assumptionAnalyzers + val assumptionAnalysisInterpreters = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].assumptionAnalysisInterpreters - PruningTest(filePrefix + "/" + fileName, program, assumptionAnalyzers).execute() + PruningTest(filePrefix + "/" + fileName, program, AssumptionAnalysisInterpreter.joinGraphsAndGetInterpreter(assumptionAnalysisInterpreters.toSet)).execute() - AnnotatedTest(program, assumptionAnalyzers).execute() + AnnotatedTest(program, assumptionAnalysisInterpreters).execute() } /** @@ -104,25 +105,24 @@ class AssumptionAnalysisTests extends AnyFunSuite { * * Statements that are only required as a trigger need to be manually annotated with @trigger() by the user. */ - case class PruningTest(fileName: String, program: Program, assumptionAnalyzers: List[AssumptionAnalyzer]) { - private val fullGraph: AssumptionAnalysisGraph = AssumptionAnalyzer.joinGraphs(assumptionAnalyzers.map(_.assumptionGraph).toSet).mergeNodesBySource() + case class PruningTest(fileName: String, program: Program, fullGraphInterpreter: AssumptionAnalysisInterpreter) { def execute(): Unit = { - val triggerNodes = fullGraph.nodes.filter(node => node.sourceInfo.getTopLevelSource.toString.contains("@trigger()")) + val triggerNodeLines = fullGraphInterpreter.getNodes.filter(node => node.sourceInfo.getTopLevelSource.toString.contains("@trigger()")).flatMap(_.sourceInfo.getLineNumber) // TODO ake: we also need all dependencies of trigger nodes! var id: Int = 0 - fullGraph.getExplicitAssertionNodes.groupBy(_.sourceInfo.getPositionString).foreach { case (_, nodes) => - pruneAndVerify(nodes ++ triggerNodes, "src/test/resources/" + fileName + s"_test$id.out") + // TODO ake: safer would be to work with position string instead of line numbers + fullGraphInterpreter.getExplicitAssertionNodes flatMap (_.sourceInfo.getLineNumber) foreach {line => + pruneAndVerify(Set(line) ++ triggerNodeLines, "src/test/resources/" + fileName + s"_test$id.out") id += 1 } } - private def pruneAndVerify(nodesToAnalyze: Set[AssumptionAnalysisNode], exportFileName: String): Unit = { - val sourcePositionsToAnalyze = nodesToAnalyze map (_.sourceInfo.getPositionString) - val explicitAssertionNodeIds = fullGraph.nodes.filter(n => sourcePositionsToAnalyze.contains(n.sourceInfo.getPositionString)).map(_.id).toSet + private def pruneAndVerify(relevantLines: Set[Int], exportFileName: String): Unit = { + val relevantNodes = relevantLines.flatMap(line => fullGraphInterpreter.getNodesByLine(line)) - val dependencies = fullGraph.getAllNonInternalDependencies(explicitAssertionNodeIds) + val dependencies = fullGraphInterpreter.getAllNonInternalDependencies(relevantNodes.map(_.id)) - val crucialNodes = nodesToAnalyze ++ dependencies + val crucialNodes = relevantNodes ++ dependencies val (newProgram, pruningFactor) = getPrunedProgram(crucialNodes) val result = frontend.verifier.verify(newProgram) exportPrunedProgram(exportFileName, newProgram, pruningFactor, result) @@ -220,16 +220,16 @@ class AssumptionAnalysisTests extends AnyFunSuite { * but multiple dependency/irrelevant annotations are allowed * */ - case class AnnotatedTest(program: Program, assumptionAnalyzers: List[AssumptionAnalyzer]) { + case class AnnotatedTest(program: Program, assumptionAnalysisInterpreters: List[AssumptionAnalysisInterpreter]) { def execute(): Unit = { - val assumptionGraphs = assumptionAnalyzers map (_.assumptionGraph) val stmtsWithAssumptionAnnotation: Set[Infoed] = extractAnnotatedStmts({ annotationInfo => annotationInfo.values.contains(irrelevantKeyword) || annotationInfo.values.contains(dependencyKeyword) }) - val allAssumptionNodes = assumptionGraphs.flatMap(_.nodes.filter(_.isInstanceOf[GeneralAssumptionNode])) + val allAssumptionNodes = assumptionAnalysisInterpreters.flatMap(_.getNodes.filter(_.isInstanceOf[GeneralAssumptionNode])) // TODO ake: move to interpreter + // TODO ake: review all methods and see if we can reuse interpreter functionality var errorMsgs = stmtsWithAssumptionAnnotation.map(checkAssumptionNodeExists(allAssumptionNodes, _)).filter(_.isDefined).map(_.get).toSeq - errorMsgs ++= assumptionAnalyzers flatMap checkTestAssertionNodeExists - errorMsgs ++= assumptionGraphs flatMap checkDependencies - val warnMsgs = assumptionGraphs flatMap checkNonDependencies + errorMsgs ++= assumptionAnalysisInterpreters flatMap checkTestAssertionNodeExists + errorMsgs ++= assumptionAnalysisInterpreters flatMap checkDependencies + val warnMsgs = assumptionAnalysisInterpreters flatMap checkNonDependencies if (CHECK_PRECISION) errorMsgs ++= warnMsgs else if (warnMsgs.nonEmpty) println(warnMsgs.mkString("\n")) // TODO ake: warning @@ -273,66 +273,65 @@ class AssumptionAnalysisTests extends AnyFunSuite { } } - private def checkTestAssertionNodeExists(assumptionAnalyzer: AssumptionAnalyzer): Seq[String] = { - val assumptionNodes = extractTestAssumptionNodesFromGraph(assumptionAnalyzer.assumptionGraph) ++ extractTestIrrelevantAssumptionNodesFromGraph(assumptionAnalyzer.assumptionGraph) - val assertionNodes = extractTestAssertionNodesFromGraph(assumptionAnalyzer.assumptionGraph) + private def checkTestAssertionNodeExists(assumptionAnalysisInterpreter: AssumptionAnalysisInterpreter): Seq[String] = { + val assumptionNodes = getTestAssumptionNodes(assumptionAnalysisInterpreter.getNodes) ++ getTestIrrelevantAssumptionNodes(assumptionAnalysisInterpreter.getNodes) + val assertionNodes = getTestAssertionNodes(assumptionAnalysisInterpreter.getNodes) if (assumptionNodes.nonEmpty && assertionNodes.isEmpty) - Seq(s"Missing testAssertion for member: ${assumptionAnalyzer.getMember.map(_.name).getOrElse("unknown")}") + Seq(s"Missing testAssertion for member: ${assumptionAnalysisInterpreter.getName}") else Seq.empty } - private def checkDependencies(assumptionGraph: AssumptionAnalysisGraph): Seq[String] = { - val assumptionNodes = extractTestAssumptionNodesFromGraph(assumptionGraph) + // TODO ake: check explicit and non internal dependencies using the interpreter functionalities + private def checkDependencies(assumptionAnalysisInterpreter: AssumptionAnalysisInterpreter): Seq[String] = { + val assumptionNodes = getTestAssumptionNodes(assumptionAnalysisInterpreter.getNodes) val assumptionsPerSource = assumptionNodes groupBy (n => extractSourceLine(n.sourceInfo.getPosition)) - val assertionNodes = extractTestAssertionNodesFromGraph(assumptionGraph) + val assertionNodes = getTestAssertionNodes(assumptionAnalysisInterpreter.getNodes) + + val dependencies = assumptionAnalysisInterpreter.getAllNonInternalDependencies(assertionNodes.map(_.id)).map(_.id) assumptionsPerSource.map({ case (_, assumptions) => - val hasDeps = checkDependenciesForSingleSource(assumptionGraph, assumptions, assertionNodes) - Option.when(!hasDeps)(s"Missing dependency: ${assumptions.head.sourceInfo.toString}") + val hasDependency = dependencies.intersect(assumptions.map(_.id)).nonEmpty + Option.when(!hasDependency)(s"Missing dependency: ${assumptions.head.sourceInfo.toString}") }).filter(_.isDefined).map(_.get).toSeq } - private def checkNonDependencies(assumptionGraph: AssumptionAnalysisGraph): Seq[String] = { - val assumptionNodes = extractTestIrrelevantAssumptionNodesFromGraph(assumptionGraph) + private def checkNonDependencies(assumptionAnalysisInterpreter: AssumptionAnalysisInterpreter): Seq[String] = { + val assumptionNodes = getTestIrrelevantAssumptionNodes(assumptionAnalysisInterpreter.getNodes) val assumptionsPerSource = assumptionNodes groupBy (n => extractSourceLine(n.sourceInfo.getPosition)) - val assertionNodes = extractTestAssertionNodesFromGraph(assumptionGraph) + val assertionNodes = getTestAssertionNodes(assumptionAnalysisInterpreter.getNodes) + + val dependencies = assumptionAnalysisInterpreter.getAllNonInternalDependencies(assertionNodes.map(_.id)).map(_.id) assumptionsPerSource.map({ case (_, assumptions) => - val hasDependency = checkDependenciesForSingleSource(assumptionGraph, assumptions, assertionNodes) + val hasDependency = dependencies.intersect(assumptions.map(_.id)).nonEmpty Option.when(hasDependency)(s"Unexpected dependency: ${assumptions.head.sourceInfo.toString}") }).filter(_.isDefined).map(_.get).toSeq } - private def checkDependenciesForSingleSource(assumptionGraph: AssumptionAnalysisGraph, assumptions: Seq[AssumptionAnalysisNode], assertions: Seq[AssumptionAnalysisNode]): Boolean = { - assumptions exists (assumption => { - assertions exists (assertion => assumptionGraph.existsAnyDependency(Set(assumption.id), Set(assertion.id))) - }) - } - - private def extractTestAssertionNodesFromGraph(graph: AssumptionAnalysisGraph): Seq[AssumptionAnalysisNode] = { - graph.nodes.filter(node => + private def getTestAssertionNodes(nodes: Set[AssumptionAnalysisNode]): Set[AssumptionAnalysisNode] = { + nodes.filter(node => (node.getNodeType.equals("Assertion") || node.getNodeType.equals("Exhale") || node.getNodeType.equals("Check")) && node.sourceInfo.toString.contains("@" + testAssertionKeyword + "(") - ).toSeq + ) } - private def extractTestAssumptionNodesFromGraph(graph: AssumptionAnalysisGraph): Seq[AssumptionAnalysisNode] = { - graph.nodes.filter(node => { + private def getTestAssumptionNodes(nodes: Set[AssumptionAnalysisNode]): Set[AssumptionAnalysisNode] = { + nodes.filter(node => { (node.getNodeType.equals("Assumption") || node.getNodeType.equals("Inhale") || node.getNodeType.equals("Infeasible")) && !node.assumptionType.equals(AssumptionType.Internal) && node.sourceInfo.toString.contains("@" + dependencyKeyword + "(") } - ).toSeq + ) } - private def extractTestIrrelevantAssumptionNodesFromGraph(graph: AssumptionAnalysisGraph): Seq[AssumptionAnalysisNode] = { - graph.nodes.filter(node => { + private def getTestIrrelevantAssumptionNodes(nodes: Set[AssumptionAnalysisNode]): Set[AssumptionAnalysisNode] = { + nodes.filter(node => { (node.getNodeType.equals("Assumption") || node.getNodeType.equals("Inhale") || node.getNodeType.equals("Infeasible")) && !node.assumptionType.equals(AssumptionType.Internal) && node.sourceInfo.toString.contains("@" + irrelevantKeyword + "(") } - ).toSeq + ) } } } From 98cb910167e79582e6112c6a30207729b360ec82 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 16 Jul 2025 09:41:35 +0200 Subject: [PATCH 174/474] fix test and add convenience methods to interpreter --- .../AssumptionAnalysisGraph.scala | 12 ++- .../AssumptionAnalysisInterpreter.scala | 84 +++++++------------ .../AssumptionAnalysisNode.scala | 5 +- .../AssumptionAnalysisUserTool.scala | 2 +- .../AssumptionAnalyzer.scala | 2 +- .../scala/verifier/DefaultMainVerifier.scala | 4 +- src/test/scala/AssumptionAnalysisTests.scala | 62 +++++--------- 7 files changed, 72 insertions(+), 99 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index de613bcc7..dce5a91ba 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -32,7 +32,14 @@ class AssumptionAnalysisGraph extends ReadOnlyAssumptionAnalysisGraph { def getNodes: Seq[AssumptionAnalysisNode] = nodes.toSeq def getDirectEdges: Map[Int, Set[Int]] = edges.toMap def getTransitiveEdges: Map[Int, Set[Int]] = transitiveEdges.toMap - def getAllEdges: Map[Int, Set[Int]] = (edges ++ transitiveEdges).toMap + def getAllEdges: Map[Int, Set[Int]] = { + val keys = edges.keySet ++ transitiveEdges.keySet + val res = mutable.Map[Int, Set[Int]]() + keys foreach {key => + res.update(key, edges.getOrElse(key, Set()) ++ transitiveEdges.getOrElse(key, Set())) + } + res.toMap + } def addNode(node: AssumptionAnalysisNode): Unit = { nodes = nodes :+ node @@ -60,9 +67,10 @@ class AssumptionAnalysisGraph extends ReadOnlyAssumptionAnalysisGraph { def existsAnyDependency(sources: Set[Int], targets: Set[Int]): Boolean = { var visited: Set[Int] = sources var queue: List[Int] = sources.toList + val allEdges = getAllEdges while(queue.nonEmpty){ val curr = queue.head - val newVisits = edges.getOrElse(curr, Set()) ++ transitiveEdges.getOrElse(curr, Set()) + val newVisits = allEdges.getOrElse(curr, Set()) if(newVisits.intersect(targets).nonEmpty) return true visited = visited ++ Set(curr) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala index 235aee355..6e82c7aef 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala @@ -1,15 +1,13 @@ package viper.silicon.assumptionAnalysis -import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType import viper.silicon.state.terms.True import viper.silicon.verifier.Verifier import viper.silver.ast -import viper.silver.ast.Position import scala.collection.mutable object AssumptionAnalysisInterpreter { - def joinGraphsAndGetInterpreter(assumptionAnalysisInterpreters: Set[AssumptionAnalysisInterpreter]): AssumptionAnalysisInterpreter = { + def joinGraphsAndGetInterpreter(name: Option[String], assumptionAnalysisInterpreters: Set[AssumptionAnalysisInterpreter]): AssumptionAnalysisInterpreter = { val newGraph = new AssumptionAnalysisGraph assumptionAnalysisInterpreters foreach (interpreter => newGraph.addNodes(interpreter.getGraph.getNodes)) @@ -24,7 +22,7 @@ object AssumptionAnalysisInterpreter { newGraph.addEdges(node.id, assumptionNodesForJoin map (_.id)) } - new AssumptionAnalysisInterpreter("joined", newGraph) + new AssumptionAnalysisInterpreter(name.getOrElse("joined"), newGraph) } } @@ -37,62 +35,42 @@ class AssumptionAnalysisInterpreter(name: String, graph: ReadOnlyAssumptionAnaly def getNodesByLine(line: Int): Set[AssumptionAnalysisNode] = getNodes.filter(node => node.sourceInfo.getLineNumber.isDefined && node.sourceInfo.getLineNumber.get == line) - def getDirectDependencies(nodeIdsToAnalyze: Set[Int]): Set[AssumptionAnalysisNode] = { - graph.getNodes.filter(node => graph.getDirectEdges.get(node.id).exists(_.intersect(nodeIdsToAnalyze).nonEmpty) && - ((node.isInstanceOf[GeneralAssumptionNode] && !node.assumptionType.equals(AssumptionType.Internal)) || - (node.isInstanceOf[GeneralAssertionNode] && node.assumptionType.equals(AssumptionType.Postcondition)) - || node.isInstanceOf[InfeasibilityNode]) - ).toSet - } + def getDirectDependencies(nodeIdsToAnalyze: Set[Int]): Set[AssumptionAnalysisNode] = + getNonInternalAssumptionNodes.filter(node => graph.getDirectEdges.get(node.id).exists(_.intersect(nodeIdsToAnalyze).nonEmpty)) - def getAllNonInternalDependencies(nodeIdsToAnalyze: Set[Int]): Set[AssumptionAnalysisNode] = { - graph.getNodes.filter(node => - ((node.isInstanceOf[GeneralAssumptionNode] && !node.assumptionType.equals(AssumptionType.Internal)) || - (node.isInstanceOf[GeneralAssertionNode] && node.assumptionType.equals(AssumptionType.Postcondition)) - || node.isInstanceOf[InfeasibilityNode]) && - graph.existsAnyDependency(Set(node.id), nodeIdsToAnalyze)).toSet - } + def getAllNonInternalDependencies(nodeIdsToAnalyze: Set[Int]): Set[AssumptionAnalysisNode] = + getNonInternalAssumptionNodes.filter(node => graph.existsAnyDependency(Set(node.id), nodeIdsToAnalyze)) - def getAllExplicitDependencies(nodeIdsToAnalyze: Set[Int]): Set[AssumptionAnalysisNode] = { - getAllNonInternalDependencies(nodeIdsToAnalyze).filter(_.assumptionType.equals(AssumptionType.Explicit)) - } + def getAllExplicitDependencies(nodeIdsToAnalyze: Set[Int]): Set[AssumptionAnalysisNode] = + getExplicitAssumptionNodes.filter(node => graph.existsAnyDependency(Set(node.id), nodeIdsToAnalyze)) - def getAllNonInternalDependendents(nodeIdsToAnalyze: Set[Int]): Set[AssumptionAnalysisNode] = { - graph.getNodes.filter(node => - ((node.isInstanceOf[GeneralAssertionNode] && !node.assumptionType.equals(AssumptionType.Internal)) - || node.isInstanceOf[InfeasibilityNode]) && - graph.existsAnyDependency(nodeIdsToAnalyze, Set(node.id))).toSet - } - private def getNodesByProperties(nodeType: Option[String], assumptionType: Option[AssumptionType], sourceInfo: Option[String], position: Option[Position]): Seq[AssumptionAnalysisNode] = { - graph.getNodes filter (node => - nodeType.forall(node.getNodeType.equals) && - assumptionType.forall(node.assumptionType.equals) && - sourceInfo.forall(node.sourceInfo.toString.equals) && - position.forall(node.sourceInfo.getPosition.equals) - ) - } + def getAllNonInternalDependents(nodeIdsToAnalyze: Set[Int]): Set[AssumptionAnalysisNode] = + getNonInternalAssertionNodes.filter(node => graph.existsAnyDependency(nodeIdsToAnalyze, Set(node.id))) - def getExplicitAssertionNodes: Set[AssumptionAnalysisNode] = { - (getNodesByProperties(Some("Assertion"), Some(AssumptionType.Explicit), None, None) ++ - getNodesByProperties(Some("Assertion"), Some(AssumptionType.Postcondition), None, None) ++ - getNodesByProperties(Some("Exhale"), Some(AssumptionType.Explicit), None, None) ++ - getNodesByProperties(Some("Exhale"), Some(AssumptionType.Postcondition), None, None)).toSet - } + def getNonInternalAssumptionNodes: Set[AssumptionAnalysisNode] = getNodes filter (node => + (node.isInstanceOf[GeneralAssumptionNode] && !node.assumptionType.equals(AssumptionType.Internal)) || + (node.isInstanceOf[GeneralAssertionNode] && node.assumptionType.equals(AssumptionType.Postcondition)) + ) - private def getNonInternalAssumptionNodesPerSource: Map[String, Seq[AssumptionAnalysisNode]] = { - getNodesPerSourceInfo filter {case (_, nodes) => - nodes exists (node => - node.isInstanceOf[GeneralAssumptionNode] && - !node.assumptionType.equals(AssumptionType.Internal) && - !node.assumptionType.equals(AssumptionType.Trigger) && - !node.assumptionType.equals(AssumptionType.Axiom)) - } - } + def getExplicitAssumptionNodes: Set[AssumptionAnalysisNode] = getNodes filter (node => + node.assumptionType.equals(AssumptionType.Explicit) || node.assumptionType.equals(AssumptionType.Postcondition)) + + private def getNonInternalAssumptionNodesPerSource: Map[String, Set[AssumptionAnalysisNode]] = + getNonInternalAssumptionNodes.groupBy(_.sourceInfo.getTopLevelSource.toString) + + + def getAssertionNodes: Set[AssumptionAnalysisNode] = + getNodes filter (node => node.isInstanceOf[GeneralAssertionNode]) + + def getNonInternalAssertionNodes: Set[AssumptionAnalysisNode] = getNodes filter (node => + node.isInstanceOf[GeneralAssertionNode] && !node.assumptionType.equals(AssumptionType.Internal) + ) + + def getExplicitAssertionNodes: Set[AssumptionAnalysisNode] = + getNonInternalAssertionNodes.filter(node => + node.assumptionType.equals(AssumptionType.Postcondition) || node.assumptionType.equals(AssumptionType.Explicit)) - private def getNodesPerSourceInfo: Map[String, Seq[AssumptionAnalysisNode]] = { - graph.getNodes.groupBy(_.sourceInfo.getTopLevelSource.toString) - } def exportGraph(): Unit = { if(Verifier.config.assumptionAnalysisExportPath.isEmpty) return diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisNode.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisNode.scala index 7c50ea153..65fa05daa 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisNode.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisNode.scala @@ -16,6 +16,9 @@ trait AssumptionAnalysisNode { def getNodeString: String def getNodeType: String + + override def hashCode(): Int = + toString.hashCode } trait GeneralAssumptionNode extends AssumptionAnalysisNode { @@ -62,7 +65,7 @@ case class LabelNode(term: Term) extends GeneralAssumptionNode { override def getNodeString: String = "assume " + description } -case class InfeasibilityNode(sourceInfo: AnalysisSourceInfo) extends AssumptionAnalysisNode { +case class InfeasibilityNode(sourceInfo: AnalysisSourceInfo) extends GeneralAssumptionNode { val term: Term = False val assumptionType: AssumptionType = AssumptionType.Implicit val isClosed: Boolean = true diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala index 260e0eb3e..509d0d6c2 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala @@ -73,7 +73,7 @@ class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpr val directDependencies = getSourceInfoString(fullGraphInterpreter.getDirectDependencies(queriedNodes.map(_.id))) val allDependencies = getSourceInfoString(fullGraphInterpreter.getAllNonInternalDependencies(queriedNodes.map(_.id))) val explicitDependencies = getSourceInfoString(fullGraphInterpreter.getAllExplicitDependencies(queriedNodes.map(_.id))) - val dependents = getSourceInfoString(fullGraphInterpreter.getAllNonInternalDependendents(queriedNodes.map(_.id))) + val dependents = getSourceInfoString(fullGraphInterpreter.getAllNonInternalDependents(queriedNodes.map(_.id))) println(s"Queried:\n\t${queriedNodes.map(_.sourceInfo.getTopLevelSource.toString).mkString("\n\t")}") diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 32ef86abc..e86800013 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -20,7 +20,7 @@ trait AssumptionAnalyzer { def getMember: Option[ast.Member] - def getNodes: Iterable[AssumptionAnalysisNode] + def getNodes: Iterable[AssumptionAnalysisNode] // TODO ake: remove? def getChunkInhaleNode(chunk: Chunk): Option[PermissionInhaleNode] def addNodes(nodes: Iterable[AssumptionAnalysisNode]): Unit diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index bf4a2d89d..328401fd9 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -320,9 +320,9 @@ class DefaultMainVerifier(config: Config, assumptionAnalysisInterpreters foreach (_.exportGraph()) assumptionAnalysisInterpreters foreach (_.exportMergedGraph()) - val joinedGraphInterpreter = AssumptionAnalysisInterpreter.joinGraphsAndGetInterpreter(assumptionAnalysisInterpreters.toSet) + val joinedGraphInterpreter = AssumptionAnalysisInterpreter.joinGraphsAndGetInterpreter(inputFile.map(_.replaceAll("\\\\", "_").replaceAll(".vpr", "")), assumptionAnalysisInterpreters.toSet) if(Verifier.config.assumptionAnalysisExportPath.isDefined) - joinedGraphInterpreter.exportGraph() + joinedGraphInterpreter.exportMergedGraph() if(Verifier.config.startAssumptionAnalysisTool()){ val commandLineTool = new AssumptionAnalysisUserTool(joinedGraphInterpreter, assumptionAnalysisInterpreters) diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index 57c09e432..6f8fad556 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -20,10 +20,10 @@ class AssumptionAnalysisTests extends AnyFunSuite { val ignores: Seq[String] = Seq("example1", "example2") val testDirectories: Seq[String] = Seq( // "dependencyAnalysisTests", -// "dependencyAnalysisTests/all", -// "dependencyAnalysisTests/unitTests", + "dependencyAnalysisTests/all", + "dependencyAnalysisTests/unitTests", // "dependencyAnalysisTests/quick" - "dependencyAnalysisTests/fromSilver" +// "dependencyAnalysisTests/fromSilver" ) val irrelevantKeyword = "irrelevant" @@ -93,9 +93,8 @@ class AssumptionAnalysisTests extends AnyFunSuite { val assumptionAnalysisInterpreters = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].assumptionAnalysisInterpreters - PruningTest(filePrefix + "/" + fileName, program, AssumptionAnalysisInterpreter.joinGraphsAndGetInterpreter(assumptionAnalysisInterpreters.toSet)).execute() - AnnotatedTest(program, assumptionAnalysisInterpreters).execute() + PruningTest(filePrefix + "/" + fileName, program, AssumptionAnalysisInterpreter.joinGraphsAndGetInterpreter(Some(fileName), assumptionAnalysisInterpreters.toSet)).execute() } /** @@ -223,9 +222,8 @@ class AssumptionAnalysisTests extends AnyFunSuite { case class AnnotatedTest(program: Program, assumptionAnalysisInterpreters: List[AssumptionAnalysisInterpreter]) { def execute(): Unit = { val stmtsWithAssumptionAnnotation: Set[Infoed] = extractAnnotatedStmts({ annotationInfo => annotationInfo.values.contains(irrelevantKeyword) || annotationInfo.values.contains(dependencyKeyword) }) - val allAssumptionNodes = assumptionAnalysisInterpreters.flatMap(_.getNodes.filter(_.isInstanceOf[GeneralAssumptionNode])) // TODO ake: move to interpreter + val allAssumptionNodes = assumptionAnalysisInterpreters.flatMap(_.getNonInternalAssumptionNodes) - // TODO ake: review all methods and see if we can reuse interpreter functionality var errorMsgs = stmtsWithAssumptionAnnotation.map(checkAssumptionNodeExists(allAssumptionNodes, _)).filter(_.isDefined).map(_.get).toSeq errorMsgs ++= assumptionAnalysisInterpreters flatMap checkTestAssertionNodeExists errorMsgs ++= assumptionAnalysisInterpreters flatMap checkDependencies @@ -258,8 +256,6 @@ class AssumptionAnalysisTests extends AnyFunSuite { .map(ai => ai.values.getOrElse(irrelevantKeyword, ai.values.getOrElse(dependencyKeyword, List.empty))).getOrElse(List.empty) val assumptionType = annotationInfo.map(AssumptionType.fromString).filter(_.isDefined).map(_.get) val nodeExists = analysisNodes exists (analysisNode => { - analysisNode.isInstanceOf[GeneralAssumptionNode] && - !analysisNode.asInstanceOf[GeneralAssumptionNode].assumptionType.equals(AssumptionType.Internal) && extractSourceLine(analysisNode.sourceInfo.getPosition) == pos && assumptionType.forall(_.equals(analysisNode.assumptionType)) }) @@ -274,21 +270,22 @@ class AssumptionAnalysisTests extends AnyFunSuite { } private def checkTestAssertionNodeExists(assumptionAnalysisInterpreter: AssumptionAnalysisInterpreter): Seq[String] = { - val assumptionNodes = getTestAssumptionNodes(assumptionAnalysisInterpreter.getNodes) ++ getTestIrrelevantAssumptionNodes(assumptionAnalysisInterpreter.getNodes) - val assertionNodes = getTestAssertionNodes(assumptionAnalysisInterpreter.getNodes) + val assumptionNodes = getTestAssumptionNodes(assumptionAnalysisInterpreter.getNonInternalAssumptionNodes) ++ getTestIrrelevantAssumptionNodes(assumptionAnalysisInterpreter.getNonInternalAssumptionNodes) + val assertionNodes = getTestAssertionNodes(assumptionAnalysisInterpreter.getNonInternalAssertionNodes) if (assumptionNodes.nonEmpty && assertionNodes.isEmpty) Seq(s"Missing testAssertion for member: ${assumptionAnalysisInterpreter.getName}") else Seq.empty } - // TODO ake: check explicit and non internal dependencies using the interpreter functionalities + private def checkDependencies(assumptionAnalysisInterpreter: AssumptionAnalysisInterpreter): Seq[String] = { - val assumptionNodes = getTestAssumptionNodes(assumptionAnalysisInterpreter.getNodes) + val assumptionNodes = getTestAssumptionNodes(assumptionAnalysisInterpreter.getNonInternalAssumptionNodes) val assumptionsPerSource = assumptionNodes groupBy (n => extractSourceLine(n.sourceInfo.getPosition)) - val assertionNodes = getTestAssertionNodes(assumptionAnalysisInterpreter.getNodes) + val assertionNodes = getTestAssertionNodes(assumptionAnalysisInterpreter.getAssertionNodes) - val dependencies = assumptionAnalysisInterpreter.getAllNonInternalDependencies(assertionNodes.map(_.id)).map(_.id) + val dependenciesTmp = assumptionAnalysisInterpreter.getAllNonInternalDependencies(assertionNodes.map(_.id)) + val dependencies = dependenciesTmp.map(_.id) assumptionsPerSource.map({ case (_, assumptions) => val hasDependency = dependencies.intersect(assumptions.map(_.id)).nonEmpty @@ -297,9 +294,9 @@ class AssumptionAnalysisTests extends AnyFunSuite { } private def checkNonDependencies(assumptionAnalysisInterpreter: AssumptionAnalysisInterpreter): Seq[String] = { - val assumptionNodes = getTestIrrelevantAssumptionNodes(assumptionAnalysisInterpreter.getNodes) + val assumptionNodes = getTestIrrelevantAssumptionNodes(assumptionAnalysisInterpreter.getNonInternalAssumptionNodes) val assumptionsPerSource = assumptionNodes groupBy (n => extractSourceLine(n.sourceInfo.getPosition)) - val assertionNodes = getTestAssertionNodes(assumptionAnalysisInterpreter.getNodes) + val assertionNodes = getTestAssertionNodes(assumptionAnalysisInterpreter.getAssertionNodes) val dependencies = assumptionAnalysisInterpreter.getAllNonInternalDependencies(assertionNodes.map(_.id)).map(_.id) @@ -309,29 +306,16 @@ class AssumptionAnalysisTests extends AnyFunSuite { }).filter(_.isDefined).map(_.get).toSeq } - private def getTestAssertionNodes(nodes: Set[AssumptionAnalysisNode]): Set[AssumptionAnalysisNode] = { - nodes.filter(node => - (node.getNodeType.equals("Assertion") || node.getNodeType.equals("Exhale") || node.getNodeType.equals("Check")) && - node.sourceInfo.toString.contains("@" + testAssertionKeyword + "(") - ) - } + private def getTestAssertionNodes(nodes: Set[AssumptionAnalysisNode]): Set[AssumptionAnalysisNode] = + nodes.filter(node => node.sourceInfo.toString.contains("@" + testAssertionKeyword + "(")) - private def getTestAssumptionNodes(nodes: Set[AssumptionAnalysisNode]): Set[AssumptionAnalysisNode] = { - nodes.filter(node => { - (node.getNodeType.equals("Assumption") || node.getNodeType.equals("Inhale") || node.getNodeType.equals("Infeasible")) && - !node.assumptionType.equals(AssumptionType.Internal) && - node.sourceInfo.toString.contains("@" + dependencyKeyword + "(") - } - ) - } - private def getTestIrrelevantAssumptionNodes(nodes: Set[AssumptionAnalysisNode]): Set[AssumptionAnalysisNode] = { - nodes.filter(node => { - (node.getNodeType.equals("Assumption") || node.getNodeType.equals("Inhale") || node.getNodeType.equals("Infeasible")) && - !node.assumptionType.equals(AssumptionType.Internal) && - node.sourceInfo.toString.contains("@" + irrelevantKeyword + "(") - } - ) - } + private def getTestAssumptionNodes(nodes: Set[AssumptionAnalysisNode]): Set[AssumptionAnalysisNode] = + nodes.filter(_.sourceInfo.toString.contains("@" + dependencyKeyword + "(")) + + + private def getTestIrrelevantAssumptionNodes(nodes: Set[AssumptionAnalysisNode]): Set[AssumptionAnalysisNode] = + nodes.filter(_.sourceInfo.toString.contains("@" + irrelevantKeyword + "(")) + } } From 7d7a0b4a46f53725e2e6cb0b13c65bd34991fa3b Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 16 Jul 2025 09:52:34 +0200 Subject: [PATCH 175/474] minor fix --- .../assumptionAnalysis/AssumptionAnalysisInterpreter.scala | 6 ++---- src/test/scala/AssumptionAnalysisTests.scala | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala index 6e82c7aef..5dfe67714 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala @@ -48,6 +48,7 @@ class AssumptionAnalysisInterpreter(name: String, graph: ReadOnlyAssumptionAnaly def getAllNonInternalDependents(nodeIdsToAnalyze: Set[Int]): Set[AssumptionAnalysisNode] = getNonInternalAssertionNodes.filter(node => graph.existsAnyDependency(nodeIdsToAnalyze, Set(node.id))) + def getNonInternalAssumptionNodes: Set[AssumptionAnalysisNode] = getNodes filter (node => (node.isInstanceOf[GeneralAssumptionNode] && !node.assumptionType.equals(AssumptionType.Internal)) || (node.isInstanceOf[GeneralAssertionNode] && node.assumptionType.equals(AssumptionType.Postcondition)) @@ -60,9 +61,6 @@ class AssumptionAnalysisInterpreter(name: String, graph: ReadOnlyAssumptionAnaly getNonInternalAssumptionNodes.groupBy(_.sourceInfo.getTopLevelSource.toString) - def getAssertionNodes: Set[AssumptionAnalysisNode] = - getNodes filter (node => node.isInstanceOf[GeneralAssertionNode]) - def getNonInternalAssertionNodes: Set[AssumptionAnalysisNode] = getNodes filter (node => node.isInstanceOf[GeneralAssertionNode] && !node.assumptionType.equals(AssumptionType.Internal) ) @@ -126,7 +124,7 @@ class AssumptionAnalysisInterpreter(name: String, graph: ReadOnlyAssumptionAnaly val explicitAssertionNodeIds = explicitAssertionNodes map (_.id) val nodesPerSourceInfo = getNonInternalAssumptionNodesPerSource val uncoveredSources = (nodesPerSourceInfo filter { case (_, nodes) => - val nodeIds = (nodes map (_.id)).toSet + val nodeIds = (nodes map (_.id)) // it is not an explicit assertion itself and has no dependency to an explicit assertion nodeIds.intersect(explicitAssertionNodeIds).isEmpty && !graph.existsAnyDependency(nodeIds, explicitAssertionNodeIds) diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index 6f8fad556..9e33f3d10 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -282,7 +282,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { private def checkDependencies(assumptionAnalysisInterpreter: AssumptionAnalysisInterpreter): Seq[String] = { val assumptionNodes = getTestAssumptionNodes(assumptionAnalysisInterpreter.getNonInternalAssumptionNodes) val assumptionsPerSource = assumptionNodes groupBy (n => extractSourceLine(n.sourceInfo.getPosition)) - val assertionNodes = getTestAssertionNodes(assumptionAnalysisInterpreter.getAssertionNodes) + val assertionNodes = getTestAssertionNodes(assumptionAnalysisInterpreter.getNonInternalAssertionNodes) val dependenciesTmp = assumptionAnalysisInterpreter.getAllNonInternalDependencies(assertionNodes.map(_.id)) val dependencies = dependenciesTmp.map(_.id) @@ -296,7 +296,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { private def checkNonDependencies(assumptionAnalysisInterpreter: AssumptionAnalysisInterpreter): Seq[String] = { val assumptionNodes = getTestIrrelevantAssumptionNodes(assumptionAnalysisInterpreter.getNonInternalAssumptionNodes) val assumptionsPerSource = assumptionNodes groupBy (n => extractSourceLine(n.sourceInfo.getPosition)) - val assertionNodes = getTestAssertionNodes(assumptionAnalysisInterpreter.getAssertionNodes) + val assertionNodes = getTestAssertionNodes(assumptionAnalysisInterpreter.getNonInternalAssertionNodes) val dependencies = assumptionAnalysisInterpreter.getAllNonInternalDependencies(assertionNodes.map(_.id)).map(_.id) From 1afd72f3c92588d06ce117e18de5a43b1137f44c Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 16 Jul 2025 17:53:34 +0200 Subject: [PATCH 176/474] review test cases --- .../all/imprecision.vpr | 56 +++++++++++++++++++ .../dependencyAnalysisTests/new/meeting.vpr | 11 ++++ .../unitTests/inhaleExhale.vpr | 53 ++++++++++++++++++ .../unitTests/loops.vpr | 13 ++++- .../unitTests/magicWands.vpr | 20 +++++++ .../unitTests/permissions.vpr | 51 +++++++++++++---- .../unitTests/predicates.vpr | 21 +++++++ .../unitTests/quasihavoc.vpr | 35 ++++++++++++ 8 files changed, 245 insertions(+), 15 deletions(-) create mode 100644 src/test/resources/dependencyAnalysisTests/unitTests/inhaleExhale.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/unitTests/quasihavoc.vpr diff --git a/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr b/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr index 47bc3ab71..c7365561f 100644 --- a/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr @@ -73,6 +73,7 @@ method exhaleImprecision(){ assert x.f > 1 } +// this is precise as opposed to later examples with wildcards method quantifiedPerm2(xs: Seq[Ref], ys: Seq[Ref]) requires @dependency("Explicit")(|xs| > 5) requires @irrelevant("Explicit")(|ys| > 3) @@ -123,4 +124,59 @@ method quantifiedPerm4(xs: Seq[Ref], ys: Seq[Ref]) @testAssertion("Explicit") assert xs[0].f > 0 +} + +// havocs all resources of the given fields conditionally on whether it can prove +// non-aliasing or not (e.g. x.f --> x == y? new snap : keep old snap) +method quasihavoc1(x: Ref, y: Ref) + requires @dependency("Explicit")(acc(x.f)) + requires @dependency("Explicit")(x.f == 3) + requires @dependency("Explicit")(x != y) +{ + // @irrelevant() // TODO ake + quasihavoc y.f // does nothing. we have no permission + @testAssertion("Explicit") + assert x.f == 3 +} + +// in this example the loop condition and invariant res >= 0 would be enough +// to prove the invariant res >= 0. However, the SMT solver chooses to prove it +// via the invariant i>=0 +// interestingly, proving "res >= 0 is preserved by the loop body" has the following unsat core +// old(res) >= 0, res = old(res) + old(i), i = old(i) - 1, i >= 0 +// the last one comes from the asserting that invariant i >= 0 is preserved! +// the last two terms could be replaced by old(i) > 0 (the loop condition) +method loop1(){ + var i: Int + var res: Int + @dependency("Implicit") + res := 0 + @dependency("Implicit") + i := 10 + while(@dependency("PathCondition")(i > 0)) + invariant @irrelevant("LoopInvariant")(i <= 10) + invariant i >= 0 + invariant @dependency("LoopInvariant")(res >= 0) + { + @dependency("Implicit") + res := res + i + i := i - 1 + } + @testAssertion("Explicit") + assert res >= 0 +} + +method inhaleImprecision(){ + var x: Ref + + @dependency("Explicit") + inhale acc(x.f, 1/2) + @dependency("Explicit") + inhale x.f > 0 + + //@irrelevant("Implicit") TODO ake: imprecision? + inhale acc(x.f, 1/2) + + @testAssertion("Explicit") + assert x.f > 0 } \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/new/meeting.vpr b/src/test/resources/dependencyAnalysisTests/new/meeting.vpr index 08dc0dcd6..f61d1a223 100644 --- a/src/test/resources/dependencyAnalysisTests/new/meeting.vpr +++ b/src/test/resources/dependencyAnalysisTests/new/meeting.vpr @@ -22,6 +22,17 @@ method exhaleImprecision(){ assert x.f > 0 } +method inhaleImprecision(){ + var x: Ref + + inhale acc(x.f, 1/2) + inhale x.f > 0 + + inhale acc(x.f, 1/2) + + assert x.f > 0 +} + method infeasible1(x: Ref) requires acc(x.f, 1/2) requires x.f > 0 diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/inhaleExhale.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/inhaleExhale.vpr new file mode 100644 index 000000000..0f8212036 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/unitTests/inhaleExhale.vpr @@ -0,0 +1,53 @@ +field f: Int + +method exhaleInhale(a: Ref) + requires @dependency("Explicit")(acc(a.f)) +{ + @dependency("Implicit") + exhale acc(a.f, 1/2) + @dependency("Explicit") + inhale acc(a.f, 1/2) + @testAssertion("Explicit") + assert perm(a.f) == write +} + +method exhaleInhale2(a: Ref) + requires @dependency("Explicit")(acc(a.f)) +{ + // @irrelevant("Implicit") TODO ake: imprecision? + exhale acc(a.f, 1/2) + // @irrelevant("Explicit") TODO ake: imprecision? + inhale acc(a.f, 1/2) + @testAssertion("Explicit") + assert perm(a.f) >= 1/2 +} + +method exhaleImprecision(){ + var x: Ref + + @dependency("Explicit") + inhale acc(x.f) + @dependency("Explicit") + inhale x.f > 0 + + // @irrelevant("Implicit") TODO ake: imprecision? + exhale acc(x.f, 1/2) + + @testAssertion("Explicit") + assert x.f > 0 +} + +method inhaleImprecision(){ + var x: Ref + + @dependency("Explicit") + inhale acc(x.f, 1/2) + @dependency("Explicit") + inhale x.f > 0 + + //@irrelevant("Implicit") TODO ake: imprecision? + inhale acc(x.f, 1/2) + + @testAssertion("Explicit") + assert x.f > 0 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/loops.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/loops.vpr index 8e26615b5..54e923053 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/loops.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/loops.vpr @@ -2,18 +2,20 @@ method loop1(){ var i: Int var res: Int + @dependency("Implicit") res := 0 + // @irrelevant("Implicit") // imprecise i := 10 - while(i > 0) + while(@dependency("PathCondition")(i > 0)) invariant @irrelevant("LoopInvariant")(i <= 10) - invariant @irrelevant("LoopInvariant")(i >= 0) + invariant i >= 0 invariant @dependency("LoopInvariant")(res >= 0) { @dependency("Implicit") res := res + i + // @irrelevant("Implicit") // imprecise i := i - 1 } - @testAssertion("Explicit") assert res >= 0 } @@ -21,7 +23,9 @@ method loop1(){ method loop2(){ var i: Int var res: Int + @irrelevant("Implicit") res := 0 + @irrelevant("Implicit") i := 10 while(@dependency("PathCondition")(i > 0)) invariant @irrelevant("LoopInvariant")(i <= 10) @@ -40,7 +44,9 @@ method loop2(){ method loop3(){ var i: Int var res: Int + @dependency("Implicit") res := 0 + @irrelevant("Implicit") i := 10 while(@dependency("PathCondition")(i > 0)) invariant @irrelevant("LoopInvariant")(i <= 10) @@ -68,6 +74,7 @@ method loop4(){ invariant @irrelevant("LoopInvariant")(i >= 0) invariant @dependency("LoopInvariant")(res >= 0) { + @irrelevant("Implicit") res := res - 1 @irrelevant("Implicit") i := i - 1 diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr index ea055d118..274baf129 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr @@ -89,4 +89,24 @@ method quantifiedWand(xs: Seq[Int], b: Int) @testAssertion("Explicit") assert greater0(xs[0] + b) +} + +method packagePrecision(l: Ref) + requires @dependency("Explicit")(list(l)) + { + @dependency("Implicit") + unfold list(l) + var tmp : Ref + @dependency("Implicit") + tmp := l.next + + @irrelevant("Implicit") + package list(tmp) --* list(l) + { + @irrelevant("Implicit") + fold list(l) + } + + @testAssertion("Explicit") + assert list(tmp) } \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr index 21d884fbe..9dcf6ff65 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr @@ -28,7 +28,7 @@ method perm3(){ @dependency("Explicit") inhale acc(x.f) @testAssertion("Implicit") - x.f := x.f + 1 + x.f := 5 } method perm4(){ @@ -53,8 +53,44 @@ method perm5(){ exhale acc(x.f) } +method fieldAccessPrecision(){ + var x: Ref + var y: Ref + + @dependency("Explicit") + inhale acc(x.f) + @dependency("Explicit") + inhale x.f > 0 + + @irrelevant("Explicit") + inhale acc(y.f) + @irrelevant("Implicit") + y.f := y.f + 1 + + @testAssertion("Explicit") + assert x.f >= 0 +} + +method fieldAccessPrecision2(){ + var x: Ref + var y: Ref + + @dependency("Explicit") + inhale acc(x.f) + @irrelevant("Explicit") + inhale x.f > 0 + + @irrelevant("Explicit") + inhale acc(y.f) + @irrelevant("Implicit") + x.f := y.f + 1 + + @testAssertion("Explicit") + exhale acc(x.f) +} + method permAmount1(x: Ref, p: Perm) - requires p > none + requires @dependency("Explicit")(p > none) requires @dependency("Explicit")(acc(x.f, p)) { @dependency("Explicit") @@ -86,14 +122,5 @@ method noAlias(a: Ref, b: Ref, c: Ref) assert a != b } -method exhaleInhale(a: Ref) - requires @dependency("Explicit")(acc(a.f)) -{ - @dependency("Implicit") - exhale acc(a.f, 1/2) - @dependency("Explicit") - inhale acc(a.f, 1/2) - @testAssertion("Explicit") - assert perm(a.f) == 1/1 -} + diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/predicates.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/predicates.vpr index ada05d864..6caeeb1e3 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/predicates.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/predicates.vpr @@ -49,4 +49,25 @@ method callWithPredicate(a: Int, b: Int) @testAssertion("Explicit") assert greater5(a + b) +} + +method unfoldPrecision(a: Int, b: Int) + requires @irrelevant("Explicit")(greater0(a)) + requires @dependency("Explicit")(greater5(b)) + ensures greater0(b) +{ + @irrelevant("Implicit") + unfold greater0(a) + @dependency("Implicit") + unfold greater5(b) + @testAssertion("Implicit") + fold greater0(b) +} + +method foldPrecision(a: Int, b: Int) + requires @irrelevant("Explicit")(a > 5) + requires @dependency("Explicit")(b > 5) +{ + @testAssertion("Implicit") + fold greater0(b) } \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/quasihavoc.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/quasihavoc.vpr new file mode 100644 index 000000000..ebe5746f0 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/unitTests/quasihavoc.vpr @@ -0,0 +1,35 @@ +field f: Int + +method quasihavoc1(x: Ref, y: Ref) + requires @dependency("Explicit")(acc(x.f)) + requires @dependency("Explicit")(x.f == 3) + requires @dependency("Explicit")(x != y) +{ + @irrelevant() // TODO ake + quasihavoc y.f // does nothing. we have no permission + @testAssertion("Explicit") + assert x.f == 3 +} + +method quasihavoc2(x: Ref, y: Ref) + requires @dependency("Explicit")(acc(x.f)) + requires @irrelevant("Explicit")(acc(y.f)) + requires @dependency("Explicit")(x.f == 42) +{ + @irrelevant() + quasihavoc y.f + @testAssertion("Explicit") + assert x.f == 42 +} + +method quasihavoc3(x: Ref) + requires @dependency("Explicit")(acc(x.f)) + requires @irrelevant("Explicit")(x.f == 0) +{ + @irrelevant() + quasihavoc x.f + @dependency("Implicit") + x.f := 3 + @testAssertion("Explicit") + assert x.f == 3 +} \ No newline at end of file From 9cc22433bc890903c9dab114a483e31fb0ddd5b2 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 17 Jul 2025 11:18:20 +0200 Subject: [PATCH 177/474] review test cases and annotate --- .../all/imprecision.vpr | 12 ++- .../dependencyAnalysisTests/new/meeting.vpr | 99 ++++++++++++++----- .../unitTests/magicWands.vpr | 27 +++++ .../unitTests/permWildcard.vpr | 41 ++++++++ .../unitTests/permissions.vpr | 46 +++++---- .../unitTests/quantifiedPermissions.vpr | 13 --- 6 files changed, 169 insertions(+), 69 deletions(-) create mode 100644 src/test/resources/dependencyAnalysisTests/unitTests/permWildcard.vpr diff --git a/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr b/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr index c7365561f..956d42063 100644 --- a/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr @@ -1,4 +1,5 @@ field f: Int +field g: Int // When executing the branch, the 1st inhale is reported as a dependency, the 2nd inhale is ignored. // If the branch is not executed, the 2nd inhale is reported as a dependency @@ -88,7 +89,7 @@ method quantifiedPerm2(xs: Seq[Ref], ys: Seq[Ref]) } // issue #01 - imprecision due to wildcards -// when accessing a qp resource, we check Z < (x in xs? …) + (y in ys? …) the unsat core contains dependencies to all qp perm amounts (e.g. wildcard) +// when accessing a qp resource, we check Z < (x in xs? k1:Z) + (y in ys? k2:Z) the unsat core contains dependencies to all qp perm amounts (e.g. wildcard) method quantifiedPerm3(xs: Seq[Ref], ys: Seq[Ref]) requires @dependency("Explicit")(|xs| > 5) requires @irrelevant("Explicit")(|ys| > 3) @@ -103,7 +104,7 @@ method quantifiedPerm3(xs: Seq[Ref], ys: Seq[Ref]) } // issue #01 - imprecision due to perm amounts -// when accessing a qp resource, we check Z < (x in xs? …) + (y in ys? …) the unsat core contains dependencies to all qp perm amounts (e.g. wildcard) +// when accessing a qp resource, we check Z < (x in xs? k1:Z) + (y in ys? k2:Z) the unsat core contains dependencies to all qp perm amounts method quantifiedPerm4(xs: Seq[Ref], ys: Seq[Ref]) requires @dependency("Explicit")(|xs| > 5) requires @irrelevant("Explicit")(|ys| > 3) @@ -119,7 +120,7 @@ method quantifiedPerm4(xs: Seq[Ref], ys: Seq[Ref]) @dependency("Explicit") inhale (forall x: Ref :: x in xs ==> (acc(x.f, p1) && x.f > 0)) - @irrelevant("Explicit") // fixed imprecision (wildcards) + // @irrelevant("Explicit") // TODO ake: unexpected dependency inhale forall y: Ref :: y in ys ==> (acc(y.f, p2) && y.f > 0) @testAssertion("Explicit") @@ -127,7 +128,7 @@ method quantifiedPerm4(xs: Seq[Ref], ys: Seq[Ref]) } // havocs all resources of the given fields conditionally on whether it can prove -// non-aliasing or not (e.g. x.f --> x == y? new snap : keep old snap) +// non-aliasing or not (e.g. x.f --> x == y? new snap : old snap) method quasihavoc1(x: Ref, y: Ref) requires @dependency("Explicit")(acc(x.f)) requires @dependency("Explicit")(x.f == 3) @@ -139,6 +140,7 @@ method quasihavoc1(x: Ref, y: Ref) assert x.f == 3 } +// imprecision due to non-uniqueness of unsat core // in this example the loop condition and invariant res >= 0 would be enough // to prove the invariant res >= 0. However, the SMT solver chooses to prove it // via the invariant i>=0 @@ -151,7 +153,7 @@ method loop1(){ var res: Int @dependency("Implicit") res := 0 - @dependency("Implicit") + i := 10 while(@dependency("PathCondition")(i > 0)) invariant @irrelevant("LoopInvariant")(i <= 10) diff --git a/src/test/resources/dependencyAnalysisTests/new/meeting.vpr b/src/test/resources/dependencyAnalysisTests/new/meeting.vpr index f61d1a223..e799800f1 100644 --- a/src/test/resources/dependencyAnalysisTests/new/meeting.vpr +++ b/src/test/resources/dependencyAnalysisTests/new/meeting.vpr @@ -1,16 +1,5 @@ field f: Int -method exhaleInhale(a: Ref) - requires acc(a.f) -{ - - exhale acc(a.f, 1/2) - - inhale acc(a.f, 1/2) - - assert perm(a.f) == write -} - method exhaleImprecision(){ var x: Ref @@ -33,6 +22,39 @@ method inhaleImprecision(){ assert x.f > 0 } +// issue #01 - imprecision due to wildcards +// when accessing a qp resource, check Z < (x in xs? k1:Z) + (y in ys? k2:Z) +// -> the unsat core contains dependencies to all qp perm amounts in order to assert k1, k2, ... >= Z +method quantifiedPerm3(xs: Seq[Ref], ys: Seq[Ref]) + requires |xs| > 5 + requires |ys| > 3 +{ + + inhale (forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0)) + // TODO: unexpected dependency (wildcards) + inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard) && y.f > 0) + + assert xs[0].f > 0 +} + +// havocs all resources of the given fields conditionally on whether it can prove non-aliasing or not +method quasihavoc1(x: Ref, y: Ref) + requires acc(x.f) + requires x.f == 3 + requires x != y +{ + // TODO: unexpected dependency + quasihavoc y.f // x.f --> x==y? new snap : old snap + + assert x.f == 3 +} + +method currentPerm(x: Ref) + requires acc(x.f, 1/2) +{ + assert perm(x.f) > 1/4 +} + method infeasible1(x: Ref) requires acc(x.f, 1/2) requires x.f > 0 @@ -56,22 +78,6 @@ method infeasible2(x: Ref) } -method currentPerm(x: Ref) - requires acc(x.f, 1/2) -{ - assert perm(x.f) > 1/4 -} - - -method automatedTest() -{ - var a: Int - a := 0 - a := 1 - a := 0 - assert a == 0 -} - field elem: Int field next: Ref @@ -103,3 +109,42 @@ method appendListFull(this: Ref, e: Int) fold list(this) } + +method automatedTest() +{ + var a: Int + a := 0 + a := 1 + a := 0 + assert a == 0 +} + + +method dependencyDefinitionViaSLP_1() +{ + var a: Int, b: Int + + // { true} + a := 1 + // { (l0 ==> a==1) } + assert a > 0 + // { (l0 ==> a==1) && (L1 ==> a > 0) } + assert a >= 0 // potentially depends only on L1? +} + +function foo(a: Int): Int { + requires a > 0 + ensures result > 0 +} + +method dependencyDefinitionViaSLP_2() +{ + var a: Int, b: Int + + // { true} + a := 1 + // { (l0 ==> a==1) } + b := foo(a) + // { (l0 ==> a==1) && (L1 ==> (a > 0 && b > 0))} + assert b > 0 // depends only on L1? +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr index 274baf129..fa9295410 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr @@ -1,5 +1,6 @@ field next : Ref field val : Int +field f: Int predicate list(start : Ref) { @@ -109,4 +110,30 @@ method packagePrecision(l: Ref) @testAssertion("Explicit") assert list(tmp) +} + +method packageExhale(x: Ref) + requires @dependency("Explicit")(acc(x.f)) + { + + + package acc(x.f, 1/2) --* acc(x.f) + + + @testAssertion("Explicit") + assert perm(x.f) == 1/2 +} + +method packageExhale2(x: Ref) + requires @dependency("Explicit")(acc(x.f)) + { + + @dependency("Implicit") + package acc(x.f, 1/2) --* acc(x.f) + + @dependency("Implicit") + apply acc(x.f, 1/2) --* acc(x.f) + + @testAssertion("Explicit") + exhale acc(x.f) } \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/permWildcard.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/permWildcard.vpr new file mode 100644 index 000000000..238a575d5 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/unitTests/permWildcard.vpr @@ -0,0 +1,41 @@ +field f: Int +field g: Int + +method wildcardPerm(x: Ref, y: Ref) + requires @dependency("Explicit")(acc(x.f, wildcard)) + requires @irrelevant("Explicit")(acc(y.f, wildcard)) + requires x != y // could be a dependency but doesn't have to +{ + @testAssertion("Implicit") + inhale x.f > 0 +} + +method wildcardPerm2(x: Ref, y: Ref) + requires @irrelevant("Explicit")(acc(y.f, wildcard)) + requires @dependency("Explicit")(acc(x.f, wildcard)) + requires x != y // could be a dependency but doesn't have to +{ + @testAssertion("Implicit") + inhale x.f > 0 +} + +method wildcardPermDistinctFields(x: Ref, y: Ref) + requires @dependency("Explicit")(acc(x.f, wildcard)) + requires @irrelevant("Explicit")(acc(y.g, wildcard)) +{ + @testAssertion("Implicit") + inhale x.f > 0 +} + +method quantifiedPermWildcard(xs: Seq[Ref], ys: Seq[Ref]) + requires @dependency("Explicit")(|xs| > 5) + requires @irrelevant("Explicit")(|ys| > 3) +{ + @dependency("Explicit") + inhale (forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0)) + @irrelevant("Explicit") + inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard)) + + @testAssertion("Explicit") + assert xs[0].f > 0 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr index 9dcf6ff65..0a72bf9c9 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr @@ -87,30 +87,28 @@ method fieldAccessPrecision2(){ @testAssertion("Explicit") exhale acc(x.f) -} - -method permAmount1(x: Ref, p: Perm) - requires @dependency("Explicit")(p > none) - requires @dependency("Explicit")(acc(x.f, p)) -{ - @dependency("Explicit") - assume p > 1/2 - @testAssertion("Explicit") - exhale acc(x.f, 1/2) -} - -method permAmount2(x: Ref, p: Perm) - requires p > none - requires @dependency("Explicit")(acc(x.f, p)) -{ - @dependency("Explicit") - inhale x.f > 0 - @dependency("Explicit") - assume p > 1/2 - exhale acc(x.f, 1/2) - @testAssertion("Explicit") - assert x.f > 0 -} +}method permAmount1(x: Ref, p: Perm) + requires @dependency("Explicit")(p > none) + requires @dependency("Explicit")(acc(x.f, p)) + { + @dependency("Explicit") + assume p > 1/2 + @testAssertion("Explicit") + exhale acc(x.f, 1/2) + } + + method permAmount2(x: Ref, p: Perm) + requires p > none + requires @dependency("Explicit")(acc(x.f, p)) + { + @dependency("Explicit") + inhale x.f > 0 + @dependency("Explicit") + assume p > 1/2 + exhale acc(x.f, 1/2) + @testAssertion("Explicit") + assert x.f > 0 + } method noAlias(a: Ref, b: Ref, c: Ref) diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr index 4358dba09..a4c63b12f 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr @@ -100,16 +100,3 @@ method quantifiedPermWrite4(xs: Seq[Ref], ys: Seq[Ref]) @testAssertion("Implicit") xs[0].f := 2 } - -method quantifiedPermWildcard(xs: Seq[Ref], ys: Seq[Ref]) - requires @dependency("Explicit")(|xs| > 5) - requires @irrelevant("Explicit")(|ys| > 3) -{ - @dependency("Explicit") - inhale (forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0)) - @irrelevant("Explicit") - inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard)) - - @testAssertion("Explicit") - assert xs[0].f > 0 -} \ No newline at end of file From fa8d3ef53ae4ee70f1fc50f1366c8bc93612a552 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 17 Jul 2025 12:19:57 +0200 Subject: [PATCH 178/474] fix mce and add test --- .../AssumptionAnalyzer.scala | 4 +--- .../rules/MoreCompleteExhaleSupporter.scala | 24 +++++++++---------- .../scala/rules/QuantifiedChunkSupport.scala | 2 +- .../dependencyAnalysisTests/mce/mce_tests.vpr | 23 ++++++++++++++++++ .../unitTests/permissions.vpr | 4 +++- src/test/scala/AssumptionAnalysisTests.scala | 23 ++++++++++++++---- 6 files changed, 58 insertions(+), 22 deletions(-) create mode 100644 src/test/resources/dependencyAnalysisTests/mce/mce_tests.vpr diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index e86800013..87a97e2c2 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -28,7 +28,6 @@ trait AssumptionAnalyzer { def addAssumption(assumption: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, description: Option[String] = None): Option[Int] def registerInhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, labelNode: Option[LabelNode], analysisInfo: AnalysisInfo, isExhale: Boolean): CH = buildChunk(perm) def registerExhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, analysisInfo: AnalysisInfo): CH = buildChunk(perm) - def addPermissionExhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] def createLabelNode(labelTerm: Term, sourceChunks: Iterable[Chunk], sourceTerms: Iterable[Term]): Option[LabelNode] def createAssertOrCheckNode(term: Term, assumptionType: AssumptionType, analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] @@ -158,7 +157,7 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { Some(node.id) } - override def addPermissionExhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { + private def addPermissionExhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { val node = PermissionExhaleNode(chunk, permAmount, sourceInfo, assumptionType, isClosed_) addNode(node) addPermissionDependencies(Set(chunk), Set(), Some(node.id)) @@ -249,7 +248,6 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { override def addNodes(nodes: Iterable[AssumptionAnalysisNode]): Unit = {} override def addNode(node: AssumptionAnalysisNode): Unit = {} override def addAssumption(assumption: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, description: Option[String] = None): Option[Int] = None - override def addPermissionExhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None override def createLabelNode(labelTerm: Term, sourceChunks: Iterable[Chunk], sourceTerms: Iterable[Term]): Option[LabelNode] = None override def createAssertOrCheckNode(term: Term, assumptionType: AssumptionType, analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] = None diff --git a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala index 75e24b011..95caab5b3 100644 --- a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala +++ b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala @@ -365,16 +365,16 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { pSum = PermPlus(pSum, Ite(eq, ch.perm, NoPerm)) pSumExp = eqExp.map(eq => ast.PermAdd(pSumExp.get, ast.CondExp(eq, ch.permExp.get, ast.NoPerm()())(eq.pos, eq.info, eq.errT))()) - val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, pTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, pTakenExp.get)(pe.pos, pe.info, pe.errT)), v.decider.getAnalysisInfo(AssumptionType.Internal)).asInstanceOf[NonQuantifiedChunk] - v.decider.assumptionAnalyzer.addPermissionExhaleNode(ch, pTaken, v.decider.analysisSourceInfoStack.getFullSourceInfo, assumptionType) + val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, pTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, pTakenExp.get)(pe.pos, pe.info, pe.errT)), v.decider.getAnalysisInfo(AssumptionType.Implicit)).asInstanceOf[NonQuantifiedChunk] + val _ = GeneralChunk.withPerm(ch, pTaken, None, v.decider.getAnalysisInfo(assumptionType), isExhale=true) pNeeded = PermMinus(pNeeded, pTaken) pNeededExp = permsExp.map(pe => ast.PermSub(pNeededExp.get, pTakenExp.get)(pe.pos, pe.info, pe.errT)) - if (!v.decider.check(IsNonPositive(newChunk.perm), Verifier.config.splitTimeout(), AssumptionType.Internal)) { + if (!v.decider.check(IsNonPositive(newChunk.perm), Verifier.config.splitTimeout(), AssumptionType.Implicit)) { newChunks.append(newChunk) } - moreNeeded = !v.decider.check(pNeeded === NoPerm, Verifier.config.splitTimeout(), AssumptionType.Internal) + moreNeeded = !v.decider.check(pNeeded === NoPerm, Verifier.config.splitTimeout(), AssumptionType.Implicit) } else { newChunks.append(ch) } @@ -387,7 +387,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { newChunks foreach { ch => val resource = Resources.resourceDescriptions(ch.resourceID) val pathCond = interpreter.buildPathConditionsForChunk(ch, resource.instanceProperties(s.mayAssumeUpperBounds)) - pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), AssumptionType.Internal)) + pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), AssumptionType.Implicit)) } val newHeap = Heap(allChunks) @@ -397,7 +397,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { if (returnSnap) { summarise(s0, relevantChunks.toSeq, resource, args, argsExp, Some(definiteAlias.map(_.snap)), v)((s1, snap, _, _, v1) => { - val condSnap = Some(if (v1.decider.check(IsPositive(perms), Verifier.config.checkTimeout(), AssumptionType.Internal)) { + val condSnap = Some(if (v1.decider.check(IsPositive(perms), Verifier.config.checkTimeout(), AssumptionType.Implicit)) { snap } else { Ite(IsPositive(perms), snap.convert(sorts.Snap), Unit) @@ -476,12 +476,12 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { ast.Implies(ast.Not(eqExp.get)(), ast.EqCmp(permTakenExp.get, ast.NoPerm()())())(pe.pos, pe.info, pe.errT)))) - v.decider.assume(constraint, Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp)), AssumptionType.Internal) + v.decider.assume(constraint, Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp)), AssumptionType.Implicit) newFr = newFr.recordPathSymbol(permTaken.applicable.asInstanceOf[Function]).recordConstraint(constraint) - v.decider.assumptionAnalyzer.addPermissionExhaleNode(ch, permTaken, v.decider.analysisSourceInfoStack.getFullSourceInfo, assumptionType) - GeneralChunk.withPerm(ch, PermMinus(ch.perm, permTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, permTakenExp.get)(pe.pos, pe.info, pe.errT)), v.decider.getAnalysisInfo(AssumptionType.Internal)).asInstanceOf[NonQuantifiedChunk] // TODO ake: casting + val _ = GeneralChunk.withPerm(ch, permTaken, None, v.decider.getAnalysisInfo(assumptionType), isExhale=true) + GeneralChunk.withPerm(ch, PermMinus(ch.perm, permTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, permTakenExp.get)(pe.pos, pe.info, pe.errT)), v.decider.getAnalysisInfo(AssumptionType.Implicit)).asInstanceOf[NonQuantifiedChunk] // TODO ake: casting }) val totalTakenBounds = @@ -493,7 +493,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { val constraintExp = permsExp.map(pe => ast.Implies(ast.NeCmp(totalPermSumExp.get, ast.NoPerm()())(), ast.And(ast.PermLeCmp(ast.NoPerm()(), totalPermTakenExp.get)(), ast.PermLeCmp(totalPermTakenExp.get, totalPermSumExp.get)())(pe.pos, pe.info, pe.errT))()) - v.decider.assume(totalTakenBounds, constraintExp, constraintExp, AssumptionType.Internal) + v.decider.assume(totalTakenBounds, constraintExp, constraintExp, AssumptionType.Implicit) newFr = newFr.recordConstraint(totalTakenBounds) @@ -502,7 +502,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { v.decider.assert(Implies(PermLess(NoPerm, perms), totalPermTaken !== NoPerm), assumptionType) { case true => val constraintExp = permsExp.map(pe => ast.EqCmp(pe, totalPermTakenExp.get)()) - v.decider.assume(perms === totalPermTaken, Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp)), AssumptionType.Internal) + v.decider.assume(perms === totalPermTaken, Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp)), AssumptionType.Implicit) if (returnSnap) { summarise(s1, relevantChunks.toSeq, resource, args, argsExp, None, v)((s2, snap, _, _, v1) => Q(s2, updatedChunks, Some(snap), v1)) @@ -546,7 +546,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { relevantChunks foreach (chunk => { val instantiatedPermSum = permissionSum.replace(freeReceiver, chunk.args.head) val exp = permissionSumExp.map(pse => ast.PermLeCmp(replaceVarsInExp(pse, Seq(freeReceiverExp.name), Seq(chunk.argsExp.get.head)), ast.FullPerm()())()) - v.decider.assume(PermAtMost(instantiatedPermSum, FullPerm), exp, exp, AssumptionType.Internal) + v.decider.assume(PermAtMost(instantiatedPermSum, FullPerm), exp, exp, AssumptionType.Implicit) }) } } diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 8cb2b3254..296d98867 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -1729,7 +1729,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.getAnalysisInfo(assumptionType)) } }else{ - v.decider.assumptionAnalyzer.addPermissionExhaleNode(ithChunk, ithPTaken, v.decider.analysisSourceInfoStack.getFullSourceInfo, assumptionType) + val _ = GeneralChunk.withPerm(ithChunk, ithPTaken, None, v.decider.getAnalysisInfo(assumptionType), isExhale=true) } } diff --git a/src/test/resources/dependencyAnalysisTests/mce/mce_tests.vpr b/src/test/resources/dependencyAnalysisTests/mce/mce_tests.vpr new file mode 100644 index 000000000..6676c66c9 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/mce/mce_tests.vpr @@ -0,0 +1,23 @@ +field f: Int + +method mce_test_1(a: Ref, b: Ref, c: Ref) +{ + var x: Int := 0 + + + @dependency("Explicit") + inhale acc(a.f) // assumption 1 + // @irrelevant("Explicit") // TODO ake: imprecise + inhale acc(b.f) // assumption 2 + + if (@dependency("PathCondition")(c == a || c == b)) { + @dependency("Implicit") + x := c.f + } + + @dependency("Explicit") + assume c == a // assumption 3 + + @testAssertion("Explicit") + assert x == a.f +} diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr index 0a72bf9c9..7d3d0b009 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr @@ -87,7 +87,9 @@ method fieldAccessPrecision2(){ @testAssertion("Explicit") exhale acc(x.f) -}method permAmount1(x: Ref, p: Perm) +} + +method permAmount1(x: Ref, p: Perm) requires @dependency("Explicit")(p > none) requires @dependency("Explicit")(acc(x.f, p)) { diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index 9e33f3d10..d381a320a 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -11,6 +11,7 @@ import viper.silver.{ast, verifier} import java.io.PrintWriter import java.nio.file.{Files, Path, Paths} +import scala.annotation.unused import scala.jdk.CollectionConverters.IterableHasAsScala @@ -30,11 +31,14 @@ class AssumptionAnalysisTests extends AnyFunSuite { val dependencyKeyword = "dependency" val testAssertionKeyword = "testAssertion" - val commandLineArguments: Seq[String] = + var commandLineArguments: Seq[String] = Seq("--timeout", "100" /* seconds */ , "--enableAssumptionAnalysis", "--z3Args", "proof=true unsat-core=true") testDirectories foreach createTests + commandLineArguments = Seq("--enableMoreCompleteExhale") ++ commandLineArguments + createTests("dependencyAnalysisTests/mce") + // test("custom test"){ // executeTest("dependencyAnalysisTests/all/", "list", frontend) // } @@ -141,6 +145,12 @@ class AssumptionAnalysisTests extends AnyFunSuite { val crucialNodesWithExpInfo = crucialNodes filter (_.sourceInfo.getTopLevelSource.isInstanceOf[ExpAnalysisSourceInfo]) map (_.sourceInfo.getTopLevelSource.asInstanceOf[ExpAnalysisSourceInfo]) var total = 0 var removed = 0 + var nonDetermBoolCount = 0 + + def getNextNonDetermBool: String = { + nonDetermBoolCount += 1 + s"nonDetermBool_$nonDetermBoolCount" + } val newProgram: ast.Program = ViperStrategy.Slim({ case s @(_: ast.Seqn | _: ast.Goto) => s @@ -153,9 +163,10 @@ class AssumptionAnalysisTests extends AnyFunSuite { case ifStmt@ast.If(cond, thenBody, elseBody) if !isCrucialExp(cond, crucialNodesWithExpInfo) => total += 1 removed += 1 + val nonDetermBool = getNextNonDetermBool ast.Seqn(Seq( - ast.LocalVarDeclStmt(ast.LocalVarDecl("nonDetermBool", ast.Bool)())(), - ast.If(ast.LocalVar("nonDetermBool", ast.Bool)(), thenBody, elseBody)()) + ast.LocalVarDeclStmt(ast.LocalVarDecl(nonDetermBool, ast.Bool)())(), + ast.If(ast.LocalVar(nonDetermBool, ast.Bool)(), thenBody, elseBody)()) , Seq())(ifStmt.pos, ifStmt.info, ifStmt.errT) case ifStmt: If => total += 1 @@ -164,9 +175,10 @@ class AssumptionAnalysisTests extends AnyFunSuite { val newInvs = invs filter (isCrucialExp(_, crucialNodesWithExpInfo)) total += 1 + invs.size removed += 1 + (invs.size - newInvs.size) + val nonDetermBool = getNextNonDetermBool ast.Seqn(Seq( - ast.LocalVarDeclStmt(ast.LocalVarDecl("nonDetermBool", ast.Bool)())(), - ast.While(ast.LocalVar("nonDetermBool", ast.Bool)(), newInvs, body)(whileStmt.pos, whileStmt.info, whileStmt.errT)) + ast.LocalVarDeclStmt(ast.LocalVarDecl(nonDetermBool, ast.Bool)())(), + ast.While(ast.LocalVar(nonDetermBool, ast.Bool)(), newInvs, body)(whileStmt.pos, whileStmt.info, whileStmt.errT)) , Seq())(whileStmt.pos, whileStmt.info, whileStmt.errT) case whileStmt@ast.While(cond, invs, body) => val newInvs = invs filter (isCrucialExp(_, crucialNodesWithExpInfo)) @@ -238,6 +250,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { private def extractAnnotatedStmts(annotationFilter: (ast.AnnotationInfo => Boolean)): Set[ast.Infoed] = { var nodesWithAnnotation: Set[ast.Infoed] = Set.empty + @unused val newP: ast.Program = ViperStrategy.Slim({ case s: ast.Seqn => s case n: ast.Infoed => From 19d57208faf8c36b5df2aa6fb2733914a8725acf Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 18 Jul 2025 13:08:47 +0200 Subject: [PATCH 179/474] add real word test examples --- .../real-world-examples/binarySearchSeq.vpr | 33 ++++++++++++ .../{all => real-world-examples}/gaussian.vpr | 0 .../listAppend.vpr} | 0 .../real-world-examples/seqClient.vpr | 24 +++++++++ .../real-world-examples/seqMerge.vpr | 53 +++++++++++++++++++ 5 files changed, 110 insertions(+) create mode 100644 src/test/resources/dependencyAnalysisTests/real-world-examples/binarySearchSeq.vpr rename src/test/resources/dependencyAnalysisTests/{all => real-world-examples}/gaussian.vpr (100%) rename src/test/resources/dependencyAnalysisTests/{all/list.vpr => real-world-examples/listAppend.vpr} (100%) create mode 100644 src/test/resources/dependencyAnalysisTests/real-world-examples/seqClient.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/real-world-examples/seqMerge.vpr diff --git a/src/test/resources/dependencyAnalysisTests/real-world-examples/binarySearchSeq.vpr b/src/test/resources/dependencyAnalysisTests/real-world-examples/binarySearchSeq.vpr new file mode 100644 index 000000000..5b67833da --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/real-world-examples/binarySearchSeq.vpr @@ -0,0 +1,33 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + + +// taken from silver test suite +method binary_search(array: Seq[Int], key: Int) returns (index: Int) + requires forall i: Int, j: Int :: 0 <= i && j < |array| && i < j ==> array[i] <= array[j] + ensures -1 <= index && index < |array| + ensures 0 <= index ==> array[index] == key + ensures -1 == index ==> (forall i: Int :: 0 <= i && i < |array| ==> array[i] != key) +{ + var low: Int := 0 + var high: Int := |array| + var mid : Int + + index := -1 + while (low < high) + invariant 0 <= low && low <= high && high <= |array| + invariant forall i: Int :: (0 <= i && i < |array| && !(low <= i && i < high)) ==> array[i] != key + { + mid := (low + high) \ 2; + if (array[mid] < key) { + low := mid + 1; + } else { + if (key < array[mid]) { + high := mid; + } else { + index := mid + } + } + } + index := -1 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/gaussian.vpr b/src/test/resources/dependencyAnalysisTests/real-world-examples/gaussian.vpr similarity index 100% rename from src/test/resources/dependencyAnalysisTests/all/gaussian.vpr rename to src/test/resources/dependencyAnalysisTests/real-world-examples/gaussian.vpr diff --git a/src/test/resources/dependencyAnalysisTests/all/list.vpr b/src/test/resources/dependencyAnalysisTests/real-world-examples/listAppend.vpr similarity index 100% rename from src/test/resources/dependencyAnalysisTests/all/list.vpr rename to src/test/resources/dependencyAnalysisTests/real-world-examples/listAppend.vpr diff --git a/src/test/resources/dependencyAnalysisTests/real-world-examples/seqClient.vpr b/src/test/resources/dependencyAnalysisTests/real-world-examples/seqClient.vpr new file mode 100644 index 000000000..3ed3062fa --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/real-world-examples/seqClient.vpr @@ -0,0 +1,24 @@ +import "seqMerge.vpr" +import "binarySearchSeq.vpr" + +method client() +{ + var idx: Int + + var as: Seq[Int] := Seq(4, 7, 9, 11, 14) + var bs: Seq[Int] := Seq(1, 3, 7, 10, 17) + + // idx := binary_search(as, 7) + // assert idx == 1 + + var cs: Seq[Int] + cs := seq_merge(as, bs) + + assert 10 in cs + assert 1 in cs + + assert |cs| == 11 + // idx := binary_search(cs, 1) + // assert idx == 1 + +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/real-world-examples/seqMerge.vpr b/src/test/resources/dependencyAnalysisTests/real-world-examples/seqMerge.vpr new file mode 100644 index 000000000..085d6b204 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/real-world-examples/seqMerge.vpr @@ -0,0 +1,53 @@ + +method seq_merge(as: Seq[Int], bs: Seq[Int]) returns (cs: Seq[Int]) + requires |as| >= 0 && |bs| >= 0 + requires forall i: Int :: {as[i]} 0 <= i && i <|as| ==> 0 <= as[i] + requires forall i: Int :: {as[i]} 0 < i && i <|as| ==> as[i-1] <= as[i] + requires forall j: Int :: {bs[j]} 0 <= j && j <|bs| ==> 0 <= bs[j] + requires forall j: Int :: {bs[j]} 0 < j && j <|bs| ==> bs[j-1] <= bs[j] + + ensures |cs| == |as| + |bs| + 1 + ensures forall k: Int :: {cs[k]} 0 < k && k < |cs| ==> 0 <= cs[k] + ensures forall k: Int :: {cs[k]} 0 < k && k < |cs| ==> cs[k-1] <= cs[k] + ensures forall i: Int :: {as[i]} 0 <= i && i < |as| ==> as[i] in cs + ensures forall j: Int :: {bs[j]} 0 <= j && j < |bs| ==> bs[j] in cs +{ + var i: Int := 0 + var j: Int := 0 + var k: Int := 1 + + inhale |cs| == |as| + |bs| + 1 + cs := cs[0 := 0] // index 0 is a dummy value + + while(k < |cs|) + invariant 0 <= i && i <= |as| + invariant 0 <= j && j <= |bs| + invariant 0 <= k && k <= |cs| + invariant |cs| == |as| + |bs| + 1 + invariant i + j + 1 == k + invariant forall k0: Int :: {cs[k0]} 0 <= k0 && k0 < k ==> 0 <= cs[k0] + invariant forall k0: Int :: {cs[k0]} 0 <= k0 && k0 < k && i < |as| ==> cs[k0] <= as[i] + invariant forall k0: Int :: {cs[k0]} 0 <= k0 && k0 < k && j < |bs| ==> cs[k0] <= bs[j] + invariant forall k0: Int :: {cs[k0]} 0 < k0 && k0 < k ==> cs[k0-1] <= cs[k0] + invariant forall i0: Int :: {as[i0]} 0 <= i0 && i0 < i ==> as[i0] in cs[..k] + invariant forall j0: Int :: {bs[j0]} 0 <= j0 && j0 < j ==> bs[j0] in cs[..k] + { + if(i < |as| && (j >= |bs| || as[i] < bs[j])){ + var tmp: Int := as[i] + cs := cs[k := tmp] + i := i + 1 + }else { + var tmp: Int := bs[j] + cs := cs[k := tmp] + j := j + 1 + } + k := k + 1 + } + + // assert i == |as| && j == |bs| && k == |cs| + // assert k == |as| + |bs| + 1 + + // assert forall i0: Int :: {as[i0]} 0 <= i0 && i0 < i ==> as[i0] in cs + // cs := cs[1..] // remove dummy value + // assert forall i0: Int :: {as[i0]} 0 <= i0 && i0 < i ==> as[i0] in cs +} \ No newline at end of file From 5253a724559b2c855f9fb73ff23b99ebc0879657 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 19 Jul 2025 14:22:08 +0200 Subject: [PATCH 180/474] add precision test generator --- src/main/scala/interfaces/state/Chunks.scala | 2 +- .../generation/precision_test_generator.py | 105 +++++++++++ .../generation/snippets.txt | 163 ++++++++++++++++++ .../unitTests/permissions.vpr | 21 +++ 4 files changed, 290 insertions(+), 1 deletion(-) create mode 100644 src/test/resources/dependencyAnalysisTests/generation/precision_test_generator.py create mode 100644 src/test/resources/dependencyAnalysisTests/generation/snippets.txt diff --git a/src/main/scala/interfaces/state/Chunks.scala b/src/main/scala/interfaces/state/Chunks.scala index 9d4845b58..9d58bf3e2 100644 --- a/src/main/scala/interfaces/state/Chunks.scala +++ b/src/main/scala/interfaces/state/Chunks.scala @@ -38,7 +38,7 @@ object GeneralChunk { def permMinus(chunk: GeneralChunk, newPerm: Term, newPermExp: Option[ast.Exp], analysisInfo: AnalysisInfo): GeneralChunk = { val newChunk = analysisInfo.decider.registerDerivedChunk[GeneralChunk](Set(chunk), {finalPerm => chunk.permMinus(finalPerm, newPermExp)}, - newPerm, analysisInfo.withAssumptionType(AssumptionType.Internal), isExhale=false, createLabel=false) + newPerm, analysisInfo.withAssumptionType(AssumptionType.Internal), isExhale=false, createLabel=false) // TODO ake: assumption type? val exhaledChunk = analysisInfo.decider.registerDerivedChunk[GeneralChunk](Set(chunk), {finalPerm => chunk.withPerm(finalPerm, newPermExp)}, newPerm, analysisInfo, isExhale=true, createLabel=false) diff --git a/src/test/resources/dependencyAnalysisTests/generation/precision_test_generator.py b/src/test/resources/dependencyAnalysisTests/generation/precision_test_generator.py new file mode 100644 index 000000000..da028cb2f --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/generation/precision_test_generator.py @@ -0,0 +1,105 @@ + +import os + +def remove_comments(lines: list[str]): + cleansed = [] + for l in lines: + if l.strip() and not l.strip().startswith("//"): + cleansed.append(l) + return cleansed + +def read_snippets_file(path): + with open(path, mode="r") as f: + content = "".join(remove_comments(f.readlines())) + snippets = content.split("SNIPPET") + preamble = snippets[0] + snippet_dict = {} + for snippet in snippets[1:]: + decl = snippet.split("$=$") + name, definition = decl[0].strip(), decl[1].strip() + snippet_dict[name] = definition + return preamble, snippet_dict + + +def read_test_template(path): + with open(path, mode="r") as f: + content = "".join(f.readlines()) + snippets = content.split("method ") + preamble = snippets[0] + methods = snippets[1:] + return preamble, methods + +def extract_vars(line: str): + _, _, after = line.partition("$PrecisionTest:") + var_decls = after.strip().split(" ") + read_write_vars = [] + read_only_vars = [] + for decl in var_decls: + tmp = decl.split("=") + if(tmp[0] == "$READ_ONLY"): + read_only_vars = read_only_vars + tmp[1].split(",") + elif(tmp[0] == "$READ_WRITE"): + read_write_vars = read_write_vars + tmp[1].split(",") + # print(f"line: {line}") + # print(f"read only: {read_only_vars}") + # print(f"read write: {read_write_vars}") + # print() + return read_only_vars, read_write_vars + +def replace_vars(snippet: str, placeholder: str, vars: list[str]): + idx = 0 + gen_vars = [] + while placeholder in snippet: + if idx < len(vars): + snippet = snippet.replace(f"{placeholder}{idx}", vars[idx]) + else: + new_var = f"gen_{placeholder.replace("$", "")}{idx}{".f" if "FIELD" in placeholder else ""}" + snippet = snippet.replace(f"{placeholder}{idx}", new_var) + gen_vars.append(new_var) + idx += 1 + return snippet, gen_vars + +def generate_from_snippet(snippet: str, line: str): + read_only_vars, read_write_vars = extract_vars(line) + snippet, gen_vars_ro_field = replace_vars(snippet, "$RO_INT_FIELD_", [v for v in read_only_vars if "." in v]) + snippet, gen_vars_rw_field = replace_vars(snippet, "$RW_INT_FIELD_", [v for v in read_write_vars if "." in v]) + snippet, gen_vars_ro = replace_vars(snippet, "$RO_INT_", read_only_vars) + snippet, gen_vars_rw = replace_vars(snippet, "$RW_INT_", read_write_vars) + snippet = "".join([f"var {v.split(".")[0]}: Ref\n@irrelevant(\"Explicit\")\ninhale acc({v}, 1/2)\n" for v in gen_vars_ro_field]) + snippet + snippet = "".join([f"var {v.split(".")[0]}: Ref\n@irrelevant(\"Explicit\")\ninhale acc({v})\n" for v in gen_vars_rw_field]) + snippet + all_vars = gen_vars_ro + gen_vars_rw + if len(all_vars) > 0: + snippet = "var " + ", ".join([f"{v}: Int" for v in all_vars]) + "\n" + snippet + return snippet + +def apply_snippet(snippet: str, method: str): + method_lines = method.splitlines() + new_method = "method " + for line in method_lines: + if not "$PrecisionTest:" in line: + new_method += line + "\n" + else: + new_method += generate_from_snippet(snippet, line) + "\n" + return new_method + + +def handle_template_file(path, snippet_preamble, snippets: dict[str, str]): + preamble, methods = read_test_template(path) + os.makedirs(path.replace(".vpr", ""), exist_ok=True) + for snippet_name, snippet in snippets.items(): + f = open(path.replace(".vpr", f"\\{snippet_name}.vpr"), "w") + f.write(preamble) + f.write("\n") + f.write(snippet_preamble) + f.write("\n") + for method in methods: + new_method = apply_snippet(snippet, method) + f.write(new_method) + f.close() + + +preamble, snippet_dict = read_snippets_file("silicon\\src\\test\\resources\\dependencyAnalysisTests\\generation\\snippets.txt") +print(preamble) + +handle_template_file("silicon\\src\\test\\resources\\dependencyAnalysisTests\\unitTests\\permissions.vpr", + preamble, snippet_dict) diff --git a/src/test/resources/dependencyAnalysisTests/generation/snippets.txt b/src/test/resources/dependencyAnalysisTests/generation/snippets.txt new file mode 100644 index 000000000..8736f2a65 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/generation/snippets.txt @@ -0,0 +1,163 @@ +field gen_f: Int + +predicate gen_confuse(i: Int, a: Int){ + i >= 0 // TODO +} + +function gen_add(a: Int, b: Int): Int + ensures result == a + b + +function gen_add_positive(a: Int, b: Int): Int + requires a >= 0 && b >= 0 + ensures result == a + b + ensures result >= 0 + +// assignments +SNIPPET assignment$=${ + @irrelevant("Implicit") + $RW_INT_0 := $RO_INT_0 + $RO_INT_1 +} + +// fold-unfold +SNIPPET fold_unfold_1$=${ + var gen_i: Int + @irrelevant("Implicit") + gen_i := 0 + @irrelevant("Implicit") + fold gen_confuse(gen_i, $RO_INT_0) + @irrelevant("Implicit") + unfold gen_confuse(gen_i, $RO_INT_0) +} + +// fold-unfold +SNIPPET fold_unfold_2$=${ + var gen_i: Int + @irrelevant("Implicit") + gen_i := 0 + @irrelevant("Implicit") + fold gen_confuse(gen_i, $RO_INT_0) + @irrelevant("Implicit") + $RW_INT_0 := $RO_INT_0 + $RO_INT_1 + @irrelevant("Implicit") + unfold gen_confuse(gen_i, $RO_INT_0) + @irrelevant("Implicit") + $RW_INT_0 := gen_i + $RW_INT_0 +} + + +// function call +SNIPPET function_add$=${ + @irrelevant("Implicit") + $RW_INT_0 := gen_add($RO_INT_0, $RO_INT_1) + @irrelevant("Implicit") + $RW_INT_1 := gen_add($RO_INT_2, $RO_INT_3) + @irrelevant("Implicit") + $RW_INT_2 := gen_add($RW_INT_0, $RW_INT_1) + + assert $RW_INT_2 == $RO_INT_0 + $RO_INT_1 + $RO_INT_2 + $RO_INT_3 +} + +// branches & function calls +SNIPPET branch_function$=${ + if(@irrelevant("PathCondition")($RO_INT_0 > 0 && $RO_INT_1 > 0)){ + @irrelevant("Implicit") + $RW_INT_0 := gen_add_positive($RO_INT_0, $RO_INT_1) + }else{ + if(@irrelevant("PathCondition")($RO_INT_0 > 0)){ + @irrelevant("Implicit") + $RW_INT_0 := gen_add_positive($RO_INT_0, -$RO_INT_1) + }else{ + @irrelevant("Implicit") + $RW_INT_0 := 0 + } + } + + + assert $RW_INT_0 >= 0 +} + + +// disjoint references +SNIPPET ref_1$=${ + var gen_x: Ref + @irrelevant("Explicit") + inhale acc(gen_x.f) + + @irrelevant("Implicit") + gen_x.f := $RO_INT_0 + $RO_INT_1 + + @irrelevant("Implicit") + $RW_INT_0 := gen_x.f +} + +// disjoint references - disjoint fields +SNIPPET ref_2$=${ + var gen_x: Ref + @irrelevant("Explicit") + inhale acc(gen_x.gen_f) + + @irrelevant("Implicit") + gen_x.gen_f := $RO_INT_0 + $RO_INT_1 + + @irrelevant("Implicit") + $RW_INT_0 := gen_x.gen_f + + @irrelevant("Implicit") + exhale acc(gen_x.gen_f, 3/4) +} + +// disjoint references - quantified +SNIPPET ref_quantified_1$=${ + var gen_xs: Seq[Ref] + @irrelevant("Explicit") + inhale |gen_xs| > 2 + @irrelevant("Explicit") + inhale forall gen_x: Ref :: gen_x in gen_xs ==> acc(gen_x.f) + @irrelevant("Explicit") + inhale forall gen_x: Ref :: gen_x in gen_xs ==> gen_x.f < 0 + + @irrelevant("Implicit") + gen_xs[0].f := gen_xs[1].f + $RO_INT_0 - $RO_INT_1 + @irrelevant("Implicit") + gen_xs[1].f := gen_xs[0].f - $RW_INT_0 + $RW_INT_1 + + @irrelevant("Implicit") + $RW_INT_0 := gen_xs[0].f + gen_xs[1].f + + @irrelevant("Implicit") + exhale forall gen_x: Ref :: gen_x in gen_xs ==> acc(gen_x.f, 1/2) +} + +// disjoint references - disjoint fields - quantified +SNIPPET ref_quantified_2$=${ + var gen_xs: Seq[Ref] + @irrelevant("Explicit") + inhale |gen_xs| > 2 + @irrelevant("Explicit") + inhale forall gen_x: Ref :: gen_x in gen_xs ==> acc(gen_x.gen_f) + @irrelevant("Explicit") + inhale forall gen_x: Ref :: gen_x in gen_xs ==> gen_x.gen_f < 0 + + @irrelevant("Implicit") + gen_xs[0].gen_f := gen_xs[1].gen_f + $RO_INT_0 - $RO_INT_1 + @irrelevant("Implicit") + gen_xs[1].gen_f := gen_xs[0].gen_f - $RW_INT_0 + $RW_INT_1 + + @irrelevant("Implicit") + $RW_INT_0 := gen_xs[0].gen_f + gen_xs[1].gen_f + + @irrelevant("Implicit") + exhale forall gen_x: Ref :: gen_x in gen_xs ==> acc(gen_x.gen_f, 1/2) +} + + +SNIPPET exhale_inhale$=${ + @irrelevant("Implicit") + exhale acc($RW_INT_FIELD_0, 1/2) + + @irrelevant("Explicit") + inhale acc($RW_INT_FIELD_0, 1/2) +} + + +// TODO: apply, package, goto, while, quasihavoc, inhale, assume, divBy0 \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr index 7d3d0b009..109232126 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr @@ -3,6 +3,8 @@ field f: Int method currentPerm(x: Ref) requires @dependency("Explicit")(acc(x.f, 1/2)) { + // $PrecisionTest: $READ_ONLY=x.f + @testAssertion("Explicit") assert perm(x.f) > 1/4 } @@ -11,6 +13,9 @@ method perm1(){ var x: Ref @dependency("Implicit") x := new(f) + + // $PrecisionTest: $READ_WRITE=x.f + @testAssertion("Explicit") inhale x.f > 0 } @@ -19,6 +24,9 @@ method perm2(){ var x: Ref @dependency("Explicit") inhale acc(x.f, 1/2) + + // $PrecisionTest: $READ_ONLY=x.f + @testAssertion("Explicit") inhale x.f > 0 } @@ -27,6 +35,9 @@ method perm3(){ var x: Ref @dependency("Explicit") inhale acc(x.f) + + // $PrecisionTest: $READ_WRITE=x.f + @testAssertion("Implicit") x.f := 5 } @@ -35,10 +46,15 @@ method perm4(){ var x: Ref @dependency("Explicit") inhale acc(x.f) + @dependency("Explicit") inhale x.f > 0 + @dependency("Implicit") x.f := x.f + 1 + + // $PrecisionTest: $READ_ONLY=x.f + @testAssertion("Explicit") assert x.f > 1 } @@ -49,6 +65,9 @@ method perm5(){ inhale acc(x.f) @irrelevant("Implicit") x.f := x.f + 1 + + // $PrecisionTest: $READ_WRITE=x.f + @testAssertion("Explicit") exhale acc(x.f) } @@ -118,6 +137,8 @@ method noAlias(a: Ref, b: Ref, c: Ref) requires @dependency("Explicit")(acc(b.f, 1/2)) requires @irrelevant("Explicit")(acc(c.f, 1/2)) { + // $PrecisionTest: $READ_WRITE=a.f $READ_ONLY=b.f,c.f + @testAssertion("Explicit") assert a != b } From eda3e1d3a85c78352bf3eb87eefcb731b7a520c0 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 19 Jul 2025 17:20:09 +0200 Subject: [PATCH 181/474] implement precision benchmark --- src/test/scala/AssumptionAnalysisTests.scala | 118 +++++++++++++++---- 1 file changed, 93 insertions(+), 25 deletions(-) diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index d381a320a..0b9b45c32 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -9,20 +9,24 @@ import viper.silver.frontend.SilFrontend import viper.silver.verifier.VerificationResult import viper.silver.{ast, verifier} -import java.io.PrintWriter +import java.io.{File, PrintWriter} import java.nio.file.{Files, Path, Paths} import scala.annotation.unused import scala.jdk.CollectionConverters.IterableHasAsScala +import java.time._ +import java.time.format.DateTimeFormatter class AssumptionAnalysisTests extends AnyFunSuite { val CHECK_PRECISION = true + val EXECUTE_PRECISION_BENCHMARK = false val ignores: Seq[String] = Seq("example1", "example2") val testDirectories: Seq[String] = Seq( // "dependencyAnalysisTests", "dependencyAnalysisTests/all", "dependencyAnalysisTests/unitTests", +// "dependencyAnalysisTests/unitTests/permissions", // "dependencyAnalysisTests/quick" // "dependencyAnalysisTests/fromSilver" ) @@ -34,40 +38,55 @@ class AssumptionAnalysisTests extends AnyFunSuite { var commandLineArguments: Seq[String] = Seq("--timeout", "100" /* seconds */ , "--enableAssumptionAnalysis", "--z3Args", "proof=true unsat-core=true") - testDirectories foreach createTests + + if(EXECUTE_PRECISION_BENCHMARK) { + val directory = new File("precisionBenchmark") + directory.mkdir() + val now: LocalDateTime = LocalDateTime.now() + val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss") + val writer = new PrintWriter(s"benchmark_${now.format(formatter)}.out") + visitFiles("dependencyAnalysisTests/unitTests/permissions", executePrecisionBenchmark(_, _, frontend, writer)) + writer.close() + } + + testDirectories foreach (dir => visitFiles(dir, createSingleTest)) commandLineArguments = Seq("--enableMoreCompleteExhale") ++ commandLineArguments - createTests("dependencyAnalysisTests/mce") + visitFiles("dependencyAnalysisTests/mce", createSingleTest) // test("custom test"){ // executeTest("dependencyAnalysisTests/all/", "list", frontend) // } - def createTests(dirName: String): Unit = { + def visitFiles(dirName: String, function: (String, String) => Unit): Unit = { val path = Paths.get(getClass.getClassLoader.getResource(dirName).toURI) - createTests(path, dirName) + visitFiles(path, dirName, function) } - def createTests(path: Path, dirName: String): Unit = { + private def createSingleTest(dirName: String, fileName: String): Unit = { + test(dirName + "/" + fileName) { + try{ + executeTest(dirName + "/", fileName, frontend) + }catch{ + case t: Throwable => fail(t.getMessage) + } + } + } + + def visitFiles(path: Path, dirName: String, function: (String, String) => Unit): Unit = { val directoryStream = Files.newDirectoryStream(path).asScala val dirContent = directoryStream.toList for (filePath: Path <- dirContent.sorted if Files.isReadable(filePath)) { if(Files.isDirectory(filePath)){ - createTests(filePath, dirName + "/" + filePath.getFileName.toString) + visitFiles(filePath, dirName + "/" + filePath.getFileName.toString, function) }else{ val rawFileName = filePath.getFileName.toString if (rawFileName.endsWith(".vpr")) { val fileName = rawFileName.replace(".vpr", "") if (!ignores.contains(fileName)) - test(dirName + "/" + fileName) { - try{ - executeTest(dirName + "/", fileName, frontend) - }catch{ - case t: Throwable => fail(t.getMessage) - } - } + function(dirName, fileName) } } } @@ -97,10 +116,28 @@ class AssumptionAnalysisTests extends AnyFunSuite { val assumptionAnalysisInterpreters = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].assumptionAnalysisInterpreters - AnnotatedTest(program, assumptionAnalysisInterpreters).execute() + new AnnotatedTest(program, assumptionAnalysisInterpreters).execute() PruningTest(filePrefix + "/" + fileName, program, AssumptionAnalysisInterpreter.joinGraphsAndGetInterpreter(Some(fileName), assumptionAnalysisInterpreters.toSet)).execute() } + def executePrecisionBenchmark(filePrefix: String, + fileName: String, + frontend: SilFrontend, + writer: PrintWriter): Unit = { + println(s"$filePrefix - $fileName") + val program: Program = tests.loadProgram(filePrefix + "/", fileName, frontend) + val result = frontend.verifier.verify(program) + if(result.isInstanceOf[verifier.Failure]) { + cancel(f"Program does not verify. Skip test.\n$result") + return + } + + val assumptionAnalysisInterpreters = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].assumptionAnalysisInterpreters + writer.println(s"$filePrefix - $fileName") + new AnnotatedPrecisionBenchmark(program, assumptionAnalysisInterpreters, writer).execute() + writer.println() + } + /** * (Almost) Fully automated test, which takes a program and its assumption analysis results and, * for each explicit assertion, builds a new program that only contains said assertion and @@ -231,7 +268,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { * but multiple dependency/irrelevant annotations are allowed * */ - case class AnnotatedTest(program: Program, assumptionAnalysisInterpreters: List[AssumptionAnalysisInterpreter]) { + class AnnotatedTest(program: Program, assumptionAnalysisInterpreters: List[AssumptionAnalysisInterpreter]) { def execute(): Unit = { val stmtsWithAssumptionAnnotation: Set[Infoed] = extractAnnotatedStmts({ annotationInfo => annotationInfo.values.contains(irrelevantKeyword) || annotationInfo.values.contains(dependencyKeyword) }) val allAssumptionNodes = assumptionAnalysisInterpreters.flatMap(_.getNonInternalAssumptionNodes) @@ -248,7 +285,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { assert(check, "\n" + errorMsgs.mkString("\n")) } - private def extractAnnotatedStmts(annotationFilter: (ast.AnnotationInfo => Boolean)): Set[ast.Infoed] = { + protected def extractAnnotatedStmts(annotationFilter: (ast.AnnotationInfo => Boolean)): Set[ast.Infoed] = { var nodesWithAnnotation: Set[ast.Infoed] = Set.empty @unused val newP: ast.Program = ViperStrategy.Slim({ @@ -263,7 +300,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { nodesWithAnnotation } - private def checkAssumptionNodeExists(analysisNodes: List[AssumptionAnalysisNode], node: ast.Infoed): Option[String] = { + protected def checkAssumptionNodeExists(analysisNodes: List[AssumptionAnalysisNode], node: ast.Infoed): Option[String] = { val pos = extractSourceLine(node.asInstanceOf[ast.Positioned].pos) val annotationInfo = node.info.getUniqueInfo[ast.AnnotationInfo] .map(ai => ai.values.getOrElse(irrelevantKeyword, ai.values.getOrElse(dependencyKeyword, List.empty))).getOrElse(List.empty) @@ -275,14 +312,14 @@ class AssumptionAnalysisTests extends AnyFunSuite { Option.when(!nodeExists)(s"Missing analysis node:\n${node.toString}\n$pos") } - private def extractSourceLine(pos: ast.Position): Int = { + protected def extractSourceLine(pos: ast.Position): Int = { pos match { case column: ast.HasLineColumn => column.line case _ => -1 } } - private def checkTestAssertionNodeExists(assumptionAnalysisInterpreter: AssumptionAnalysisInterpreter): Seq[String] = { + protected def checkTestAssertionNodeExists(assumptionAnalysisInterpreter: AssumptionAnalysisInterpreter): Seq[String] = { val assumptionNodes = getTestAssumptionNodes(assumptionAnalysisInterpreter.getNonInternalAssumptionNodes) ++ getTestIrrelevantAssumptionNodes(assumptionAnalysisInterpreter.getNonInternalAssumptionNodes) val assertionNodes = getTestAssertionNodes(assumptionAnalysisInterpreter.getNonInternalAssertionNodes) if (assumptionNodes.nonEmpty && assertionNodes.isEmpty) @@ -292,7 +329,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { } - private def checkDependencies(assumptionAnalysisInterpreter: AssumptionAnalysisInterpreter): Seq[String] = { + protected def checkDependencies(assumptionAnalysisInterpreter: AssumptionAnalysisInterpreter): Seq[String] = { val assumptionNodes = getTestAssumptionNodes(assumptionAnalysisInterpreter.getNonInternalAssumptionNodes) val assumptionsPerSource = assumptionNodes groupBy (n => extractSourceLine(n.sourceInfo.getPosition)) val assertionNodes = getTestAssertionNodes(assumptionAnalysisInterpreter.getNonInternalAssertionNodes) @@ -306,7 +343,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { }).filter(_.isDefined).map(_.get).toSeq } - private def checkNonDependencies(assumptionAnalysisInterpreter: AssumptionAnalysisInterpreter): Seq[String] = { + protected def checkNonDependencies(assumptionAnalysisInterpreter: AssumptionAnalysisInterpreter): Seq[String] = { val assumptionNodes = getTestIrrelevantAssumptionNodes(assumptionAnalysisInterpreter.getNonInternalAssumptionNodes) val assumptionsPerSource = assumptionNodes groupBy (n => extractSourceLine(n.sourceInfo.getPosition)) val assertionNodes = getTestAssertionNodes(assumptionAnalysisInterpreter.getNonInternalAssertionNodes) @@ -319,16 +356,47 @@ class AssumptionAnalysisTests extends AnyFunSuite { }).filter(_.isDefined).map(_.get).toSeq } - private def getTestAssertionNodes(nodes: Set[AssumptionAnalysisNode]): Set[AssumptionAnalysisNode] = + protected def getTestAssertionNodes(nodes: Set[AssumptionAnalysisNode]): Set[AssumptionAnalysisNode] = nodes.filter(node => node.sourceInfo.toString.contains("@" + testAssertionKeyword + "(")) - private def getTestAssumptionNodes(nodes: Set[AssumptionAnalysisNode]): Set[AssumptionAnalysisNode] = + protected def getTestAssumptionNodes(nodes: Set[AssumptionAnalysisNode]): Set[AssumptionAnalysisNode] = nodes.filter(_.sourceInfo.toString.contains("@" + dependencyKeyword + "(")) - private def getTestIrrelevantAssumptionNodes(nodes: Set[AssumptionAnalysisNode]): Set[AssumptionAnalysisNode] = + protected def getTestIrrelevantAssumptionNodes(nodes: Set[AssumptionAnalysisNode]): Set[AssumptionAnalysisNode] = nodes.filter(_.sourceInfo.toString.contains("@" + irrelevantKeyword + "(")) } + + + class AnnotatedPrecisionBenchmark(program: Program, assumptionAnalysisInterpreters: List[AssumptionAnalysisInterpreter], + writer: PrintWriter) extends AnnotatedTest(program, assumptionAnalysisInterpreters) { + override def execute(): Unit = { + + assumptionAnalysisInterpreters foreach {a => + val prec = computePrecision(a) + writer.println(s"${a.getMember.map(_.name).getOrElse("unknown")}: $prec") + } + } + + protected def computePrecision(assumptionAnalysisInterpreter: AssumptionAnalysisInterpreter): Double = { + val assumptionNodes = getTestIrrelevantAssumptionNodes(assumptionAnalysisInterpreter.getNonInternalAssumptionNodes) + val assumptionsPerSource = assumptionNodes groupBy (n => extractSourceLine(n.sourceInfo.getPosition)) + val assertionNodes = getTestAssertionNodes(assumptionAnalysisInterpreter.getNonInternalAssertionNodes) + + val dependencies = assumptionAnalysisInterpreter.getAllNonInternalDependencies(assertionNodes.map(_.id)) + val dependenciesPerSource = dependencies groupBy (n => extractSourceLine(n.sourceInfo.getPosition)) + val dependencyIds = dependencies.map(_.id) + + if(dependenciesPerSource.nonEmpty){ + val wrongDependencies = assumptionsPerSource.filter({ case (_, assumptions) => dependencyIds.intersect(assumptions.map(_.id)).nonEmpty }) + 1.0 - (wrongDependencies.size.toDouble / dependenciesPerSource.size.toDouble) + }else{ + 1.0 + } + } + + } } + From ddcf88a2ae6375ee5bc15b1f2ec737dced0703a1 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 19 Jul 2025 21:55:04 +0200 Subject: [PATCH 182/474] annotate unit tests for precision benchmarks --- .../generation/precision_test_generator.py | 21 ++++--- .../generation/snippets.txt | 5 ++ .../dependencyAnalysisTests/new/meeting.vpr | 2 +- .../unitTests/branches.vpr | 10 ++++ .../unitTests/inhaleExhale.vpr | 22 ++++++-- .../unitTests/loops.vpr | 10 ++++ .../unitTests/magicWands.vpr | 16 ++++++ .../unitTests/method-call.vpr | 8 +++ .../unitTests/misc.vpr | 6 ++ .../unitTests/permWildcard.vpr | 14 ++--- .../unitTests/predicates.vpr | 26 ++++++++- .../unitTests/quantifiedPermissions.vpr | 21 +++++++ src/test/scala/AssumptionAnalysisTests.scala | 55 ++++++++++++------- 13 files changed, 170 insertions(+), 46 deletions(-) diff --git a/src/test/resources/dependencyAnalysisTests/generation/precision_test_generator.py b/src/test/resources/dependencyAnalysisTests/generation/precision_test_generator.py index da028cb2f..dd9b4632e 100644 --- a/src/test/resources/dependencyAnalysisTests/generation/precision_test_generator.py +++ b/src/test/resources/dependencyAnalysisTests/generation/precision_test_generator.py @@ -83,11 +83,14 @@ def apply_snippet(snippet: str, method: str): return new_method -def handle_template_file(path, snippet_preamble, snippets: dict[str, str]): +def handle_template_file(path: str, output_path: str, snippet_preamble: str, snippets: dict[str, str]): preamble, methods = read_test_template(path) - os.makedirs(path.replace(".vpr", ""), exist_ok=True) + if not path.endswith(".vpr"): + return + program_foldername = path.replace(".vpr", "").split("\\")[-1] + os.makedirs(f"{output_path}\\{program_foldername}", exist_ok=True) for snippet_name, snippet in snippets.items(): - f = open(path.replace(".vpr", f"\\{snippet_name}.vpr"), "w") + f = open(f"{output_path}\\{program_foldername}\\{snippet_name}.vpr", "w") f.write(preamble) f.write("\n") f.write(snippet_preamble) @@ -98,8 +101,12 @@ def handle_template_file(path, snippet_preamble, snippets: dict[str, str]): f.close() -preamble, snippet_dict = read_snippets_file("silicon\\src\\test\\resources\\dependencyAnalysisTests\\generation\\snippets.txt") -print(preamble) +def process_folder(folder_path: str, output_path: str, snippet_preamble: str, snippets: dict[str, str]): + files = [os.path.join(folder_path, f) for f in os.listdir(folder_path) if os.path.isfile(os.path.join(folder_path, f))] + for file in files: + handle_template_file(file, output_path, snippet_preamble, snippets) -handle_template_file("silicon\\src\\test\\resources\\dependencyAnalysisTests\\unitTests\\permissions.vpr", - preamble, snippet_dict) +preamble, snippet_dict = read_snippets_file("silicon\\src\\test\\resources\\dependencyAnalysisTests\\generation\\snippets.txt") +process_folder("silicon\\src\\test\\resources\\dependencyAnalysisTests\\unitTests", + "silicon\\src\\test\\resources\\dependencyAnalysisTests\\precisionTests", + preamble, snippet_dict) diff --git a/src/test/resources/dependencyAnalysisTests/generation/snippets.txt b/src/test/resources/dependencyAnalysisTests/generation/snippets.txt index 8736f2a65..0bf6e3a8d 100644 --- a/src/test/resources/dependencyAnalysisTests/generation/snippets.txt +++ b/src/test/resources/dependencyAnalysisTests/generation/snippets.txt @@ -18,6 +18,11 @@ SNIPPET assignment$=${ $RW_INT_0 := $RO_INT_0 + $RO_INT_1 } +SNIPPET assignment2$=${ + @irrelevant("Implicit") + $RW_INT_1 := $RO_INT_0 + $RO_INT_1 +} + // fold-unfold SNIPPET fold_unfold_1$=${ var gen_i: Int diff --git a/src/test/resources/dependencyAnalysisTests/new/meeting.vpr b/src/test/resources/dependencyAnalysisTests/new/meeting.vpr index e799800f1..70c73ff02 100644 --- a/src/test/resources/dependencyAnalysisTests/new/meeting.vpr +++ b/src/test/resources/dependencyAnalysisTests/new/meeting.vpr @@ -23,7 +23,7 @@ method inhaleImprecision(){ } // issue #01 - imprecision due to wildcards -// when accessing a qp resource, check Z < (x in xs? k1:Z) + (y in ys? k2:Z) +// when accessing a qp resource, check Z < (l0? (x in xs? k1:Z): Z) + (y in ys? k2:Z) // -> the unsat core contains dependencies to all qp perm amounts in order to assert k1, k2, ... >= Z method quantifiedPerm3(xs: Seq[Ref], ys: Seq[Ref]) requires |xs| > 5 diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/branches.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/branches.vpr index ef40a5744..32cb12205 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/branches.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/branches.vpr @@ -1,3 +1,4 @@ +field f: Int method branch1(){ var x: Int, y: Int @@ -10,6 +11,8 @@ method branch1(){ y := -x + 1 } + // $PrecisionTest: $READ_WRITE=x $READ_ONLY=y + @testAssertion("Explicit") assert y > 0 } @@ -25,6 +28,8 @@ method branch2(){ assume y >= 10 } + // $PrecisionTest: $READ_WRITE=x $READ_ONLY=y + @testAssertion("Explicit") assert y > 0 } @@ -32,6 +37,8 @@ method branch2(){ method branch3(){ var x: Int, y: Int + // $PrecisionTest: $READ_WRITE=x,y + if(@dependency("PathCondition")(x > 0)){ @dependency("Implicit") y := x + 1 @@ -50,6 +57,7 @@ method branch4(){ if(@dependency("PathCondition")(x > 0)){ @irrelevant("Implicit") y := x + 1 + // $PrecisionTest: $READ_WRITE=x,y }else{ @dependency("Implicit") y := 10 - x @@ -63,6 +71,8 @@ method branch5(){ @dependency("Explicit") assume y > 0 + // $PrecisionTest: $READ_ONLY=x,y + if(@irrelevant("PathCondition")(x > 0)){ @irrelevant("Implicit") y := x + 1 diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/inhaleExhale.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/inhaleExhale.vpr index 0f8212036..41d221cf9 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/inhaleExhale.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/inhaleExhale.vpr @@ -3,10 +3,13 @@ field f: Int method exhaleInhale(a: Ref) requires @dependency("Explicit")(acc(a.f)) { - @dependency("Implicit") + @irrelevant("Implicit") // TODO ake exhale acc(a.f, 1/2) - @dependency("Explicit") + @irrelevant("Explicit") inhale acc(a.f, 1/2) + + // $PrecisionTest: READ_WRITE=a.f + @testAssertion("Explicit") assert perm(a.f) == write } @@ -14,10 +17,13 @@ method exhaleInhale(a: Ref) method exhaleInhale2(a: Ref) requires @dependency("Explicit")(acc(a.f)) { - // @irrelevant("Implicit") TODO ake: imprecision? + @irrelevant("Implicit") // TODO ake: imprecision exhale acc(a.f, 1/2) - // @irrelevant("Explicit") TODO ake: imprecision? + @irrelevant("Explicit") // TODO ake: imprecision inhale acc(a.f, 1/2) + + // $PrecisionTest: READ_WRITE=a.f + @testAssertion("Explicit") assert perm(a.f) >= 1/2 } @@ -30,9 +36,11 @@ method exhaleImprecision(){ @dependency("Explicit") inhale x.f > 0 - // @irrelevant("Implicit") TODO ake: imprecision? + @irrelevant("Implicit") // TODO ake: imprecision exhale acc(x.f, 1/2) + // $PrecisionTest: READ_ONLY=x.f + @testAssertion("Explicit") assert x.f > 0 } @@ -45,9 +53,11 @@ method inhaleImprecision(){ @dependency("Explicit") inhale x.f > 0 - //@irrelevant("Implicit") TODO ake: imprecision? + @irrelevant("Implicit") // TODO ake: imprecision inhale acc(x.f, 1/2) + // $PrecisionTest: READ_ONLY=x.f + @testAssertion("Explicit") assert x.f > 0 } \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/loops.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/loops.vpr index 54e923053..c9d8085d2 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/loops.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/loops.vpr @@ -1,3 +1,4 @@ +field f:Int method loop1(){ var i: Int @@ -16,6 +17,9 @@ method loop1(){ // @irrelevant("Implicit") // imprecise i := i - 1 } + + // $PrecisionTest: $READ_ONLY=res $READ_WRITE=i + @testAssertion("Explicit") assert res >= 0 } @@ -34,6 +38,7 @@ method loop2(){ { @irrelevant("Implicit") res := res + i + // $PrecisionTest: $READ_ONLY=res,i @dependency("Implicit") i := i - 1 @testAssertion("Explicit") @@ -46,6 +51,7 @@ method loop3(){ var res: Int @dependency("Implicit") res := 0 + // $PrecisionTest: $READ_ONLY=res $READ_WRITE=i @irrelevant("Implicit") i := 10 while(@dependency("PathCondition")(i > 0)) @@ -65,6 +71,10 @@ method loop3(){ method loop4(){ var i: Int var res: Int + + + // $PrecisionTest: $READ_WRITE=i,res + @dependency("Implicit") res := 0 @irrelevant("Implicit") diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr index fa9295410..2a117940b 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr @@ -17,8 +17,12 @@ method basicApply() inhale x > 0 @dependency("Explicit") inhale x > 0 --* y > 0 + + // $PrecisionTest: $READ_ONLY=x,y + @dependency("Implicit") apply x > 0 --* y > 0 + @testAssertion("Explicit") assert y > 0 } @@ -41,6 +45,8 @@ method basicPackage(l: Ref) fold list(l) } + // $PrecisionTest: // TODO ake + @dependency("Implicit") apply list(tmp) --* list(l) @@ -72,6 +78,8 @@ method advancedPackage(a: Int, b: Int) @dependency("Explicit") inhale greater0(b) + // $PrecisionTest: $READ_ONLY=a,b + @dependency("Implicit") apply (greater0(a) && greater0(b)) --* greater0(a + b) @@ -88,6 +96,8 @@ method quantifiedWand(xs: Seq[Int], b: Int) @dependency("Implicit") apply greater0(xs[0]) --* greater0(xs[0] + b) + // $PrecisionTest: $READ_ONLY=xs[0],b,xs[1] // TODO ake: sequence update + @testAssertion("Explicit") assert greater0(xs[0] + b) } @@ -101,6 +111,8 @@ method packagePrecision(l: Ref) @dependency("Implicit") tmp := l.next + // $PrecisionTest: $READ_WRITE=l.val + @irrelevant("Implicit") package list(tmp) --* list(l) { @@ -116,7 +128,9 @@ method packageExhale(x: Ref) requires @dependency("Explicit")(acc(x.f)) { + // $PrecisionTest: $READ_WRITE=x.f + @dependency("Implicit") package acc(x.f, 1/2) --* acc(x.f) @@ -131,6 +145,8 @@ method packageExhale2(x: Ref) @dependency("Implicit") package acc(x.f, 1/2) --* acc(x.f) + // $PrecisionTest: $READ_ONLY=x.f + @dependency("Implicit") apply acc(x.f, 1/2) --* acc(x.f) diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/method-call.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/method-call.vpr index d0d370311..df39345f7 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/method-call.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/method-call.vpr @@ -28,6 +28,8 @@ method call2(){ @dependency("Implicit") z := sum(x, y) + // $PrecisionTest: $READ_ONLY=x,y,z + @testAssertion("Explicit") assert z == x + y } @@ -42,6 +44,9 @@ method call3(x: Int, y: Int) var n: Int, n2: Int @dependency("Implicit") n := sum(x, y) + + // $PrecisionTest: $READ_WRITE=n2 $READ_ONLY=x,y,n + @dependency("Implicit") n2 := sum(x, y) @dependency("Implicit") @@ -65,6 +70,9 @@ method call4(x: Int, y: Int, z: Int) var n: Int, n2: Int @dependency("Implicit") n := sum(x, y) + + // $PrecisionTest: $READ_WRITE=n2 $READ_ONLY=n,z,x,y + @irrelevant("Implicit") n2 := sum(x, z) diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/misc.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/misc.vpr index 07140d26f..26d139f97 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/misc.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/misc.vpr @@ -6,6 +6,9 @@ method divBy0(a: Ref, n: Int) requires @irrelevant("Explicit")(a.f > 0) { var res: Int + + // $PrecisionTest: $READ_WRITE=a.f,res $READ_ONLY=n + @testAssertion("Implicit") res := a.f / n } @@ -15,6 +18,9 @@ method assumeFalse() var a: Int @dependency("Explicit") assume false + + // $PrecisionTest: $READ_WRITE=a + @testAssertion("Explicit") assert a == 2 } \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/permWildcard.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/permWildcard.vpr index 238a575d5..379411b50 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/permWildcard.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/permWildcard.vpr @@ -6,23 +6,19 @@ method wildcardPerm(x: Ref, y: Ref) requires @irrelevant("Explicit")(acc(y.f, wildcard)) requires x != y // could be a dependency but doesn't have to { - @testAssertion("Implicit") - inhale x.f > 0 -} + // $PrecisionTest: $READ_ONLY=x.f,y.f -method wildcardPerm2(x: Ref, y: Ref) - requires @irrelevant("Explicit")(acc(y.f, wildcard)) - requires @dependency("Explicit")(acc(x.f, wildcard)) - requires x != y // could be a dependency but doesn't have to -{ @testAssertion("Implicit") inhale x.f > 0 } + method wildcardPermDistinctFields(x: Ref, y: Ref) requires @dependency("Explicit")(acc(x.f, wildcard)) requires @irrelevant("Explicit")(acc(y.g, wildcard)) { + // $PrecisionTest: $READ_ONLY=x.f,y.g + @testAssertion("Implicit") inhale x.f > 0 } @@ -36,6 +32,8 @@ method quantifiedPermWildcard(xs: Seq[Ref], ys: Seq[Ref]) @irrelevant("Explicit") inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard)) + // $PrecisionTest: $READ_ONLY=xs[0].f,xs[1].f,ys[0].f,ys[1].f + @testAssertion("Explicit") assert xs[0].f > 0 } \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/predicates.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/predicates.vpr index 6caeeb1e3..33e482bd6 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/predicates.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/predicates.vpr @@ -1,4 +1,4 @@ - +field f: Int predicate greater0(n: Int){ n > 0 @@ -11,7 +11,12 @@ predicate greater5(n: Int){ method foldP(n: Int) requires @dependency("Explicit")(n > 10) { - var x: Int := 1 + var x: Int + @dependency("Implicit") + x := 1 + + // $PrecisionTest: $READ_ONLY=n,x + @testAssertion("Implicit") fold greater0(x + n) } @@ -19,11 +24,16 @@ method foldP(n: Int) method unfoldP(n: Int) requires @dependency("Explicit")(greater0(n)) { - var x: Int := 1 + var x: Int + @dependency("Implicit") + x := 1 @dependency("Implicit") unfold greater0(n) @dependency("Implicit") x := n + x + + // $PrecisionTest: $READ_ONLY=x,n + @testAssertion("Explicit") assert x > 1 } @@ -36,6 +46,9 @@ method unfoldFoldP(a: Int, b: Int) unfold greater0(a) @dependency("Implicit") unfold greater5(b) + + // $PrecisionTest: $READ_ONLY=a,b + @testAssertion("Implicit") fold greater5(a + b) } @@ -47,6 +60,8 @@ method callWithPredicate(a: Int, b: Int) @dependency("Implicit") unfoldFoldP(a, b) + // $PrecisionTest: $READ_ONLY=a,b + @testAssertion("Explicit") assert greater5(a + b) } @@ -60,6 +75,9 @@ method unfoldPrecision(a: Int, b: Int) unfold greater0(a) @dependency("Implicit") unfold greater5(b) + + // $PrecisionTest: $READ_ONLY=a,b + @testAssertion("Implicit") fold greater0(b) } @@ -68,6 +86,8 @@ method foldPrecision(a: Int, b: Int) requires @irrelevant("Explicit")(a > 5) requires @dependency("Explicit")(b > 5) { + // $PrecisionTest: $READ_ONLY=a,b + @testAssertion("Implicit") fold greater0(b) } \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr index a4c63b12f..150ed8425 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr @@ -4,6 +4,8 @@ method quantifiedPerm1(xs: Seq[Ref]) { assume @dependency("Explicit")(|xs| > 5) inhale @dependency("Explicit")(forall x: Ref :: x in xs ==> acc(x.f)) + // $PrecisionTest: $READ_WRITE=xs[0].f,xs[1].f,xs[2].f + @testAssertion() xs[0].f := 10 } @@ -12,6 +14,8 @@ method quantifiedPerm2(xs: Seq[Ref]) { assume @dependency("Explicit")(|xs| > 5) inhale @dependency("Explicit")(forall x: Ref :: x in xs ==> acc(x.f)) + // $PrecisionTest: $READ_WRITE=xs[0].f,xs[1].f,xs[4].f + @testAssertion() xs[0].f := xs[1].f + xs[4].f } @@ -21,6 +25,8 @@ method quantifiedPerm3(xs: Seq[Ref], y: Ref) { inhale @dependency("Explicit")(forall x: Ref :: x in xs ==> acc(x.f)) inhale @dependency("Explicit")(acc(y.f, wildcard)) + // $PrecisionTest: $READ_WRITE=xs[0].f,y.f,xs[1].f,xs[4].f + @testAssertion("Explicit") assert xs[0] != y } @@ -30,6 +36,8 @@ method quantifiedExhalePartiallyTest(xs: Seq[Ref]) { assume |xs| > 5 inhale @dependency("Explicit")(forall x: Ref :: x in xs ==> acc(x.f)) + // $PrecisionTest: $READ_WRITE=xs[0].f,xs[1].f,xs[4].f + @testAssertion("Explicit") exhale forall x: Ref :: x in xs ==> acc(x.f, 1/2) } @@ -40,6 +48,9 @@ method quantifiedExhalePartially(xs: Seq[Ref]) { inhale @dependency("Explicit")(forall x: Ref :: x in xs ==> acc(x.f)) exhale forall x: Ref :: x in xs ==> acc(x.f, 1/2) + + // $PrecisionTest: $READ_WRITE=res $READ_ONLY=xs[1].f,xs[0].f,xs[2].f + @testAssertion("Implicit") res := xs[1].f + 1 } @@ -48,6 +59,8 @@ method quantifiedExhaleFully(xs: Seq[Ref]) { assume @irrelevant("Explicit")(|xs| > 5) inhale @dependency("Explicit")(forall x: Ref :: x in xs ==> acc(x.f)) + // $PrecisionTest: $READ_WRITE=xs[0].f,xs[2].f $READ_ONLY=xs[1].f + @testAssertion("Explicit") exhale forall x: Ref :: x in xs ==> acc(x.f) } @@ -58,6 +71,8 @@ method quantifiedPermWrite1(xs: Seq[Ref]) { @dependency("Explicit") inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) + // $PrecisionTest: $READ_WRITE=xs[2].f $READ_ONLY=xs[0].f,xs[1].f + @testAssertion("Implicit") xs[0].f := 0 } @@ -71,6 +86,8 @@ method quantifiedPermWrite2(xs: Seq[Ref]) { @dependency("Implicit") xs[0].f := 0 + // $PrecisionTest: $READ_WRITE=xs[2].f $READ_ONLY=xs[0].f,xs[1].f + @testAssertion("Explicit") assert xs[0].f == 0 } @@ -84,6 +101,8 @@ method quantifiedPermWrite3(xs: Seq[Ref]) { @irrelevant("Implicit") xs[0].f := 0 + // $PrecisionTest: $READ_WRITE=xs[0].f,xs[2].f $READ_ONLY=xs[1].f + @testAssertion("Explicit") assert xs[0] != xs[1] ==> xs[1].f > 0 } @@ -97,6 +116,8 @@ method quantifiedPermWrite4(xs: Seq[Ref], ys: Seq[Ref]) @irrelevant("Explicit") inhale forall y: Ref :: y in ys ==> acc(y.f) + // $PrecisionTest: $READ_WRITE=xs[1].f,ys[0].f $READ_ONLY=xs[0].f,ys[1].f + @testAssertion("Implicit") xs[0].f := 2 } diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index 0b9b45c32..18ebf2e26 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -19,8 +19,9 @@ import java.time.format.DateTimeFormatter class AssumptionAnalysisTests extends AnyFunSuite { - val CHECK_PRECISION = true + val CHECK_PRECISION = false val EXECUTE_PRECISION_BENCHMARK = false + val EXECUTE_TEST=true val ignores: Seq[String] = Seq("example1", "example2") val testDirectories: Seq[String] = Seq( // "dependencyAnalysisTests", @@ -40,19 +41,24 @@ class AssumptionAnalysisTests extends AnyFunSuite { if(EXECUTE_PRECISION_BENCHMARK) { - val directory = new File("precisionBenchmark") - directory.mkdir() - val now: LocalDateTime = LocalDateTime.now() - val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss") - val writer = new PrintWriter(s"benchmark_${now.format(formatter)}.out") - visitFiles("dependencyAnalysisTests/unitTests/permissions", executePrecisionBenchmark(_, _, frontend, writer)) - writer.close() + test("precision benchmark") { + val basePath = "src/test/resources/dependencyAnalysisTests/precisionTests/results" + val directory = new File(basePath) + directory.mkdir() + val now: LocalDateTime = LocalDateTime.now() + val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss") + val writer = new PrintWriter(s"$basePath/result_${now.format(formatter)}.out") + visitFiles("dependencyAnalysisTests/precisionTests", executePrecisionBenchmark(_, _, frontend, writer)) + writer.close() + } } - testDirectories foreach (dir => visitFiles(dir, createSingleTest)) + if(EXECUTE_TEST) + testDirectories foreach (dir => visitFiles(dir, createSingleTest)) commandLineArguments = Seq("--enableMoreCompleteExhale") ++ commandLineArguments - visitFiles("dependencyAnalysisTests/mce", createSingleTest) + if(EXECUTE_TEST) + visitFiles("dependencyAnalysisTests/mce", createSingleTest) // test("custom test"){ // executeTest("dependencyAnalysisTests/all/", "list", frontend) @@ -124,18 +130,25 @@ class AssumptionAnalysisTests extends AnyFunSuite { fileName: String, frontend: SilFrontend, writer: PrintWriter): Unit = { - println(s"$filePrefix - $fileName") - val program: Program = tests.loadProgram(filePrefix + "/", fileName, frontend) - val result = frontend.verifier.verify(program) - if(result.isInstanceOf[verifier.Failure]) { - cancel(f"Program does not verify. Skip test.\n$result") - return + println(s"Precision Benchmark for $filePrefix - $fileName started...") + try{ + val program: Program = tests.loadProgram(filePrefix + "/", fileName, frontend) + val result = frontend.verifier.verify(program) + if(result.isInstanceOf[verifier.Failure]) { + writer.println("Program does not verify. Skip") + println(f"Program does not verify. Skip.\n$result") + return + } + val assumptionAnalysisInterpreters = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].assumptionAnalysisInterpreters + writer.println(s"$filePrefix - $fileName") + new AnnotatedPrecisionBenchmark(program, assumptionAnalysisInterpreters, writer).execute() + writer.println() + println(s"Precision Benchmark for $filePrefix - $fileName done.") + }catch{ + case e: Exception => + writer.println("Program caused an exception. Skip") + println(s"Exception caught: ${e.getMessage}") } - - val assumptionAnalysisInterpreters = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].assumptionAnalysisInterpreters - writer.println(s"$filePrefix - $fileName") - new AnnotatedPrecisionBenchmark(program, assumptionAnalysisInterpreters, writer).execute() - writer.println() } /** From 73d47697aa032691b3dd24d5476faa2fd6262a1c Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 21 Jul 2025 09:18:28 +0200 Subject: [PATCH 183/474] implement precision benchmark soundness check, minor benchmark fixes --- .../generation/snippets.txt | 23 ++------- .../unitTests/quantifiedPermissions.vpr | 7 ++- src/test/scala/AssumptionAnalysisTests.scala | 47 ++++++++++++++++--- 3 files changed, 49 insertions(+), 28 deletions(-) diff --git a/src/test/resources/dependencyAnalysisTests/generation/snippets.txt b/src/test/resources/dependencyAnalysisTests/generation/snippets.txt index 0bf6e3a8d..0ec599e4c 100644 --- a/src/test/resources/dependencyAnalysisTests/generation/snippets.txt +++ b/src/test/resources/dependencyAnalysisTests/generation/snippets.txt @@ -34,21 +34,6 @@ SNIPPET fold_unfold_1$=${ unfold gen_confuse(gen_i, $RO_INT_0) } -// fold-unfold -SNIPPET fold_unfold_2$=${ - var gen_i: Int - @irrelevant("Implicit") - gen_i := 0 - @irrelevant("Implicit") - fold gen_confuse(gen_i, $RO_INT_0) - @irrelevant("Implicit") - $RW_INT_0 := $RO_INT_0 + $RO_INT_1 - @irrelevant("Implicit") - unfold gen_confuse(gen_i, $RO_INT_0) - @irrelevant("Implicit") - $RW_INT_0 := gen_i + $RW_INT_0 -} - // function call SNIPPET function_add$=${ @@ -129,8 +114,8 @@ SNIPPET ref_quantified_1$=${ @irrelevant("Implicit") $RW_INT_0 := gen_xs[0].f + gen_xs[1].f - @irrelevant("Implicit") - exhale forall gen_x: Ref :: gen_x in gen_xs ==> acc(gen_x.f, 1/2) + //@irrelevant("Implicit") + //exhale forall gen_x: Ref :: gen_x in gen_xs ==> acc(gen_x.f, 1/2) // TODO ake } // disjoint references - disjoint fields - quantified @@ -151,8 +136,8 @@ SNIPPET ref_quantified_2$=${ @irrelevant("Implicit") $RW_INT_0 := gen_xs[0].gen_f + gen_xs[1].gen_f - @irrelevant("Implicit") - exhale forall gen_x: Ref :: gen_x in gen_xs ==> acc(gen_x.gen_f, 1/2) + //@irrelevant("Implicit") + //exhale forall gen_x: Ref :: gen_x in gen_xs ==> acc(gen_x.gen_f, 1/2) // TODO ake } diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr index 150ed8425..2d243491f 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr @@ -73,6 +73,8 @@ method quantifiedPermWrite1(xs: Seq[Ref]) { // $PrecisionTest: $READ_WRITE=xs[2].f $READ_ONLY=xs[0].f,xs[1].f + inhale xs[0] != xs[2] // relevant for precision test + @testAssertion("Implicit") xs[0].f := 0 } @@ -88,6 +90,7 @@ method quantifiedPermWrite2(xs: Seq[Ref]) { // $PrecisionTest: $READ_WRITE=xs[2].f $READ_ONLY=xs[0].f,xs[1].f + inhale xs[0] != xs[2] // relevant for precision test @testAssertion("Explicit") assert xs[0].f == 0 } @@ -101,7 +104,7 @@ method quantifiedPermWrite3(xs: Seq[Ref]) { @irrelevant("Implicit") xs[0].f := 0 - // $PrecisionTest: $READ_WRITE=xs[0].f,xs[2].f $READ_ONLY=xs[1].f + // $PrecisionTest: $READ_WRITE=xs[0].f $READ_ONLY=xs[1].f,xs[2].f @testAssertion("Explicit") assert xs[0] != xs[1] ==> xs[1].f > 0 @@ -116,7 +119,7 @@ method quantifiedPermWrite4(xs: Seq[Ref], ys: Seq[Ref]) @irrelevant("Explicit") inhale forall y: Ref :: y in ys ==> acc(y.f) - // $PrecisionTest: $READ_WRITE=xs[1].f,ys[0].f $READ_ONLY=xs[0].f,ys[1].f + // $PrecisionTest: $READ_WRITE=ys[0].f,ys[1].f $READ_ONLY=xs[0].f,xs[1].f @testAssertion("Implicit") xs[0].f := 2 diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index 18ebf2e26..f914181af 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -20,8 +20,8 @@ import java.time.format.DateTimeFormatter class AssumptionAnalysisTests extends AnyFunSuite { val CHECK_PRECISION = false - val EXECUTE_PRECISION_BENCHMARK = false - val EXECUTE_TEST=true + val EXECUTE_PRECISION_BENCHMARK = true + val EXECUTE_TEST=false val ignores: Seq[String] = Seq("example1", "example2") val testDirectories: Seq[String] = Seq( // "dependencyAnalysisTests", @@ -141,6 +141,8 @@ class AssumptionAnalysisTests extends AnyFunSuite { } val assumptionAnalysisInterpreters = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].assumptionAnalysisInterpreters writer.println(s"$filePrefix - $fileName") + val fullGraphInterpreter = AssumptionAnalysisInterpreter.joinGraphsAndGetInterpreter(Some(fileName), assumptionAnalysisInterpreters.toSet) + new PrecisionBenchmarkSoundnessTest(filePrefix + "/" + fileName, program, fullGraphInterpreter, writer).execute() new AnnotatedPrecisionBenchmark(program, assumptionAnalysisInterpreters, writer).execute() writer.println() println(s"Precision Benchmark for $filePrefix - $fileName done.") @@ -170,7 +172,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { } } - private def pruneAndVerify(relevantLines: Set[Int], exportFileName: String): Unit = { + protected def pruneAndVerify(relevantLines: Set[Int], exportFileName: String): Unit = { val relevantNodes = relevantLines.flatMap(line => fullGraphInterpreter.getNodesByLine(line)) val dependencies = fullGraphInterpreter.getAllNonInternalDependencies(relevantNodes.map(_.id)) @@ -182,7 +184,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { assert(!result.isInstanceOf[verifier.Failure], s"Failed to verify new program ${newProgram.toString()}") } - private def exportPrunedProgram(exportFileName: String, newProgram: Program, pruningFactor: Double, result: VerificationResult): Unit = { + protected def exportPrunedProgram(exportFileName: String, newProgram: Program, pruningFactor: Double, result: VerificationResult): Unit = { val writer = new PrintWriter(exportFileName) writer.println("// test result: " + !result.isInstanceOf[verifier.Failure]) writer.println("// cleanse factor: " + pruningFactor) @@ -190,7 +192,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { writer.close() } - private def getPrunedProgram(crucialNodes: Set[AssumptionAnalysisNode]): (ast.Program, Double) = { + protected def getPrunedProgram(crucialNodes: Set[AssumptionAnalysisNode]): (ast.Program, Double) = { val crucialNodesWithStmtInfo = crucialNodes filter (_.sourceInfo.getTopLevelSource.isInstanceOf[StmtAnalysisSourceInfo]) map (_.sourceInfo.getTopLevelSource.asInstanceOf[StmtAnalysisSourceInfo]) val crucialNodesWithExpInfo = crucialNodes filter (_.sourceInfo.getTopLevelSource.isInstanceOf[ExpAnalysisSourceInfo]) map (_.sourceInfo.getTopLevelSource.asInstanceOf[ExpAnalysisSourceInfo]) var total = 0 @@ -259,11 +261,11 @@ class AssumptionAnalysisTests extends AnyFunSuite { (newProgram, removed.toDouble / total.toDouble) } - private def isCrucialExp(exp: ast.Exp, crucialNodesWithExpInfo: Set[ExpAnalysisSourceInfo]): Boolean = { + protected def isCrucialExp(exp: ast.Exp, crucialNodesWithExpInfo: Set[ExpAnalysisSourceInfo]): Boolean = { crucialNodesWithExpInfo exists (n => n.getPositionString.equals(AnalysisSourceInfo.extractPositionString(exp.pos))) // TODO ake: currently we compare only lines not columns! } - private def isCrucialStmt(stmt: ast.Stmt, crucialNodesWithStmtInfo: Set[StmtAnalysisSourceInfo]): Boolean = { + protected def isCrucialStmt(stmt: ast.Stmt, crucialNodesWithStmtInfo: Set[StmtAnalysisSourceInfo]): Boolean = { crucialNodesWithStmtInfo exists (n => n.getPositionString.equals(AnalysisSourceInfo.extractPositionString(stmt.pos))) } } @@ -411,5 +413,36 @@ class AssumptionAnalysisTests extends AnyFunSuite { } } + + + class PrecisionBenchmarkSoundnessTest(name: String, program: Program, fullGraphInterpreter: AssumptionAnalysisInterpreter , + writer: PrintWriter) extends PruningTest(name, program, fullGraphInterpreter) { + + override def execute(): Unit = { + val irrelevantNodes = fullGraphInterpreter.getNodes.filter(node => node.sourceInfo.toString.contains("@irrelevant(")).flatMap(_.sourceInfo.getLineNumber) + + val testAssertionLines = fullGraphInterpreter.getNodes.filter(node => node.sourceInfo.toString.contains("@testAssertion(")).flatMap(_.sourceInfo.getLineNumber) + val testAssertionNodes = testAssertionLines.flatMap(line => fullGraphInterpreter.getNodesByLine(line)) + + val relevantLines = fullGraphInterpreter.getAllNonInternalDependencies(testAssertionNodes.map(_.id)).flatMap(_.sourceInfo.getLineNumber).diff(irrelevantNodes) + + pruneAndVerify(testAssertionLines ++ relevantLines, "src/test/resources/" + fileName + s"_test.out") + } + + override protected def pruneAndVerify(relevantLines: Set[Int], exportFileName: String): Unit = { + val relevantNodes = relevantLines.flatMap(line => fullGraphInterpreter.getNodesByLine(line)) + + val crucialNodes = relevantNodes + val (newProgram, pruningFactor) = getPrunedProgram(crucialNodes) + val result = frontend.verifier.verify(newProgram) + exportPrunedProgram(exportFileName, newProgram, pruningFactor, result) + if(result.isInstanceOf[verifier.Failure]) { + writer.println(s"!!!!!!!!!!!\nFailed to verify new program $exportFileName\n") + println(s"!!!!!!!!!!!\nFailed to verify new program $exportFileName\n") + } + } + + } } + From 900b1fcda3338fdfdf218d39c929155e7c1a80c3 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 21 Jul 2025 17:05:44 +0200 Subject: [PATCH 184/474] benchmark fixes --- .../generation/precision_benchmark_plotter.py | 67 +++++++++++++++++++ .../generation/precision_test_generator.py | 29 +++++++- src/test/scala/AssumptionAnalysisTests.scala | 28 ++++++-- 3 files changed, 114 insertions(+), 10 deletions(-) create mode 100644 src/test/resources/dependencyAnalysisTests/generation/precision_benchmark_plotter.py diff --git a/src/test/resources/dependencyAnalysisTests/generation/precision_benchmark_plotter.py b/src/test/resources/dependencyAnalysisTests/generation/precision_benchmark_plotter.py new file mode 100644 index 000000000..c148990e3 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/generation/precision_benchmark_plotter.py @@ -0,0 +1,67 @@ +import os + +def read_results_file(result_file_path: str) -> dict[tuple[str, str], list[tuple[str, float]]]: + with open(result_file_path) as f: + lines = f.readlines() + + current_test = None + current_results = [] + results = {} + + for line in lines: + line = line.strip() + print(line) + if not line or line.startswith("!") or line.startswith("Failed") \ + or line.startswith("Program caused an exception") \ + or line.startswith("Program does not verify"): # TODO ake + continue + if line.startswith("dependencyAnalysisTests/precisionTests/"): + # Save previous block if any + if current_test and current_results: + results[current_test] = current_results + # Start new block + parts = line.strip().split(" - ") + current_test = (parts[0], parts[1]) + current_results = [] + else: + method_name, precision = map(str.strip, line.split(":")) + current_results.append((method_name, float(precision))) + + if current_test and current_results: + results[current_test] = current_results + + return results + +def build_table(out_file_path: str, results: dict[tuple[str, str], list[tuple[str, float]]]): + f = open(out_file_path, mode="w") + header = sorted(set([interference_name for (_, interference_name) in results.keys()])) + header_summary_columns = ["|", "max", "min", "avg"] + base_test_names = sorted(set([base_name.strip() for (base_name, _) in results.keys()])) + column_1_width = max([len(h) for h in base_test_names]) + 4 + column_widths = [len(h + " ") for h in (header + header_summary_columns)] + f.write("".ljust(column_1_width) + " " + " ".join(header + header_summary_columns)) + f.write("\n") + + for base_test in base_test_names: + f.write(base_test.ljust(column_1_width)) + current_test_results = [] + for idx, h in enumerate(header): + if not (base_test, h) in results.keys(): + f.write("NaN".center(column_widths[idx])) + continue + result = results[(base_test, h)] + avg = sum([prec for (_, prec) in result]) / len(result) + current_test_results.append(avg) + f.write(f"{avg:.3f}".center(column_widths[idx])) + + # print summary + f.write("|".center(column_widths[idx+1])) + f.write(f"{max(current_test_results):.3f}".center(column_widths[idx+2])) + f.write(f"{min(current_test_results):.3f}".center(column_widths[idx+3])) + f.write(f"{sum(current_test_results)/len(current_test_results):.3f}".center(column_widths[idx+4])) + f.write("\n") + +result_file_name = input("file name: ") +raw_results = read_results_file("silicon\\src\\test\\resources\\dependencyAnalysisTests\\precisionTests\\results\\" + result_file_name) + +build_table("silicon\\src\\test\\resources\\dependencyAnalysisTests\\precisionTests\\results\\result_table.out", raw_results) \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/generation/precision_test_generator.py b/src/test/resources/dependencyAnalysisTests/generation/precision_test_generator.py index dd9b4632e..20fc1e6ca 100644 --- a/src/test/resources/dependencyAnalysisTests/generation/precision_test_generator.py +++ b/src/test/resources/dependencyAnalysisTests/generation/precision_test_generator.py @@ -34,17 +34,20 @@ def extract_vars(line: str): var_decls = after.strip().split(" ") read_write_vars = [] read_only_vars = [] + invariant = "" for decl in var_decls: tmp = decl.split("=") if(tmp[0] == "$READ_ONLY"): read_only_vars = read_only_vars + tmp[1].split(",") elif(tmp[0] == "$READ_WRITE"): read_write_vars = read_write_vars + tmp[1].split(",") + elif(tmp[0] == "$INVARIANT"): + invariant = tmp[1] # print(f"line: {line}") # print(f"read only: {read_only_vars}") # print(f"read write: {read_write_vars}") # print() - return read_only_vars, read_write_vars + return read_only_vars, read_write_vars, invariant def replace_vars(snippet: str, placeholder: str, vars: list[str]): idx = 0 @@ -60,16 +63,36 @@ def replace_vars(snippet: str, placeholder: str, vars: list[str]): return snippet, gen_vars def generate_from_snippet(snippet: str, line: str): - read_only_vars, read_write_vars = extract_vars(line) + read_only_vars, read_write_vars, invariant = extract_vars(line) + + snippet = snippet.replace("$INVARIANT", invariant if invariant != "" else "true") + + # replace variables, generate new ones if necessary + snippet, gen_ro_refs = replace_vars(snippet, "$RO_REF_F_", [v.split(".")[0] for v in read_only_vars if v.endswith(".f")]) + snippet, gen_rw_refs = replace_vars(snippet, "$RW_REF_F_", [v.split(".")[0] for v in read_write_vars if v.endswith(".f")]) snippet, gen_vars_ro_field = replace_vars(snippet, "$RO_INT_FIELD_", [v for v in read_only_vars if "." in v]) snippet, gen_vars_rw_field = replace_vars(snippet, "$RW_INT_FIELD_", [v for v in read_write_vars if "." in v]) + snippet, gen_vars_ro_pure = replace_vars(snippet, "$RO_INT_PURE_", [v for v in read_only_vars if not "." in v]) + snippet, gen_vars_rw_pure = replace_vars(snippet, "$RW_INT_PURE_", [v for v in read_write_vars if not "." in v]) snippet, gen_vars_ro = replace_vars(snippet, "$RO_INT_", read_only_vars) snippet, gen_vars_rw = replace_vars(snippet, "$RW_INT_", read_write_vars) + + # assume non-aliasing of references + generated_refs = set(gen_rw_refs + gen_ro_refs + [v.split(".")[0] for v in (gen_vars_rw_field + gen_vars_ro_field) if "." in v]) + existing_refs = set([v.split(".")[0] for v in (read_write_vars+read_only_vars) if "." in v]) + # snippet = f"\n//generated: {generated_refs}\n//existing: {existing_refs}\n" + snippet + snippet = "\nvar gen_dummy_int: Int\n" + snippet + snippet = "\n".join([f"@irrelevant(\"Explicit\")\ninhale {a} != {b}" for a in generated_refs for b in existing_refs if a != b]) + snippet + + # declare and initialize newly generated vars snippet = "".join([f"var {v.split(".")[0]}: Ref\n@irrelevant(\"Explicit\")\ninhale acc({v}, 1/2)\n" for v in gen_vars_ro_field]) + snippet snippet = "".join([f"var {v.split(".")[0]}: Ref\n@irrelevant(\"Explicit\")\ninhale acc({v})\n" for v in gen_vars_rw_field]) + snippet - all_vars = gen_vars_ro + gen_vars_rw + snippet = "".join([f"var {v}: Ref\n@irrelevant(\"Explicit\")\ninhale acc({v}.f)\n" for v in gen_rw_refs]) + snippet + snippet = "".join([f"var {v}: Ref\n@irrelevant(\"Explicit\")\ninhale acc({v}.f, 1/2)\n" for v in gen_ro_refs]) + snippet + all_vars = gen_vars_ro + gen_vars_rw + gen_vars_ro_pure + gen_vars_rw_pure if len(all_vars) > 0: snippet = "var " + ", ".join([f"{v}: Int" for v in all_vars]) + "\n" + snippet + return snippet def apply_snippet(snippet: str, method: str): diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index f914181af..c274b5123 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -48,11 +48,13 @@ class AssumptionAnalysisTests extends AnyFunSuite { val now: LocalDateTime = LocalDateTime.now() val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss") val writer = new PrintWriter(s"$basePath/result_${now.format(formatter)}.out") - visitFiles("dependencyAnalysisTests/precisionTests", executePrecisionBenchmark(_, _, frontend, writer)) + visitFiles("dependencyAnalysisTests/precisionTests", executePrecisionBenchmark(_, _, writer)) writer.close() } } +// createSingleTest("dependencyAnalysisTests/quick", "test") + if(EXECUTE_TEST) testDirectories foreach (dir => visitFiles(dir, createSingleTest)) @@ -60,9 +62,8 @@ class AssumptionAnalysisTests extends AnyFunSuite { if(EXECUTE_TEST) visitFiles("dependencyAnalysisTests/mce", createSingleTest) -// test("custom test"){ -// executeTest("dependencyAnalysisTests/all/", "list", frontend) -// } + + def visitFiles(dirName: String, function: (String, String) => Unit): Unit = { val path = Paths.get(getClass.getClassLoader.getResource(dirName).toURI) @@ -72,6 +73,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { private def createSingleTest(dirName: String, fileName: String): Unit = { test(dirName + "/" + fileName) { try{ + initFrontend() executeTest(dirName + "/", fileName, frontend) }catch{ case t: Throwable => fail(t.getMessage) @@ -98,7 +100,14 @@ class AssumptionAnalysisTests extends AnyFunSuite { } } - def frontend: SiliconFrontend = { + var frontend: SiliconFrontend = createFrontend() + + def initFrontend(): Unit = { + frontend.verifier.stop() + frontend = createFrontend() + } + + def createFrontend(): SiliconFrontend = { val reporter = DependencyAnalysisReporter() val fe = new SiliconFrontend(reporter) val backend = fe.createVerifier("") @@ -128,8 +137,8 @@ class AssumptionAnalysisTests extends AnyFunSuite { def executePrecisionBenchmark(filePrefix: String, fileName: String, - frontend: SilFrontend, writer: PrintWriter): Unit = { + initFrontend() println(s"Precision Benchmark for $filePrefix - $fileName started...") try{ val program: Program = tests.loadProgram(filePrefix + "/", fileName, frontend) @@ -148,7 +157,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { println(s"Precision Benchmark for $filePrefix - $fileName done.") }catch{ case e: Exception => - writer.println("Program caused an exception. Skip") + writer.println("Failed. Skip") println(s"Exception caught: ${e.getMessage}") } } @@ -250,6 +259,10 @@ class AssumptionAnalysisTests extends AnyFunSuite { total += 1 + invs.size removed += (invs.size - newInvs.size) ast.Label(name, newInvs)(label.pos, label.info, label.errT) + case s: ast.Package if !isCrucialStmt(s, crucialNodesWithStmtInfo) => + total += 1 + removed += 1 + ast.Inhale(ast.TrueLit()())() case s: Stmt if !isCrucialStmt(s, crucialNodesWithStmtInfo) => total += 1 removed += 1 @@ -439,6 +452,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { if(result.isInstanceOf[verifier.Failure]) { writer.println(s"!!!!!!!!!!!\nFailed to verify new program $exportFileName\n") println(s"!!!!!!!!!!!\nFailed to verify new program $exportFileName\n") + throw new Exception("Error: " + result.toString) } } From 388687ba84a3af731985e563900bc41cd79f5ee2 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 21 Jul 2025 17:10:57 +0200 Subject: [PATCH 185/474] update test cases --- .../generation/snippets.txt | 204 +++++++++++++++--- .../unitTests/branches.vpr | 6 +- .../unitTests/inhaleExhale.vpr | 8 +- .../unitTests/magicWands.vpr | 2 +- .../unitTests/quantifiedPermissions.vpr | 2 +- src/test/scala/AssumptionAnalysisTests.scala | 4 +- 6 files changed, 182 insertions(+), 44 deletions(-) diff --git a/src/test/resources/dependencyAnalysisTests/generation/snippets.txt b/src/test/resources/dependencyAnalysisTests/generation/snippets.txt index 0ec599e4c..a8b69fbf0 100644 --- a/src/test/resources/dependencyAnalysisTests/generation/snippets.txt +++ b/src/test/resources/dependencyAnalysisTests/generation/snippets.txt @@ -1,7 +1,11 @@ field gen_f: Int -predicate gen_confuse(i: Int, a: Int){ - i >= 0 // TODO +predicate gen_confuse(i: Int, inv: Bool){ + i >= 0 && inv +} + +predicate gen_confuse_with_impl(i: Int, inv: Bool){ + i >= 0 ==> inv } function gen_add(a: Int, b: Int): Int @@ -12,6 +16,14 @@ function gen_add_positive(a: Int, b: Int): Int ensures result == a + b ensures result >= 0 +method incr(a: Ref) + requires acc(a.f) + ensures acc(a.f) + ensures a.f == old(a.f) + 1 +{ + a.f := a.f + 1 +} + // assignments SNIPPET assignment$=${ @irrelevant("Implicit") @@ -20,31 +32,115 @@ SNIPPET assignment$=${ SNIPPET assignment2$=${ @irrelevant("Implicit") - $RW_INT_1 := $RO_INT_0 + $RO_INT_1 + $RW_INT_0 := $RW_INT_1 + $RO_INT_0 + $RO_INT_1 +} + +SNIPPET div_by_0$=${ + if($RO_INT_0 > 0){ + $RW_INT_0 := $RO_INT_1 / $RO_INT_0 + }else{ + $RW_INT_0 := $RO_INT_1 * $RO_INT_0 + } +} + +SNIPPET inhale1$=${ + @irrelevant("Explicit") + inhale $INVARIANT +} + +SNIPPET assert1$=${ + // @irrelevant() // TODO ake: missing node + assert $INVARIANT +} + + +SNIPPET exhale_inhale$=${ + @irrelevant("Implicit") + exhale acc($RW_INT_FIELD_0, 1/2) + + @irrelevant("Explicit") + inhale acc($RW_INT_FIELD_0, 1/2) } // fold-unfold -SNIPPET fold_unfold_1$=${ +SNIPPET fold_unfold_basic$=${ var gen_i: Int @irrelevant("Implicit") - gen_i := 0 + inhale gen_i > 10 @irrelevant("Implicit") - fold gen_confuse(gen_i, $RO_INT_0) + fold gen_confuse(gen_i, $INVARIANT) @irrelevant("Implicit") - unfold gen_confuse(gen_i, $RO_INT_0) + unfold gen_confuse(gen_i, $INVARIANT) } +// fold-unfold with implication +SNIPPET fold_unfold_with_implication$=${ + var gen_i: Int + @irrelevant("Implicit") + fold gen_confuse_with_impl(gen_i, $INVARIANT) + @irrelevant("Implicit") + unfold gen_confuse_with_impl(gen_i, $INVARIANT) +} // function call SNIPPET function_add$=${ @irrelevant("Implicit") $RW_INT_0 := gen_add($RO_INT_0, $RO_INT_1) @irrelevant("Implicit") - $RW_INT_1 := gen_add($RO_INT_2, $RO_INT_3) - @irrelevant("Implicit") - $RW_INT_2 := gen_add($RW_INT_0, $RW_INT_1) + $RW_INT_1 := gen_add($RW_INT_0, $RO_INT_2) - assert $RW_INT_2 == $RO_INT_0 + $RO_INT_1 + $RO_INT_2 + $RO_INT_3 + // @irrelevant() // TODO ake: might not hold due to aliasing + // assert $RW_INT_1 == $RO_INT_0 + $RO_INT_1 + $RO_INT_2 +} + +SNIPPET method_call$=${ + @irrelevant("Implicit") + incr($RW_REF_F_0) +} + +SNIPPET branch$=${ + var gen_b: Bool + if(@irrelevant("PathCondition")($RO_INT_0 > 0 && $RO_INT_1 > 0 && gen_b)){ + $RW_INT_0 := 10 + }else{ + var gen_i: Int + @irrelevant("Implicit") + gen_i := $RO_INT_0 + $RO_INT_1 + } +} + +SNIPPET nested_branch$=${ + var gen_b: Bool + if(@irrelevant("PathCondition")($RO_INT_0 > 0 && $RO_INT_1 > 0 && gen_b)){ + var gen_i: Int + @irrelevant("Implicit") + gen_i := $RO_INT_0 + $RO_INT_1 + @irrelevant("Implicit") + $RW_INT_0 := 10 + }else{ + if(@irrelevant("PathCondition")($RO_INT_0 > 0)){ + @irrelevant("Implicit") + $RW_INT_0 := 30 + }else{ + var gen_i: Int + @irrelevant("Implicit") + gen_i := $RO_INT_0 + $RO_INT_1 + } + } +} + +SNIPPET infeasible_branch$=${ + if(@dependency("PathCondition")($INVARIANT)){ + @irrelevant("Implicit") + $RW_INT_0 := 10 + }else{ + // unreachable + @irrelevant("Implicit") + $RO_INT_FIELD_0 := $RO_INT_1 - $RO_INT_0 + + // @irrelevant() // TODO missing node + assert false + } } // branches & function calls @@ -62,13 +158,13 @@ SNIPPET branch_function$=${ } } - + // @irrelevant() // TODO ake assert $RW_INT_0 >= 0 } // disjoint references -SNIPPET ref_1$=${ +SNIPPET ref_field_f$=${ var gen_x: Ref @irrelevant("Explicit") inhale acc(gen_x.f) @@ -81,7 +177,7 @@ SNIPPET ref_1$=${ } // disjoint references - disjoint fields -SNIPPET ref_2$=${ +SNIPPET ref_disjoint_field$=${ var gen_x: Ref @irrelevant("Explicit") inhale acc(gen_x.gen_f) @@ -91,13 +187,10 @@ SNIPPET ref_2$=${ @irrelevant("Implicit") $RW_INT_0 := gen_x.gen_f - - @irrelevant("Implicit") - exhale acc(gen_x.gen_f, 3/4) } // disjoint references - quantified -SNIPPET ref_quantified_1$=${ +SNIPPET ref_quantified_field_f$=${ var gen_xs: Seq[Ref] @irrelevant("Explicit") inhale |gen_xs| > 2 @@ -108,18 +201,13 @@ SNIPPET ref_quantified_1$=${ @irrelevant("Implicit") gen_xs[0].f := gen_xs[1].f + $RO_INT_0 - $RO_INT_1 - @irrelevant("Implicit") - gen_xs[1].f := gen_xs[0].f - $RW_INT_0 + $RW_INT_1 @irrelevant("Implicit") - $RW_INT_0 := gen_xs[0].f + gen_xs[1].f - - //@irrelevant("Implicit") - //exhale forall gen_x: Ref :: gen_x in gen_xs ==> acc(gen_x.f, 1/2) // TODO ake + $RW_INT_0 := gen_xs[0].f + gen_xs[1].f } // disjoint references - disjoint fields - quantified -SNIPPET ref_quantified_2$=${ +SNIPPET ref_quantified_disjoint_field$=${ var gen_xs: Seq[Ref] @irrelevant("Explicit") inhale |gen_xs| > 2 @@ -130,24 +218,74 @@ SNIPPET ref_quantified_2$=${ @irrelevant("Implicit") gen_xs[0].gen_f := gen_xs[1].gen_f + $RO_INT_0 - $RO_INT_1 - @irrelevant("Implicit") - gen_xs[1].gen_f := gen_xs[0].gen_f - $RW_INT_0 + $RW_INT_1 @irrelevant("Implicit") $RW_INT_0 := gen_xs[0].gen_f + gen_xs[1].gen_f +} - //@irrelevant("Implicit") - //exhale forall gen_x: Ref :: gen_x in gen_xs ==> acc(gen_x.gen_f, 1/2) // TODO ake +SNIPPET while_pure$=${ + var gen_start: Int + @irrelevant("Implicit") + gen_start := $RW_INT_PURE_0 + @irrelevant("Implicit") + $RW_INT_PURE_1 := 0 + while(@irrelevant("PathCondition")($RW_INT_PURE_0 > 0)) + invariant @irrelevant("LoopInvariant")($RW_INT_PURE_0 <= gen_start) + invariant @irrelevant("LoopInvariant")($RW_INT_PURE_1 == (gen_start-$RW_INT_PURE_0)*$RO_INT_PURE_0) + { + @irrelevant("Implicit") + $RW_INT_PURE_1 := $RW_INT_PURE_1 + $RO_INT_PURE_0 + @irrelevant("Implicit") + $RW_INT_PURE_0 := $RW_INT_PURE_0 - 1 + } } +SNIPPET while_perm$=${ + var gen_start: Int + @irrelevant("Implicit") + gen_start := $RW_INT_FIELD_0 + @irrelevant("Implicit") + $RW_INT_FIELD_1 := 0 + @irrelevant("Explicit") + while(@irrelevant("PathCondition")($RW_INT_FIELD_0 > 0)) + invariant @irrelevant("LoopInvariant")(acc($RW_INT_FIELD_0, write)) + invariant @irrelevant("LoopInvariant")(acc($RW_INT_FIELD_1, write)) + invariant @irrelevant("LoopInvariant")($RW_INT_FIELD_0 <= gen_start) + invariant @irrelevant("LoopInvariant")($RW_INT_FIELD_1 == (gen_start-$RW_INT_FIELD_0)*$RO_INT_PURE_0) + { + @irrelevant("Implicit") + $RW_INT_FIELD_1 := $RW_INT_FIELD_1 + $RO_INT_PURE_0 + @irrelevant("Implicit") + $RW_INT_FIELD_0 := $RW_INT_FIELD_0 - 1 + } +} -SNIPPET exhale_inhale$=${ +SNIPPET magic_wand_1$=${ + var gen_i: Int + @irrelevant("Explicit") + inhale gen_i > 0 @irrelevant("Implicit") - exhale acc($RW_INT_FIELD_0, 1/2) + package gen_i >= 0 --* $INVARIANT + @irrelevant("Implicit") + apply gen_i >= 0 --* $INVARIANT +} +SNIPPET magic_wand_2$=${ + var gen_i: Int @irrelevant("Explicit") - inhale acc($RW_INT_FIELD_0, 1/2) + inhale gen_i > 0 + @irrelevant("Implicit") + package $INVARIANT --* gen_i >= 0 + @irrelevant("Implicit") + apply $INVARIANT --* gen_i >= 0 +} + +SNIPPET magic_wand_perm$=${ + @irrelevant("Implicit") + package acc($RW_INT_FIELD_0, 1/2) --* acc($RW_INT_FIELD_0, write) + @irrelevant("Implicit") + apply acc($RW_INT_FIELD_0, 1/2) --* acc($RW_INT_FIELD_0, write) } -// TODO: apply, package, goto, while, quasihavoc, inhale, assume, divBy0 \ No newline at end of file +// TODO: goto, quasihavoc \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/branches.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/branches.vpr index 32cb12205..f7a9b4d1c 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/branches.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/branches.vpr @@ -37,9 +37,9 @@ method branch2(){ method branch3(){ var x: Int, y: Int - // $PrecisionTest: $READ_WRITE=x,y + // $PrecisionTest: $READ_WRITE=y $READ_ONLY=x - if(@dependency("PathCondition")(x > 0)){ + if(@dependency("PathCondition")(x > 10)){ @dependency("Implicit") y := x + 1 }else{ @@ -48,7 +48,7 @@ method branch3(){ } @testAssertion("Explicit") - assert x > 0 ==> y > 0 + assert x > 10 ==> y > 0 } method branch4(){ diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/inhaleExhale.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/inhaleExhale.vpr index 41d221cf9..c55e5fd50 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/inhaleExhale.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/inhaleExhale.vpr @@ -8,7 +8,7 @@ method exhaleInhale(a: Ref) @irrelevant("Explicit") inhale acc(a.f, 1/2) - // $PrecisionTest: READ_WRITE=a.f + // $PrecisionTest: $READ_WRITE=a.f @testAssertion("Explicit") assert perm(a.f) == write @@ -22,7 +22,7 @@ method exhaleInhale2(a: Ref) @irrelevant("Explicit") // TODO ake: imprecision inhale acc(a.f, 1/2) - // $PrecisionTest: READ_WRITE=a.f + // $PrecisionTest: $READ_WRITE=a.f @testAssertion("Explicit") assert perm(a.f) >= 1/2 @@ -39,7 +39,7 @@ method exhaleImprecision(){ @irrelevant("Implicit") // TODO ake: imprecision exhale acc(x.f, 1/2) - // $PrecisionTest: READ_ONLY=x.f + // $PrecisionTest: $READ_ONLY=x.f @testAssertion("Explicit") assert x.f > 0 @@ -56,7 +56,7 @@ method inhaleImprecision(){ @irrelevant("Implicit") // TODO ake: imprecision inhale acc(x.f, 1/2) - // $PrecisionTest: READ_ONLY=x.f + // $PrecisionTest: $READ_ONLY=x.f @testAssertion("Explicit") assert x.f > 0 diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr index 2a117940b..6fbc9d33f 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr @@ -142,7 +142,7 @@ method packageExhale2(x: Ref) requires @dependency("Explicit")(acc(x.f)) { - @dependency("Implicit") + @dependency("Implicit") // TODO ake: irrelevant package acc(x.f, 1/2) --* acc(x.f) // $PrecisionTest: $READ_ONLY=x.f diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr index 2d243491f..367b77865 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr @@ -25,7 +25,7 @@ method quantifiedPerm3(xs: Seq[Ref], y: Ref) { inhale @dependency("Explicit")(forall x: Ref :: x in xs ==> acc(x.f)) inhale @dependency("Explicit")(acc(y.f, wildcard)) - // $PrecisionTest: $READ_WRITE=xs[0].f,y.f,xs[1].f,xs[4].f + // $PrecisionTest: $READ_WRITE=xs[0].f,xs[1].f,xs[4].f $READ_ONLY=y.f @testAssertion("Explicit") assert xs[0] != y diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index c274b5123..72d2788b5 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -20,8 +20,8 @@ import java.time.format.DateTimeFormatter class AssumptionAnalysisTests extends AnyFunSuite { val CHECK_PRECISION = false - val EXECUTE_PRECISION_BENCHMARK = true - val EXECUTE_TEST=false + val EXECUTE_PRECISION_BENCHMARK = false + val EXECUTE_TEST=true val ignores: Seq[String] = Seq("example1", "example2") val testDirectories: Seq[String] = Seq( // "dependencyAnalysisTests", From eca0a53c99045c6c6a12bda1ae2ae678d6581c4a Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 21 Jul 2025 19:39:01 +0200 Subject: [PATCH 186/474] update test cases --- .../generation/snippets.txt | 6 ++++-- .../unitTests/branches.vpr | 10 +++++----- .../unitTests/inhaleExhale.vpr | 6 +++--- .../unitTests/loops.vpr | 6 +++--- .../unitTests/magicWands.vpr | 10 +++++----- .../unitTests/method-call.vpr | 8 +++++--- .../unitTests/misc.vpr | 4 ++-- .../unitTests/permWildcard.vpr | 2 +- .../unitTests/permissions.vpr | 2 +- .../unitTests/predicates.vpr | 12 +++++------ .../unitTests/quantifiedPermissions.vpr | 20 +++++++++---------- src/test/scala/AssumptionAnalysisTests.scala | 3 +-- 12 files changed, 46 insertions(+), 43 deletions(-) diff --git a/src/test/resources/dependencyAnalysisTests/generation/snippets.txt b/src/test/resources/dependencyAnalysisTests/generation/snippets.txt index a8b69fbf0..bc6f90430 100644 --- a/src/test/resources/dependencyAnalysisTests/generation/snippets.txt +++ b/src/test/resources/dependencyAnalysisTests/generation/snippets.txt @@ -43,7 +43,7 @@ SNIPPET div_by_0$=${ } } -SNIPPET inhale1$=${ +SNIPPET inhale=${ @irrelevant("Explicit") inhale $INVARIANT } @@ -230,6 +230,7 @@ SNIPPET while_pure$=${ @irrelevant("Implicit") $RW_INT_PURE_1 := 0 while(@irrelevant("PathCondition")($RW_INT_PURE_0 > 0)) + invariant @irrelevant("LoopInvariant")($INVARIANT) invariant @irrelevant("LoopInvariant")($RW_INT_PURE_0 <= gen_start) invariant @irrelevant("LoopInvariant")($RW_INT_PURE_1 == (gen_start-$RW_INT_PURE_0)*$RO_INT_PURE_0) { @@ -250,6 +251,7 @@ SNIPPET while_perm$=${ while(@irrelevant("PathCondition")($RW_INT_FIELD_0 > 0)) invariant @irrelevant("LoopInvariant")(acc($RW_INT_FIELD_0, write)) invariant @irrelevant("LoopInvariant")(acc($RW_INT_FIELD_1, write)) + invariant @irrelevant("LoopInvariant")($INVARIANT) invariant @irrelevant("LoopInvariant")($RW_INT_FIELD_0 <= gen_start) invariant @irrelevant("LoopInvariant")($RW_INT_FIELD_1 == (gen_start-$RW_INT_FIELD_0)*$RO_INT_PURE_0) { @@ -288,4 +290,4 @@ SNIPPET magic_wand_perm$=${ } -// TODO: goto, quasihavoc \ No newline at end of file +// TODO: goto, quasihavoc, assume, inhale \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/branches.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/branches.vpr index f7a9b4d1c..1ca2394cc 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/branches.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/branches.vpr @@ -11,7 +11,7 @@ method branch1(){ y := -x + 1 } - // $PrecisionTest: $READ_WRITE=x $READ_ONLY=y + // $PrecisionTest: $READ_WRITE=x $READ_ONLY=y $INVARIANT=y>0 @testAssertion("Explicit") assert y > 0 @@ -28,7 +28,7 @@ method branch2(){ assume y >= 10 } - // $PrecisionTest: $READ_WRITE=x $READ_ONLY=y + // $PrecisionTest: $READ_WRITE=x $READ_ONLY=y $INVARIANT=y>0 @testAssertion("Explicit") assert y > 0 @@ -47,7 +47,7 @@ method branch3(){ assume y >= 10 } - @testAssertion("Explicit") + @testAssertion("Explicit") $INVARIANT=y>0 assert x > 10 ==> y > 0 } @@ -57,7 +57,7 @@ method branch4(){ if(@dependency("PathCondition")(x > 0)){ @irrelevant("Implicit") y := x + 1 - // $PrecisionTest: $READ_WRITE=x,y + // $PrecisionTest: $READ_WRITE=x,y $INVARIANT=x>0 }else{ @dependency("Implicit") y := 10 - x @@ -71,7 +71,7 @@ method branch5(){ @dependency("Explicit") assume y > 0 - // $PrecisionTest: $READ_ONLY=x,y + // $PrecisionTest: $READ_ONLY=x,y $INVARIANT=y>0 if(@irrelevant("PathCondition")(x > 0)){ @irrelevant("Implicit") diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/inhaleExhale.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/inhaleExhale.vpr index c55e5fd50..38734f8a2 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/inhaleExhale.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/inhaleExhale.vpr @@ -8,7 +8,7 @@ method exhaleInhale(a: Ref) @irrelevant("Explicit") inhale acc(a.f, 1/2) - // $PrecisionTest: $READ_WRITE=a.f + // $PrecisionTest: $READ_WRITE=a.f $INVARIANT=perm(a.f)>none @testAssertion("Explicit") assert perm(a.f) == write @@ -39,7 +39,7 @@ method exhaleImprecision(){ @irrelevant("Implicit") // TODO ake: imprecision exhale acc(x.f, 1/2) - // $PrecisionTest: $READ_ONLY=x.f + // $PrecisionTest: $READ_ONLY=x.f $INVARIANT=x.f>0 @testAssertion("Explicit") assert x.f > 0 @@ -56,7 +56,7 @@ method inhaleImprecision(){ @irrelevant("Implicit") // TODO ake: imprecision inhale acc(x.f, 1/2) - // $PrecisionTest: $READ_ONLY=x.f + // $PrecisionTest: $READ_ONLY=x.f $INVARIANT=x.f>0 @testAssertion("Explicit") assert x.f > 0 diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/loops.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/loops.vpr index c9d8085d2..d556e6b74 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/loops.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/loops.vpr @@ -18,7 +18,7 @@ method loop1(){ i := i - 1 } - // $PrecisionTest: $READ_ONLY=res $READ_WRITE=i + // $PrecisionTest: $READ_ONLY=res $READ_WRITE=i $INVARIANT=i>-1 @testAssertion("Explicit") assert res >= 0 @@ -38,7 +38,7 @@ method loop2(){ { @irrelevant("Implicit") res := res + i - // $PrecisionTest: $READ_ONLY=res,i + // $PrecisionTest: $READ_ONLY=res,i $INVARIANT=i>-1 @dependency("Implicit") i := i - 1 @testAssertion("Explicit") @@ -51,7 +51,7 @@ method loop3(){ var res: Int @dependency("Implicit") res := 0 - // $PrecisionTest: $READ_ONLY=res $READ_WRITE=i + // $PrecisionTest: $READ_ONLY=res $READ_WRITE=i $INVARIANT=res>-1 @irrelevant("Implicit") i := 10 while(@dependency("PathCondition")(i > 0)) diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr index 6fbc9d33f..394aa2216 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr @@ -18,7 +18,7 @@ method basicApply() @dependency("Explicit") inhale x > 0 --* y > 0 - // $PrecisionTest: $READ_ONLY=x,y + // $PrecisionTest: $READ_ONLY=x,y $INVARIANT=x>0 @dependency("Implicit") apply x > 0 --* y > 0 @@ -78,7 +78,7 @@ method advancedPackage(a: Int, b: Int) @dependency("Explicit") inhale greater0(b) - // $PrecisionTest: $READ_ONLY=a,b + // $PrecisionTest: $READ_ONLY=a,b $INVARIANT=greater0(b) @dependency("Implicit") apply (greater0(a) && greater0(b)) --* greater0(a + b) @@ -96,7 +96,7 @@ method quantifiedWand(xs: Seq[Int], b: Int) @dependency("Implicit") apply greater0(xs[0]) --* greater0(xs[0] + b) - // $PrecisionTest: $READ_ONLY=xs[0],b,xs[1] // TODO ake: sequence update + // $PrecisionTest: $READ_ONLY=xs[0],b,xs[1] $INVARIANT=greater0(xs[0]) // TODO ake: sequence update @testAssertion("Explicit") assert greater0(xs[0] + b) @@ -128,7 +128,7 @@ method packageExhale(x: Ref) requires @dependency("Explicit")(acc(x.f)) { - // $PrecisionTest: $READ_WRITE=x.f + // $PrecisionTest: $READ_WRITE=x.f $INVARIANT=perm(x.f)>none @dependency("Implicit") package acc(x.f, 1/2) --* acc(x.f) @@ -145,7 +145,7 @@ method packageExhale2(x: Ref) @dependency("Implicit") // TODO ake: irrelevant package acc(x.f, 1/2) --* acc(x.f) - // $PrecisionTest: $READ_ONLY=x.f + // $PrecisionTest: $READ_ONLY=x.f $INVARIANT=perm(x.f)>none @dependency("Implicit") apply acc(x.f, 1/2) --* acc(x.f) diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/method-call.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/method-call.vpr index df39345f7..6df02906a 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/method-call.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/method-call.vpr @@ -14,6 +14,8 @@ method call1(){ @dependency("Explicit") assume y > 0 + // $PrecisionTest: $READ_ONLY=x,y $INVARIANT=x>0 + @testAssertion("Implicit") x := sum(x, y) } @@ -28,7 +30,7 @@ method call2(){ @dependency("Implicit") z := sum(x, y) - // $PrecisionTest: $READ_ONLY=x,y,z + // $PrecisionTest: $READ_ONLY=x,y,z $INVARIANT=x>0 @testAssertion("Explicit") assert z == x + y @@ -45,7 +47,7 @@ method call3(x: Int, y: Int) @dependency("Implicit") n := sum(x, y) - // $PrecisionTest: $READ_WRITE=n2 $READ_ONLY=x,y,n + // $PrecisionTest: $READ_WRITE=n2 $READ_ONLY=x,y,n $INVARIANT=x>0 @dependency("Implicit") n2 := sum(x, y) @@ -71,7 +73,7 @@ method call4(x: Int, y: Int, z: Int) @dependency("Implicit") n := sum(x, y) - // $PrecisionTest: $READ_WRITE=n2 $READ_ONLY=n,z,x,y + // $PrecisionTest: $READ_WRITE=n2 $READ_ONLY=n,z,x,y $INVARIANT=x>0 @irrelevant("Implicit") n2 := sum(x, z) diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/misc.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/misc.vpr index 26d139f97..219039519 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/misc.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/misc.vpr @@ -7,7 +7,7 @@ method divBy0(a: Ref, n: Int) { var res: Int - // $PrecisionTest: $READ_WRITE=a.f,res $READ_ONLY=n + // $PrecisionTest: $READ_WRITE=a.f,res $READ_ONLY=n $INVARIANT=n>0 @testAssertion("Implicit") res := a.f / n @@ -19,7 +19,7 @@ method assumeFalse() @dependency("Explicit") assume false - // $PrecisionTest: $READ_WRITE=a + // $PrecisionTest: $READ_WRITE=a $INVARIANT=a>0 @testAssertion("Explicit") assert a == 2 diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/permWildcard.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/permWildcard.vpr index 379411b50..57790570c 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/permWildcard.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/permWildcard.vpr @@ -32,7 +32,7 @@ method quantifiedPermWildcard(xs: Seq[Ref], ys: Seq[Ref]) @irrelevant("Explicit") inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard)) - // $PrecisionTest: $READ_ONLY=xs[0].f,xs[1].f,ys[0].f,ys[1].f + // $PrecisionTest: $READ_ONLY=xs[0].f,xs[1].f,ys[0].f,ys[1].f $INVARIANT=xs[0].f>0 @testAssertion("Explicit") assert xs[0].f > 0 diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr index 109232126..456c87816 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr @@ -53,7 +53,7 @@ method perm4(){ @dependency("Implicit") x.f := x.f + 1 - // $PrecisionTest: $READ_ONLY=x.f + // $PrecisionTest: $READ_ONLY=x.f $INVARIANT=x.f>1 @testAssertion("Explicit") assert x.f > 1 diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/predicates.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/predicates.vpr index 33e482bd6..9d28c5648 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/predicates.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/predicates.vpr @@ -15,7 +15,7 @@ method foldP(n: Int) @dependency("Implicit") x := 1 - // $PrecisionTest: $READ_ONLY=n,x + // $PrecisionTest: $READ_ONLY=n,x $INVARIANT=n>0 @testAssertion("Implicit") fold greater0(x + n) @@ -32,7 +32,7 @@ method unfoldP(n: Int) @dependency("Implicit") x := n + x - // $PrecisionTest: $READ_ONLY=x,n + // $PrecisionTest: $READ_ONLY=x,n $INVARIANT=x>n @testAssertion("Explicit") assert x > 1 @@ -47,7 +47,7 @@ method unfoldFoldP(a: Int, b: Int) @dependency("Implicit") unfold greater5(b) - // $PrecisionTest: $READ_ONLY=a,b + // $PrecisionTest: $READ_ONLY=a,b $INVARIANT=a>0 @testAssertion("Implicit") fold greater5(a + b) @@ -60,7 +60,7 @@ method callWithPredicate(a: Int, b: Int) @dependency("Implicit") unfoldFoldP(a, b) - // $PrecisionTest: $READ_ONLY=a,b + // $PrecisionTest: $READ_ONLY=a,b $INVARIANT=greater5(a+b) @testAssertion("Explicit") assert greater5(a + b) @@ -76,7 +76,7 @@ method unfoldPrecision(a: Int, b: Int) @dependency("Implicit") unfold greater5(b) - // $PrecisionTest: $READ_ONLY=a,b + // $PrecisionTest: $READ_ONLY=a,b $INVARIANT=b>0 @testAssertion("Implicit") fold greater0(b) @@ -86,7 +86,7 @@ method foldPrecision(a: Int, b: Int) requires @irrelevant("Explicit")(a > 5) requires @dependency("Explicit")(b > 5) { - // $PrecisionTest: $READ_ONLY=a,b + // $PrecisionTest: $READ_ONLY=a,b $INVARIANT=a>0 @testAssertion("Implicit") fold greater0(b) diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr index 367b77865..b92676303 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr @@ -4,7 +4,7 @@ method quantifiedPerm1(xs: Seq[Ref]) { assume @dependency("Explicit")(|xs| > 5) inhale @dependency("Explicit")(forall x: Ref :: x in xs ==> acc(x.f)) - // $PrecisionTest: $READ_WRITE=xs[0].f,xs[1].f,xs[2].f + // $PrecisionTest: $READ_WRITE=xs[0].f,xs[1].f,xs[2].f $INVARIANT=|xs|>0 @testAssertion() xs[0].f := 10 @@ -14,7 +14,7 @@ method quantifiedPerm2(xs: Seq[Ref]) { assume @dependency("Explicit")(|xs| > 5) inhale @dependency("Explicit")(forall x: Ref :: x in xs ==> acc(x.f)) - // $PrecisionTest: $READ_WRITE=xs[0].f,xs[1].f,xs[4].f + // $PrecisionTest: $READ_WRITE=xs[0].f,xs[1].f,xs[4].f $INVARIANT=|xs|>4 @testAssertion() xs[0].f := xs[1].f + xs[4].f @@ -25,7 +25,7 @@ method quantifiedPerm3(xs: Seq[Ref], y: Ref) { inhale @dependency("Explicit")(forall x: Ref :: x in xs ==> acc(x.f)) inhale @dependency("Explicit")(acc(y.f, wildcard)) - // $PrecisionTest: $READ_WRITE=xs[0].f,xs[1].f,xs[4].f $READ_ONLY=y.f + // $PrecisionTest: $READ_WRITE=xs[0].f,xs[1].f,xs[4].f $READ_ONLY=y.f $INVARIANT=|xs|>0 @testAssertion("Explicit") assert xs[0] != y @@ -36,7 +36,7 @@ method quantifiedExhalePartiallyTest(xs: Seq[Ref]) { assume |xs| > 5 inhale @dependency("Explicit")(forall x: Ref :: x in xs ==> acc(x.f)) - // $PrecisionTest: $READ_WRITE=xs[0].f,xs[1].f,xs[4].f + // $PrecisionTest: $READ_WRITE=xs[0].f,xs[1].f,xs[4].f $INVARIANT=|xs|>0 @testAssertion("Explicit") exhale forall x: Ref :: x in xs ==> acc(x.f, 1/2) @@ -49,7 +49,7 @@ method quantifiedExhalePartially(xs: Seq[Ref]) { exhale forall x: Ref :: x in xs ==> acc(x.f, 1/2) - // $PrecisionTest: $READ_WRITE=res $READ_ONLY=xs[1].f,xs[0].f,xs[2].f + // $PrecisionTest: $READ_WRITE=res $READ_ONLY=xs[1].f,xs[0].f,xs[2].f $INVARIANT=|xs|>0 @testAssertion("Implicit") res := xs[1].f + 1 @@ -59,7 +59,7 @@ method quantifiedExhaleFully(xs: Seq[Ref]) { assume @irrelevant("Explicit")(|xs| > 5) inhale @dependency("Explicit")(forall x: Ref :: x in xs ==> acc(x.f)) - // $PrecisionTest: $READ_WRITE=xs[0].f,xs[2].f $READ_ONLY=xs[1].f + // $PrecisionTest: $READ_WRITE=xs[0].f,xs[2].f $READ_ONLY=xs[1].f $INVARIANT=|xs|>0 @testAssertion("Explicit") exhale forall x: Ref :: x in xs ==> acc(x.f) @@ -71,7 +71,7 @@ method quantifiedPermWrite1(xs: Seq[Ref]) { @dependency("Explicit") inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) - // $PrecisionTest: $READ_WRITE=xs[2].f $READ_ONLY=xs[0].f,xs[1].f + // $PrecisionTest: $READ_WRITE=xs[2].f $READ_ONLY=xs[0].f,xs[1].f $INVARIANT=xs[0].f>0 inhale xs[0] != xs[2] // relevant for precision test @@ -88,7 +88,7 @@ method quantifiedPermWrite2(xs: Seq[Ref]) { @dependency("Implicit") xs[0].f := 0 - // $PrecisionTest: $READ_WRITE=xs[2].f $READ_ONLY=xs[0].f,xs[1].f + // $PrecisionTest: $READ_WRITE=xs[2].f $READ_ONLY=xs[0].f,xs[1].f $INVARIANT=xs[0].f<3 inhale xs[0] != xs[2] // relevant for precision test @testAssertion("Explicit") @@ -104,7 +104,7 @@ method quantifiedPermWrite3(xs: Seq[Ref]) { @irrelevant("Implicit") xs[0].f := 0 - // $PrecisionTest: $READ_WRITE=xs[0].f $READ_ONLY=xs[1].f,xs[2].f + // $PrecisionTest: $READ_WRITE=xs[0].f $READ_ONLY=xs[1].f,xs[2].f $INVARIANT=xs[1].f>0 @testAssertion("Explicit") assert xs[0] != xs[1] ==> xs[1].f > 0 @@ -119,7 +119,7 @@ method quantifiedPermWrite4(xs: Seq[Ref], ys: Seq[Ref]) @irrelevant("Explicit") inhale forall y: Ref :: y in ys ==> acc(y.f) - // $PrecisionTest: $READ_WRITE=ys[0].f,ys[1].f $READ_ONLY=xs[0].f,xs[1].f + // $PrecisionTest: $READ_WRITE=ys[0].f,ys[1].f $READ_ONLY=xs[0].f,xs[1].f $INVARIANT=|ys|>3 @testAssertion("Implicit") xs[0].f := 2 diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index 72d2788b5..e3a15bea6 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -24,10 +24,9 @@ class AssumptionAnalysisTests extends AnyFunSuite { val EXECUTE_TEST=true val ignores: Seq[String] = Seq("example1", "example2") val testDirectories: Seq[String] = Seq( -// "dependencyAnalysisTests", "dependencyAnalysisTests/all", "dependencyAnalysisTests/unitTests", -// "dependencyAnalysisTests/unitTests/permissions", + "dependencyAnalysisTests/real-world-examples", // "dependencyAnalysisTests/quick" // "dependencyAnalysisTests/fromSilver" ) From af813ff22fe7d97cb097919bb25ca6f9d594fceb Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 22 Jul 2025 12:15:29 +0200 Subject: [PATCH 187/474] update test cases --- .../generation/precision_test_generator.py | 5 +- .../generation/snippets.txt | 90 +++++++++++++------ .../unitTests/branches.vpr | 4 +- .../unitTests/inhaleExhale.vpr | 8 +- .../unitTests/magicWands.vpr | 10 ++- .../unitTests/permWildcard.vpr | 2 +- .../unitTests/permissions.vpr | 3 +- .../unitTests/predicates.vpr | 3 +- .../unitTests/quantifiedPermissions.vpr | 41 +++++++-- src/test/scala/AssumptionAnalysisTests.scala | 9 +- 10 files changed, 120 insertions(+), 55 deletions(-) diff --git a/src/test/resources/dependencyAnalysisTests/generation/precision_test_generator.py b/src/test/resources/dependencyAnalysisTests/generation/precision_test_generator.py index 20fc1e6ca..af4de7ed9 100644 --- a/src/test/resources/dependencyAnalysisTests/generation/precision_test_generator.py +++ b/src/test/resources/dependencyAnalysisTests/generation/precision_test_generator.py @@ -42,7 +42,7 @@ def extract_vars(line: str): elif(tmp[0] == "$READ_WRITE"): read_write_vars = read_write_vars + tmp[1].split(",") elif(tmp[0] == "$INVARIANT"): - invariant = tmp[1] + invariant = "=".join(tmp[1:]).replace("$_$", " ") # print(f"line: {line}") # print(f"read only: {read_only_vars}") # print(f"read write: {read_write_vars}") @@ -77,11 +77,12 @@ def generate_from_snippet(snippet: str, line: str): snippet, gen_vars_ro = replace_vars(snippet, "$RO_INT_", read_only_vars) snippet, gen_vars_rw = replace_vars(snippet, "$RW_INT_", read_write_vars) + snippet = "\nvar gen_dummy_int: Int\n" + snippet + # assume non-aliasing of references generated_refs = set(gen_rw_refs + gen_ro_refs + [v.split(".")[0] for v in (gen_vars_rw_field + gen_vars_ro_field) if "." in v]) existing_refs = set([v.split(".")[0] for v in (read_write_vars+read_only_vars) if "." in v]) # snippet = f"\n//generated: {generated_refs}\n//existing: {existing_refs}\n" + snippet - snippet = "\nvar gen_dummy_int: Int\n" + snippet snippet = "\n".join([f"@irrelevant(\"Explicit\")\ninhale {a} != {b}" for a in generated_refs for b in existing_refs if a != b]) + snippet # declare and initialize newly generated vars diff --git a/src/test/resources/dependencyAnalysisTests/generation/snippets.txt b/src/test/resources/dependencyAnalysisTests/generation/snippets.txt index bc6f90430..a4da3185c 100644 --- a/src/test/resources/dependencyAnalysisTests/generation/snippets.txt +++ b/src/test/resources/dependencyAnalysisTests/generation/snippets.txt @@ -24,37 +24,39 @@ method incr(a: Ref) a.f := a.f + 1 } -// assignments -SNIPPET assignment$=${ + +SNIPPET assignment_pure$=${ @irrelevant("Implicit") - $RW_INT_0 := $RO_INT_0 + $RO_INT_1 + $RW_INT_PURE_0 := $RO_INT_0 + $RO_INT_1 } -SNIPPET assignment2$=${ +SNIPPET assignment_field$=${ @irrelevant("Implicit") - $RW_INT_0 := $RW_INT_1 + $RO_INT_0 + $RO_INT_1 + $RW_INT_FIELD_0 := $RO_INT_0 + $RO_INT_1 } SNIPPET div_by_0$=${ - if($RO_INT_0 > 0){ + if(@irrelevant("PathCondition")($RO_INT_0 > 0)){ + @irrelevant("Implicit") $RW_INT_0 := $RO_INT_1 / $RO_INT_0 }else{ + @irrelevant("Implicit") $RW_INT_0 := $RO_INT_1 * $RO_INT_0 } } -SNIPPET inhale=${ +SNIPPET inhale_invariant$=${ @irrelevant("Explicit") inhale $INVARIANT } -SNIPPET assert1$=${ - // @irrelevant() // TODO ake: missing node +SNIPPET assert$=${ + @irrelevant() assert $INVARIANT } -SNIPPET exhale_inhale$=${ +SNIPPET exhale_inhale_perm$=${ @irrelevant("Implicit") exhale acc($RW_INT_FIELD_0, 1/2) @@ -88,9 +90,6 @@ SNIPPET function_add$=${ $RW_INT_0 := gen_add($RO_INT_0, $RO_INT_1) @irrelevant("Implicit") $RW_INT_1 := gen_add($RW_INT_0, $RO_INT_2) - - // @irrelevant() // TODO ake: might not hold due to aliasing - // assert $RW_INT_1 == $RO_INT_0 + $RO_INT_1 + $RO_INT_2 } SNIPPET method_call$=${ @@ -101,6 +100,7 @@ SNIPPET method_call$=${ SNIPPET branch$=${ var gen_b: Bool if(@irrelevant("PathCondition")($RO_INT_0 > 0 && $RO_INT_1 > 0 && gen_b)){ + @irrelevant("Implicit") $RW_INT_0 := 10 }else{ var gen_i: Int @@ -138,7 +138,7 @@ SNIPPET infeasible_branch$=${ @irrelevant("Implicit") $RO_INT_FIELD_0 := $RO_INT_1 - $RO_INT_0 - // @irrelevant() // TODO missing node + @irrelevant() assert false } } @@ -158,13 +158,12 @@ SNIPPET branch_function$=${ } } - // @irrelevant() // TODO ake + @irrelevant() assert $RW_INT_0 >= 0 } -// disjoint references -SNIPPET ref_field_f$=${ +SNIPPET disjoint_ref$=${ var gen_x: Ref @irrelevant("Explicit") inhale acc(gen_x.f) @@ -176,8 +175,7 @@ SNIPPET ref_field_f$=${ $RW_INT_0 := gen_x.f } -// disjoint references - disjoint fields -SNIPPET ref_disjoint_field$=${ +SNIPPET disjoint_ref_disjoint_field$=${ var gen_x: Ref @irrelevant("Explicit") inhale acc(gen_x.gen_f) @@ -189,8 +187,8 @@ SNIPPET ref_disjoint_field$=${ $RW_INT_0 := gen_x.gen_f } -// disjoint references - quantified -SNIPPET ref_quantified_field_f$=${ + +SNIPPET disjoint_ref_quantified$=${ var gen_xs: Seq[Ref] @irrelevant("Explicit") inhale |gen_xs| > 2 @@ -206,8 +204,8 @@ SNIPPET ref_quantified_field_f$=${ $RW_INT_0 := gen_xs[0].f + gen_xs[1].f } -// disjoint references - disjoint fields - quantified -SNIPPET ref_quantified_disjoint_field$=${ + +SNIPPET disjoint_ref_disjoint_field_quantified$=${ var gen_xs: Seq[Ref] @irrelevant("Explicit") inhale |gen_xs| > 2 @@ -278,8 +276,6 @@ SNIPPET magic_wand_2$=${ inhale gen_i > 0 @irrelevant("Implicit") package $INVARIANT --* gen_i >= 0 - @irrelevant("Implicit") - apply $INVARIANT --* gen_i >= 0 } SNIPPET magic_wand_perm$=${ @@ -290,4 +286,46 @@ SNIPPET magic_wand_perm$=${ } -// TODO: goto, quasihavoc, assume, inhale \ No newline at end of file +SNIPPET quasihavoc$=${ + @irrelevant("Implicit") + quasihavoc $RW_INT_FIELD_0 +} + +SNIPPET quasihavoc_disjoint_ref$=${ + var gen_x: Ref + gen_x := new(f) + @irrelevant("Implicit") + quasihavoc gen_x.f +} + +SNIPPET quasihavoc_disjoint_ref_disjoint_field$=${ + var gen_x: Ref + @irrelevant("Implicit") + gen_x := new(gen_f) + @irrelevant("Implicit") + quasihavoc gen_x.gen_f +} + + +SNIPPET goto_pure$=${ + @irrelevant("Implicit") + $RW_INT_PURE_0 := 0 + var gen_n: Int + @irrelevant("Implicit") + gen_n := 0 + label head + invariant @irrelevant("LoopInvariant")(gen_n >= 0) + invariant @irrelevant("LoopInvariant")($RW_INT_PURE_0 == gen_n * $RO_INT_PURE_0) + + @irrelevant("Implicit") + $RW_INT_PURE_0 := $RW_INT_PURE_0 + $RO_INT_PURE_0 + @irrelevant("Implicit") + gen_n := gen_n + 1 + + if(@irrelevant("PathCondition")($RW_INT_PURE_0 != 5 * $RO_INT_PURE_0)){ + goto head + } + + @irrelevant() + assert $RW_INT_PURE_0 == gen_n * $RO_INT_PURE_0 +} diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/branches.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/branches.vpr index 1ca2394cc..cc98ede27 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/branches.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/branches.vpr @@ -47,7 +47,7 @@ method branch3(){ assume y >= 10 } - @testAssertion("Explicit") $INVARIANT=y>0 + @testAssertion("Explicit") assert x > 10 ==> y > 0 } @@ -57,7 +57,7 @@ method branch4(){ if(@dependency("PathCondition")(x > 0)){ @irrelevant("Implicit") y := x + 1 - // $PrecisionTest: $READ_WRITE=x,y $INVARIANT=x>0 + // $PrecisionTest: $READ_WRITE=y $READ_ONLY=x $INVARIANT=x>0 }else{ @dependency("Implicit") y := 10 - x diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/inhaleExhale.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/inhaleExhale.vpr index 38734f8a2..4ccef2278 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/inhaleExhale.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/inhaleExhale.vpr @@ -8,7 +8,7 @@ method exhaleInhale(a: Ref) @irrelevant("Explicit") inhale acc(a.f, 1/2) - // $PrecisionTest: $READ_WRITE=a.f $INVARIANT=perm(a.f)>none + // $PrecisionTest: $READ_WRITE=a.f @testAssertion("Explicit") assert perm(a.f) == write @@ -39,7 +39,8 @@ method exhaleImprecision(){ @irrelevant("Implicit") // TODO ake: imprecision exhale acc(x.f, 1/2) - // $PrecisionTest: $READ_ONLY=x.f $INVARIANT=x.f>0 + // $PrecisionTest: $READ_ONLY=x.f + // $INVARIANT=x.f>0 @testAssertion("Explicit") assert x.f > 0 @@ -56,7 +57,8 @@ method inhaleImprecision(){ @irrelevant("Implicit") // TODO ake: imprecision inhale acc(x.f, 1/2) - // $PrecisionTest: $READ_ONLY=x.f $INVARIANT=x.f>0 + // $PrecisionTest: $READ_ONLY=x.f + // $INVARIANT=x.f>0 @testAssertion("Explicit") assert x.f > 0 diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr index 394aa2216..2949468dc 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr @@ -78,7 +78,8 @@ method advancedPackage(a: Int, b: Int) @dependency("Explicit") inhale greater0(b) - // $PrecisionTest: $READ_ONLY=a,b $INVARIANT=greater0(b) + // $PrecisionTest: $READ_ONLY=a,b + // $INVARIANT=unfolding$_$greater0(b)$_$in$_$b>0 @dependency("Implicit") apply (greater0(a) && greater0(b)) --* greater0(a + b) @@ -96,7 +97,8 @@ method quantifiedWand(xs: Seq[Int], b: Int) @dependency("Implicit") apply greater0(xs[0]) --* greater0(xs[0] + b) - // $PrecisionTest: $READ_ONLY=xs[0],b,xs[1] $INVARIANT=greater0(xs[0]) // TODO ake: sequence update + // $PrecisionTest: $READ_ONLY=xs[0],b,xs[1] + // $INVARIANT=unfolding$_$greater0(xs[0]+b)$_$in$_$xs[0]+b>0 // TODO ake: sequence update @testAssertion("Explicit") assert greater0(xs[0] + b) @@ -128,7 +130,7 @@ method packageExhale(x: Ref) requires @dependency("Explicit")(acc(x.f)) { - // $PrecisionTest: $READ_WRITE=x.f $INVARIANT=perm(x.f)>none + // $PrecisionTest: $READ_WRITE=x.f @dependency("Implicit") package acc(x.f, 1/2) --* acc(x.f) @@ -145,7 +147,7 @@ method packageExhale2(x: Ref) @dependency("Implicit") // TODO ake: irrelevant package acc(x.f, 1/2) --* acc(x.f) - // $PrecisionTest: $READ_ONLY=x.f $INVARIANT=perm(x.f)>none + // $PrecisionTest: $READ_ONLY=x.f @dependency("Implicit") apply acc(x.f, 1/2) --* acc(x.f) diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/permWildcard.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/permWildcard.vpr index 57790570c..379411b50 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/permWildcard.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/permWildcard.vpr @@ -32,7 +32,7 @@ method quantifiedPermWildcard(xs: Seq[Ref], ys: Seq[Ref]) @irrelevant("Explicit") inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard)) - // $PrecisionTest: $READ_ONLY=xs[0].f,xs[1].f,ys[0].f,ys[1].f $INVARIANT=xs[0].f>0 + // $PrecisionTest: $READ_ONLY=xs[0].f,xs[1].f,ys[0].f,ys[1].f @testAssertion("Explicit") assert xs[0].f > 0 diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr index 456c87816..a08bb24dd 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr @@ -53,7 +53,8 @@ method perm4(){ @dependency("Implicit") x.f := x.f + 1 - // $PrecisionTest: $READ_ONLY=x.f $INVARIANT=x.f>1 + // $PrecisionTest: $READ_ONLY=x.f + // $INVARIANT=acc(x.f)==>x.f>1 @testAssertion("Explicit") assert x.f > 1 diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/predicates.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/predicates.vpr index 9d28c5648..0b55c44c1 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/predicates.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/predicates.vpr @@ -60,7 +60,8 @@ method callWithPredicate(a: Int, b: Int) @dependency("Implicit") unfoldFoldP(a, b) - // $PrecisionTest: $READ_ONLY=a,b $INVARIANT=greater5(a+b) + // $PrecisionTest: $READ_ONLY=a,b + // $INVARIANT=unfolding$_$greater5(a+b)$_$in$_$a+b>0 @testAssertion("Explicit") assert greater5(a + b) diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr index b92676303..b1adfc494 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr @@ -4,7 +4,10 @@ method quantifiedPerm1(xs: Seq[Ref]) { assume @dependency("Explicit")(|xs| > 5) inhale @dependency("Explicit")(forall x: Ref :: x in xs ==> acc(x.f)) - // $PrecisionTest: $READ_WRITE=xs[0].f,xs[1].f,xs[2].f $INVARIANT=|xs|>0 + @irrelevant("Explicit") + inhale xs[0] != xs[1] + + // $PrecisionTest: $READ_WRITE=xs[0].f,xs[1].f $INVARIANT=|xs|>0 @testAssertion() xs[0].f := 10 @@ -14,7 +17,10 @@ method quantifiedPerm2(xs: Seq[Ref]) { assume @dependency("Explicit")(|xs| > 5) inhale @dependency("Explicit")(forall x: Ref :: x in xs ==> acc(x.f)) - // $PrecisionTest: $READ_WRITE=xs[0].f,xs[1].f,xs[4].f $INVARIANT=|xs|>4 + @irrelevant("Explicit") + inhale xs[0] != xs[4] + + // $PrecisionTest: $READ_WRITE=xs[0].f,xs[4].f $INVARIANT=|xs|>4 @testAssertion() xs[0].f := xs[1].f + xs[4].f @@ -25,7 +31,7 @@ method quantifiedPerm3(xs: Seq[Ref], y: Ref) { inhale @dependency("Explicit")(forall x: Ref :: x in xs ==> acc(x.f)) inhale @dependency("Explicit")(acc(y.f, wildcard)) - // $PrecisionTest: $READ_WRITE=xs[0].f,xs[1].f,xs[4].f $READ_ONLY=y.f $INVARIANT=|xs|>0 + // $PrecisionTest: $READ_WRITE=xs[0].f $READ_ONLY=y.f $INVARIANT=|xs|>0 @testAssertion("Explicit") assert xs[0] != y @@ -36,7 +42,10 @@ method quantifiedExhalePartiallyTest(xs: Seq[Ref]) { assume |xs| > 5 inhale @dependency("Explicit")(forall x: Ref :: x in xs ==> acc(x.f)) - // $PrecisionTest: $READ_WRITE=xs[0].f,xs[1].f,xs[4].f $INVARIANT=|xs|>0 + @irrelevant("Explicit") + inhale xs[0] != xs[4] + + // $PrecisionTest: $READ_WRITE=xs[0].f,xs[4].f $INVARIANT=|xs|>0 @testAssertion("Explicit") exhale forall x: Ref :: x in xs ==> acc(x.f, 1/2) @@ -59,6 +68,9 @@ method quantifiedExhaleFully(xs: Seq[Ref]) { assume @irrelevant("Explicit")(|xs| > 5) inhale @dependency("Explicit")(forall x: Ref :: x in xs ==> acc(x.f)) + @irrelevant("Explicit") + inhale xs[0] != xs[2] + // $PrecisionTest: $READ_WRITE=xs[0].f,xs[2].f $READ_ONLY=xs[1].f $INVARIANT=|xs|>0 @testAssertion("Explicit") @@ -71,9 +83,12 @@ method quantifiedPermWrite1(xs: Seq[Ref]) { @dependency("Explicit") inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) - // $PrecisionTest: $READ_WRITE=xs[2].f $READ_ONLY=xs[0].f,xs[1].f $INVARIANT=xs[0].f>0 - inhale xs[0] != xs[2] // relevant for precision test + @irrelevant("Explicit") + inhale xs[0] != xs[2] + + // $PrecisionTest: $READ_WRITE=xs[2].f $READ_ONLY=xs[0].f,xs[1].f + // $INVARIANT=|xs|>0$_$&&$_$acc(xs[0].f)==>xs[0].f>0 @testAssertion("Implicit") xs[0].f := 0 @@ -88,9 +103,13 @@ method quantifiedPermWrite2(xs: Seq[Ref]) { @dependency("Implicit") xs[0].f := 0 - // $PrecisionTest: $READ_WRITE=xs[2].f $READ_ONLY=xs[0].f,xs[1].f $INVARIANT=xs[0].f<3 - inhale xs[0] != xs[2] // relevant for precision test + @irrelevant("Explicit") + inhale xs[0] != xs[2] + + // $PrecisionTest: $READ_WRITE=xs[2].f $READ_ONLY=xs[0].f,xs[1].f + // $INVARIANT=|xs|>0$_$&&$_$acc(xs[0].f)==>xs[0].f<3 + @testAssertion("Explicit") assert xs[0].f == 0 } @@ -104,7 +123,8 @@ method quantifiedPermWrite3(xs: Seq[Ref]) { @irrelevant("Implicit") xs[0].f := 0 - // $PrecisionTest: $READ_WRITE=xs[0].f $READ_ONLY=xs[1].f,xs[2].f $INVARIANT=xs[1].f>0 + // $PrecisionTest: $READ_WRITE=xs[0].f $READ_ONLY=xs[1].f,xs[2].f + // $INVARIANT=|xs|>1$_$&&$_$acc(xs[1].f)==>xs[1].f>=0 @testAssertion("Explicit") assert xs[0] != xs[1] ==> xs[1].f > 0 @@ -119,6 +139,9 @@ method quantifiedPermWrite4(xs: Seq[Ref], ys: Seq[Ref]) @irrelevant("Explicit") inhale forall y: Ref :: y in ys ==> acc(y.f) + @irrelevant("Explicit") + inhale ys[0] != ys[1] + // $PrecisionTest: $READ_WRITE=ys[0].f,ys[1].f $READ_ONLY=xs[0].f,xs[1].f $INVARIANT=|ys|>3 @testAssertion("Implicit") diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index e3a15bea6..4b8d3ad4d 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -418,7 +418,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { if(dependenciesPerSource.nonEmpty){ val wrongDependencies = assumptionsPerSource.filter({ case (_, assumptions) => dependencyIds.intersect(assumptions.map(_.id)).nonEmpty }) - 1.0 - (wrongDependencies.size.toDouble / dependenciesPerSource.size.toDouble) + 1.0 - (wrongDependencies.size.toDouble / dependenciesPerSource.size.toDouble) // TODO ake: or / assumptionsPerSource.size.toDouble? }else{ 1.0 } @@ -433,12 +433,9 @@ class AssumptionAnalysisTests extends AnyFunSuite { override def execute(): Unit = { val irrelevantNodes = fullGraphInterpreter.getNodes.filter(node => node.sourceInfo.toString.contains("@irrelevant(")).flatMap(_.sourceInfo.getLineNumber) - val testAssertionLines = fullGraphInterpreter.getNodes.filter(node => node.sourceInfo.toString.contains("@testAssertion(")).flatMap(_.sourceInfo.getLineNumber) - val testAssertionNodes = testAssertionLines.flatMap(line => fullGraphInterpreter.getNodesByLine(line)) + val relevantLines = fullGraphInterpreter.getNodes.flatMap(_.sourceInfo.getLineNumber).diff(irrelevantNodes) - val relevantLines = fullGraphInterpreter.getAllNonInternalDependencies(testAssertionNodes.map(_.id)).flatMap(_.sourceInfo.getLineNumber).diff(irrelevantNodes) - - pruneAndVerify(testAssertionLines ++ relevantLines, "src/test/resources/" + fileName + s"_test.out") + pruneAndVerify(relevantLines, "src/test/resources/" + fileName + s"_test.out") } override protected def pruneAndVerify(relevantLines: Set[Int], exportFileName: String): Unit = { From a6e4d6ba4154fafffe1d54a4aa3f14df23f45e3a Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 22 Jul 2025 14:31:03 +0200 Subject: [PATCH 188/474] minor fix --- src/test/scala/AssumptionAnalysisTests.scala | 33 ++++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index 4b8d3ad4d..4433ce101 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -22,11 +22,11 @@ class AssumptionAnalysisTests extends AnyFunSuite { val CHECK_PRECISION = false val EXECUTE_PRECISION_BENCHMARK = false val EXECUTE_TEST=true - val ignores: Seq[String] = Seq("example1", "example2") + val ignores: Seq[String] = Seq("example1", "example2", "graph-copy") val testDirectories: Seq[String] = Seq( "dependencyAnalysisTests/all", "dependencyAnalysisTests/unitTests", - "dependencyAnalysisTests/real-world-examples", +// "dependencyAnalysisTests/real-world-examples", // "dependencyAnalysisTests/quick" // "dependencyAnalysisTests/fromSilver" ) @@ -201,8 +201,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { } protected def getPrunedProgram(crucialNodes: Set[AssumptionAnalysisNode]): (ast.Program, Double) = { - val crucialNodesWithStmtInfo = crucialNodes filter (_.sourceInfo.getTopLevelSource.isInstanceOf[StmtAnalysisSourceInfo]) map (_.sourceInfo.getTopLevelSource.asInstanceOf[StmtAnalysisSourceInfo]) - val crucialNodesWithExpInfo = crucialNodes filter (_.sourceInfo.getTopLevelSource.isInstanceOf[ExpAnalysisSourceInfo]) map (_.sourceInfo.getTopLevelSource.asInstanceOf[ExpAnalysisSourceInfo]) + val crucialNodeSourceInfos = crucialNodes map (_.sourceInfo.getTopLevelSource) var total = 0 var removed = 0 var nonDetermBoolCount = 0 @@ -215,12 +214,12 @@ class AssumptionAnalysisTests extends AnyFunSuite { val newProgram: ast.Program = ViperStrategy.Slim({ case s @(_: ast.Seqn | _: ast.Goto) => s case meth@ast.Method(name, inVars, outVars, pres, posts, body) => - val newPres = pres filter (isCrucialExp(_, crucialNodesWithExpInfo)) - val newPosts = posts filter (isCrucialExp(_, crucialNodesWithExpInfo)) + val newPres = pres filter (isCrucialExp(_, crucialNodeSourceInfos)) + val newPosts = posts filter (isCrucialExp(_, crucialNodeSourceInfos)) total += pres.size + posts.size removed += (pres.size - newPres.size) + (posts.size - newPosts.size) ast.Method(name, inVars, outVars, newPres, newPosts, body)(meth.pos, meth.info, meth.errT) - case ifStmt@ast.If(cond, thenBody, elseBody) if !isCrucialExp(cond, crucialNodesWithExpInfo) => + case ifStmt@ast.If(cond, thenBody, elseBody) if !isCrucialExp(cond, crucialNodeSourceInfos) => total += 1 removed += 1 val nonDetermBool = getNextNonDetermBool @@ -231,8 +230,8 @@ class AssumptionAnalysisTests extends AnyFunSuite { case ifStmt: If => total += 1 ifStmt - case whileStmt@ast.While(cond, invs, body) if !isCrucialExp(cond, crucialNodesWithExpInfo) => - val newInvs = invs filter (isCrucialExp(_, crucialNodesWithExpInfo)) + case whileStmt@ast.While(cond, invs, body) if !isCrucialExp(cond, crucialNodeSourceInfos) => + val newInvs = invs filter (isCrucialExp(_, crucialNodeSourceInfos)) total += 1 + invs.size removed += 1 + (invs.size - newInvs.size) val nonDetermBool = getNextNonDetermBool @@ -241,28 +240,28 @@ class AssumptionAnalysisTests extends AnyFunSuite { ast.While(ast.LocalVar(nonDetermBool, ast.Bool)(), newInvs, body)(whileStmt.pos, whileStmt.info, whileStmt.errT)) , Seq())(whileStmt.pos, whileStmt.info, whileStmt.errT) case whileStmt@ast.While(cond, invs, body) => - val newInvs = invs filter (isCrucialExp(_, crucialNodesWithExpInfo)) + val newInvs = invs filter (isCrucialExp(_, crucialNodeSourceInfos)) total += 1 + invs.size removed += (invs.size - newInvs.size) ast.While(cond, newInvs, body)(whileStmt.pos, whileStmt.info, whileStmt.errT) -// case fieldAssign @ FieldAssign(lhs, _) if !isCrucialStmt(fieldAssign, crucialNodesWithStmtInfo) => // TODO ake: index into sequences need to be checked! +// case fieldAssign @ FieldAssign(lhs, _) if !isCrucialStmt(fieldAssign, crucialNodeSourceInfos) => // TODO ake: index into sequences need to be checked! // total += 1 // removed += 1 // ast.Quasihavoc(Some(ast.PermGeCmp(ast.CurrentPerm(lhs)(), ast.FullPerm()())()), lhs)(fieldAssign.pos, fieldAssign.info, fieldAssign.errT) -// case ass @ ast.LocalVarAssign(lhs, _) if !isCrucialStmt(ass, crucialNodesWithStmtInfo) => // TODO ake: is this valid`? +// case ass @ ast.LocalVarAssign(lhs, _) if !isCrucialStmt(ass, crucialNodeSourceInfos) => // TODO ake: is this valid`? // total += 1 // removed += 1 // ast.LocalVarDeclStmt(ast.LocalVarDecl(lhs.name, lhs.typ)())(ass.pos, ass.info, ass.errT) case label@ast.Label(name, invs) => - val newInvs = invs filter (isCrucialExp(_, crucialNodesWithExpInfo)) + val newInvs = invs filter (isCrucialExp(_, crucialNodeSourceInfos)) total += 1 + invs.size removed += (invs.size - newInvs.size) ast.Label(name, newInvs)(label.pos, label.info, label.errT) - case s: ast.Package if !isCrucialStmt(s, crucialNodesWithStmtInfo) => + case s: ast.Package if !isCrucialStmt(s, crucialNodeSourceInfos) => total += 1 removed += 1 ast.Inhale(ast.TrueLit()())() - case s: Stmt if !isCrucialStmt(s, crucialNodesWithStmtInfo) => + case s: Stmt if !isCrucialStmt(s, crucialNodeSourceInfos) => total += 1 removed += 1 ast.Inhale(ast.TrueLit()())() @@ -273,11 +272,11 @@ class AssumptionAnalysisTests extends AnyFunSuite { (newProgram, removed.toDouble / total.toDouble) } - protected def isCrucialExp(exp: ast.Exp, crucialNodesWithExpInfo: Set[ExpAnalysisSourceInfo]): Boolean = { + protected def isCrucialExp(exp: ast.Exp, crucialNodesWithExpInfo: Set[AnalysisSourceInfo]): Boolean = { crucialNodesWithExpInfo exists (n => n.getPositionString.equals(AnalysisSourceInfo.extractPositionString(exp.pos))) // TODO ake: currently we compare only lines not columns! } - protected def isCrucialStmt(stmt: ast.Stmt, crucialNodesWithStmtInfo: Set[StmtAnalysisSourceInfo]): Boolean = { + protected def isCrucialStmt(stmt: ast.Stmt, crucialNodesWithStmtInfo: Set[AnalysisSourceInfo]): Boolean = { crucialNodesWithStmtInfo exists (n => n.getPositionString.equals(AnalysisSourceInfo.extractPositionString(stmt.pos))) } } From 34d723786b36325c7cd20ac316f3030750e25011 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 22 Jul 2025 14:37:00 +0200 Subject: [PATCH 189/474] cleanup --- silver | 2 +- src/main/scala/rules/Consumer.scala | 2 +- src/main/scala/rules/Executor.scala | 2 +- src/main/scala/rules/QuantifiedChunkSupport.scala | 2 +- .../generation/precision_benchmark_plotter.py | 2 +- src/test/scala/AssumptionAnalysisTests.scala | 12 ++---------- 6 files changed, 7 insertions(+), 15 deletions(-) diff --git a/silver b/silver index 1cde18f4f..e61c768a6 160000 --- a/silver +++ b/silver @@ -1 +1 @@ -Subproject commit 1cde18f4fa827500ed4a3ff13c57a5e9e9fce840 +Subproject commit e61c768a68ad18fd6f87946ba078275b4f011e81 diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index bf581f394..e6800058b 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -588,7 +588,7 @@ object consumer extends ConsumptionRules { State.mergeHeap( entry1.data._1, And(entry1.pathConditions.branchConditions), Option.when(withExp)(BigAnd(entry1.pathConditions.branchConditionExps.map(_._2.get))), entry2.data._1, And(entry2.pathConditions.branchConditions), Option.when(withExp)(BigAnd(entry2.pathConditions.branchConditionExps.map(_._2.get))), - AnalysisInfo(v.decider, v.decider.assumptionAnalyzer, StringAnalysisSourceInfo("conditional join", e0.pos), AssumptionType.Implicit) // TODO ake + AnalysisInfo(v.decider, v.decider.assumptionAnalyzer, StringAnalysisSourceInfo("conditional join", e0.pos), AssumptionType.Implicit) ), // Assume that entry1.pcs is inverse of entry2.pcs (entry1.data._2, entry2.data._2) match { diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 06b0f4666..d2954aafa 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -578,7 +578,7 @@ object executor extends ExecutionRules { // Calling hack510() triggers a state consolidation. // See also Silicon issue #510. case ast.MethodCall(`hack510_method_name`, _, _) => - val s1 = v.stateConsolidator(s).consolidate(s, v) // TODO ake: assumption Type + val s1 = v.stateConsolidator(s).consolidate(s, v) // TODO ake: pass assumption Type Q(s1, v) case call @ ast.MethodCall(methodName, eArgs, lhs) => diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 296d98867..3c068052f 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -1076,7 +1076,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { triggers = effectiveTriggers, qidPrefix = qid ) - v.decider.assume(pcsForChunk, pcsForChunkExp, pcsForChunkExp, AssumptionType.Internal) // TODO ake: issue #01 + v.decider.assume(pcsForChunk, pcsForChunkExp, pcsForChunkExp, AssumptionType.Internal) }) val (fr1, h1) = v.stateConsolidator(s).merge(s.functionRecorder, s, s.h, Heap(Seq(ch)), v) diff --git a/src/test/resources/dependencyAnalysisTests/generation/precision_benchmark_plotter.py b/src/test/resources/dependencyAnalysisTests/generation/precision_benchmark_plotter.py index c148990e3..809a2d484 100644 --- a/src/test/resources/dependencyAnalysisTests/generation/precision_benchmark_plotter.py +++ b/src/test/resources/dependencyAnalysisTests/generation/precision_benchmark_plotter.py @@ -13,7 +13,7 @@ def read_results_file(result_file_path: str) -> dict[tuple[str, str], list[tuple print(line) if not line or line.startswith("!") or line.startswith("Failed") \ or line.startswith("Program caused an exception") \ - or line.startswith("Program does not verify"): # TODO ake + or line.startswith("Program does not verify"): continue if line.startswith("dependencyAnalysisTests/precisionTests/"): # Save previous block if any diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index 4433ce101..694832a31 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -171,7 +171,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { case class PruningTest(fileName: String, program: Program, fullGraphInterpreter: AssumptionAnalysisInterpreter) { def execute(): Unit = { - val triggerNodeLines = fullGraphInterpreter.getNodes.filter(node => node.sourceInfo.getTopLevelSource.toString.contains("@trigger()")).flatMap(_.sourceInfo.getLineNumber) // TODO ake: we also need all dependencies of trigger nodes! + val triggerNodeLines = fullGraphInterpreter.getNodes.filter(node => node.sourceInfo.getTopLevelSource.toString.contains("@trigger()")).flatMap(_.sourceInfo.getLineNumber) var id: Int = 0 // TODO ake: safer would be to work with position string instead of line numbers fullGraphInterpreter.getExplicitAssertionNodes flatMap (_.sourceInfo.getLineNumber) foreach {line => @@ -244,14 +244,6 @@ class AssumptionAnalysisTests extends AnyFunSuite { total += 1 + invs.size removed += (invs.size - newInvs.size) ast.While(cond, newInvs, body)(whileStmt.pos, whileStmt.info, whileStmt.errT) -// case fieldAssign @ FieldAssign(lhs, _) if !isCrucialStmt(fieldAssign, crucialNodeSourceInfos) => // TODO ake: index into sequences need to be checked! -// total += 1 -// removed += 1 -// ast.Quasihavoc(Some(ast.PermGeCmp(ast.CurrentPerm(lhs)(), ast.FullPerm()())()), lhs)(fieldAssign.pos, fieldAssign.info, fieldAssign.errT) -// case ass @ ast.LocalVarAssign(lhs, _) if !isCrucialStmt(ass, crucialNodeSourceInfos) => // TODO ake: is this valid`? -// total += 1 -// removed += 1 -// ast.LocalVarDeclStmt(ast.LocalVarDecl(lhs.name, lhs.typ)())(ass.pos, ass.info, ass.errT) case label@ast.Label(name, invs) => val newInvs = invs filter (isCrucialExp(_, crucialNodeSourceInfos)) total += 1 + invs.size @@ -305,7 +297,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { val warnMsgs = assumptionAnalysisInterpreters flatMap checkNonDependencies if (CHECK_PRECISION) errorMsgs ++= warnMsgs - else if (warnMsgs.nonEmpty) println(warnMsgs.mkString("\n")) // TODO ake: warning + else if (warnMsgs.nonEmpty) println(warnMsgs.mkString("\n")) // TODO ake: should be a warning val check = errorMsgs.isEmpty assert(check, "\n" + errorMsgs.mkString("\n")) From 41459cd2478d132da75f4e11c23c158942d5a5b9 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 22 Jul 2025 15:00:46 +0200 Subject: [PATCH 190/474] minor fix --- src/main/scala/interfaces/state/Chunks.scala | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/scala/interfaces/state/Chunks.scala b/src/main/scala/interfaces/state/Chunks.scala index 9d58bf3e2..d1883edb3 100644 --- a/src/main/scala/interfaces/state/Chunks.scala +++ b/src/main/scala/interfaces/state/Chunks.scala @@ -6,11 +6,13 @@ package viper.silicon.interfaces.state -import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType} +import viper.silicon.assumptionAnalysis.AnalysisInfo import viper.silicon.resources.ResourceID import viper.silicon.state.terms.{Term, Var} import viper.silver.ast +import scala.annotation.unused + trait Chunk { val perm: Term val permExp: Option[ast.Exp] @@ -38,8 +40,9 @@ object GeneralChunk { def permMinus(chunk: GeneralChunk, newPerm: Term, newPermExp: Option[ast.Exp], analysisInfo: AnalysisInfo): GeneralChunk = { val newChunk = analysisInfo.decider.registerDerivedChunk[GeneralChunk](Set(chunk), {finalPerm => chunk.permMinus(finalPerm, newPermExp)}, - newPerm, analysisInfo.withAssumptionType(AssumptionType.Internal), isExhale=false, createLabel=false) // TODO ake: assumption type? - val exhaledChunk = analysisInfo.decider.registerDerivedChunk[GeneralChunk](Set(chunk), {finalPerm => + newPerm, analysisInfo, isExhale=false, createLabel=false) // TODO ake: assumption type? + @unused // we still need to register the chunk to have a sound analysis + val exhaledChunk = analysisInfo.decider.registerDerivedChunk[GeneralChunk](Set(chunk), { finalPerm => chunk.withPerm(finalPerm, newPermExp)}, newPerm, analysisInfo, isExhale=true, createLabel=false) newChunk From d152480237ab3fc758dc2d306ef21415e55e7fb4 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 22 Jul 2025 15:01:21 +0200 Subject: [PATCH 191/474] pruning tests: add pruning rule for domain axioms --- src/test/scala/AssumptionAnalysisTests.scala | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index 694832a31..b557ca595 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -11,10 +11,10 @@ import viper.silver.{ast, verifier} import java.io.{File, PrintWriter} import java.nio.file.{Files, Path, Paths} -import scala.annotation.unused -import scala.jdk.CollectionConverters.IterableHasAsScala import java.time._ import java.time.format.DateTimeFormatter +import scala.annotation.unused +import scala.jdk.CollectionConverters.IterableHasAsScala class AssumptionAnalysisTests extends AnyFunSuite { @@ -24,9 +24,9 @@ class AssumptionAnalysisTests extends AnyFunSuite { val EXECUTE_TEST=true val ignores: Seq[String] = Seq("example1", "example2", "graph-copy") val testDirectories: Seq[String] = Seq( - "dependencyAnalysisTests/all", - "dependencyAnalysisTests/unitTests", -// "dependencyAnalysisTests/real-world-examples", +// "dependencyAnalysisTests/all", +// "dependencyAnalysisTests/unitTests", + "dependencyAnalysisTests/real-world-examples", // "dependencyAnalysisTests/quick" // "dependencyAnalysisTests/fromSilver" ) @@ -213,6 +213,12 @@ class AssumptionAnalysisTests extends AnyFunSuite { val newProgram: ast.Program = ViperStrategy.Slim({ case s @(_: ast.Seqn | _: ast.Goto) => s + case domain@ast.Domain(name, functions, axioms, typVars, interpretations) => + val newAxioms = axioms filter (a => + crucialNodeSourceInfos exists (n => n.getPositionString.equals(AnalysisSourceInfo.extractPositionString(a.exp.pos)) || + n.getPositionString.equals(AnalysisSourceInfo.extractPositionString(a.pos)))) + ast.Domain(name, functions, newAxioms, typVars, interpretations)(domain.pos, domain.info, domain.errT) + case meth@ast.Method(name, inVars, outVars, pres, posts, body) => val newPres = pres filter (isCrucialExp(_, crucialNodeSourceInfos)) val newPosts = posts filter (isCrucialExp(_, crucialNodeSourceInfos)) From 975c6911beb7adbf17b126cdf9dde04d94aebe39 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 22 Jul 2025 15:23:45 +0200 Subject: [PATCH 192/474] command line tool: allow querying for file + line --- .../AssumptionAnalysisInterpreter.scala | 5 ++++- .../AssumptionAnalysisUserTool.scala | 16 ++++++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala index 5dfe67714..4e124a846 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala @@ -35,6 +35,9 @@ class AssumptionAnalysisInterpreter(name: String, graph: ReadOnlyAssumptionAnaly def getNodesByLine(line: Int): Set[AssumptionAnalysisNode] = getNodes.filter(node => node.sourceInfo.getLineNumber.isDefined && node.sourceInfo.getLineNumber.get == line) + def getNodesByPosition(file: String, line: Int): Set[AssumptionAnalysisNode] = + getNodes.filter(node => node.sourceInfo.getLineNumber.isDefined && node.sourceInfo.getLineNumber.get == line && node.sourceInfo.getPositionString.startsWith(file + ".vpr")) + def getDirectDependencies(nodeIdsToAnalyze: Set[Int]): Set[AssumptionAnalysisNode] = getNonInternalAssumptionNodes.filter(node => graph.getDirectEdges.get(node.id).exists(_.intersect(nodeIdsToAnalyze).nonEmpty)) @@ -124,7 +127,7 @@ class AssumptionAnalysisInterpreter(name: String, graph: ReadOnlyAssumptionAnaly val explicitAssertionNodeIds = explicitAssertionNodes map (_.id) val nodesPerSourceInfo = getNonInternalAssumptionNodesPerSource val uncoveredSources = (nodesPerSourceInfo filter { case (_, nodes) => - val nodeIds = (nodes map (_.id)) + val nodeIds = nodes map (_.id) // it is not an explicit assertion itself and has no dependency to an explicit assertion nodeIds.intersect(explicitAssertionNodeIds).isEmpty && !graph.existsAnyDependency(nodeIds, explicitAssertionNodeIds) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala index 509d0d6c2..26ac748e8 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala @@ -37,8 +37,7 @@ class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpr private def handleUserInput(userInput: String): Unit = { val inputParts = userInput.split(" ") if (inputParts(0).equalsIgnoreCase("d") || inputParts(0).equalsIgnoreCase("dep")) { - val lines = inputParts.tail.flatMap(_.toIntOption).toSet - handleDependencyQuery(lines) + handleDependencyQuery(inputParts.tail.toSet) } else if (inputParts(0).equalsIgnoreCase("coverage") || inputParts(0).equalsIgnoreCase("cov")) { handleProofCoverageQuery(inputParts.tail) } else { @@ -64,12 +63,21 @@ class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpr }) } - private def handleDependencyQuery(lines: Set[Int]): Unit = { + private def handleDependencyQuery(inputs: Set[String]): Unit = { def getSourceInfoString(nodes: Set[AssumptionAnalysisNode]) = { nodes.map(_.sourceInfo.getTopLevelSource).toList.sortBy(_.getLineNumber).mkString("\n\t") } - val queriedNodes = lines flatMap fullGraphInterpreter.getNodesByLine + val queriedNodes = inputs flatMap (input => { + val parts = input.split("@") + if(parts.size == 2) + parts(1).toIntOption.map(fullGraphInterpreter.getNodesByPosition(parts(0), _)).getOrElse(Set.empty) + else if(parts.size == 1){ + parts(0).toIntOption map fullGraphInterpreter.getNodesByLine getOrElse Set.empty + }else{ + Set.empty + } + }) val directDependencies = getSourceInfoString(fullGraphInterpreter.getDirectDependencies(queriedNodes.map(_.id))) val allDependencies = getSourceInfoString(fullGraphInterpreter.getAllNonInternalDependencies(queriedNodes.map(_.id))) val explicitDependencies = getSourceInfoString(fullGraphInterpreter.getAllExplicitDependencies(queriedNodes.map(_.id))) From 5df889bc64464496578ec32737c553f8a8ed1a69 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 23 Jul 2025 11:08:21 +0200 Subject: [PATCH 193/474] minor fix --- src/main/scala/rules/MagicWandSupporter.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 6cd70d7c6..2a3c2d374 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -429,7 +429,7 @@ object magicWandSupporter extends SymbolicExecutionRules { // some of the analysis labels, introduced while verifying the package statement, might be needed later on -> reassume them analysisLabels foreach (l => v.decider.assume(v.decider.wrapWithAssumptionAnalysisLabel(l, Set.empty, Set(l)), None, AssumptionType.Internal)) - val currentAnalysisSourceInfoStack = v.decider.analysisSourceInfoStack + val currentAnalysisSourceInfos = v.decider.analysisSourceInfoStack.getAnalysisSourceInfos recordedBranches.foldLeft(tempResult)((prevRes, recordedState) => { prevRes && { val (state, branchConditions, branchConditionsExp, conservedPcs, magicWandChunk) = recordedState @@ -440,7 +440,7 @@ object magicWandSupporter extends SymbolicExecutionRules { // We execute the continuation Q in a new scope with all branch conditions and all conserved path conditions. executionFlowController.locally(s1, v)((s2, v1) => { - v1.decider.analysisSourceInfoStack = currentAnalysisSourceInfoStack + v1.decider.analysisSourceInfoStack.setAnalysisSourceInfo(currentAnalysisSourceInfos) val exp = viper.silicon.utils.ast.BigAnd(branchConditionsExp.map(_._1)) val expNew = Option.when(withExp)(viper.silicon.utils.ast.BigAnd(branchConditionsExp.map(_._2.get))) // Set the branch conditions From 422616facc1301409a049c448cd25860e938343e Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 23 Jul 2025 11:08:54 +0200 Subject: [PATCH 194/474] implement basic performance benchmark --- src/test/scala/AssumptionAnalysisTests.scala | 107 ++++++++++++++++--- 1 file changed, 90 insertions(+), 17 deletions(-) diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index b557ca595..e5d6c0056 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -21,12 +21,13 @@ class AssumptionAnalysisTests extends AnyFunSuite { val CHECK_PRECISION = false val EXECUTE_PRECISION_BENCHMARK = false - val EXECUTE_TEST=true + val EXECUTE_TEST = true + val EXECUTE_PERFORMANCE_BENCHMARK = false val ignores: Seq[String] = Seq("example1", "example2", "graph-copy") val testDirectories: Seq[String] = Seq( -// "dependencyAnalysisTests/all", -// "dependencyAnalysisTests/unitTests", - "dependencyAnalysisTests/real-world-examples", + "dependencyAnalysisTests/all", + "dependencyAnalysisTests/unitTests", +// "dependencyAnalysisTests/real-world-examples", // "dependencyAnalysisTests/quick" // "dependencyAnalysisTests/fromSilver" ) @@ -35,8 +36,9 @@ class AssumptionAnalysisTests extends AnyFunSuite { val dependencyKeyword = "dependency" val testAssertionKeyword = "testAssertion" - var commandLineArguments: Seq[String] = - Seq("--timeout", "100" /* seconds */ , "--enableAssumptionAnalysis", "--z3Args", "proof=true unsat-core=true") + var baseCommandLineArguments: Seq[String] = Seq("--timeout", "100" /* seconds */) + var analysisCommandLineArguments: Seq[String] = + baseCommandLineArguments ++ Seq("--enableAssumptionAnalysis", "--z3Args", "proof=true unsat-core=true") if(EXECUTE_PRECISION_BENCHMARK) { @@ -52,12 +54,24 @@ class AssumptionAnalysisTests extends AnyFunSuite { } } + if(EXECUTE_PERFORMANCE_BENCHMARK) + test("performance benchmark") { + val basePath = "src/test/resources/dependencyAnalysisTests/performanceBenchmark" + val directory = new File(basePath) + directory.mkdir() + val now: LocalDateTime = LocalDateTime.now() + val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss") + val writer = new PrintWriter(s"$basePath/result_${now.format(formatter)}.out") + testDirectories foreach (dir => visitFiles(dir, executePerformanceBenchmark(_, _, writer))) + writer.close() + } + // createSingleTest("dependencyAnalysisTests/quick", "test") if(EXECUTE_TEST) testDirectories foreach (dir => visitFiles(dir, createSingleTest)) - commandLineArguments = Seq("--enableMoreCompleteExhale") ++ commandLineArguments + analysisCommandLineArguments = Seq("--enableMoreCompleteExhale") ++ analysisCommandLineArguments if(EXECUTE_TEST) visitFiles("dependencyAnalysisTests/mce", createSingleTest) @@ -72,7 +86,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { private def createSingleTest(dirName: String, fileName: String): Unit = { test(dirName + "/" + fileName) { try{ - initFrontend() + resetFrontend() executeTest(dirName + "/", fileName, frontend) }catch{ case t: Throwable => fail(t.getMessage) @@ -99,24 +113,32 @@ class AssumptionAnalysisTests extends AnyFunSuite { } } - var frontend: SiliconFrontend = createFrontend() + var frontend: SiliconFrontend = createFrontend(analysisCommandLineArguments) - def initFrontend(): Unit = { - frontend.verifier.stop() - frontend = createFrontend() - } - - def createFrontend(): SiliconFrontend = { + def createFrontend(commandLineArgs: Seq[String]): SiliconFrontend = { val reporter = DependencyAnalysisReporter() val fe = new SiliconFrontend(reporter) val backend = fe.createVerifier("") - backend.parseCommandLine(commandLineArguments ++ List("--ignoreFile", "dummy.sil")) + backend.parseCommandLine(commandLineArgs ++ List("--ignoreFile", "dummy.sil")) fe.init(backend) fe.setVerifier(backend) backend.start() fe } + def resetFrontend(): Unit = { + frontend.verifier.stop() + frontend = createFrontend(analysisCommandLineArguments) + } + + var baselineFrontend: SiliconFrontend = createFrontend(baseCommandLineArguments) + + def resetBaselineFrontend(): Unit = { + baselineFrontend.verifier.stop() + baselineFrontend = createFrontend(baseCommandLineArguments) + } + + def executeTest(filePrefix: String, fileName: String, frontend: SilFrontend): Unit = { @@ -137,7 +159,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { def executePrecisionBenchmark(filePrefix: String, fileName: String, writer: PrintWriter): Unit = { - initFrontend() + resetFrontend() println(s"Precision Benchmark for $filePrefix - $fileName started...") try{ val program: Program = tests.loadProgram(filePrefix + "/", fileName, frontend) @@ -161,6 +183,13 @@ class AssumptionAnalysisTests extends AnyFunSuite { } } + def executePerformanceBenchmark(filePrefix: String, + fileName: String, + writer: PrintWriter): Unit = { + val program: Program = tests.loadProgram(filePrefix + "/", fileName, frontend) + new PerformanceBenchmark(filePrefix + "/" + fileName, program, writer).execute() + } + /** * (Almost) Fully automated test, which takes a program and its assumption analysis results and, * for each explicit assertion, builds a new program that only contains said assertion and @@ -448,7 +477,51 @@ class AssumptionAnalysisTests extends AnyFunSuite { throw new Exception("Error: " + result.toString) } } + } + + class PerformanceBenchmark(name: String, program: Program, writer: PrintWriter) { + + def execute(): Unit = { + writer.println(f"TEST: $name") + println(f"TEST: $name") + + + val analysisDurationMs: Double = verifyAndMeasure(frontend) + + val baselineDurationMs = verifyAndMeasure(baselineFrontend) + + writer.println(f"analysis duration (ms): $analysisDurationMs") + writer.println(f"baseline duration (ms): $baselineDurationMs") + writer.println(f"diff analysis-baseline (ms): ${analysisDurationMs-baselineDurationMs}") + writer.println(f"analysis overhead (analysis/baseline) (ms): ${analysisDurationMs/baselineDurationMs}") + println(f"analysis overhead (analysis/baseline) (ms): ${analysisDurationMs/baselineDurationMs}") + writer.println() + } + + private def verifyAndMeasure(frontend_ : SiliconFrontend) = { + val NUM_RUNS = 5 + try { + resetFrontend() + resetBaselineFrontend() + // warumup + for (_ <- 0 until 2) + { + frontend_.verifier.verify(program) + } + Thread.sleep(100) + val startAnalysis = System.nanoTime() + for (_ <- 0 until NUM_RUNS) { + @unused + val result = frontend_.verifier.verify(program) + } + val endAnalysis = System.nanoTime() + val analysisDurationMs = (endAnalysis - startAnalysis) / 1e6 / NUM_RUNS + analysisDurationMs + }catch{ + case _: Throwable => Double.NaN + } + } } } From 12a6797e9ed5d8899749ef3793a9c0861faf8ccc Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 23 Jul 2025 14:55:23 +0200 Subject: [PATCH 195/474] fix performance benchmark --- src/test/scala/AssumptionAnalysisTests.scala | 62 ++++++++++++-------- 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index e5d6c0056..8d3f5672a 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -23,20 +23,21 @@ class AssumptionAnalysisTests extends AnyFunSuite { val EXECUTE_PRECISION_BENCHMARK = false val EXECUTE_TEST = true val EXECUTE_PERFORMANCE_BENCHMARK = false - val ignores: Seq[String] = Seq("example1", "example2", "graph-copy") + val ignores: Seq[String] = Seq("example1", "example2", "graph-copy", "listAppend_wands", "iterativeTreeDelete") val testDirectories: Seq[String] = Seq( "dependencyAnalysisTests/all", "dependencyAnalysisTests/unitTests", // "dependencyAnalysisTests/real-world-examples", // "dependencyAnalysisTests/quick" -// "dependencyAnalysisTests/fromSilver" +// "dependencyAnalysisTests/fromSilver", +// "dependencyAnalysisTests/performanceBenchmark" ) val irrelevantKeyword = "irrelevant" val dependencyKeyword = "dependency" val testAssertionKeyword = "testAssertion" - var baseCommandLineArguments: Seq[String] = Seq("--timeout", "100" /* seconds */) + var baseCommandLineArguments: Seq[String] = Seq("--timeout", "300" /* seconds */) var analysisCommandLineArguments: Seq[String] = baseCommandLineArguments ++ Seq("--enableAssumptionAnalysis", "--z3Args", "proof=true unsat-core=true") @@ -186,8 +187,15 @@ class AssumptionAnalysisTests extends AnyFunSuite { def executePerformanceBenchmark(filePrefix: String, fileName: String, writer: PrintWriter): Unit = { + if(fileName.endsWith("_naive")) return + val program: Program = tests.loadProgram(filePrefix + "/", fileName, frontend) - new PerformanceBenchmark(filePrefix + "/" + fileName, program, writer).execute() + val naiveProgram: Option[Program] = try{ + Some(tests.loadProgram(filePrefix + "/", fileName + "_naive", frontend)) + }catch{ + case _: Throwable => None + } + new PerformanceBenchmark(filePrefix + "/" + fileName, program, naiveProgram, writer).execute() } /** @@ -338,7 +346,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { assert(check, "\n" + errorMsgs.mkString("\n")) } - protected def extractAnnotatedStmts(annotationFilter: (ast.AnnotationInfo => Boolean)): Set[ast.Infoed] = { + protected def extractAnnotatedStmts(annotationFilter: ast.AnnotationInfo => Boolean): Set[ast.Infoed] = { var nodesWithAnnotation: Set[ast.Infoed] = Set.empty @unused val newP: ast.Program = ViperStrategy.Slim({ @@ -479,47 +487,53 @@ class AssumptionAnalysisTests extends AnyFunSuite { } } - class PerformanceBenchmark(name: String, program: Program, writer: PrintWriter) { + class PerformanceBenchmark(name: String, program: Program, naiveProgram: Option[Program], writer: PrintWriter) { def execute(): Unit = { writer.println(f"TEST: $name") println(f"TEST: $name") - val analysisDurationMs: Double = verifyAndMeasure(frontend) - + val analysisDurationMs: Double = verifyAndMeasure(program, analysisCommandLineArguments) + val baselineDurationMs: Double = verifyAndMeasure(program, baseCommandLineArguments) - val baselineDurationMs = verifyAndMeasure(baselineFrontend) writer.println(f"analysis duration (ms): $analysisDurationMs") writer.println(f"baseline duration (ms): $baselineDurationMs") writer.println(f"diff analysis-baseline (ms): ${analysisDurationMs-baselineDurationMs}") - writer.println(f"analysis overhead (analysis/baseline) (ms): ${analysisDurationMs/baselineDurationMs}") - println(f"analysis overhead (analysis/baseline) (ms): ${analysisDurationMs/baselineDurationMs}") + writer.println(f"analysis overhead (analysis/baseline): ${analysisDurationMs/baselineDurationMs}") + println(f"analysis overhead (analysis/baseline): ${analysisDurationMs/baselineDurationMs}") + + if(naiveProgram.isDefined){ + val naiveDurationMs: Double = verifyAndMeasure(naiveProgram.get, baseCommandLineArguments) + writer.println(f"naive duration (ms): $naiveDurationMs") + writer.println(f"diff naive-baseline (ms): ${naiveDurationMs-baselineDurationMs}") + writer.println(f"naive overhead (naive/baseline): ${naiveDurationMs/baselineDurationMs}") + println(f"naive overhead (naive/baseline): ${naiveDurationMs/baselineDurationMs}") + } + writer.println() } - private def verifyAndMeasure(frontend_ : SiliconFrontend) = { + private def verifyAndMeasure(program_ : Program, commandLineArgs: Seq[String]) = { val NUM_RUNS = 5 + val frontend_ = createFrontend(commandLineArgs) try { - resetFrontend() - resetBaselineFrontend() - // warumup - for (_ <- 0 until 2) - { - frontend_.verifier.verify(program) - } - Thread.sleep(100) val startAnalysis = System.nanoTime() - for (_ <- 0 until NUM_RUNS) { - @unused - val result = frontend_.verifier.verify(program) + for (i <- 0 until NUM_RUNS) { + val result = frontend_.verifier.verify(program_) + if(result.isInstanceOf[verifier.Failure]) { + println(f"$i ${result.toString}") + throw new Exception(f"Failed $result") + } } val endAnalysis = System.nanoTime() val analysisDurationMs = (endAnalysis - startAnalysis) / 1e6 / NUM_RUNS analysisDurationMs }catch{ - case _: Throwable => Double.NaN + case t: Throwable => + println(t) + Double.NaN } } } From 99be60d01dfec65b0e89c1ba777559dfbea8cbf7 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 24 Jul 2025 09:38:27 +0200 Subject: [PATCH 196/474] implement performance plot script and update tests --- .../benchmark_scripts/plotter.py | 87 +++++++++ .../precision_benchmark_plotter.py | 0 .../precision_test_generator.py | 0 .../snippets.txt | 0 ..._2025-07-24_09-53-19_absolute_runtimes.png | Bin 0 -> 2398 bytes .../result_2025-07-24_09-53-19_overhead.png | Bin 0 -> 2398 bytes ...t_2025-07-24_09-53-19_overhead_vs_size.png | Bin 0 -> 2398 bytes .../real-world-examples/arrays.vpr | 124 +++++++++++++ .../real-world-examples/graph-copy.vpr | 175 ++++++++++++++++++ .../iterativeTreeDelete.vpr | 95 ++++++++++ .../real-world-examples/listAppend_wands.vpr | 65 +++++++ src/test/scala/AssumptionAnalysisTests.scala | 24 ++- 12 files changed, 557 insertions(+), 13 deletions(-) create mode 100644 src/test/resources/dependencyAnalysisTests/benchmark_scripts/plotter.py rename src/test/resources/dependencyAnalysisTests/{generation => benchmark_scripts}/precision_benchmark_plotter.py (100%) rename src/test/resources/dependencyAnalysisTests/{generation => benchmark_scripts}/precision_test_generator.py (100%) rename src/test/resources/dependencyAnalysisTests/{generation => benchmark_scripts}/snippets.txt (100%) create mode 100644 src/test/resources/dependencyAnalysisTests/performanceBenchmark/result_2025-07-24_09-53-19_absolute_runtimes.png create mode 100644 src/test/resources/dependencyAnalysisTests/performanceBenchmark/result_2025-07-24_09-53-19_overhead.png create mode 100644 src/test/resources/dependencyAnalysisTests/performanceBenchmark/result_2025-07-24_09-53-19_overhead_vs_size.png create mode 100644 src/test/resources/dependencyAnalysisTests/real-world-examples/arrays.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/real-world-examples/graph-copy.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/real-world-examples/iterativeTreeDelete.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/real-world-examples/listAppend_wands.vpr diff --git a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/plotter.py b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/plotter.py new file mode 100644 index 000000000..cf3b3e6eb --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/plotter.py @@ -0,0 +1,87 @@ +import matplotlib.pyplot as plt +import numpy as np + +def import_result(file: str): + with open(file, mode="r") as f: + contents = f.readlines() + header = [c.strip() for c in contents[0].split(";")] + results = {} + for line in contents[1:]: + parts = line.split(';') + test_name = parts[0].strip() + runtimes = [float(x.strip()) for x in parts[1:]] + results[test_name] = runtimes + return header, results + +def plot_absolute_runtimes(header: list[str], test_results: dict[str, list[float]], out_file: str): + names = [] + result1 = [] + result2 = [] + for name, result in test_results.items(): + names.append(name) + result1.append(result[0]) + result2.append(result[1]) + + x = np.arange(len(names)) + width = 0.35 + + fig, ax = plt.subplots() + ax.bar(x - width/2, result1, width, label=header[1]) + ax.bar(x + width/2, result2, width, label=header[2]) + + ax.set_ylabel('Runtime (ms)') + ax.set_title('Absolute Runtimes in ms') + ax.set_xticks(x) + ax.set_xticklabels(names, rotation=90) + ax.legend() + + plt.tight_layout() + plt.show() + plt.savefig(out_file.replace(".out", "_absolute_runtimes.png")) + +def plot_overhead(header: list[str], test_results: dict[str, list[float]], out_file: str): + names = [] + result1 = [] + for name, result in test_results.items(): + names.append(name) + result1.append(result[2]) + + fig, ax = plt.subplots() + ax.bar(names, result1, label=header[3]) + + ax.set_ylabel('analysis runtime/baseline runtime') + ax.set_title('Overhead of the Analysis') + ax.set_xticklabels(names, rotation=90) + ax.legend() + + plt.tight_layout() + plt.show() + plt.savefig(out_file.replace(".out", "_overhead.png")) + +def plot_overhead_vs_program_size(header: list[str], test_results: dict[str, list[float]], out_file: str): + names = [] + result1 = [] + result2 = [] + for name, result in test_results.items(): + names.append(name) + result1.append(result[2]) + result2.append(result[3]) + + plt.scatter(result2, result1) + + plt.ylabel('analysis runtime/baseline runtime') + plt.xlabel('program size (#lines of code)') + plt.title('Overhead of the Analysis vs Program Size') + plt.legend() + + plt.grid(True) + plt.tight_layout() + plt.show() + plt.savefig(out_file.replace(".out", "_overhead_vs_size.png")) + +input_file = input("Input file: ") +header, result = import_result(input_file) + +plot_absolute_runtimes(header, result, input_file) +plot_overhead(header, result, input_file) +plot_overhead_vs_program_size(header, result, input_file) \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/generation/precision_benchmark_plotter.py b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_benchmark_plotter.py similarity index 100% rename from src/test/resources/dependencyAnalysisTests/generation/precision_benchmark_plotter.py rename to src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_benchmark_plotter.py diff --git a/src/test/resources/dependencyAnalysisTests/generation/precision_test_generator.py b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_test_generator.py similarity index 100% rename from src/test/resources/dependencyAnalysisTests/generation/precision_test_generator.py rename to src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_test_generator.py diff --git a/src/test/resources/dependencyAnalysisTests/generation/snippets.txt b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/snippets.txt similarity index 100% rename from src/test/resources/dependencyAnalysisTests/generation/snippets.txt rename to src/test/resources/dependencyAnalysisTests/benchmark_scripts/snippets.txt diff --git a/src/test/resources/dependencyAnalysisTests/performanceBenchmark/result_2025-07-24_09-53-19_absolute_runtimes.png b/src/test/resources/dependencyAnalysisTests/performanceBenchmark/result_2025-07-24_09-53-19_absolute_runtimes.png new file mode 100644 index 0000000000000000000000000000000000000000..38abef5e34b8fcf56df76f3cd3b5199766662e23 GIT binary patch literal 2398 zcmeAS@N?(olHy`uVBq!ia0y~yU}|7sV0^&A1{5*9c;^X_vMOsbDlCjFttX m#o=g;Fj^_nvt1B&npLfmea};?BU6D576wmOKbLh*2~7YkC!hKN literal 0 HcmV?d00001 diff --git a/src/test/resources/dependencyAnalysisTests/performanceBenchmark/result_2025-07-24_09-53-19_overhead.png b/src/test/resources/dependencyAnalysisTests/performanceBenchmark/result_2025-07-24_09-53-19_overhead.png new file mode 100644 index 0000000000000000000000000000000000000000..38abef5e34b8fcf56df76f3cd3b5199766662e23 GIT binary patch literal 2398 zcmeAS@N?(olHy`uVBq!ia0y~yU}|7sV0^&A1{5*9c;^X_vMOsbDlCjFttX m#o=g;Fj^_nvt1B&npLfmea};?BU6D576wmOKbLh*2~7YkC!hKN literal 0 HcmV?d00001 diff --git a/src/test/resources/dependencyAnalysisTests/performanceBenchmark/result_2025-07-24_09-53-19_overhead_vs_size.png b/src/test/resources/dependencyAnalysisTests/performanceBenchmark/result_2025-07-24_09-53-19_overhead_vs_size.png new file mode 100644 index 0000000000000000000000000000000000000000..38abef5e34b8fcf56df76f3cd3b5199766662e23 GIT binary patch literal 2398 zcmeAS@N?(olHy`uVBq!ia0y~yU}|7sV0^&A1{5*9c;^X_vMOsbDlCjFttX m#o=g;Fj^_nvt1B&npLfmea};?BU6D576wmOKbLh*2~7YkC!hKN literal 0 HcmV?d00001 diff --git a/src/test/resources/dependencyAnalysisTests/real-world-examples/arrays.vpr b/src/test/resources/dependencyAnalysisTests/real-world-examples/arrays.vpr new file mode 100644 index 000000000..e0fc9353c --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/real-world-examples/arrays.vpr @@ -0,0 +1,124 @@ +/* Finding the maximum in an array by elimination. + * Problem 1 from http://foveoos2011.cost-ic0701.org/verification-competition + */ + +define access(a) forall j: Int :: 0 <= j && j < len(a) ==> acc(loc(a, j).val) +define untouched(a) forall j: Int :: 0 <= j && j < len(a) ==> loc(a, j).val == old(loc(a, j).val) +define is_max(i, a, u) forall j: Int :: 0 <= j && j < u ==> loc(a, j).val <= loc(a, i).val + +method max(a: IArray) returns (x: Int) + requires access(a) + ensures access(a) + ensures untouched(a) + ensures len(a) == 0 ? x == -1 : (0 <= x && x < len(a)) + ensures is_max(x, a, len(a)) +{ + if (len(a) == 0) { + x := -1 + } else { + var y: Int + x := 0; + y := len(a) - 1; + + while (x != y) + invariant access(a) + invariant untouched(a) + invariant 0 <= x + invariant x <= y + invariant y < len(a) + invariant (forall i: Int :: + ((0 <= i && i < x) || (y < i && i < len(a))) + ==> loc(a, i).val < loc(a, x).val) + || (forall i: Int :: + ((0 <= i && i < x) || (y < i && i < len(a))) + ==> loc(a, i).val <= loc(a, y).val) + { + if (loc(a, x).val <= loc(a, y).val) { + x := x + 1 + } else { + y := y - 1 + } + } + } +} + +method client() { + var a: IArray + inhale len(a) == 3 + inhale access(a) + inhale forall i: Int :: 0 <= i && i < len(a) ==> loc(a, i).val == i + + var x: Int + x := max(a) + + assert loc(a, 0).val <= x + + @trigger() + assert x == loc(a, len(a) - 1).val + /* Necessary to prove the final assertion (due to triggering) */ + + assert x == 2 + + assert loc(a, 1).val < x +} + +/* Task A */ + +// used to write matching trigger terms below +function offset(k:Int, x:Int, y:Int) : Int { + y + k - x +} + +// longest common prefix +method lcp(a: IArray, x: Int, y: Int) returns (n: Int) + requires access(a) + requires 0 <= x + requires 0 <= y + requires x < len(a) + requires y < len(a) + ensures access(a) + ensures 0 <= n + ensures x + n <= len(a) + ensures y + n <= len(a) + ensures forall k: Int :: {loc(a, offset(k,x,y))} // instantiation trigger term + x <= k && k < x + n ==> loc(a, k).val == loc(a, offset(k,x,y)).val + /* The following postcondition is logically equivalent to the previous one, and a bit + * easier to understand. However, it can currently not be used because it contains + * no possible triggers (due to the arithmetic operations inside the loc-expressions). + */ + // ensures forall k: Int :: 0 <= k && k < n ==> loc(a, x + k).val == loc(a, y + k).val + ensures x + n < len(a) && y + n < len(a) ==> loc(a, x + n).val != loc(a, y + n).val +{ + n := 0 + while (x + n < len(a) && y + n < len(a) && loc(a, x + n).val == loc(a, y + n).val) + invariant n >= 0 + invariant access(a) + invariant x + n <= len(a) + invariant y + n <= len(a) + invariant forall k: Int :: {loc(a, offset(k,x,y))} // instantiation trigger term + x <= k && k < x + n ==> loc(a, k).val == loc(a, offset(k,x,y)).val + { + n := n + 1 + } +} + + +/* Encoding of arrays */ + +field val: Int + +domain IArray { + function loc(a: IArray, i: Int): Ref + function len(a: IArray): Int + function first(r: Ref): IArray + function second(r: Ref): Int + + axiom all_diff { + forall a: IArray, i: Int :: {loc(a, i)} + first(loc(a, i)) == a && second(loc(a, i)) == i + } + + axiom len_nonneg { + forall a: IArray :: len(a) >= 0 + } +} diff --git a/src/test/resources/dependencyAnalysisTests/real-world-examples/graph-copy.vpr b/src/test/resources/dependencyAnalysisTests/real-world-examples/graph-copy.vpr new file mode 100644 index 000000000..33524ddba --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/real-world-examples/graph-copy.vpr @@ -0,0 +1,175 @@ +field val: Int +field edges: IEdges + +// Total function that returns null for those elements that are not present. +domain IEdges { + function edge_lookup(e: IEdges, i: Int): Ref + function has_edge(e: IEdges, i: Int): Bool + function insert_edge(e: IEdges, i: Int, node: Ref): IEdges + function edges_domain(e: IEdges): Set[Int] + function empty_edges(): IEdges + + // INSERTION + + axiom inserted_edge_present { + forall e: IEdges, i: Int, node: Ref :: edge_lookup(insert_edge(e, i, node), i) == node + } + + axiom other_edges_preserved_after_insertion { + forall e: IEdges, i: Int, node: Ref, j: Int :: i != j ==> edge_lookup(e, j) == edge_lookup(insert_edge(e, i, node), j) + } + + axiom inserted_edge_defined { + forall e: IEdges, i: Int, node: Ref :: has_edge(insert_edge(e, i, node), i) + } + + // HAS EDGE + + axiom has_edge_complete { + forall e: IEdges, i: Int :: has_edge(e, i) <==> edge_lookup(e, i) != null + } + + // DOMAIN + + axiom edges_domain_defined { + forall e: IEdges, i: Int :: i in edges_domain(e) <==> has_edge(e, i) + } + + // EMPTY MAP + + axiom empty_edges_has_no_nodes { + forall i: Int :: !(has_edge(empty_edges(), i)) + } +} + +domain INodeMap { + function lookup(node_map: INodeMap, node: Ref): Ref + function has_node(node_map: INodeMap, node: Ref): Bool + function insert(node_map: INodeMap, key_node: Ref, val_node: Ref): INodeMap + function map_domain(node_map: INodeMap): Seq[Ref] + function empty_map(): INodeMap + + // INSERTION + + axiom inserted_node_present { + forall node_map: INodeMap, key_node: Ref, val_node: Ref :: lookup(insert(node_map, key_node, val_node), key_node) == val_node + } + + axiom other_nodes_preserved_after_insertion { + forall node_map: INodeMap, key_node: Ref, val_node: Ref, node: Ref :: node != key_node ==> lookup(node_map, node) == lookup(insert(node_map, key_node, val_node), node) + } + + axiom inserted_node_defined { + forall node_map: INodeMap, key_node: Ref, val_node: Ref :: has_node(insert(node_map, key_node, val_node), key_node) + } + + // HAS NODE + + axiom has_node_complete { + forall node_map: INodeMap, node: Ref :: has_node(node_map, node) <==> lookup(node_map, node) != null + } + + // DOMAIN + + axiom domain_is_defined { + forall node_map: INodeMap, key: Ref:: key in map_domain(node_map) <==> has_node(node_map, key) + } + + // EMPTY MAP + + axiom empty_map_has_no_nodes { + forall node: Ref :: !(has_node(empty_map(), node)) + } +} + +method graph_copy_rec(this: Ref, node_map: INodeMap, setOfRef: Set[Ref], node_map_image: Set[Ref], rd: Perm) + returns (nodeCopy: Ref, res_node_map: INodeMap, res_copy_nodes: Set[Ref]) + + requires none < rd + requires this != null + // Precondition about setOfRef + requires this in setOfRef + requires |setOfRef intersection node_map_image| == 0 + requires forall x: Ref :: x in setOfRef ==> acc(x.val, rd) + requires forall x: Ref :: x in setOfRef ==> acc(x.edges, rd) + requires forall x: Ref, i: Int :: x in setOfRef && i in edges_domain(x.edges) ==> edge_lookup(x.edges, i) in setOfRef + // Precondition about node_map_image + requires forall x: Ref :: x in map_domain(node_map) ==> lookup(node_map, x) in node_map_image + requires forall x: Ref :: x in node_map_image ==> acc(x.val) + requires forall x: Ref :: x in node_map_image ==> acc(x.edges) + + ensures nodeCopy != null && nodeCopy in res_copy_nodes + ensures |setOfRef intersection res_copy_nodes| == 0 + ensures forall x: Ref :: x in setOfRef ==> acc(x.val, rd) + ensures forall x: Ref :: x in setOfRef ==> x.val == old(x.val) + ensures forall x: Ref :: x in setOfRef ==> acc(x.edges, rd) + ensures forall x: Ref :: x in setOfRef ==> x.edges == old(x.edges) + ensures forall x: Ref, i: Int :: x in setOfRef && i in edges_domain(x.edges) ==> edge_lookup(x.edges, i) in setOfRef + ensures res_copy_nodes == res_copy_nodes union old(node_map_image) + ensures forall x: Ref :: x in map_domain(res_node_map) ==> lookup(res_node_map,x) in res_copy_nodes + ensures forall x: Ref :: x in res_copy_nodes ==> acc(x.val) + ensures forall x: Ref :: x in res_copy_nodes ==> acc(x.edges) +{ + var i: Int // an edge index + var S: Set[Int] + + if (has_node(node_map, this)) { + nodeCopy := lookup(node_map, this) + res_node_map := node_map + res_copy_nodes := node_map_image + } else { + nodeCopy := new(val, edges) + nodeCopy.val := this.val + + res_node_map := insert(node_map, this, nodeCopy) + + res_copy_nodes := node_map_image union Set(nodeCopy) + + /* The next assert is needed in order to prove that the loop invariant + * assert |setOfRef intersection res_copy_nodes| == 0 + * holds before the loop. + * + * See https://bitbucket.org/viperproject/silver/issues/111 + */ + @trigger() + assert (setOfRef intersection node_map_image) union (setOfRef intersection Set(nodeCopy)) + == setOfRef intersection res_copy_nodes + + S := edges_domain(this.edges) + + while (|S| > 0) + invariant nodeCopy in res_copy_nodes + invariant this in setOfRef + invariant forall x: Ref :: x in setOfRef ==> acc(x.val, rd) + invariant forall x: Ref :: x in setOfRef ==> x.val == old(x.val) + invariant forall x: Ref :: x in setOfRef ==> acc(x.edges, rd) + invariant forall x: Ref :: x in setOfRef ==> x.edges == old(x.edges) + invariant forall j: Int :: j in S ==> edge_lookup(this.edges, j) in setOfRef + invariant forall r: Ref, j: Int :: r in setOfRef && j in edges_domain(r.edges) ==> edge_lookup(r.edges, j) in setOfRef + invariant node_map_image subset res_copy_nodes + invariant |setOfRef intersection res_copy_nodes| == 0 + invariant forall r: Ref :: r in map_domain(res_node_map) ==> lookup(res_node_map,r) in res_copy_nodes + invariant forall r: Ref :: r in res_copy_nodes ==> acc(r.val) + invariant forall r: Ref :: r in res_copy_nodes ==> acc(r.edges) + { + S, i := pop(S) + + var newNode: Ref + var newResultMap: INodeMap + var nodeForId: Ref + + nodeForId := edge_lookup(this.edges, i) + + newNode, res_node_map, res_copy_nodes := graph_copy_rec(nodeForId, res_node_map, setOfRef, res_copy_nodes, rd/2) + + nodeCopy.edges := insert_edge(nodeCopy.edges, i, newNode) + } + } +} + +/* Non-deterministically remove an int from s1, yielding an updated s2 and the removed int i */ +method pop(s1: Set[Int]) returns (s2: Set[Int], i: Int) + requires 0 < |s1| + ensures i in s1 + ensures s2 == s1 setminus Set(i) +{ assume false /* Effectively an unimplemented helper method */ } diff --git a/src/test/resources/dependencyAnalysisTests/real-world-examples/iterativeTreeDelete.vpr b/src/test/resources/dependencyAnalysisTests/real-world-examples/iterativeTreeDelete.vpr new file mode 100644 index 000000000..018bd298a --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/real-world-examples/iterativeTreeDelete.vpr @@ -0,0 +1,95 @@ + + +/* This example shows how magic wands can be used to specify the + * imperative version of challenge 3 from the VerifyThis@FM2012 + * verification competition. Method tree_delete_min below is an + * iterative implementation of the removal of the minimal element + * in a binary search tree. + * + * The example contains two assertions (marked with "TODO") that + * help overcoming an incompleteness with respect to sequences. + * + * At present, this example uses syntax which is only supported + * by the default Viper verifier (Silicon). + */ + +field v: Int +field l: Ref +field r: Ref + +predicate Tree(x: Ref) { + x == null + ? true + : acc(x.v) + && acc(x.l) && acc(Tree(x.l)) + && acc(x.r) && acc(Tree(x.r)) +} + +function val(x: Ref): Int + requires x != null && acc(Tree(x)) +{ unfolding acc(Tree(x)) in x.v } + +function vals(x: Ref): Seq[Int] + requires acc(Tree(x)) +{ x == null ? Seq[Int]() : unfolding acc(Tree(x)) in vals(x.l) ++ Seq(x.v) ++ vals(x.r) } + +/* Deletes the minimal element of a binary tree, assuming that the + * tree is a binary search tree (which, for simplicity, is not made + * explicit in the definition of predicate Tree). + */ +method tree_delete_min(x: Ref) returns (z: Ref) + requires x != null && acc(Tree(x)) + ensures acc(Tree(z)) /* POST1 */ + ensures vals(z) == old(vals(x))[1..] /* POST2 */ +{ + var p: Ref := x + var plvs: Seq[Int] + + define A(p, plvs) acc(p.l) && acc(Tree(p.l)) && vals(p.l) == plvs[1..] + define B acc(Tree(x)) && vals(x) == old(vals(x))[1..] + + unfold acc(Tree(p)) + plvs := vals(p.l) + + if (p.l == null) { + z := p.r + + assert vals(x.l) == Seq[Int]() /* TODO: Required by Silicon for POST2 */ + } else { + package A(p, plvs) --* B { + fold acc(Tree(p)) + } + + + while (unfolding acc(Tree(p.l)) in p.l.l != null) + invariant p != null && acc(p.l) + invariant acc(Tree(p.l)) + invariant p.l != null + invariant plvs == vals(p.l) + invariant A(p, plvs) --* B + { + var prevP: Ref := p + var prevPlvs: Seq[Int] := plvs + + unfold acc(Tree(p.l)) + p := p.l + plvs := vals(p.l) + + package A(p, plvs) --* B { + fold Tree(p) + apply A(prevP, prevPlvs) --* B + } + + } + + unfold acc(Tree(p.l)) + @trigger() + assert vals(p.l.l) == Seq[Int]() /* TODO: Required by Silicon for POST2 */ + + p.l := p.l.r + + apply A(p, plvs) --* B + + z := x + } +} diff --git a/src/test/resources/dependencyAnalysisTests/real-world-examples/listAppend_wands.vpr b/src/test/resources/dependencyAnalysisTests/real-world-examples/listAppend_wands.vpr new file mode 100644 index 000000000..b0162963f --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/real-world-examples/listAppend_wands.vpr @@ -0,0 +1,65 @@ +field next : Ref +field val : Int + +predicate list(start : Ref) +{ + acc(start.val) && acc(start.next) && + (start.next != null ==> list(start.next)) +} + +function elems(start: Ref) : Seq[Int] + requires list(start) +{ + unfolding list(start) in ( + (start.next == null ? Seq(start.val) : + Seq(start.val) ++ elems(start.next) )) +} + +method appendit_wand(l1 : Ref, l2: Ref) + requires list(l1) + requires list(l2) + requires l2 != null + ensures list(l1) + ensures elems(l1) == old(elems(l1) ++ elems(l2)) + { + unfold list(l1) + if(l1.next == null) { + l1.next := l2 + fold list(l1) + } else { + var tmp : Ref + var index : Int + tmp := l1.next + index := 1 + + package list(tmp) --* list(l1) && elems(l1) == old(elems(l1)[..index]) ++ old[lhs](elems(tmp)) + { + fold list(l1) + } + + while(unfolding list(tmp) in tmp.next != null) + invariant index >= 0 + invariant list(tmp) + invariant elems(tmp) == old(elems(l1))[index..] + invariant list(tmp) --* list(l1) && elems(l1) == old(elems(l1)[..index]) ++ old[lhs](elems(tmp)) + { + var prev : Ref + var old_index: Int + unfold list(tmp) + prev := tmp + old_index := index + tmp := tmp.next + index := index + 1 + + package list(tmp) --* list(l1) && elems(l1) == old(elems(l1)[..index]) ++ old[lhs](elems(tmp)) + { + fold list(prev) + apply list(prev) --* list(l1) && elems(l1) == old(elems(l1)[..old_index]) ++ old[lhs](elems(prev)) + } + } + unfold list(tmp) + tmp.next := l2 + fold list(tmp) + apply list(tmp) --* list(l1) && elems(l1) == old(elems(l1)[..index]) ++ old[lhs](elems(tmp)) + } + } \ No newline at end of file diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index 8d3f5672a..38cc00ddb 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -63,6 +63,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { val now: LocalDateTime = LocalDateTime.now() val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss") val writer = new PrintWriter(s"$basePath/result_${now.format(formatter)}.out") + writer.println(f"test name;\tbaseline (ms);\tanalysis (ms);\tanalysis/baseline\tprogram size") testDirectories foreach (dir => visitFiles(dir, executePerformanceBenchmark(_, _, writer))) writer.close() } @@ -195,7 +196,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { }catch{ case _: Throwable => None } - new PerformanceBenchmark(filePrefix + "/" + fileName, program, naiveProgram, writer).execute() + new PerformanceBenchmark(filePrefix + "/" + fileName, program, naiveProgram, writer, program.toString().split("\n").length).execute() } /** @@ -487,32 +488,29 @@ class AssumptionAnalysisTests extends AnyFunSuite { } } - class PerformanceBenchmark(name: String, program: Program, naiveProgram: Option[Program], writer: PrintWriter) { + class PerformanceBenchmark(name: String, program: Program, naiveProgram: Option[Program], writer: PrintWriter, programSize: Int) { - def execute(): Unit = { - writer.println(f"TEST: $name") - println(f"TEST: $name") + private def printResult(str: String): Unit = { + writer.print(str) + print(str) + } + def execute(): Unit = { + printResult(f"$name;\t") val analysisDurationMs: Double = verifyAndMeasure(program, analysisCommandLineArguments) val baselineDurationMs: Double = verifyAndMeasure(program, baseCommandLineArguments) - - writer.println(f"analysis duration (ms): $analysisDurationMs") - writer.println(f"baseline duration (ms): $baselineDurationMs") - writer.println(f"diff analysis-baseline (ms): ${analysisDurationMs-baselineDurationMs}") - writer.println(f"analysis overhead (analysis/baseline): ${analysisDurationMs/baselineDurationMs}") - println(f"analysis overhead (analysis/baseline): ${analysisDurationMs/baselineDurationMs}") + printResult(f"$baselineDurationMs;\t$analysisDurationMs;\t${analysisDurationMs/baselineDurationMs};\t$programSize\n") if(naiveProgram.isDefined){ val naiveDurationMs: Double = verifyAndMeasure(naiveProgram.get, baseCommandLineArguments) - writer.println(f"naive duration (ms): $naiveDurationMs") + writer.println(f"naive duration (ms): $naiveDurationMs") // TODO ake writer.println(f"diff naive-baseline (ms): ${naiveDurationMs-baselineDurationMs}") writer.println(f"naive overhead (naive/baseline): ${naiveDurationMs/baselineDurationMs}") println(f"naive overhead (naive/baseline): ${naiveDurationMs/baselineDurationMs}") } - writer.println() } private def verifyAndMeasure(program_ : Program, commandLineArgs: Seq[String]) = { From f57aaa3211496b10ef33c3e042277f8a1955475e Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 24 Jul 2025 12:18:06 +0200 Subject: [PATCH 197/474] extend precision benchmark, minor fix --- .../precision_test_generator.py | 23 +++++++--- .../benchmark_scripts/snippets.txt | 46 ++++++++++--------- .../unitTests/inhaleExhale.vpr | 20 ++++---- .../unitTests/magicWands.vpr | 14 +++--- .../unitTests/permissions.vpr | 15 +++--- .../unitTests/predicates.vpr | 3 +- .../unitTests/quantifiedPermissions.vpr | 23 ++++------ 7 files changed, 73 insertions(+), 71 deletions(-) diff --git a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_test_generator.py b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_test_generator.py index af4de7ed9..b6be2eb18 100644 --- a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_test_generator.py +++ b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_test_generator.py @@ -35,6 +35,7 @@ def extract_vars(line: str): read_write_vars = [] read_only_vars = [] invariant = "" + acc_invariant = "" for decl in var_decls: tmp = decl.split("=") if(tmp[0] == "$READ_ONLY"): @@ -43,11 +44,13 @@ def extract_vars(line: str): read_write_vars = read_write_vars + tmp[1].split(",") elif(tmp[0] == "$INVARIANT"): invariant = "=".join(tmp[1:]).replace("$_$", " ") + elif(tmp[0] == "$ACC_INVARIANT"): + acc_invariant = "=".join(tmp[1:]).replace("$_$", " ") # print(f"line: {line}") # print(f"read only: {read_only_vars}") # print(f"read write: {read_write_vars}") # print() - return read_only_vars, read_write_vars, invariant + return read_only_vars + read_write_vars, read_write_vars, invariant, acc_invariant def replace_vars(snippet: str, placeholder: str, vars: list[str]): idx = 0 @@ -63,9 +66,9 @@ def replace_vars(snippet: str, placeholder: str, vars: list[str]): return snippet, gen_vars def generate_from_snippet(snippet: str, line: str): - read_only_vars, read_write_vars, invariant = extract_vars(line) - - snippet = snippet.replace("$INVARIANT", invariant if invariant != "" else "true") + read_only_vars, read_write_vars, invariant, acc_invariant = extract_vars(line) + # print(f"read_only_vars: {read_only_vars}") + # print(f"read_write_vars: {read_write_vars}") # replace variables, generate new ones if necessary snippet, gen_ro_refs = replace_vars(snippet, "$RO_REF_F_", [v.split(".")[0] for v in read_only_vars if v.endswith(".f")]) @@ -85,11 +88,17 @@ def generate_from_snippet(snippet: str, line: str): # snippet = f"\n//generated: {generated_refs}\n//existing: {existing_refs}\n" + snippet snippet = "\n".join([f"@irrelevant(\"Explicit\")\ninhale {a} != {b}" for a in generated_refs for b in existing_refs if a != b]) + snippet + gen_vars_ro_field = gen_vars_ro_field + [f"{v}.f" for v in gen_ro_refs] + gen_vars_rw_field = gen_vars_rw_field + [f"{v}.f" for v in gen_rw_refs] + + snippet = snippet.replace("$INVARIANT", invariant if invariant != "" else "true") + acc_invariants = ([inv for inv in acc_invariant.split("&&") if inv != ""] ) + [f"acc({v}, 1/2)" for v in gen_vars_ro_field] + [f"acc({v})" for v in gen_vars_rw_field] + acc_invariant = " && ".join([f"@irrelevant(\"LoopInvariant\")({v})" for v in acc_invariants]) + snippet = snippet.replace("$ACC_INVARIANT", acc_invariant if acc_invariant != "" else "true") + # declare and initialize newly generated vars snippet = "".join([f"var {v.split(".")[0]}: Ref\n@irrelevant(\"Explicit\")\ninhale acc({v}, 1/2)\n" for v in gen_vars_ro_field]) + snippet snippet = "".join([f"var {v.split(".")[0]}: Ref\n@irrelevant(\"Explicit\")\ninhale acc({v})\n" for v in gen_vars_rw_field]) + snippet - snippet = "".join([f"var {v}: Ref\n@irrelevant(\"Explicit\")\ninhale acc({v}.f)\n" for v in gen_rw_refs]) + snippet - snippet = "".join([f"var {v}: Ref\n@irrelevant(\"Explicit\")\ninhale acc({v}.f, 1/2)\n" for v in gen_ro_refs]) + snippet all_vars = gen_vars_ro + gen_vars_rw + gen_vars_ro_pure + gen_vars_rw_pure if len(all_vars) > 0: snippet = "var " + ", ".join([f"{v}: Int" for v in all_vars]) + "\n" + snippet @@ -130,7 +139,7 @@ def process_folder(folder_path: str, output_path: str, snippet_preamble: str, sn for file in files: handle_template_file(file, output_path, snippet_preamble, snippets) -preamble, snippet_dict = read_snippets_file("silicon\\src\\test\\resources\\dependencyAnalysisTests\\generation\\snippets.txt") +preamble, snippet_dict = read_snippets_file("silicon\\src\\test\\resources\\dependencyAnalysisTests\\benchmark_scripts\\snippets.txt") process_folder("silicon\\src\\test\\resources\\dependencyAnalysisTests\\unitTests", "silicon\\src\\test\\resources\\dependencyAnalysisTests\\precisionTests", preamble, snippet_dict) diff --git a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/snippets.txt b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/snippets.txt index a4da3185c..79437f3f8 100644 --- a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/snippets.txt +++ b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/snippets.txt @@ -24,6 +24,7 @@ method incr(a: Ref) a.f := a.f + 1 } +SNIPPET baseline$=${} SNIPPET assignment_pure$=${ @irrelevant("Implicit") @@ -64,25 +65,26 @@ SNIPPET exhale_inhale_perm$=${ inhale acc($RW_INT_FIELD_0, 1/2) } +// TODO ake // fold-unfold -SNIPPET fold_unfold_basic$=${ - var gen_i: Int - @irrelevant("Implicit") - inhale gen_i > 10 - @irrelevant("Implicit") - fold gen_confuse(gen_i, $INVARIANT) - @irrelevant("Implicit") - unfold gen_confuse(gen_i, $INVARIANT) -} +// SNIPPET fold_unfold_basic$=${ +// var gen_i: Int +// @irrelevant("Implicit") +// inhale gen_i > 10 +// @irrelevant("Implicit") +// fold gen_confuse(gen_i, $ACC_INVARIANT && $INVARIANT) +// @irrelevant("Implicit") +// unfold gen_confuse(gen_i, $ACC_INVARIANT && $INVARIANT) +// } // fold-unfold with implication -SNIPPET fold_unfold_with_implication$=${ - var gen_i: Int - @irrelevant("Implicit") - fold gen_confuse_with_impl(gen_i, $INVARIANT) - @irrelevant("Implicit") - unfold gen_confuse_with_impl(gen_i, $INVARIANT) -} +// SNIPPET fold_unfold_with_implication$=${ +// var gen_i: Int +// @irrelevant("Implicit") +// fold gen_confuse_with_impl(gen_i, $ACC_INVARIANT && $INVARIANT) +// @irrelevant("Implicit") +// unfold gen_confuse_with_impl(gen_i, $ACC_INVARIANT && $INVARIANT) +// } // function call SNIPPET function_add$=${ @@ -228,6 +230,7 @@ SNIPPET while_pure$=${ @irrelevant("Implicit") $RW_INT_PURE_1 := 0 while(@irrelevant("PathCondition")($RW_INT_PURE_0 > 0)) + invariant @irrelevant("LoopInvariant")($ACC_INVARIANT) invariant @irrelevant("LoopInvariant")($INVARIANT) invariant @irrelevant("LoopInvariant")($RW_INT_PURE_0 <= gen_start) invariant @irrelevant("LoopInvariant")($RW_INT_PURE_1 == (gen_start-$RW_INT_PURE_0)*$RO_INT_PURE_0) @@ -247,14 +250,13 @@ SNIPPET while_perm$=${ $RW_INT_FIELD_1 := 0 @irrelevant("Explicit") while(@irrelevant("PathCondition")($RW_INT_FIELD_0 > 0)) - invariant @irrelevant("LoopInvariant")(acc($RW_INT_FIELD_0, write)) - invariant @irrelevant("LoopInvariant")(acc($RW_INT_FIELD_1, write)) + invariant @irrelevant("LoopInvariant")($ACC_INVARIANT) invariant @irrelevant("LoopInvariant")($INVARIANT) invariant @irrelevant("LoopInvariant")($RW_INT_FIELD_0 <= gen_start) - invariant @irrelevant("LoopInvariant")($RW_INT_FIELD_1 == (gen_start-$RW_INT_FIELD_0)*$RO_INT_PURE_0) + invariant @irrelevant("LoopInvariant")($RW_INT_FIELD_1 == (gen_start-$RW_INT_FIELD_0)*10) { @irrelevant("Implicit") - $RW_INT_FIELD_1 := $RW_INT_FIELD_1 + $RO_INT_PURE_0 + $RW_INT_FIELD_1 := $RW_INT_FIELD_1 + 10 @irrelevant("Implicit") $RW_INT_FIELD_0 := $RW_INT_FIELD_0 - 1 } @@ -265,9 +267,9 @@ SNIPPET magic_wand_1$=${ @irrelevant("Explicit") inhale gen_i > 0 @irrelevant("Implicit") - package gen_i >= 0 --* $INVARIANT + package gen_i >= 0 --* $ACC_INVARIANT && $INVARIANT @irrelevant("Implicit") - apply gen_i >= 0 --* $INVARIANT + apply gen_i >= 0 --* $ACC_INVARIANT && $INVARIANT } SNIPPET magic_wand_2$=${ diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/inhaleExhale.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/inhaleExhale.vpr index 4ccef2278..765675104 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/inhaleExhale.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/inhaleExhale.vpr @@ -3,12 +3,12 @@ field f: Int method exhaleInhale(a: Ref) requires @dependency("Explicit")(acc(a.f)) { - @irrelevant("Implicit") // TODO ake + @irrelevant("Implicit") exhale acc(a.f, 1/2) @irrelevant("Explicit") inhale acc(a.f, 1/2) - // $PrecisionTest: $READ_WRITE=a.f + // $PrecisionTest: $READ_WRITE=a.f $ACC_INVARIANT=acc(a.f) @testAssertion("Explicit") assert perm(a.f) == write @@ -17,12 +17,12 @@ method exhaleInhale(a: Ref) method exhaleInhale2(a: Ref) requires @dependency("Explicit")(acc(a.f)) { - @irrelevant("Implicit") // TODO ake: imprecision + @irrelevant("Implicit") exhale acc(a.f, 1/2) - @irrelevant("Explicit") // TODO ake: imprecision + @irrelevant("Explicit") inhale acc(a.f, 1/2) - // $PrecisionTest: $READ_WRITE=a.f + // $PrecisionTest: $READ_WRITE=a.f $ACC_INVARIANT=acc(a.f) @testAssertion("Explicit") assert perm(a.f) >= 1/2 @@ -36,11 +36,10 @@ method exhaleImprecision(){ @dependency("Explicit") inhale x.f > 0 - @irrelevant("Implicit") // TODO ake: imprecision + @irrelevant("Implicit") exhale acc(x.f, 1/2) - // $PrecisionTest: $READ_ONLY=x.f - // $INVARIANT=x.f>0 + // $PrecisionTest: $READ_ONLY=x.f $INVARIANT=x.f>0 $ACC_INVARIANT=acc(x.f,1/2) @testAssertion("Explicit") assert x.f > 0 @@ -54,11 +53,10 @@ method inhaleImprecision(){ @dependency("Explicit") inhale x.f > 0 - @irrelevant("Implicit") // TODO ake: imprecision + @irrelevant("Implicit") inhale acc(x.f, 1/2) - // $PrecisionTest: $READ_ONLY=x.f - // $INVARIANT=x.f>0 + // $PrecisionTest: $READ_ONLY=x.f $INVARIANT=x.f>0 $ACC_INVARIANT=acc(x.f) @testAssertion("Explicit") assert x.f > 0 diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr index 2949468dc..070a5c0bd 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr @@ -78,8 +78,7 @@ method advancedPackage(a: Int, b: Int) @dependency("Explicit") inhale greater0(b) - // $PrecisionTest: $READ_ONLY=a,b - // $INVARIANT=unfolding$_$greater0(b)$_$in$_$b>0 + // $PrecisionTest: $READ_ONLY=a,b $INVARIANT=unfolding$_$greater0(b)$_$in$_$b>0 $ACC_INVARIANT=greater0(b) @dependency("Implicit") apply (greater0(a) && greater0(b)) --* greater0(a + b) @@ -97,8 +96,7 @@ method quantifiedWand(xs: Seq[Int], b: Int) @dependency("Implicit") apply greater0(xs[0]) --* greater0(xs[0] + b) - // $PrecisionTest: $READ_ONLY=xs[0],b,xs[1] - // $INVARIANT=unfolding$_$greater0(xs[0]+b)$_$in$_$xs[0]+b>0 // TODO ake: sequence update + // $PrecisionTest: $READ_ONLY=xs[0],b,xs[1] $INVARIANT=unfolding$_$greater0(xs[0]+b)$_$in$_$xs[0]+b>0 $ACC_INVARIANT=greater0(xs[0]+b) // TODO ake: sequence update @testAssertion("Explicit") assert greater0(xs[0] + b) @@ -113,7 +111,7 @@ method packagePrecision(l: Ref) @dependency("Implicit") tmp := l.next - // $PrecisionTest: $READ_WRITE=l.val + // $PrecisionTest: $READ_WRITE=l.val $ACC_INVARIANT=acc(l.val) @irrelevant("Implicit") package list(tmp) --* list(l) @@ -130,7 +128,7 @@ method packageExhale(x: Ref) requires @dependency("Explicit")(acc(x.f)) { - // $PrecisionTest: $READ_WRITE=x.f + // $PrecisionTest: $READ_WRITE=x.f $ACC_INVARIANT=acc(x.f) @dependency("Implicit") package acc(x.f, 1/2) --* acc(x.f) @@ -144,10 +142,10 @@ method packageExhale2(x: Ref) requires @dependency("Explicit")(acc(x.f)) { - @dependency("Implicit") // TODO ake: irrelevant + @dependency("Implicit") // TODO ake: irrelevant? package acc(x.f, 1/2) --* acc(x.f) - // $PrecisionTest: $READ_ONLY=x.f + // $PrecisionTest: $READ_ONLY=x.f $ACC_INVARIANT=acc(x.f,1/2) @dependency("Implicit") apply acc(x.f, 1/2) --* acc(x.f) diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr index a08bb24dd..8ff4cb97e 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr @@ -3,7 +3,7 @@ field f: Int method currentPerm(x: Ref) requires @dependency("Explicit")(acc(x.f, 1/2)) { - // $PrecisionTest: $READ_ONLY=x.f + // $PrecisionTest: $READ_ONLY=x.f $ACC_INVARIANT=acc(x.f,1/2) @testAssertion("Explicit") assert perm(x.f) > 1/4 @@ -14,7 +14,7 @@ method perm1(){ @dependency("Implicit") x := new(f) - // $PrecisionTest: $READ_WRITE=x.f + // $PrecisionTest: $READ_WRITE=x.f $ACC_INVARIANT=acc(x.f) @testAssertion("Explicit") inhale x.f > 0 @@ -25,7 +25,7 @@ method perm2(){ @dependency("Explicit") inhale acc(x.f, 1/2) - // $PrecisionTest: $READ_ONLY=x.f + // $PrecisionTest: $READ_ONLY=x.f $ACC_INVARIANT=acc(x.f,1/2) @testAssertion("Explicit") inhale x.f > 0 @@ -36,7 +36,7 @@ method perm3(){ @dependency("Explicit") inhale acc(x.f) - // $PrecisionTest: $READ_WRITE=x.f + // $PrecisionTest: $READ_WRITE=x.f $ACC_INVARIANT=acc(x.f) @testAssertion("Implicit") x.f := 5 @@ -53,8 +53,7 @@ method perm4(){ @dependency("Implicit") x.f := x.f + 1 - // $PrecisionTest: $READ_ONLY=x.f - // $INVARIANT=acc(x.f)==>x.f>1 + // $PrecisionTest: $READ_ONLY=x.f $INVARIANT=x.f>1 $ACC_INVARIANT=acc(x.f,1/2) @testAssertion("Explicit") assert x.f > 1 @@ -67,7 +66,7 @@ method perm5(){ @irrelevant("Implicit") x.f := x.f + 1 - // $PrecisionTest: $READ_WRITE=x.f + // $PrecisionTest: $READ_WRITE=x.f $ACC_INVARIANT=acc(x.f) @testAssertion("Explicit") exhale acc(x.f) @@ -138,7 +137,7 @@ method noAlias(a: Ref, b: Ref, c: Ref) requires @dependency("Explicit")(acc(b.f, 1/2)) requires @irrelevant("Explicit")(acc(c.f, 1/2)) { - // $PrecisionTest: $READ_WRITE=a.f $READ_ONLY=b.f,c.f + // $PrecisionTest: $READ_WRITE=a.f $READ_ONLY=b.f,c.f $ACC_INVARIANT=acc(a.f)&&acc(b.f,1/2)&&acc(c.f,1/2) @testAssertion("Explicit") assert a != b diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/predicates.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/predicates.vpr index 0b55c44c1..ba1da95da 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/predicates.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/predicates.vpr @@ -60,8 +60,7 @@ method callWithPredicate(a: Int, b: Int) @dependency("Implicit") unfoldFoldP(a, b) - // $PrecisionTest: $READ_ONLY=a,b - // $INVARIANT=unfolding$_$greater5(a+b)$_$in$_$a+b>0 + // $PrecisionTest: $READ_ONLY=a,b $INVARIANT=unfolding$_$greater5(a+b)$_$in$_$a+b>0 $ACC_INVARIANT=greater5(a+b) @testAssertion("Explicit") assert greater5(a + b) diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr index b1adfc494..0f13eb710 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr @@ -7,7 +7,7 @@ method quantifiedPerm1(xs: Seq[Ref]) { @irrelevant("Explicit") inhale xs[0] != xs[1] - // $PrecisionTest: $READ_WRITE=xs[0].f,xs[1].f $INVARIANT=|xs|>0 + // $PrecisionTest: $READ_WRITE=xs[0].f,xs[1].f $INVARIANT=|xs|>0 $ACC_INVARIANT=forall$_$x:Ref::x$_$in$_$xs$_$==>$_$acc(x.f) @testAssertion() xs[0].f := 10 @@ -20,7 +20,7 @@ method quantifiedPerm2(xs: Seq[Ref]) { @irrelevant("Explicit") inhale xs[0] != xs[4] - // $PrecisionTest: $READ_WRITE=xs[0].f,xs[4].f $INVARIANT=|xs|>4 + // $PrecisionTest: $READ_WRITE=xs[0].f,xs[4].f $INVARIANT=|xs|>4 $ACC_INVARIANT=forall$_$x:Ref::x$_$in$_$xs$_$==>$_$acc(x.f) @testAssertion() xs[0].f := xs[1].f + xs[4].f @@ -31,7 +31,7 @@ method quantifiedPerm3(xs: Seq[Ref], y: Ref) { inhale @dependency("Explicit")(forall x: Ref :: x in xs ==> acc(x.f)) inhale @dependency("Explicit")(acc(y.f, wildcard)) - // $PrecisionTest: $READ_WRITE=xs[0].f $READ_ONLY=y.f $INVARIANT=|xs|>0 + // $PrecisionTest: $READ_WRITE=xs[0].f $READ_ONLY=y.f $INVARIANT=|xs|>0 $ACC_INVARIANT=(forall$_$x:Ref::x$_$in$_$xs$_$==>$_$acc(x.f)) @testAssertion("Explicit") assert xs[0] != y @@ -45,7 +45,7 @@ method quantifiedExhalePartiallyTest(xs: Seq[Ref]) { @irrelevant("Explicit") inhale xs[0] != xs[4] - // $PrecisionTest: $READ_WRITE=xs[0].f,xs[4].f $INVARIANT=|xs|>0 + // $PrecisionTest: $READ_WRITE=xs[0].f,xs[4].f $INVARIANT=|xs|>0 $ACC_INVARIANT=forall$_$x:Ref::x$_$in$_$xs$_$==>$_$acc(x.f,1/2) @testAssertion("Explicit") exhale forall x: Ref :: x in xs ==> acc(x.f, 1/2) @@ -58,7 +58,7 @@ method quantifiedExhalePartially(xs: Seq[Ref]) { exhale forall x: Ref :: x in xs ==> acc(x.f, 1/2) - // $PrecisionTest: $READ_WRITE=res $READ_ONLY=xs[1].f,xs[0].f,xs[2].f $INVARIANT=|xs|>0 + // $PrecisionTest: $READ_WRITE=res $READ_ONLY=xs[1].f,xs[0].f,xs[2].f $INVARIANT=|xs|>0 $ACC_INVARIANT=forall$_$x:Ref::x$_$in$_$xs$_$==>$_$acc(x.f) @testAssertion("Implicit") res := xs[1].f + 1 @@ -71,7 +71,7 @@ method quantifiedExhaleFully(xs: Seq[Ref]) { @irrelevant("Explicit") inhale xs[0] != xs[2] - // $PrecisionTest: $READ_WRITE=xs[0].f,xs[2].f $READ_ONLY=xs[1].f $INVARIANT=|xs|>0 + // $PrecisionTest: $READ_WRITE=xs[0].f,xs[2].f $READ_ONLY=xs[1].f $INVARIANT=|xs|>0 $ACC_INVARIANT=|xs|>5&&forall$_$x:Ref::x$_$in$_$xs$_$==>$_$acc(x.f) @testAssertion("Explicit") exhale forall x: Ref :: x in xs ==> acc(x.f) @@ -87,8 +87,7 @@ method quantifiedPermWrite1(xs: Seq[Ref]) { @irrelevant("Explicit") inhale xs[0] != xs[2] - // $PrecisionTest: $READ_WRITE=xs[2].f $READ_ONLY=xs[0].f,xs[1].f - // $INVARIANT=|xs|>0$_$&&$_$acc(xs[0].f)==>xs[0].f>0 + // $PrecisionTest: $READ_WRITE=xs[2].f $READ_ONLY=xs[0].f,xs[1].f $INVARIANT=xs[0].f>0 $ACC_INVARIANT=|xs|>5&&(forall$_$x:Ref::x$_$in$_$xs$_$==>$_$acc(x.f)) @testAssertion("Implicit") xs[0].f := 0 @@ -107,8 +106,7 @@ method quantifiedPermWrite2(xs: Seq[Ref]) { @irrelevant("Explicit") inhale xs[0] != xs[2] - // $PrecisionTest: $READ_WRITE=xs[2].f $READ_ONLY=xs[0].f,xs[1].f - // $INVARIANT=|xs|>0$_$&&$_$acc(xs[0].f)==>xs[0].f<3 + // $PrecisionTest: $READ_WRITE=xs[2].f $READ_ONLY=xs[0].f,xs[1].f $INVARIANT=xs[0].f<3 $ACC_INVARIANT=|xs|>5&&(forall$_$x:Ref::x$_$in$_$xs$_$==>$_$acc(x.f)) @testAssertion("Explicit") assert xs[0].f == 0 @@ -123,8 +121,7 @@ method quantifiedPermWrite3(xs: Seq[Ref]) { @irrelevant("Implicit") xs[0].f := 0 - // $PrecisionTest: $READ_WRITE=xs[0].f $READ_ONLY=xs[1].f,xs[2].f - // $INVARIANT=|xs|>1$_$&&$_$acc(xs[1].f)==>xs[1].f>=0 + // $PrecisionTest: $READ_WRITE=xs[0].f $READ_ONLY=xs[1].f,xs[2].f $INVARIANT=xs[1].f>=0 $ACC_INVARIANT=|xs|>1&&(forall$_$x:Ref::x$_$in$_$xs$_$==>$_$acc(x.f)) @testAssertion("Explicit") assert xs[0] != xs[1] ==> xs[1].f > 0 @@ -142,7 +139,7 @@ method quantifiedPermWrite4(xs: Seq[Ref], ys: Seq[Ref]) @irrelevant("Explicit") inhale ys[0] != ys[1] - // $PrecisionTest: $READ_WRITE=ys[0].f,ys[1].f $READ_ONLY=xs[0].f,xs[1].f $INVARIANT=|ys|>3 + // $PrecisionTest: $READ_WRITE=ys[0].f,ys[1].f $READ_ONLY=xs[0].f,xs[1].f $INVARIANT=|ys|>3 $ACC_INVARIANT=|xs|>5&&|ys|>3&&(forall$_$x:Ref::x$_$in$_$xs$_$==>$_$acc(x.f))&&(forall$_$y:Ref::y$_$in$_$ys$_$==>$_$acc(y.f)) @testAssertion("Implicit") xs[0].f := 2 From 7e24c276f2062cb56fb924f9d7f4a4c9983f38d0 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 24 Jul 2025 16:05:21 +0200 Subject: [PATCH 198/474] add proof coverage benchmark --- .../AssumptionAnalysisInterpreter.scala | 16 ++++++++---- .../AssumptionAnalysisUserTool.scala | 2 +- src/test/scala/AssumptionAnalysisTests.scala | 25 +++++++++++++++++-- 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala index 4e124a846..3782541fd 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala @@ -122,17 +122,23 @@ class AssumptionAnalysisInterpreter(name: String, graph: ReadOnlyAssumptionAnaly mergedGraph } - def computeProofCoverage(): Option[(Double, Seq[String])] = { + def computeProofCoverage(): (Double, Seq[String]) = { val explicitAssertionNodes = getExplicitAssertionNodes - val explicitAssertionNodeIds = explicitAssertionNodes map (_.id) + computeProofCoverage(explicitAssertionNodes) + } + + def computeProofCoverage(assertionNodes: Set[AssumptionAnalysisNode]): (Double, Seq[String]) = { + val assertionNodeIds = assertionNodes map (_.id) val nodesPerSourceInfo = getNonInternalAssumptionNodesPerSource + if(nodesPerSourceInfo.isEmpty) return (Double.NaN, Seq()) + val uncoveredSources = (nodesPerSourceInfo filter { case (_, nodes) => val nodeIds = nodes map (_.id) // it is not an explicit assertion itself and has no dependency to an explicit assertion - nodeIds.intersect(explicitAssertionNodeIds).isEmpty && - !graph.existsAnyDependency(nodeIds, explicitAssertionNodeIds) + nodeIds.intersect(assertionNodeIds).isEmpty && + !graph.existsAnyDependency(nodeIds, assertionNodeIds) }).keys.toSeq val proofCoverage = 1.0 - (uncoveredSources.size.toDouble / nodesPerSourceInfo.size.toDouble) - Some((proofCoverage, uncoveredSources)) + (proofCoverage, uncoveredSources) } } diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala index 26ac748e8..ccc8eb05c 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala @@ -54,7 +54,7 @@ class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpr case _ => false }) .foreach(aa => { - val (coverage, uncoveredSources) = aa.computeProofCoverage().get + val (coverage, uncoveredSources) = aa.computeProofCoverage() println(s"${aa.getMember.map(_.name).getOrElse("")}") println(s"coverage: $coverage") if (!coverage.equals(1.0)) diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index 38cc00ddb..1f40efbd7 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -63,9 +63,11 @@ class AssumptionAnalysisTests extends AnyFunSuite { val now: LocalDateTime = LocalDateTime.now() val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss") val writer = new PrintWriter(s"$basePath/result_${now.format(formatter)}.out") + val proofCoverageWriter = new PrintWriter(s"$basePath/proofCoverage_${now.format(formatter)}.out") writer.println(f"test name;\tbaseline (ms);\tanalysis (ms);\tanalysis/baseline\tprogram size") - testDirectories foreach (dir => visitFiles(dir, executePerformanceBenchmark(_, _, writer))) + testDirectories foreach (dir => visitFiles(dir, executePerformanceBenchmark(_, _, writer, proofCoverageWriter))) writer.close() + proofCoverageWriter.close() } // createSingleTest("dependencyAnalysisTests/quick", "test") @@ -187,7 +189,8 @@ class AssumptionAnalysisTests extends AnyFunSuite { def executePerformanceBenchmark(filePrefix: String, fileName: String, - writer: PrintWriter): Unit = { + writer: PrintWriter, + proofCoverageWriter: PrintWriter): Unit = { if(fileName.endsWith("_naive")) return val program: Program = tests.loadProgram(filePrefix + "/", fileName, frontend) @@ -196,6 +199,24 @@ class AssumptionAnalysisTests extends AnyFunSuite { }catch{ case _: Throwable => None } + + val frontend_ = createFrontend(analysisCommandLineArguments) + val result = frontend_.verifier.verify(program) + if(result.isInstanceOf[verifier.Failure]) { + cancel(f"Program does not verify. Skip test.\n$result") + return + } + + val assumptionAnalysisInterpreters = frontend_.reporter.asInstanceOf[DependencyAnalysisReporter].assumptionAnalysisInterpreters + + proofCoverageWriter.println(filePrefix + "/" + fileName) + assumptionAnalysisInterpreters foreach (memberInterpreter => { + memberInterpreter.getExplicitAssertionNodes.groupBy(_.sourceInfo.getTopLevelSource) foreach {case (source, nodes) => + proofCoverageWriter.println(memberInterpreter.getName + " " + source.toString.replace("\n", " ") + " ---> " + memberInterpreter.computeProofCoverage(nodes)._1)} + proofCoverageWriter.println("overall " + memberInterpreter.getName + " ---> + " + memberInterpreter.computeProofCoverage()._1) + }) + proofCoverageWriter.println() + new PerformanceBenchmark(filePrefix + "/" + fileName, program, naiveProgram, writer, program.toString().split("\n").length).execute() } From b6f3ba43b96877b7cc68e6ecc5a349380327dbfa Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 25 Jul 2025 10:36:21 +0200 Subject: [PATCH 199/474] merge nodes before adding transitive edges --- .../AssumptionAnalysisInterpreter.scala | 44 --------------- .../AssumptionAnalyzer.scala | 54 +++++++++++++++---- .../DependencyAnalysisReporter.scala | 3 +- .../scala/supporters/MethodSupporter.scala | 5 +- .../functions/FunctionVerificationUnit.scala | 5 +- .../scala/verifier/DefaultMainVerifier.scala | 6 +-- src/test/scala/AssumptionAnalysisTests.scala | 14 ++--- 7 files changed, 62 insertions(+), 69 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala index 3782541fd..cb916426a 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala @@ -78,50 +78,6 @@ class AssumptionAnalysisInterpreter(name: String, graph: ReadOnlyAssumptionAnaly graph.exportGraph(Verifier.config.assumptionAnalysisExportPath() + "/" + name) } - def exportMergedGraph(): Unit = { - if(Verifier.config.assumptionAnalysisExportPath.isEmpty) return - val mergedGraph = mergeNodesBySource() - mergedGraph.exportGraph(Verifier.config.assumptionAnalysisExportPath() + "/" + name + "_merged") - } - - private def mergeNodesBySource(): AssumptionAnalysisGraph = { - def keepNode(n: AssumptionAnalysisNode): Boolean = n.isClosed || n.isInstanceOf[InfeasibilityNode] - - val mergedGraph = new AssumptionAnalysisGraph - val nodeMap = mutable.HashMap[Int, Int]() - graph.getNodes.filter(keepNode).foreach{n => - nodeMap.put(n.id, n.id) - mergedGraph.addNode(n) - } - - val nodesBySource = graph.getNodes.filter(!keepNode(_)) - .groupBy(n => (n.sourceInfo.getSourceForTransitiveEdges.toString, n.sourceInfo.getTopLevelSource.toString, n.assumptionType)) - nodesBySource foreach {case ((_, _, assumptionType), nodes) => - val assumptionNodes = nodes.filter(_.isInstanceOf[GeneralAssumptionNode]) - if(assumptionNodes.nonEmpty) { - val newNode = SimpleAssumptionNode(True, None, assumptionNodes.head.sourceInfo, assumptionType, isClosed = true) - assumptionNodes foreach (n => nodeMap.put(n.id, newNode.id)) - mergedGraph.addNode(newNode) - } - } - - nodesBySource foreach {case ((_, _, assumptionType), nodes) => - val assertionNodes = nodes.filter(_.isInstanceOf[GeneralAssertionNode]) - if(assertionNodes.nonEmpty){ - val newNode = SimpleAssertionNode(True, assumptionType, assertionNodes.head.sourceInfo, isClosed=true) - assertionNodes foreach (n => nodeMap.put(n.id, newNode.id)) - mergedGraph.addNode(newNode) - } - } - - graph.getAllEdges foreach {case (source, targets) => - val newSource = nodeMap(source) - mergedGraph.addEdges(newSource, targets.map(nodeMap(_))) - } - - mergedGraph - } - def computeProofCoverage(): (Double, Seq[String]) = { val explicitAssertionNodes = getExplicitAssertionNodes computeProofCoverage(explicitAssertionNodes) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 87a97e2c2..ac940a636 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -5,6 +5,8 @@ import viper.silicon.interfaces.state.{Chunk, GeneralChunk} import viper.silicon.state.terms._ import viper.silver.ast +import scala.collection.mutable + trait AssumptionAnalyzer { protected val assumptionGraph: AssumptionAnalysisGraph = new AssumptionAnalysisGraph() @@ -39,8 +41,7 @@ trait AssumptionAnalyzer { def addPermissionDependencies(sourceChunks: Set[Chunk], sourceTerms: Set[Term], targetChunk: Chunk): Unit def addCustomTransitiveDependency(sourceSourceInfo: AnalysisSourceInfo, targetSourceInfo: AnalysisSourceInfo): Unit - def finalizeGraph(): Unit - def convertToInterpreter(name: String): Option[AssumptionAnalysisInterpreter] + def buildFinalGraph(): Option[AssumptionAnalysisGraph] } object AssumptionAnalyzer { @@ -229,13 +230,50 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { assumptionGraph.addEdges(sourceNodes map (_.id), targetNodes map (_.id)) } - override def finalizeGraph(): Unit = { + override def buildFinalGraph(): Option[AssumptionAnalysisGraph] = { assumptionGraph.removeLabelNodes() - assumptionGraph.addTransitiveEdges() + val mergedGraph = mergeNodesAndGetNewGraph() + mergedGraph.addTransitiveEdges() + Some(mergedGraph) } - override def convertToInterpreter(name: String): Option[AssumptionAnalysisInterpreter] = - Some(new AssumptionAnalysisInterpreter(name, assumptionGraph, getMember)) + private def mergeNodesAndGetNewGraph(): AssumptionAnalysisGraph = { + def keepNode(n: AssumptionAnalysisNode): Boolean = n.isClosed || n.isInstanceOf[InfeasibilityNode] + + val mergedGraph = new AssumptionAnalysisGraph + val nodeMap = mutable.HashMap[Int, Int]() + assumptionGraph.getNodes.filter(keepNode).foreach { n => + nodeMap.put(n.id, n.id) + mergedGraph.addNode(n) + } + + val nodesBySource = assumptionGraph.getNodes.filter(!keepNode(_)) + .groupBy(n => (n.sourceInfo.getSourceForTransitiveEdges.toString, n.sourceInfo.getTopLevelSource.toString, n.sourceInfo.getFineGrainedSource, n.assumptionType)) + nodesBySource foreach { case ((_, _, _, assumptionType), nodes) => + val assumptionNodes = nodes.filter(_.isInstanceOf[GeneralAssumptionNode]) + if (assumptionNodes.nonEmpty) { + val newNode = SimpleAssumptionNode(True, None, assumptionNodes.head.sourceInfo, assumptionType, isClosed = false) + assumptionNodes foreach (n => nodeMap.put(n.id, newNode.id)) + mergedGraph.addNode(newNode) + } + } + + nodesBySource foreach { case ((_, _, _, assumptionType), nodes) => + val assertionNodes = nodes.filter(_.isInstanceOf[GeneralAssertionNode]) + if (assertionNodes.nonEmpty) { + val newNode = SimpleAssertionNode(True, assumptionType, assertionNodes.head.sourceInfo, isClosed = false) + assertionNodes foreach (n => nodeMap.put(n.id, newNode.id)) + mergedGraph.addNode(newNode) + } + } + + assumptionGraph.getAllEdges foreach { case (source, targets) => + val newSource = nodeMap(source) + mergedGraph.addEdges(newSource, targets.map(nodeMap(_))) + } + + mergedGraph + } } class NoAssumptionAnalyzer extends AssumptionAnalyzer { @@ -259,7 +297,5 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { override def addPermissionDependencies(sourceChunks: Set[Chunk], sourceTerms: Set[Term], targetChunk: Chunk): Unit = {} override def addCustomTransitiveDependency(sourceSourceInfo: AnalysisSourceInfo, targetSourceInfo: AnalysisSourceInfo): Unit = {} - override def finalizeGraph(): Unit = {} - - override def convertToInterpreter(name: String): Option[AssumptionAnalysisInterpreter] = None + override def buildFinalGraph(): Option[AssumptionAnalysisGraph] = None } \ No newline at end of file diff --git a/src/main/scala/assumptionAnalysis/DependencyAnalysisReporter.scala b/src/main/scala/assumptionAnalysis/DependencyAnalysisReporter.scala index 0af7ed3c6..c10a7b549 100644 --- a/src/main/scala/assumptionAnalysis/DependencyAnalysisReporter.scala +++ b/src/main/scala/assumptionAnalysis/DependencyAnalysisReporter.scala @@ -3,7 +3,8 @@ package viper.silicon.assumptionAnalysis import viper.silver.reporter.{Message, Reporter} case class DependencyAnalysisReporter(name: String = "dependencyAnalysis_reporter", path: String = "report.csv") extends Reporter { - var assumptionAnalysisInterpreters: List[AssumptionAnalysisInterpreter] = List.empty + var assumptionAnalysisInterpretersPerMember: List[AssumptionAnalysisInterpreter] = List.empty + var joinedAssumptionAnalysisInterpreter: Option[AssumptionAnalysisInterpreter] = None override def report(msg: Message): Unit = {} } diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index 3e44a5a9e..6a9600079 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -7,7 +7,7 @@ package viper.silicon.supporters import com.typesafe.scalalogging.Logger -import viper.silicon.assumptionAnalysis.{AssumptionAnalyzer, AssumptionType} +import viper.silicon.assumptionAnalysis.{AssumptionAnalysisInterpreter, AssumptionAnalyzer, AssumptionType} import viper.silicon.decider.Decider import viper.silicon.interfaces._ import viper.silicon.logger.records.data.WellformednessCheckRecord @@ -118,8 +118,7 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif Success()))}) } )})}) - v.decider.assumptionAnalyzer.finalizeGraph() - result.assumptionAnalysisInterpreter = v.decider.assumptionAnalyzer.convertToInterpreter(method.name) + result.assumptionAnalysisInterpreter = v.decider.assumptionAnalyzer.buildFinalGraph().map(new AssumptionAnalysisInterpreter(method.name, _, Some(method))) v.decider.resetProverOptions() diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index 6909d154f..33afe52f8 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -8,7 +8,7 @@ package viper.silicon.supporters.functions import com.typesafe.scalalogging.Logger import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType -import viper.silicon.assumptionAnalysis.{AnalysisSourceInfo, AssumptionAnalyzer, AssumptionType, ExpAnalysisSourceInfo} +import viper.silicon.assumptionAnalysis._ import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.decider.Decider @@ -161,8 +161,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver val res = handleFunction(sInit, function) symbExLog.closeMemberScope() - v.decider.assumptionAnalyzer.finalizeGraph() - res.assumptionAnalysisInterpreter = v.decider.assumptionAnalyzer.convertToInterpreter(function.name) + res.assumptionAnalysisInterpreter = v.decider.assumptionAnalyzer.buildFinalGraph().map(new AssumptionAnalysisInterpreter(function.name, _, Some(function))) Seq(res) } diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index 328401fd9..b6241bfcf 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -318,11 +318,10 @@ class DefaultMainVerifier(config: Config, val assumptionAnalysisInterpreters = verificationResults.filter(_.assumptionAnalysisInterpreter.isDefined).map(_.assumptionAnalysisInterpreter.get) assumptionAnalysisInterpreters foreach (_.exportGraph()) - assumptionAnalysisInterpreters foreach (_.exportMergedGraph()) val joinedGraphInterpreter = AssumptionAnalysisInterpreter.joinGraphsAndGetInterpreter(inputFile.map(_.replaceAll("\\\\", "_").replaceAll(".vpr", "")), assumptionAnalysisInterpreters.toSet) if(Verifier.config.assumptionAnalysisExportPath.isDefined) - joinedGraphInterpreter.exportMergedGraph() + joinedGraphInterpreter.exportGraph() if(Verifier.config.startAssumptionAnalysisTool()){ val commandLineTool = new AssumptionAnalysisUserTool(joinedGraphInterpreter, assumptionAnalysisInterpreters) @@ -331,7 +330,8 @@ class DefaultMainVerifier(config: Config, reporter match { case analysisReporter: DependencyAnalysisReporter => - analysisReporter.assumptionAnalysisInterpreters = assumptionAnalysisInterpreters + analysisReporter.assumptionAnalysisInterpretersPerMember = assumptionAnalysisInterpreters + analysisReporter.joinedAssumptionAnalysisInterpreter = Some(joinedGraphInterpreter) case _ => } diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index 1f40efbd7..fd6916122 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -154,10 +154,11 @@ class AssumptionAnalysisTests extends AnyFunSuite { return } - val assumptionAnalysisInterpreters = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].assumptionAnalysisInterpreters + val assumptionAnalysisInterpreters = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].assumptionAnalysisInterpretersPerMember + val joinedAssumptionAnalysisInterpreter = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].joinedAssumptionAnalysisInterpreter new AnnotatedTest(program, assumptionAnalysisInterpreters).execute() - PruningTest(filePrefix + "/" + fileName, program, AssumptionAnalysisInterpreter.joinGraphsAndGetInterpreter(Some(fileName), assumptionAnalysisInterpreters.toSet)).execute() + PruningTest(filePrefix + "/" + fileName, program, joinedAssumptionAnalysisInterpreter.get).execute() } def executePrecisionBenchmark(filePrefix: String, @@ -173,10 +174,11 @@ class AssumptionAnalysisTests extends AnyFunSuite { println(f"Program does not verify. Skip.\n$result") return } - val assumptionAnalysisInterpreters = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].assumptionAnalysisInterpreters + val assumptionAnalysisInterpreters = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].assumptionAnalysisInterpretersPerMember + val fullGraphInterpreter = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].joinedAssumptionAnalysisInterpreter + writer.println(s"$filePrefix - $fileName") - val fullGraphInterpreter = AssumptionAnalysisInterpreter.joinGraphsAndGetInterpreter(Some(fileName), assumptionAnalysisInterpreters.toSet) - new PrecisionBenchmarkSoundnessTest(filePrefix + "/" + fileName, program, fullGraphInterpreter, writer).execute() + new PrecisionBenchmarkSoundnessTest(filePrefix + "/" + fileName, program, fullGraphInterpreter.get, writer).execute() new AnnotatedPrecisionBenchmark(program, assumptionAnalysisInterpreters, writer).execute() writer.println() println(s"Precision Benchmark for $filePrefix - $fileName done.") @@ -207,7 +209,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { return } - val assumptionAnalysisInterpreters = frontend_.reporter.asInstanceOf[DependencyAnalysisReporter].assumptionAnalysisInterpreters + val assumptionAnalysisInterpreters = frontend_.reporter.asInstanceOf[DependencyAnalysisReporter].assumptionAnalysisInterpretersPerMember proofCoverageWriter.println(filePrefix + "/" + fileName) assumptionAnalysisInterpreters foreach (memberInterpreter => { From 2277005696a5ed2cf6b9fa6c49248cbf74f8b725 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 25 Jul 2025 11:16:26 +0200 Subject: [PATCH 200/474] review assumption types --- .../assumptionAnalysis/AnalysisInfo.scala | 2 +- src/main/scala/interfaces/decider/Prover.scala | 2 +- src/main/scala/interfaces/state/Chunks.scala | 4 ++-- src/main/scala/rules/Consumer.scala | 2 +- src/main/scala/rules/Evaluator.scala | 11 ++++++----- src/main/scala/rules/HavocSupporter.scala | 2 +- src/main/scala/rules/Joiner.scala | 4 ++-- src/main/scala/rules/MagicWandSupporter.scala | 2 +- src/main/scala/rules/Producer.scala | 6 +++--- .../scala/rules/QuantifiedChunkSupport.scala | 18 +++++++++--------- .../functions/FunctionVerificationUnit.scala | 13 +++++++------ .../verifier/VerificationPoolManager.scala | 2 +- 12 files changed, 35 insertions(+), 33 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AnalysisInfo.scala b/src/main/scala/assumptionAnalysis/AnalysisInfo.scala index 0c80502fd..a5f900585 100644 --- a/src/main/scala/assumptionAnalysis/AnalysisInfo.scala +++ b/src/main/scala/assumptionAnalysis/AnalysisInfo.scala @@ -2,7 +2,7 @@ package viper.silicon.assumptionAnalysis object AssumptionType extends Enumeration { type AssumptionType = Value - val Explicit, LoopInvariant, PathCondition, Rewrite, Implicit, Internal, Axiom, Trigger, Postcondition = Value + val Explicit, LoopInvariant, PathCondition, Rewrite, Implicit, Internal, Trigger, Postcondition = Value def fromString(s: String): Option[Value] = values.find(_.toString == s) } diff --git a/src/main/scala/interfaces/decider/Prover.scala b/src/main/scala/interfaces/decider/Prover.scala index 7f9ac5e60..d6d5d693c 100644 --- a/src/main/scala/interfaces/decider/Prover.scala +++ b/src/main/scala/interfaces/decider/Prover.scala @@ -39,7 +39,7 @@ trait ProverLike { terms foreach assume } - def assumeAxiomsWithAnalysisInfo(axioms: InsertionOrderedSet[(Term, Option[AnalysisSourceInfo])], description: String, assumptionType: AssumptionType=AssumptionType.Axiom): Unit = { + def assumeAxiomsWithAnalysisInfo(axioms: InsertionOrderedSet[(Term, Option[AnalysisSourceInfo])], description: String, assumptionType: AssumptionType=AssumptionType.Explicit): Unit = { if (debugMode) preambleAssumptions :+= new DebugAxiom(description, axioms.map(_._1)) diff --git a/src/main/scala/interfaces/state/Chunks.scala b/src/main/scala/interfaces/state/Chunks.scala index d1883edb3..7e88d6ead 100644 --- a/src/main/scala/interfaces/state/Chunks.scala +++ b/src/main/scala/interfaces/state/Chunks.scala @@ -6,7 +6,7 @@ package viper.silicon.interfaces.state -import viper.silicon.assumptionAnalysis.AnalysisInfo +import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType} import viper.silicon.resources.ResourceID import viper.silicon.state.terms.{Term, Var} import viper.silver.ast @@ -40,7 +40,7 @@ object GeneralChunk { def permMinus(chunk: GeneralChunk, newPerm: Term, newPermExp: Option[ast.Exp], analysisInfo: AnalysisInfo): GeneralChunk = { val newChunk = analysisInfo.decider.registerDerivedChunk[GeneralChunk](Set(chunk), {finalPerm => chunk.permMinus(finalPerm, newPermExp)}, - newPerm, analysisInfo, isExhale=false, createLabel=false) // TODO ake: assumption type? + newPerm, analysisInfo.withAssumptionType(AssumptionType.Internal), isExhale=false, createLabel=false) // TODO ake: assumption type? maybe for exhale we want to have Implicit? @unused // we still need to register the chunk to have a sound analysis val exhaledChunk = analysisInfo.decider.registerDerivedChunk[GeneralChunk](Set(chunk), { finalPerm => chunk.withPerm(finalPerm, newPermExp)}, diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index e6800058b..920161d6a 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -632,7 +632,7 @@ object consumer extends ConsumptionRules { val termToAssert = t match { case Quantification(q, vars, body, trgs, name, isGlob, weight) => val transformed = FunctionPreconditionTransformer.transform(body, s3.program) - v2.decider.assume(Quantification(q, vars, transformed, trgs, name+"_precondition", isGlob, weight), Option.when(withExp)(e), eNew, AssumptionType.Internal) + v2.decider.assume(Quantification(q, vars, transformed, trgs, name+"_precondition", isGlob, weight), Option.when(withExp)(e), eNew, assumptionType) Quantification(q, vars, Implies(transformed, body), trgs, name, isGlob, weight) case _ => t } diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 8967b060a..ff413eb3e 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -224,7 +224,7 @@ object evaluator extends EvaluationRules { * quantifier in whose body field 'fa.field' was accessed) * which is protected by a trigger term that we currently don't have. */ - v1.decider.assume(And(fvfDef.valueDefinitions), Option.when(withExp)(DebugExp.createInstance("Value definitions", isInternal_ = true)), AssumptionType.Internal) + v1.decider.assume(And(fvfDef.valueDefinitions), Option.when(withExp)(DebugExp.createInstance("Value definitions", isInternal_ = true)), AssumptionType.Implicit) if (s1.heapDependentTriggers.contains(fa.field)){ val trigger = FieldTrigger(fa.field.name, fvfDef.sm, tRcvr) val triggerExp = Option.when(withExp)(DebugExp.createInstance(s"FieldTrigger(${eRcvr.toString()}.${fa.field.name})")) @@ -844,7 +844,7 @@ object evaluator extends EvaluationRules { val exp = ast.Forall(eQuant.variables, eTriggers, body)(sourceQuant.pos, sourceQuant.info, sourceQuant.errT) DebugExp.createInstance(exp, expNew) }) - v1.decider.assume(Quantification(Forall, tVars, FunctionPreconditionTransformer.transform(tBody, s1.program), tTriggers, name, quantWeight), debugExp, AssumptionType.Internal) + v1.decider.assume(Quantification(Forall, tVars, FunctionPreconditionTransformer.transform(tBody, s1.program), tTriggers, name, quantWeight), debugExp, AssumptionType.Implicit) } val tQuant = Quantification(qantOp, tVars, tBody, tTriggers, name, quantWeight) @@ -1013,7 +1013,7 @@ object evaluator extends EvaluationRules { if (!Verifier.config.disableFunctionUnfoldTrigger()) { val eArgsString = eArgsNew.mkString(", ") val debugExp = Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}($eArgsString))", isInternal_ = true)) - v4.decider.assume(App(s.predicateData(predicate).triggerFunction, snap.get.convert(terms.sorts.Snap) +: tArgs), debugExp, AssumptionType.Internal) + v4.decider.assume(App(s.predicateData(predicate).triggerFunction, snap.get.convert(terms.sorts.Snap) +: tArgs), debugExp, AssumptionType.Implicit) } val body = predicate.body.get /* Only non-abstract predicates can be unfolded */ val s7 = s6.scalePermissionFactor(tPerm, ePermNew) @@ -1666,7 +1666,8 @@ object evaluator extends EvaluationRules { (r, optRemainingTriggerTerms) match { case (Success(), Some(remainingTriggerTerms)) => - v.decider.assume(pcDelta, Option.when(withExp)(DebugExp.createInstance("pcDeltaExp", children = pcDeltaExp)), enforceAssumption = false, assumptionType=AssumptionType.Internal) + // TODO ake: wrap pcDelta with labels? + v.decider.assume(pcDelta, Option.when(withExp)(DebugExp.createInstance("pcDeltaExp", children = pcDeltaExp)), enforceAssumption = false, assumptionType=AssumptionType.Implicit) Q(s, cachedTriggerTerms ++ remainingTriggerTerms, v) case _ => for (e <- remainingTriggerExpressions) @@ -1708,7 +1709,7 @@ object evaluator extends EvaluationRules { var sJoined = entries.tail.foldLeft(entries.head.s)((sAcc, entry) => sAcc.merge(entry.s)) sJoined = sJoined.copy(functionRecorder = sJoined.functionRecorder.recordPathSymbol(joinSymbol)) - joinDefEqs foreach { case (t, exp, expNew) => v.decider.assume(t, exp, expNew, AssumptionType.Internal)} + joinDefEqs foreach { case (t, exp, expNew) => v.decider.assume(t, exp, expNew, AssumptionType.Implicit)} (sJoined, (joinTerm, joinExp)) } diff --git a/src/main/scala/rules/HavocSupporter.scala b/src/main/scala/rules/HavocSupporter.scala index d5d00afc9..b5bff573a 100644 --- a/src/main/scala/rules/HavocSupporter.scala +++ b/src/main/scala/rules/HavocSupporter.scala @@ -132,7 +132,7 @@ object havocSupporter extends SymbolicExecutionRules { val notInjectiveReason = QuasihavocallNotInjective(havocall) val comment = "QP receiver injectivity check is well-defined" val injectivityDebugExp = Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)) - v.decider.assume(FunctionPreconditionTransformer.transform(receiverInjectivityCheck, s.program), injectivityDebugExp, AssumptionType.Internal) + v.decider.assume(FunctionPreconditionTransformer.transform(receiverInjectivityCheck, s.program), injectivityDebugExp, assumptionType) v.decider.assert(receiverInjectivityCheck) { case false => createFailure(pve dueTo notInjectiveReason, v, s1, receiverInjectivityCheck, "QP receiver injective") case true => diff --git a/src/main/scala/rules/Joiner.scala b/src/main/scala/rules/Joiner.scala index d8abd2483..006b48d31 100644 --- a/src/main/scala/rules/Joiner.scala +++ b/src/main/scala/rules/Joiner.scala @@ -103,13 +103,13 @@ object joiner extends JoiningRules { val pcsExp = Option.when(withExp)(entry.pathConditions.conditionalizedExp) val comment = "Joined path conditions" v.decider.prover.comment(comment) - v.decider.assume(pcs, Option.when(withExp)(DebugExp.createInstance(comment, InsertionOrderedSet(pcsExp.get))), enforceAssumption = false, AssumptionType.Internal) + v.decider.assume(pcs, Option.when(withExp)(DebugExp.createInstance(comment, InsertionOrderedSet(pcsExp.get))), enforceAssumption = false, AssumptionType.Implicit) feasibleBranches = And(entry.pathConditions.branchConditions) :: feasibleBranches feasibleBranchesExp = feasibleBranchesExp.map(fbe => BigAnd(entry.pathConditions.branchConditionExps.map(_._1)) :: fbe) feasibleBranchesExpNew = feasibleBranchesExpNew.map(fbe => BigAnd(entry.pathConditions.branchConditionExps.map(_._2.get)) :: fbe) }) // Assume we are in a feasible branch - v.decider.assume(Or(feasibleBranches), Option.when(withExp)(DebugExp.createInstance(Some("Feasible Branches"), feasibleBranchesExp.map(BigOr(_)), feasibleBranchesExpNew.map(BigOr(_)), InsertionOrderedSet.empty)), AssumptionType.Internal) + v.decider.assume(Or(feasibleBranches), Option.when(withExp)(DebugExp.createInstance(Some("Feasible Branches"), feasibleBranchesExp.map(BigOr(_)), feasibleBranchesExpNew.map(BigOr(_)), InsertionOrderedSet.empty)), AssumptionType.Implicit) Q(sJoined, dataJoined, v) } } diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 2a3c2d374..94021df47 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -371,7 +371,7 @@ object magicWandSupporter extends SymbolicExecutionRules { val freshSnapRoot = freshSnap(sorts.Snap, v1) // Produce the wand's LHS. - produce(s1.copy(conservingSnapshotGeneration = true), toSf(freshSnapRoot), wand.left, pve, v1, AssumptionType.Internal)((sLhs, v2) => { + produce(s1.copy(conservingSnapshotGeneration = true), toSf(freshSnapRoot), wand.left, pve, v1, AssumptionType.Implicit)((sLhs, v2) => { val proofScriptCfg = proofScript.toCfg() /* Expected shape of reserveHeaps is either diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index cf2c09c8d..2b845b620 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -249,7 +249,7 @@ object producer extends ProductionRules { QB(s3, null, v3) }), (s2, v2) => { - v2.decider.assume(sf(sorts.Snap, v2) === Unit, Option.when(withExp)(DebugExp.createInstance("Empty snapshot", true)), AssumptionType.Internal) + v2.decider.assume(sf(sorts.Snap, v2) === Unit, Option.when(withExp)(DebugExp.createInstance("Empty snapshot", true)), assumptionType) /* TODO: Avoid creating a fresh var (by invoking) `sf` that is not used * otherwise. In order words, only make this assumption if `sf` has * already been used, e.g. in a snapshot equality such as `s0 == (s1, s2)`. @@ -280,7 +280,7 @@ object producer extends ProductionRules { Q(s3, v3) }), (s2, v2) => { - v2.decider.assume(sf(sorts.Snap, v2) === Unit, Option.when(withExp)(DebugExp.createInstance("Empty snapshot", isInternal_ = true)), AssumptionType.Internal) + v2.decider.assume(sf(sorts.Snap, v2) === Unit, Option.when(withExp)(DebugExp.createInstance("Empty snapshot", isInternal_ = true)), assumptionType) /* TODO: Avoid creating a fresh var (by invoking) `sf` that is not used * otherwise. In order words, only make this assumption if `sf` has * already been used, e.g. in a snapshot equality such as `s0 == (s1, s2)`. @@ -571,7 +571,7 @@ object producer extends ProductionRules { /* Any regular expressions, i.e. boolean and arithmetic. */ case _ => v.decider.assume(sf(sorts.Snap, v) === Unit, - Option.when(withExp)(DebugExp.createInstance("Empty snapshot", true)), AssumptionType.Internal) /* TODO: See comment for case ast.Implies above */ + Option.when(withExp)(DebugExp.createInstance("Empty snapshot", true)), assumptionType) /* TODO: See comment for case ast.Implies above */ eval(s, a, pve, v)((s1, t, aNew, v1) => { v1.decider.assume(t, Option.when(withExp)(a), aNew, assumptionType) Q(s1, v1)}) diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 3c068052f..c2e2915cf 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -1042,7 +1042,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val comment = "Check receiver injectivity" v.decider.prover.comment(comment) v.decider.assume(FunctionPreconditionTransformer.transform(receiverInjectivityCheck, s.program), - Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), AssumptionType.Internal) + Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), assumptionType) v.decider.assert(receiverInjectivityCheck) { case true => val ax = inverseFunctions.axiomInversesOfInvertibles @@ -1299,7 +1299,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { qidPrefix = qid, program = s.program) v.decider.prover.comment("Check receiver injectivity") - v.decider.assume(FunctionPreconditionTransformer.transform(receiverInjectivityCheck, s.program), Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), AssumptionType.Internal) + v.decider.assume(FunctionPreconditionTransformer.transform(receiverInjectivityCheck, s.program), Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), assumptionType) v.decider.assert(receiverInjectivityCheck, assumptionType) { case true => val qvarsToInvOfLoc = inverseFunctions.qvarsToInversesOf(formalQVars) @@ -1314,8 +1314,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.prover.comment("Definitional axioms for inverse functions") v.decider.assume(inverseFunctions.definitionalAxioms.map(a => FunctionPreconditionTransformer.transform(a, s.program)), - Option.when(withExp)(DebugExp.createInstance("Inverse Function Axioms", isInternal_ = true)), enforceAssumption = false, assumptionType=AssumptionType.Internal) - v.decider.assume(inverseFunctions.definitionalAxioms, Option.when(withExp)(DebugExp.createInstance("Inverse function axiom", isInternal_ = true)), enforceAssumption = false, assumptionType=AssumptionType.Internal) + Option.when(withExp)(DebugExp.createInstance("Inverse Function Axioms", isInternal_ = true)), enforceAssumption = false, assumptionType=assumptionType) + v.decider.assume(inverseFunctions.definitionalAxioms, Option.when(withExp)(DebugExp.createInstance("Inverse function axiom", isInternal_ = true)), enforceAssumption = false, assumptionType=assumptionType) if (s.heapDependentTriggers.contains(resourceIdentifier)){ v.decider.assume( @@ -1323,7 +1323,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { formalQVars, Implies(condOfInvOfLoc, ResourceTriggerFunction(resource, smDef1.get.sm, formalQVars, s.program)), Trigger(inverseFunctions.inversesOf(formalQVars)))), - Option.when(withExp)(DebugExp.createInstance("Inverse Function", isInternal_ = true)), enforceAssumption = false, assumptionType=AssumptionType.Internal) + Option.when(withExp)(DebugExp.createInstance("Inverse Function", isInternal_ = true)), enforceAssumption = false, assumptionType=assumptionType) } @@ -1389,11 +1389,11 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { isExhale = true ) val debugExp = Option.when(withExp)(DebugExp.createInstance("Inverse functions for quantified permission", isInternal_ = true)) - v.decider.assume(FunctionPreconditionTransformer.transform(inverseFunctions.axiomInvertiblesOfInverses, s3.program), debugExp, AssumptionType.Internal) - v.decider.assume(inverseFunctions.axiomInvertiblesOfInverses, debugExp, AssumptionType.Internal) + v.decider.assume(FunctionPreconditionTransformer.transform(inverseFunctions.axiomInvertiblesOfInverses, s3.program), debugExp, assumptionType) + v.decider.assume(inverseFunctions.axiomInvertiblesOfInverses, debugExp, assumptionType) val substitutedAxiomInversesOfInvertibles = inverseFunctions.axiomInversesOfInvertibles.replace(formalQVars, tArgs) - v.decider.assume(FunctionPreconditionTransformer.transform(substitutedAxiomInversesOfInvertibles, s3.program), debugExp, AssumptionType.Internal) - v.decider.assume(substitutedAxiomInversesOfInvertibles, debugExp, AssumptionType.Internal) + v.decider.assume(FunctionPreconditionTransformer.transform(substitutedAxiomInversesOfInvertibles, s3.program), debugExp, assumptionType) + v.decider.assume(substitutedAxiomInversesOfInvertibles, debugExp, assumptionType) val h2 = Heap(remainingChunks ++ otherChunks) val s4 = s3.copy(smCache = smCache2, constrainableARPs = s.constrainableARPs) diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index 33afe52f8..21b5e90d7 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -168,6 +168,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver private def handleFunction(sInit: State, function: ast.Function): VerificationResult = { val data = functionData(function) + val assumptionType = if(function.body.isDefined) AssumptionType.Implicit else AssumptionType.Explicit val s = sInit.copy(functionRecorder = ActualFunctionRecorder(data), conservingSnapshotGeneration = true, assertReadAccessOnly = !Verifier.config.respectFunctionPrePermAmounts()) @@ -180,10 +181,10 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver result1 case (result1, phase1data) => - emitAndRecordFunctionAxioms(AssumptionType.Axiom, data.limitedAxiom) - emitAndRecordFunctionAxioms(AssumptionType.Axiom, data.triggerAxiom) - emitAndRecordFunctionAxioms(AssumptionType.Axiom, data.postAxiom: _*) // FIXME ake: if(function.body.isDefined) AssumptionType.Implicit else AssumptionType.Explicit - emitAndRecordFunctionAxioms(AssumptionType.Axiom, data.postPreconditionPropagationAxiom: _*) + emitAndRecordFunctionAxioms(assumptionType, data.limitedAxiom) + emitAndRecordFunctionAxioms(assumptionType, data.triggerAxiom) + emitAndRecordFunctionAxioms(assumptionType, data.postAxiom: _*) + emitAndRecordFunctionAxioms(assumptionType, data.postPreconditionPropagationAxiom: _*) this.postConditionAxioms = this.postConditionAxioms ++ data.postAxiom if (function.body.isEmpty) { @@ -196,8 +197,8 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver case fatalResult: FatalResult => data.verificationFailures = data.verificationFailures :+ fatalResult case _ => - emitAndRecordFunctionAxioms(AssumptionType.Axiom, data.definitionalAxiom.toSeq: _*) - emitAndRecordFunctionAxioms(AssumptionType.Axiom, data.bodyPreconditionPropagationAxiom: _*) + emitAndRecordFunctionAxioms(assumptionType, data.definitionalAxiom.toSeq: _*) + emitAndRecordFunctionAxioms(assumptionType, data.bodyPreconditionPropagationAxiom: _*) } result1 && result2 diff --git a/src/main/scala/verifier/VerificationPoolManager.scala b/src/main/scala/verifier/VerificationPoolManager.scala index 9b8b20d4c..9c3500351 100644 --- a/src/main/scala/verifier/VerificationPoolManager.scala +++ b/src/main/scala/verifier/VerificationPoolManager.scala @@ -32,7 +32,7 @@ class VerificationPoolManager(mainVerifier: MainVerifier) extends StatefulCompon def assume(term: Term): Unit = workerVerifiers foreach (_.decider.prover.assume(term)) def assume(term: Term, label: String): Unit = workerVerifiers foreach (_.decider.prover.assume(term, label)) override def assumeAxioms(terms: InsertionOrderedSet[Term], description: String): Unit = workerVerifiers foreach (_.decider.prover.assumeAxioms(terms, description)) - override def assumeAxiomsWithAnalysisInfo(axioms: InsertionOrderedSet[(Term, Option[AnalysisSourceInfo])], description: String, assumptionType: AssumptionType=AssumptionType.Axiom): Unit = workerVerifiers foreach (_.decider.prover.assumeAxiomsWithAnalysisInfo(axioms, description, assumptionType)) + override def assumeAxiomsWithAnalysisInfo(axioms: InsertionOrderedSet[(Term, Option[AnalysisSourceInfo])], description: String, assumptionType: AssumptionType=AssumptionType.Explicit): Unit = workerVerifiers foreach (_.decider.prover.assumeAxiomsWithAnalysisInfo(axioms, description, assumptionType)) def declare(decl: Decl): Unit = workerVerifiers foreach (_.decider.prover.declare(decl)) def comment(content: String): Unit = workerVerifiers foreach (_.decider.prover.comment(content)) From b97c7073ae8aed720fa30789ed21dec2b0ad17d5 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 25 Jul 2025 12:47:58 +0200 Subject: [PATCH 201/474] distinguish between verified and unverified methods/functions --- .../assumptionAnalysis/AnalysisInfo.scala | 2 +- .../AssumptionAnalysisInterpreter.scala | 19 ++++++----- .../scala/interfaces/decider/Prover.scala | 5 +-- src/main/scala/supporters/Domains.scala | 7 ++-- .../scala/supporters/MethodSupporter.scala | 3 +- .../supporters/functions/FunctionData.scala | 33 ++++++++++--------- .../functions/FunctionVerificationUnit.scala | 24 +++++++------- .../verifier/VerificationPoolManager.scala | 2 +- 8 files changed, 52 insertions(+), 43 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AnalysisInfo.scala b/src/main/scala/assumptionAnalysis/AnalysisInfo.scala index a5f900585..334175440 100644 --- a/src/main/scala/assumptionAnalysis/AnalysisInfo.scala +++ b/src/main/scala/assumptionAnalysis/AnalysisInfo.scala @@ -2,7 +2,7 @@ package viper.silicon.assumptionAnalysis object AssumptionType extends Enumeration { type AssumptionType = Value - val Explicit, LoopInvariant, PathCondition, Rewrite, Implicit, Internal, Trigger, Postcondition = Value + val Explicit, LoopInvariant, PathCondition, Rewrite, Implicit, Internal, Trigger, ExplicitPostcondition, ImplicitPostcondition = Value def fromString(s: String): Option[Value] = values.find(_.toString == s) } diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala index cb916426a..fe31bef69 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala @@ -1,12 +1,11 @@ package viper.silicon.assumptionAnalysis -import viper.silicon.state.terms.True import viper.silicon.verifier.Verifier import viper.silver.ast -import scala.collection.mutable - object AssumptionAnalysisInterpreter { + private val postconditionTypes = Set(AssumptionType.ExplicitPostcondition, AssumptionType.ImplicitPostcondition) + def joinGraphsAndGetInterpreter(name: Option[String], assumptionAnalysisInterpreters: Set[AssumptionAnalysisInterpreter]): AssumptionAnalysisInterpreter = { val newGraph = new AssumptionAnalysisGraph @@ -16,7 +15,7 @@ object AssumptionAnalysisInterpreter { val types = Set(AssumptionType.Implicit, AssumptionType.Explicit) val relevantAssumptionNodes = newGraph.nodes filter (node => node.isInstanceOf[GeneralAssumptionNode] && types.contains(node.assumptionType)) - newGraph.nodes filter (node => node.isInstanceOf[GeneralAssertionNode] && node.assumptionType.equals(AssumptionType.Postcondition)) foreach {node => // TODO ake: check if this also works for functions + newGraph.nodes filter (node => node.isInstanceOf[GeneralAssertionNode] && postconditionTypes.contains(node.assumptionType)) foreach { node => // TODO ake: check if this also works for functions val nodeSourceInfoString = node.sourceInfo.getTopLevelSource.toString val assumptionNodesForJoin = relevantAssumptionNodes filter (aNode => aNode.sourceInfo.getFineGrainedSource.toString.equals(nodeSourceInfoString)) newGraph.addEdges(node.id, assumptionNodesForJoin map (_.id)) @@ -27,6 +26,10 @@ object AssumptionAnalysisInterpreter { } class AssumptionAnalysisInterpreter(name: String, graph: ReadOnlyAssumptionAnalysisGraph, member: Option[ast.Member]=None) { + protected val explicitAssumptionTypes = Set(AssumptionType.Explicit, AssumptionType.ExplicitPostcondition) + protected val postconditionTypes = Set(AssumptionType.ExplicitPostcondition, AssumptionType.ImplicitPostcondition) + protected val explicitAssertionTypes = Set(AssumptionType.Explicit) ++ postconditionTypes + private def getGraph: ReadOnlyAssumptionAnalysisGraph = graph def getName: String = name def getMember: Option[ast.Member] = member @@ -54,11 +57,12 @@ class AssumptionAnalysisInterpreter(name: String, graph: ReadOnlyAssumptionAnaly def getNonInternalAssumptionNodes: Set[AssumptionAnalysisNode] = getNodes filter (node => (node.isInstanceOf[GeneralAssumptionNode] && !node.assumptionType.equals(AssumptionType.Internal)) || - (node.isInstanceOf[GeneralAssertionNode] && node.assumptionType.equals(AssumptionType.Postcondition)) + (node.isInstanceOf[GeneralAssertionNode] && postconditionTypes.contains(node.assumptionType)) ) def getExplicitAssumptionNodes: Set[AssumptionAnalysisNode] = getNodes filter (node => - node.assumptionType.equals(AssumptionType.Explicit) || node.assumptionType.equals(AssumptionType.Postcondition)) + explicitAssumptionTypes.contains(node.assumptionType) + ) private def getNonInternalAssumptionNodesPerSource: Map[String, Set[AssumptionAnalysisNode]] = getNonInternalAssumptionNodes.groupBy(_.sourceInfo.getTopLevelSource.toString) @@ -69,8 +73,7 @@ class AssumptionAnalysisInterpreter(name: String, graph: ReadOnlyAssumptionAnaly ) def getExplicitAssertionNodes: Set[AssumptionAnalysisNode] = - getNonInternalAssertionNodes.filter(node => - node.assumptionType.equals(AssumptionType.Postcondition) || node.assumptionType.equals(AssumptionType.Explicit)) + getNonInternalAssertionNodes.filter(node => explicitAssertionTypes.contains(node.assumptionType)) def exportGraph(): Unit = { diff --git a/src/main/scala/interfaces/decider/Prover.scala b/src/main/scala/interfaces/decider/Prover.scala index d6d5d693c..862963caf 100644 --- a/src/main/scala/interfaces/decider/Prover.scala +++ b/src/main/scala/interfaces/decider/Prover.scala @@ -39,13 +39,14 @@ trait ProverLike { terms foreach assume } - def assumeAxiomsWithAnalysisInfo(axioms: InsertionOrderedSet[(Term, Option[AnalysisSourceInfo])], description: String, assumptionType: AssumptionType=AssumptionType.Explicit): Unit = { + def assumeAxiomsWithAnalysisInfo(axioms: InsertionOrderedSet[(Term, Option[(AnalysisSourceInfo, AssumptionType)])], description: String): Unit = { if (debugMode) preambleAssumptions :+= new DebugAxiom(description, axioms.map(_._1)) if(Verifier.config.enableAssumptionAnalysis()){ axioms.foreach(axiom => { - val id = if(axiom._2.isDefined) preambleAssumptionAnalyzer.addAssumption(axiom._1, axiom._2.get, assumptionType) else None + val axiomInfo = axiom._2 + val id = if(axiomInfo.isDefined) preambleAssumptionAnalyzer.addAssumption(axiom._1, axiomInfo.get._1, axiomInfo.get._2) else None assume(axiom._1, AssumptionAnalyzer.createAxiomLabel(id)) }) } else{ diff --git a/src/main/scala/supporters/Domains.scala b/src/main/scala/supporters/Domains.scala index 05b5bf1a7..38320937a 100644 --- a/src/main/scala/supporters/Domains.scala +++ b/src/main/scala/supporters/Domains.scala @@ -6,7 +6,8 @@ package viper.silicon.supporters -import viper.silicon.assumptionAnalysis.{AnalysisSourceInfo, AssumptionAnalyzer, ExpAnalysisSourceInfo} +import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType +import viper.silicon.assumptionAnalysis.{AnalysisSourceInfo, AssumptionAnalyzer, AssumptionType, ExpAnalysisSourceInfo} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.common.collections.immutable.MultiMap._ import viper.silicon.interfaces.PreambleContributor @@ -28,7 +29,7 @@ class DefaultDomainsContributor(symbolConverter: SymbolConverter, private var collectedSorts = InsertionOrderedSet[Sort]() private var collectedFunctions = InsertionOrderedSet[terms.DomainFun]() - private var collectedAxioms = InsertionOrderedSet[(Term, Option[AnalysisSourceInfo])]() + private var collectedAxioms = InsertionOrderedSet[(Term, Option[(AnalysisSourceInfo, AssumptionType)])]() private var uniqueSymbols = MultiMap.empty[Sort, DomainFun] /* Lifetime */ @@ -105,7 +106,7 @@ class DefaultDomainsContributor(symbolConverter: SymbolConverter, val tAx = domainTranslator.translateAxiom(axiom, symbolConverter.toSort) val tAxPres = FunctionPreconditionTransformer.transform(tAx, program) val enableAnalysis = AssumptionAnalyzer.extractEnableAnalysisFromInfo(axiom.info).getOrElse(isAnalysisForDomainEnabled) - collectedAxioms = collectedAxioms.incl(terms.And(tAxPres, tAx), Option.when(enableAnalysis)(ExpAnalysisSourceInfo(axiom.exp))) + collectedAxioms = collectedAxioms.incl((terms.And(tAxPres, tAx), Option.when(enableAnalysis)((ExpAnalysisSourceInfo(axiom.exp), AssumptionType.Explicit)))) }) }) } diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index 6a9600079..7e9cb5bd2 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -94,6 +94,7 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif } val annotatedAssumptionTypeOpt = AssumptionAnalyzer.extractAssumptionTypeFromInfo(method.info) + val postConditionType = annotatedAssumptionTypeOpt.getOrElse(if(method.body.isDefined) AssumptionType.ImplicitPostcondition else AssumptionType.ExplicitPostcondition) errorsReportedSoFar.set(0) val result = @@ -114,7 +115,7 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif && { executionFlowController.locally(s2a, v2)((s3, v3) => { exec(s3, body, v3)((s4, v4) => - consumes(s4, posts, false, postViolated, v4, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Postcondition))((_, _, _) => + consumes(s4, posts, false, postViolated, v4, postConditionType)((_, _, _) => Success()))}) } )})}) diff --git a/src/main/scala/supporters/functions/FunctionData.scala b/src/main/scala/supporters/functions/FunctionData.scala index ffd49b9a4..ac32b9fae 100644 --- a/src/main/scala/supporters/functions/FunctionData.scala +++ b/src/main/scala/supporters/functions/FunctionData.scala @@ -7,7 +7,8 @@ package viper.silicon.supporters.functions import com.typesafe.scalalogging.LazyLogging -import viper.silicon.assumptionAnalysis.{AnalysisSourceInfo, AssumptionAnalyzer, ExpAnalysisSourceInfo, StringAnalysisSourceInfo} +import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType +import viper.silicon.assumptionAnalysis.{AnalysisSourceInfo, AssumptionAnalyzer, AssumptionType, ExpAnalysisSourceInfo, StringAnalysisSourceInfo} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.interfaces.FatalResult import viper.silicon.rules.{InverseFunctions, SnapshotMapDefinition, functionSupporter} @@ -85,15 +86,15 @@ class FunctionData(val programFunction: ast.Function, val triggerFunctionApplication = App(statelessFunction, formalArgs.values.toSeq) val preconditionFunctionApplication = App(preconditionFunction, `?s` +: formalArgs.values.toSeq) - val limitedAxiom: (Quantification, Option[StringAnalysisSourceInfo]) = + val limitedAxiom: (Quantification, Option[(StringAnalysisSourceInfo, AssumptionType)]) = (Forall(arguments, BuiltinEquals(limitedFunctionApplication, functionApplication), Trigger(functionApplication)), - Option.when(isAnalysisEnabled)(StringAnalysisSourceInfo("Limited Axiom", programFunction.pos))) + Option.when(isAnalysisEnabled)((StringAnalysisSourceInfo("Limited Axiom", programFunction.pos), AssumptionType.Internal))) - val triggerAxiom: (Quantification, Option[StringAnalysisSourceInfo]) = + val triggerAxiom: (Quantification, Option[(StringAnalysisSourceInfo, AssumptionType)]) = (Forall(arguments, triggerFunctionApplication, Trigger(limitedFunctionApplication)), - Option.when(isAnalysisEnabled)(StringAnalysisSourceInfo("Trigger Axiom", programFunction.pos))) + Option.when(isAnalysisEnabled)((StringAnalysisSourceInfo("Trigger Axiom", programFunction.pos), AssumptionType.Trigger))) /* * Data collected during phases 1 (well-definedness checking) and 2 (verification) @@ -206,7 +207,7 @@ class FunctionData(val programFunction: ast.Function, } } - lazy val postAxiom: Seq[(Term, Option[AnalysisSourceInfo])] = { + lazy val postAxiom: Seq[(Term, Option[(AnalysisSourceInfo, AssumptionType)])] = { assert(phase == 1, s"Postcondition axiom must be generated in phase 1, current phase is $phase") if (programFunction.posts.nonEmpty) { @@ -216,14 +217,15 @@ class FunctionData(val programFunction: ast.Function, def wrapBody(body: Term): Term = Let(toMap(bodyBindings), body) if(Verifier.config.enableAssumptionAnalysis()){ - (Forall(arguments, wrapBody(And(generateNestedDefinitionalAxioms)), Trigger(limitedFunctionApplication)), Option.empty[AnalysisSourceInfo]) +: + val assumptionType = if(programFunction.body.isDefined) AssumptionType.ImplicitPostcondition else AssumptionType.ExplicitPostcondition + (Forall(arguments, wrapBody(And(generateNestedDefinitionalAxioms)), Trigger(limitedFunctionApplication)), Option.empty[(AnalysisSourceInfo, AssumptionType)]) +: programFunction.posts.flatMap(_.topLevelConjuncts).map({p => val terms = expressionTranslator.translatePostcondition(program, Seq(p), this) - (And(Forall(arguments, wrapBody(Implies(pre, And(terms))), Trigger(limitedFunctionApplication)), True), Some(ExpAnalysisSourceInfo(p))) + (And(Forall(arguments, wrapBody(Implies(pre, And(terms))), Trigger(limitedFunctionApplication)), True), Some((ExpAnalysisSourceInfo(p), assumptionType))) }) }else{ val innermostBody = And(generateNestedDefinitionalAxioms ++ List(Implies(pre, And(translatedPosts)))) - Seq((Forall(arguments, wrapBody(innermostBody), Trigger(limitedFunctionApplication)), Option.empty[AnalysisSourceInfo])) + Seq((Forall(arguments, wrapBody(innermostBody), Trigger(limitedFunctionApplication)), None)) } } else Seq.empty @@ -280,7 +282,7 @@ class FunctionData(val programFunction: ast.Function, expressionTranslator.translate(program, programFunction, this) } - lazy val definitionalAxiom: Option[(Term, Option[AnalysisSourceInfo])] = { + lazy val definitionalAxiom: Option[(Term, Option[(AnalysisSourceInfo, AssumptionType)])] = { assert(phase == 2, s"Definitional axiom must be generated in phase 2, current phase is $phase") optBody.map(translatedBody => { @@ -295,27 +297,28 @@ class FunctionData(val programFunction: ast.Function, val allTriggers = ( Seq(Trigger(functionApplication)) ++ actualPredicateTriggers) - (Forall(arguments, body, allTriggers), Option.when(isAnalysisEnabled)(StringAnalysisSourceInfo("definitionalAxiom", programFunction.pos))) + (Forall(arguments, body, allTriggers), Option.when(isAnalysisEnabled)((ExpAnalysisSourceInfo(programFunction.body.get), AssumptionType.Implicit))) }) } - lazy val bodyPreconditionPropagationAxiom: Seq[(Term, Option[AnalysisSourceInfo])] = { + lazy val bodyPreconditionPropagationAxiom: Seq[(Term, Option[(AnalysisSourceInfo, AssumptionType)])] = { val pre = preconditionFunctionApplication val bodyPreconditions = if (programFunction.body.isDefined) optBody.map(translatedBody => { val body = Implies(pre, FunctionPreconditionTransformer.transform(translatedBody, program)) (Forall(arguments, body, Seq(Trigger(functionApplication))), - Option.when(isAnalysisEnabled)(StringAnalysisSourceInfo("bodyPreconditionPropagationAxiom", programFunction.pos))) + Option.when(isAnalysisEnabled)((StringAnalysisSourceInfo("bodyPreconditionPropagationAxiom", programFunction.pos), AssumptionType.Implicit))) }) else None bodyPreconditions.toSeq } - lazy val postPreconditionPropagationAxiom: Seq[(Term, Option[AnalysisSourceInfo])] = { + lazy val postPreconditionPropagationAxiom: Seq[(Term, Option[(AnalysisSourceInfo, AssumptionType)])] = { val pre = preconditionFunctionApplication val postPreconditions = if (programFunction.posts.nonEmpty) { + val assumptionType = if(programFunction.body.isDefined) AssumptionType.ImplicitPostcondition else AssumptionType.ExplicitPostcondition val bodyBindings: Map[Var, Term] = Map(formalResult -> limitedFunctionApplication) val bodies = translatedPosts.map(tPost => Let(bodyBindings, Implies(pre, FunctionPreconditionTransformer.transform(tPost, program)))) bodies.map(b => (Forall(arguments, b, Seq(Trigger(limitedFunctionApplication))), - Option.when(isAnalysisEnabled)(StringAnalysisSourceInfo("postPreconditionPropagationAxiom", programFunction.pos)))) + Option.when(isAnalysisEnabled)((StringAnalysisSourceInfo("postPreconditionPropagationAxiom", programFunction.pos), assumptionType)))) } else Seq() postPreconditions } diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index 21b5e90d7..edbb79c0a 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -53,9 +53,9 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver @unused private var program: ast.Program = _ /*private*/ var functionData: Map[ast.Function, FunctionData] = Map.empty - private var emittedFunctionAxioms: Vector[(Term, Option[AnalysisSourceInfo])] = Vector.empty + private var emittedFunctionAxioms: Vector[(Term, Option[(AnalysisSourceInfo, AssumptionType)])] = Vector.empty private var freshVars: Vector[Var] = Vector.empty - private var postConditionAxioms: Vector[(Term, Option[AnalysisSourceInfo])] = Vector.empty + private var postConditionAxioms: Vector[(Term, Option[(AnalysisSourceInfo, AssumptionType)])] = Vector.empty private val expressionTranslator = { def resolutionFailureMessage(exp: ast.Positioned, data: FunctionData): String = ( @@ -168,7 +168,6 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver private def handleFunction(sInit: State, function: ast.Function): VerificationResult = { val data = functionData(function) - val assumptionType = if(function.body.isDefined) AssumptionType.Implicit else AssumptionType.Explicit val s = sInit.copy(functionRecorder = ActualFunctionRecorder(data), conservingSnapshotGeneration = true, assertReadAccessOnly = !Verifier.config.respectFunctionPrePermAmounts()) @@ -181,10 +180,10 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver result1 case (result1, phase1data) => - emitAndRecordFunctionAxioms(assumptionType, data.limitedAxiom) - emitAndRecordFunctionAxioms(assumptionType, data.triggerAxiom) - emitAndRecordFunctionAxioms(assumptionType, data.postAxiom: _*) - emitAndRecordFunctionAxioms(assumptionType, data.postPreconditionPropagationAxiom: _*) + emitAndRecordFunctionAxioms(data.limitedAxiom) + emitAndRecordFunctionAxioms(data.triggerAxiom) + emitAndRecordFunctionAxioms(data.postAxiom: _*) + emitAndRecordFunctionAxioms(data.postPreconditionPropagationAxiom: _*) this.postConditionAxioms = this.postConditionAxioms ++ data.postAxiom if (function.body.isEmpty) { @@ -197,8 +196,8 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver case fatalResult: FatalResult => data.verificationFailures = data.verificationFailures :+ fatalResult case _ => - emitAndRecordFunctionAxioms(assumptionType, data.definitionalAxiom.toSeq: _*) - emitAndRecordFunctionAxioms(assumptionType, data.bodyPreconditionPropagationAxiom: _*) + emitAndRecordFunctionAxioms(data.definitionalAxiom.toSeq: _*) + emitAndRecordFunctionAxioms(data.bodyPreconditionPropagationAxiom: _*) } result1 && result2 @@ -258,6 +257,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver var recorders: Seq[FunctionRecorder] = Vector.empty val wExp = evaluator.withExp val annotatedAssumptionTypeOpt = AssumptionAnalyzer.extractAssumptionTypeFromInfo(function.info) + val postConditionType = annotatedAssumptionTypeOpt.getOrElse(if(function.body.isDefined) AssumptionType.ImplicitPostcondition else AssumptionType.ExplicitPostcondition) decider.assumptionAnalyzer.addNodes(v.decider.prover.getPreambleAnalysisNodes) val result = phase1data.foldLeft(Success(): VerificationResult) { @@ -278,7 +278,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver decider.analysisSourceInfoStack.setForcedSource(ExpAnalysisSourceInfo(body)) decider.assume(BuiltinEquals(data.formalResult, tBody), debugExp, AssumptionType.Implicit) decider.analysisSourceInfoStack.removeForcedSource() - consumes(s2, posts, false, postconditionViolated, v, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Postcondition))((s3, _, _) => { + consumes(s2, posts, false, postconditionViolated, v, postConditionType)((s3, _, _) => { recorders :+= s3.functionRecorder Success()})})})} @@ -287,8 +287,8 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver result } - private def emitAndRecordFunctionAxioms(assumptionType: AssumptionType, axiom: (Term, Option[AnalysisSourceInfo])*): Unit = { - decider.prover.assumeAxiomsWithAnalysisInfo(InsertionOrderedSet(axiom), "Function axioms", assumptionType) + private def emitAndRecordFunctionAxioms(axiom: (Term, Option[(AnalysisSourceInfo, AssumptionType)])*): Unit = { + decider.prover.assumeAxiomsWithAnalysisInfo(InsertionOrderedSet(axiom), "Function axioms") emittedFunctionAxioms = emittedFunctionAxioms ++ axiom // FIXME ake: propagate assumption type } diff --git a/src/main/scala/verifier/VerificationPoolManager.scala b/src/main/scala/verifier/VerificationPoolManager.scala index 9c3500351..d25d12ae9 100644 --- a/src/main/scala/verifier/VerificationPoolManager.scala +++ b/src/main/scala/verifier/VerificationPoolManager.scala @@ -32,7 +32,7 @@ class VerificationPoolManager(mainVerifier: MainVerifier) extends StatefulCompon def assume(term: Term): Unit = workerVerifiers foreach (_.decider.prover.assume(term)) def assume(term: Term, label: String): Unit = workerVerifiers foreach (_.decider.prover.assume(term, label)) override def assumeAxioms(terms: InsertionOrderedSet[Term], description: String): Unit = workerVerifiers foreach (_.decider.prover.assumeAxioms(terms, description)) - override def assumeAxiomsWithAnalysisInfo(axioms: InsertionOrderedSet[(Term, Option[AnalysisSourceInfo])], description: String, assumptionType: AssumptionType=AssumptionType.Explicit): Unit = workerVerifiers foreach (_.decider.prover.assumeAxiomsWithAnalysisInfo(axioms, description, assumptionType)) + override def assumeAxiomsWithAnalysisInfo(axioms: InsertionOrderedSet[(Term, Option[(AnalysisSourceInfo, AssumptionType)])], description: String): Unit = workerVerifiers foreach (_.decider.prover.assumeAxiomsWithAnalysisInfo(axioms, description)) def declare(decl: Decl): Unit = workerVerifiers foreach (_.decider.prover.declare(decl)) def comment(content: String): Unit = workerVerifiers foreach (_.decider.prover.comment(content)) From ea774667af7df0745c41a61ed02bf9f4063b6ef1 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 25 Jul 2025 13:17:34 +0200 Subject: [PATCH 202/474] update tests --- .../unitTests/domain.vpr | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 src/test/resources/dependencyAnalysisTests/unitTests/domain.vpr diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/domain.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/domain.vpr new file mode 100644 index 000000000..ec1674987 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/unitTests/domain.vpr @@ -0,0 +1,61 @@ +field val: Int +define access(a) + (forall j: Int :: 0 <= j && j < len(a) ==> acc(slot(a,j).val)) + + +domain IArray { + function slot(a: IArray, i: Int): Ref + function len(a: IArray): Int + function first(r: Ref): IArray + function second(r: Ref): Int + + + axiom all_diff { + forall a: IArray, i: Int :: { slot(a,i) } + first(slot(a,i)) == a && second(slot(a,i)) == i + } + + axiom len_nonneg { + forall a: IArray :: { len(a) } + len(a) >= 0 + } +} + +method domain1() +{ + // Create an integer array with three elements + var a: IArray + @irrelevant("Explicit") + inhale len(a) == 3 + + // $PrecisionTest: $ACC_INVARIANT=access(a) $INVARIANT=len(a)==3 + + @testAssertion("Implicit") + inhale access(a) // access to all array slots +} + +method domain2() +{ + // Create an integer array with three elements + var a: IArray + @irrelevant("Explicit") + inhale len(a) == 3 + @dependency("Explicit") + inhale access(a) // access to all array slots + + // Initialize the elements of an array + var i: Int + @dependency("Implicit") + i := 0 + while (@dependency("PathCondition")(i < len(a))) + invariant @dependency("LoopInvariant")(access(a)) + invariant @dependency("LoopInvariant")(0 <= i) + invariant @irrelevant("LoopInvariant")(i <= len(a)) + { + // $PrecisionTest: $READ_ONLY=i $INVARIANT=i<=len(a) $ACC_INVARIANT=access(a) + @testAssertion("Implicit") + slot(a,i).val := -i // models a[i] := -i + @dependency("Implicit") + i := i + 1 + } +} \ No newline at end of file From d01e5467abd10dec2e176b9c1f82afe78d957448 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 25 Jul 2025 14:33:33 +0200 Subject: [PATCH 203/474] add possibility to exclude infeasibility node dependencies --- .../AssumptionAnalysisGraph.scala | 7 ++++--- .../AssumptionAnalysisInterpreter.scala | 14 +++++++------- .../AssumptionAnalysisUserTool.scala | 2 ++ .../assumptionAnalysis/AssumptionAnalyzer.scala | 4 ++-- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index dce5a91ba..9167ee50b 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -19,7 +19,7 @@ trait ReadOnlyAssumptionAnalysisGraph { def getTransitiveEdges: Map[Int, Set[Int]] def getAllEdges: Map[Int, Set[Int]] - def existsAnyDependency(sources: Set[Int], targets: Set[Int]): Boolean + def existsAnyDependency(sources: Set[Int], targets: Set[Int], includeInfeasibilityNodes: Boolean): Boolean def exportGraph(dirName: String): Unit } @@ -64,13 +64,14 @@ class AssumptionAnalysisGraph extends ReadOnlyAssumptionAnalysisGraph { sources foreach (addEdges(_, targets)) } - def existsAnyDependency(sources: Set[Int], targets: Set[Int]): Boolean = { + def existsAnyDependency(sources: Set[Int], targets: Set[Int], includeInfeasibilityNodes: Boolean): Boolean = { + val infeasibilityNodeIds: Set[Int] = if(includeInfeasibilityNodes) Set.empty else (getNodes filter (_.isInstanceOf[InfeasibilityNode]) map (_.id)).toSet var visited: Set[Int] = sources var queue: List[Int] = sources.toList val allEdges = getAllEdges while(queue.nonEmpty){ val curr = queue.head - val newVisits = allEdges.getOrElse(curr, Set()) + val newVisits = allEdges.getOrElse(curr, Set()).diff(infeasibilityNodeIds) if(newVisits.intersect(targets).nonEmpty) return true visited = visited ++ Set(curr) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala index fe31bef69..336c6da87 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala @@ -44,15 +44,15 @@ class AssumptionAnalysisInterpreter(name: String, graph: ReadOnlyAssumptionAnaly def getDirectDependencies(nodeIdsToAnalyze: Set[Int]): Set[AssumptionAnalysisNode] = getNonInternalAssumptionNodes.filter(node => graph.getDirectEdges.get(node.id).exists(_.intersect(nodeIdsToAnalyze).nonEmpty)) - def getAllNonInternalDependencies(nodeIdsToAnalyze: Set[Int]): Set[AssumptionAnalysisNode] = - getNonInternalAssumptionNodes.filter(node => graph.existsAnyDependency(Set(node.id), nodeIdsToAnalyze)) + def getAllNonInternalDependencies(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[AssumptionAnalysisNode] = + getNonInternalAssumptionNodes.filter(node => graph.existsAnyDependency(Set(node.id), nodeIdsToAnalyze, includeInfeasibilityNodes)) - def getAllExplicitDependencies(nodeIdsToAnalyze: Set[Int]): Set[AssumptionAnalysisNode] = - getExplicitAssumptionNodes.filter(node => graph.existsAnyDependency(Set(node.id), nodeIdsToAnalyze)) + def getAllExplicitDependencies(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[AssumptionAnalysisNode] = + getExplicitAssumptionNodes.filter(node => graph.existsAnyDependency(Set(node.id), nodeIdsToAnalyze, includeInfeasibilityNodes)) - def getAllNonInternalDependents(nodeIdsToAnalyze: Set[Int]): Set[AssumptionAnalysisNode] = - getNonInternalAssertionNodes.filter(node => graph.existsAnyDependency(nodeIdsToAnalyze, Set(node.id))) + def getAllNonInternalDependents(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[AssumptionAnalysisNode] = + getNonInternalAssertionNodes.filter(node => graph.existsAnyDependency(nodeIdsToAnalyze, Set(node.id), includeInfeasibilityNodes)) def getNonInternalAssumptionNodes: Set[AssumptionAnalysisNode] = getNodes filter (node => @@ -95,7 +95,7 @@ class AssumptionAnalysisInterpreter(name: String, graph: ReadOnlyAssumptionAnaly val nodeIds = nodes map (_.id) // it is not an explicit assertion itself and has no dependency to an explicit assertion nodeIds.intersect(assertionNodeIds).isEmpty && - !graph.existsAnyDependency(nodeIds, assertionNodeIds) + !graph.existsAnyDependency(nodeIds, assertionNodeIds, includeInfeasibilityNodes=true) }).keys.toSeq val proofCoverage = 1.0 - (uncoveredSources.size.toDouble / nodesPerSourceInfo.size.toDouble) (proofCoverage, uncoveredSources) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala index ccc8eb05c..c0c62a8a8 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala @@ -80,6 +80,7 @@ class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpr }) val directDependencies = getSourceInfoString(fullGraphInterpreter.getDirectDependencies(queriedNodes.map(_.id))) val allDependencies = getSourceInfoString(fullGraphInterpreter.getAllNonInternalDependencies(queriedNodes.map(_.id))) + val allDependenciesWithoutInfeasibility = getSourceInfoString(fullGraphInterpreter.getAllNonInternalDependencies(queriedNodes.map(_.id), includeInfeasibilityNodes=false)) val explicitDependencies = getSourceInfoString(fullGraphInterpreter.getAllExplicitDependencies(queriedNodes.map(_.id))) val dependents = getSourceInfoString(fullGraphInterpreter.getAllNonInternalDependents(queriedNodes.map(_.id))) @@ -87,6 +88,7 @@ class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpr println(s"\nDirect Dependencies:\n\t$directDependencies") println(s"\nAll Dependencies:\n\t$allDependencies") + println(s"\nDependencies without infeasibility:\n\t$allDependenciesWithoutInfeasibility") println(s"\nExplicit Dependencies:\n\t$explicitDependencies") println(s"\nAll Dependents:\n\t$dependents") diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index ac940a636..dc0aefbc1 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -232,12 +232,12 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { override def buildFinalGraph(): Option[AssumptionAnalysisGraph] = { assumptionGraph.removeLabelNodes() - val mergedGraph = mergeNodesAndGetNewGraph() + val mergedGraph = buildAndGetMergedGraph() mergedGraph.addTransitiveEdges() Some(mergedGraph) } - private def mergeNodesAndGetNewGraph(): AssumptionAnalysisGraph = { + private def buildAndGetMergedGraph(): AssumptionAnalysisGraph = { def keepNode(n: AssumptionAnalysisNode): Boolean = n.isClosed || n.isInstanceOf[InfeasibilityNode] val mergedGraph = new AssumptionAnalysisGraph From 421473121a2ba788947560900a7c8d24e8157f9c Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 25 Jul 2025 14:46:34 +0200 Subject: [PATCH 204/474] add pruning rule for functions --- src/test/scala/AssumptionAnalysisTests.scala | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index fd6916122..fa046a5be 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -176,7 +176,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { } val assumptionAnalysisInterpreters = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].assumptionAnalysisInterpretersPerMember val fullGraphInterpreter = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].joinedAssumptionAnalysisInterpreter - + writer.println(s"$filePrefix - $fileName") new PrecisionBenchmarkSoundnessTest(filePrefix + "/" + fileName, program, fullGraphInterpreter.get, writer).execute() new AnnotatedPrecisionBenchmark(program, assumptionAnalysisInterpreters, writer).execute() @@ -279,7 +279,11 @@ class AssumptionAnalysisTests extends AnyFunSuite { crucialNodeSourceInfos exists (n => n.getPositionString.equals(AnalysisSourceInfo.extractPositionString(a.exp.pos)) || n.getPositionString.equals(AnalysisSourceInfo.extractPositionString(a.pos)))) ast.Domain(name, functions, newAxioms, typVars, interpretations)(domain.pos, domain.info, domain.errT) - + case function@ast.Function(name, formalArgs, typ, pres, posts, body) => + val newPres = pres filter (isCrucialExp(_, crucialNodeSourceInfos)) + val newPosts = posts filter (isCrucialExp(_, crucialNodeSourceInfos)) + val newBody = body filter (isCrucialExp(_, crucialNodeSourceInfos)) + ast.Function(name, formalArgs, typ, newPres, newPosts, newBody)(function.pos, function.info, function.errT) case meth@ast.Method(name, inVars, outVars, pres, posts, body) => val newPres = pres filter (isCrucialExp(_, crucialNodeSourceInfos)) val newPosts = posts filter (isCrucialExp(_, crucialNodeSourceInfos)) From 3ba0e78f0b14d402a49d48923486b85e4ea24e1f Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 25 Jul 2025 14:46:44 +0200 Subject: [PATCH 205/474] add pruning rule for functions --- .../all/function-sum.vpr | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 src/test/resources/dependencyAnalysisTests/all/function-sum.vpr diff --git a/src/test/resources/dependencyAnalysisTests/all/function-sum.vpr b/src/test/resources/dependencyAnalysisTests/all/function-sum.vpr new file mode 100644 index 000000000..fb4ae0f9d --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/all/function-sum.vpr @@ -0,0 +1,47 @@ +field f: Int + +function sum(x: Int, y: Int) : Int + requires x >= 0 && y >= 0 + ensures result == x + y + ensures result >= 0 +{ + x + y +} + +function sumUnverified(x: Int, y: Int): Int + requires x >= 0 && y >= 0 + ensures result == x + y + ensures result >= 0 + +method call2(){ + var x: Int, y: Int, z: Int + @dependency("Explicit") + assume x > 10 + @dependency("Explicit") + assume y > 0 + + @dependency("Implicit") + z := sum(x, y) + + // $PrecisionTest: $READ_ONLY=x,y,z $INVARIANT=x>0 + + @testAssertion("Explicit") + assert z == x + y +} + +method call2Unv(){ + var x: Int, y: Int, z: Int + @dependency("Explicit") + assume x > 10 + @dependency("Explicit") + assume y > 0 + + @dependency("Implicit") + z := sumUnverified(x, y) + + // $PrecisionTest: $READ_ONLY=x,y,z $INVARIANT=x>0 + + @testAssertion("Explicit") + assert z == x + y +} + From ace6360ff6cd96d537e4ee0a4d22e9a1ed0049fd Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 25 Jul 2025 17:12:01 +0200 Subject: [PATCH 206/474] handle functions without body correctly --- .../AssumptionAnalysisInterpreter.scala | 14 ++++++--- .../AssumptionAnalysisNode.scala | 4 +++ .../AssumptionAnalyzer.scala | 30 ++++++++++++++++++- .../scala/interfaces/decider/Prover.scala | 2 +- .../scala/supporters/MethodSupporter.scala | 3 ++ .../functions/FunctionVerificationUnit.scala | 8 +++-- 6 files changed, 53 insertions(+), 8 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala index 336c6da87..4fe89146a 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala @@ -12,10 +12,15 @@ object AssumptionAnalysisInterpreter { assumptionAnalysisInterpreters foreach (interpreter => newGraph.addNodes(interpreter.getGraph.getNodes)) assumptionAnalysisInterpreters foreach (interpreter => interpreter.getGraph.getAllEdges foreach {case (s, t) => newGraph.addEdges(s, t)}) + // add edges between identical axioms since they were added to each interpreter // TODO ake: merge instead? + newGraph.getNodes.filter(_.isInstanceOf[AxiomAssumptionNode]).groupBy(n => (n.sourceInfo.toString, n.assumptionType)).foreach{case (_, nodes) => + newGraph.addEdges(nodes.map(_.id), nodes.map(_.id)) + } + val types = Set(AssumptionType.Implicit, AssumptionType.Explicit) val relevantAssumptionNodes = newGraph.nodes filter (node => node.isInstanceOf[GeneralAssumptionNode] && types.contains(node.assumptionType)) - newGraph.nodes filter (node => node.isInstanceOf[GeneralAssertionNode] && postconditionTypes.contains(node.assumptionType)) foreach { node => // TODO ake: check if this also works for functions + newGraph.nodes filter (node => postconditionTypes.contains(node.assumptionType)) foreach { node => val nodeSourceInfoString = node.sourceInfo.getTopLevelSource.toString val assumptionNodesForJoin = relevantAssumptionNodes filter (aNode => aNode.sourceInfo.getFineGrainedSource.toString.equals(nodeSourceInfoString)) newGraph.addEdges(node.id, assumptionNodesForJoin map (_.id)) @@ -57,10 +62,10 @@ class AssumptionAnalysisInterpreter(name: String, graph: ReadOnlyAssumptionAnaly def getNonInternalAssumptionNodes: Set[AssumptionAnalysisNode] = getNodes filter (node => (node.isInstanceOf[GeneralAssumptionNode] && !node.assumptionType.equals(AssumptionType.Internal)) || - (node.isInstanceOf[GeneralAssertionNode] && postconditionTypes.contains(node.assumptionType)) + postconditionTypes.contains(node.assumptionType) ) - def getExplicitAssumptionNodes: Set[AssumptionAnalysisNode] = getNodes filter (node => + def getExplicitAssumptionNodes: Set[AssumptionAnalysisNode] = getNonInternalAssumptionNodes filter (node => explicitAssumptionTypes.contains(node.assumptionType) ) @@ -69,7 +74,8 @@ class AssumptionAnalysisInterpreter(name: String, graph: ReadOnlyAssumptionAnaly def getNonInternalAssertionNodes: Set[AssumptionAnalysisNode] = getNodes filter (node => - node.isInstanceOf[GeneralAssertionNode] && !node.assumptionType.equals(AssumptionType.Internal) + (node.isInstanceOf[GeneralAssertionNode] && !node.assumptionType.equals(AssumptionType.Internal)) || + postconditionTypes.contains(node.assumptionType) ) def getExplicitAssertionNodes: Set[AssumptionAnalysisNode] = diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisNode.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisNode.scala index 65fa05daa..a7dc52489 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisNode.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisNode.scala @@ -37,6 +37,10 @@ case class SimpleAssumptionNode(term: Term, description: Option[String], sourceI override def getNodeString: String = "assume " + term.toString + description.map(" (" + _ + ")").getOrElse("") } +case class AxiomAssumptionNode(term: Term, description: Option[String], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean) extends GeneralAssumptionNode { + override def getNodeString: String = "assume axiom " + term.toString + description.map(" (" + _ + ")").getOrElse("") +} + case class SimpleAssertionNode(term: Term, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo, isClosed: Boolean) extends GeneralAssertionNode { override def getNodeString: String = "assert " + term.toString } diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index dc0aefbc1..673a9bf0e 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -28,6 +28,7 @@ trait AssumptionAnalyzer { def addNodes(nodes: Iterable[AssumptionAnalysisNode]): Unit def addNode(node: AssumptionAnalysisNode): Unit def addAssumption(assumption: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, description: Option[String] = None): Option[Int] + def addAxiom(assumption: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, description: Option[String] = None): Option[Int] def registerInhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, labelNode: Option[LabelNode], analysisInfo: AnalysisInfo, isExhale: Boolean): CH = buildChunk(perm) def registerExhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, analysisInfo: AnalysisInfo): CH = buildChunk(perm) def createLabelNode(labelTerm: Term, sourceChunks: Iterable[Chunk], sourceTerms: Iterable[Term]): Option[LabelNode] @@ -40,6 +41,8 @@ trait AssumptionAnalyzer { def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit def addPermissionDependencies(sourceChunks: Set[Chunk], sourceTerms: Set[Term], targetChunk: Chunk): Unit def addCustomTransitiveDependency(sourceSourceInfo: AnalysisSourceInfo, targetSourceInfo: AnalysisSourceInfo): Unit + def addCustomExpDependency(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp]): Unit + def addFunctionAxiomEdges(): Unit def buildFinalGraph(): Option[AssumptionAnalysisGraph] } @@ -135,6 +138,12 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { Some(node.id) } + override def addAxiom(assumption: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, description: Option[String]): Option[Int] = { + val node = AxiomAssumptionNode(assumption, description, analysisSourceInfo, assumptionType, isClosed_) + addNode(node) + Some(node.id) + } + override def registerExhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, analysisInfo: AnalysisInfo): CH = { val chunk = buildChunk(perm) val chunkNode = addPermissionExhaleNode(chunk, chunk.perm, analysisInfo.sourceInfo, analysisInfo.assumptionType) @@ -230,6 +239,12 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { assumptionGraph.addEdges(sourceNodes map (_.id), targetNodes map (_.id)) } + def addCustomExpDependency(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp]): Unit = { + val sourceNodeIds = sourceExps.flatMap(e => addAssumption(True, ExpAnalysisSourceInfo(e), AssumptionType.Explicit, None)) + val targetNodes = targetExps.flatMap(e => addAssumption(True, ExpAnalysisSourceInfo(e), AssumptionType.ExplicitPostcondition, None)) + assumptionGraph.addEdges(sourceNodeIds, targetNodes) + } + override def buildFinalGraph(): Option[AssumptionAnalysisGraph] = { assumptionGraph.removeLabelNodes() val mergedGraph = buildAndGetMergedGraph() @@ -237,8 +252,17 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { Some(mergedGraph) } + override def addFunctionAxiomEdges(): Unit = { + val axiomNodes = getNodes.filter(_.isInstanceOf[AxiomAssumptionNode]) + val postcondNodes = getNodes.filter(n => n.assumptionType.equals(AssumptionType.ExplicitPostcondition) || n.assumptionType.equals(AssumptionType.ImplicitPostcondition)) + axiomNodes foreach {aNode => + val pNodes = postcondNodes filter (_.sourceInfo.toString.equals(aNode.sourceInfo.toString)) map (_.id) + assumptionGraph.addEdges(pNodes, aNode.id) + } + } + private def buildAndGetMergedGraph(): AssumptionAnalysisGraph = { - def keepNode(n: AssumptionAnalysisNode): Boolean = n.isClosed || n.isInstanceOf[InfeasibilityNode] + def keepNode(n: AssumptionAnalysisNode): Boolean = n.isClosed || n.isInstanceOf[InfeasibilityNode] || n.isInstanceOf[AxiomAssumptionNode] val mergedGraph = new AssumptionAnalysisGraph val nodeMap = mutable.HashMap[Int, Int]() @@ -249,6 +273,7 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { val nodesBySource = assumptionGraph.getNodes.filter(!keepNode(_)) .groupBy(n => (n.sourceInfo.getSourceForTransitiveEdges.toString, n.sourceInfo.getTopLevelSource.toString, n.sourceInfo.getFineGrainedSource, n.assumptionType)) + nodesBySource foreach { case ((_, _, _, assumptionType), nodes) => val assumptionNodes = nodes.filter(_.isInstanceOf[GeneralAssumptionNode]) if (assumptionNodes.nonEmpty) { @@ -286,6 +311,7 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { override def addNodes(nodes: Iterable[AssumptionAnalysisNode]): Unit = {} override def addNode(node: AssumptionAnalysisNode): Unit = {} override def addAssumption(assumption: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, description: Option[String] = None): Option[Int] = None + override def addAxiom(assumption: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, description: Option[String]): Option[Int] = None override def createLabelNode(labelTerm: Term, sourceChunks: Iterable[Chunk], sourceTerms: Iterable[Term]): Option[LabelNode] = None override def createAssertOrCheckNode(term: Term, assumptionType: AssumptionType, analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] = None @@ -296,6 +322,8 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { override def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit = {} override def addPermissionDependencies(sourceChunks: Set[Chunk], sourceTerms: Set[Term], targetChunk: Chunk): Unit = {} override def addCustomTransitiveDependency(sourceSourceInfo: AnalysisSourceInfo, targetSourceInfo: AnalysisSourceInfo): Unit = {} + override def addCustomExpDependency(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp]): Unit = {} + override def addFunctionAxiomEdges(): Unit = {} override def buildFinalGraph(): Option[AssumptionAnalysisGraph] = None } \ No newline at end of file diff --git a/src/main/scala/interfaces/decider/Prover.scala b/src/main/scala/interfaces/decider/Prover.scala index 862963caf..74226dcf6 100644 --- a/src/main/scala/interfaces/decider/Prover.scala +++ b/src/main/scala/interfaces/decider/Prover.scala @@ -46,7 +46,7 @@ trait ProverLike { if(Verifier.config.enableAssumptionAnalysis()){ axioms.foreach(axiom => { val axiomInfo = axiom._2 - val id = if(axiomInfo.isDefined) preambleAssumptionAnalyzer.addAssumption(axiom._1, axiomInfo.get._1, axiomInfo.get._2) else None + val id = if(axiomInfo.isDefined) preambleAssumptionAnalyzer.addAxiom(axiom._1, axiomInfo.get._1, axiomInfo.get._2) else None assume(axiom._1, AssumptionAnalyzer.createAxiomLabel(id)) }) } else{ diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index 7e9cb5bd2..eee09089a 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -118,6 +118,9 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif consumes(s4, posts, false, postViolated, v4, postConditionType)((_, _, _) => Success()))}) } )})}) + if(method.body.isEmpty){ + v.decider.assumptionAnalyzer.addCustomExpDependency(method.pres.flatMap(_.topLevelConjuncts), method.posts.flatMap(_.topLevelConjuncts)) + } result.assumptionAnalysisInterpreter = v.decider.assumptionAnalyzer.buildFinalGraph().map(new AssumptionAnalysisInterpreter(method.name, _, Some(method))) diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index edbb79c0a..91051aaca 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -161,6 +161,8 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver val res = handleFunction(sInit, function) symbExLog.closeMemberScope() + v.decider.assumptionAnalyzer.addFunctionAxiomEdges() + res.assumptionAnalysisInterpreter = v.decider.assumptionAnalyzer.buildFinalGraph().map(new AssumptionAnalysisInterpreter(function.name, _, Some(function))) Seq(res) @@ -187,6 +189,8 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver this.postConditionAxioms = this.postConditionAxioms ++ data.postAxiom if (function.body.isEmpty) { + decider.assumptionAnalyzer.addNodes(v.decider.prover.getPreambleAnalysisNodes) + decider.assumptionAnalyzer.addCustomExpDependency(function.pres.flatMap(_.topLevelConjuncts), function.posts.flatMap(_.topLevelConjuncts)) result1 } else { /* Phase 2: Verify the function's postcondition */ @@ -233,7 +237,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver // The postcondition must be produced with a fresh snapshot (different from `?s`) because // the postcondition's snapshot structure is most likely different than that of the // precondition - produces(s1, freshSnap, posts, ContractNotWellformed, v, AssumptionType.Explicit)((s2, _) => { + produces(s1, freshSnap, posts, ContractNotWellformed, v, AssumptionType.ExplicitPostcondition)((s2, _) => { recorders :+= s2.functionRecorder Success()})})}) @@ -289,7 +293,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver private def emitAndRecordFunctionAxioms(axiom: (Term, Option[(AnalysisSourceInfo, AssumptionType)])*): Unit = { decider.prover.assumeAxiomsWithAnalysisInfo(InsertionOrderedSet(axiom), "Function axioms") - emittedFunctionAxioms = emittedFunctionAxioms ++ axiom // FIXME ake: propagate assumption type + emittedFunctionAxioms = emittedFunctionAxioms ++ axiom } private def generateFunctionSymbolsAfterVerification: Iterable[Either[String, Decl]] = { From 295514cbdb4de98c83b674a53075ad4a70cb4222 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 26 Jul 2025 10:01:36 +0200 Subject: [PATCH 207/474] minor fix --- .../assumptionAnalysis/AssumptionAnalysisInterpreter.scala | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala index 4fe89146a..a4fea9184 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala @@ -3,6 +3,8 @@ package viper.silicon.assumptionAnalysis import viper.silicon.verifier.Verifier import viper.silver.ast +import java.io.File + object AssumptionAnalysisInterpreter { private val postconditionTypes = Set(AssumptionType.ExplicitPostcondition, AssumptionType.ImplicitPostcondition) @@ -84,6 +86,8 @@ class AssumptionAnalysisInterpreter(name: String, graph: ReadOnlyAssumptionAnaly def exportGraph(): Unit = { if(Verifier.config.assumptionAnalysisExportPath.isEmpty) return + val directory = new File(Verifier.config.assumptionAnalysisExportPath()) + directory.mkdir() graph.exportGraph(Verifier.config.assumptionAnalysisExportPath() + "/" + name) } From 3acc4f94400d88d24aae8c50cbb7a09052970e3b Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 26 Jul 2025 11:27:10 +0200 Subject: [PATCH 208/474] add explicit dependency test and minor refactoring --- .../AssumptionAnalysisInterpreter.scala | 8 +-- src/test/scala/AssumptionAnalysisTests.scala | 72 ++++++++++--------- 2 files changed, 43 insertions(+), 37 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala index a4fea9184..aee7b35c8 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala @@ -63,8 +63,8 @@ class AssumptionAnalysisInterpreter(name: String, graph: ReadOnlyAssumptionAnaly def getNonInternalAssumptionNodes: Set[AssumptionAnalysisNode] = getNodes filter (node => - (node.isInstanceOf[GeneralAssumptionNode] && !node.assumptionType.equals(AssumptionType.Internal)) || - postconditionTypes.contains(node.assumptionType) + (node.isInstanceOf[GeneralAssumptionNode] && !node.assumptionType.equals(AssumptionType.Internal)) + || postconditionTypes.contains(node.assumptionType) // postconditions act as assumptions for callers ) def getExplicitAssumptionNodes: Set[AssumptionAnalysisNode] = getNonInternalAssumptionNodes filter (node => @@ -76,8 +76,8 @@ class AssumptionAnalysisInterpreter(name: String, graph: ReadOnlyAssumptionAnaly def getNonInternalAssertionNodes: Set[AssumptionAnalysisNode] = getNodes filter (node => - (node.isInstanceOf[GeneralAssertionNode] && !node.assumptionType.equals(AssumptionType.Internal)) || - postconditionTypes.contains(node.assumptionType) + (node.isInstanceOf[GeneralAssertionNode] && !node.assumptionType.equals(AssumptionType.Internal)) + || postconditionTypes.contains(node.assumptionType) ) def getExplicitAssertionNodes: Set[AssumptionAnalysisNode] = diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index fa046a5be..35547b57a 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -74,14 +74,10 @@ class AssumptionAnalysisTests extends AnyFunSuite { if(EXECUTE_TEST) testDirectories foreach (dir => visitFiles(dir, createSingleTest)) - - analysisCommandLineArguments = Seq("--enableMoreCompleteExhale") ++ analysisCommandLineArguments - if(EXECUTE_TEST) + analysisCommandLineArguments = Seq("--enableMoreCompleteExhale") ++ analysisCommandLineArguments visitFiles("dependencyAnalysisTests/mce", createSingleTest) - - def visitFiles(dirName: String, function: (String, String) => Unit): Unit = { val path = Paths.get(getClass.getClassLoader.getResource(dirName).toURI) visitFiles(path, dirName, function) @@ -364,11 +360,8 @@ class AssumptionAnalysisTests extends AnyFunSuite { var errorMsgs = stmtsWithAssumptionAnnotation.map(checkAssumptionNodeExists(allAssumptionNodes, _)).filter(_.isDefined).map(_.get).toSeq errorMsgs ++= assumptionAnalysisInterpreters flatMap checkTestAssertionNodeExists - errorMsgs ++= assumptionAnalysisInterpreters flatMap checkDependencies - val warnMsgs = assumptionAnalysisInterpreters flatMap checkNonDependencies - if (CHECK_PRECISION) - errorMsgs ++= warnMsgs - else if (warnMsgs.nonEmpty) println(warnMsgs.mkString("\n")) // TODO ake: should be a warning + errorMsgs ++= assumptionAnalysisInterpreters flatMap checkAllDependencies + errorMsgs ++= assumptionAnalysisInterpreters flatMap checkExplicitDependencies val check = errorMsgs.isEmpty assert(check, "\n" + errorMsgs.mkString("\n")) @@ -418,31 +411,43 @@ class AssumptionAnalysisTests extends AnyFunSuite { } - protected def checkDependencies(assumptionAnalysisInterpreter: AssumptionAnalysisInterpreter): Seq[String] = { - val assumptionNodes = getTestAssumptionNodes(assumptionAnalysisInterpreter.getNonInternalAssumptionNodes) - val assumptionsPerSource = assumptionNodes groupBy (n => extractSourceLine(n.sourceInfo.getPosition)) + protected def checkAllDependencies(assumptionAnalysisInterpreter: AssumptionAnalysisInterpreter): Seq[String] = { val assertionNodes = getTestAssertionNodes(assumptionAnalysisInterpreter.getNonInternalAssertionNodes) + val dependencies = assumptionAnalysisInterpreter.getAllNonInternalDependencies(assertionNodes.map(_.id))map(_.id) - val dependenciesTmp = assumptionAnalysisInterpreter.getAllNonInternalDependencies(assertionNodes.map(_.id)) - val dependencies = dependenciesTmp.map(_.id) + val relevantAssumptionNodes = getTestAssumptionNodes(assumptionAnalysisInterpreter.getNonInternalAssumptionNodes) + val resRelevant: Seq[String] = checkDependenciesAndGetErrorMsgs(relevantAssumptionNodes, dependencies, isDependencyExpected = true, "Missing dependency") - assumptionsPerSource.map({ case (_, assumptions) => - val hasDependency = dependencies.intersect(assumptions.map(_.id)).nonEmpty - Option.when(!hasDependency)(s"Missing dependency: ${assumptions.head.sourceInfo.toString}") - }).filter(_.isDefined).map(_.get).toSeq + val resIrrelevant = if(CHECK_PRECISION){ + val irrelevantNodes = getTestIrrelevantAssumptionNodes(assumptionAnalysisInterpreter.getNonInternalAssumptionNodes) + checkDependenciesAndGetErrorMsgs(irrelevantNodes, dependencies, isDependencyExpected = false, "Unexpected dependency") + } else Seq.empty + + resRelevant ++ resIrrelevant } - protected def checkNonDependencies(assumptionAnalysisInterpreter: AssumptionAnalysisInterpreter): Seq[String] = { - val assumptionNodes = getTestIrrelevantAssumptionNodes(assumptionAnalysisInterpreter.getNonInternalAssumptionNodes) - val assumptionsPerSource = assumptionNodes groupBy (n => extractSourceLine(n.sourceInfo.getPosition)) + protected def checkExplicitDependencies(assumptionAnalysisInterpreter: AssumptionAnalysisInterpreter): Seq[String] = { val assertionNodes = getTestAssertionNodes(assumptionAnalysisInterpreter.getNonInternalAssertionNodes) + val dependencies = assumptionAnalysisInterpreter.getAllExplicitDependencies(assertionNodes.map(_.id)).map(_.id) + + val allTestAssumptionNodes = getTestAssumptionNodes(assumptionAnalysisInterpreter.getNonInternalAssumptionNodes) + + val relevantAssumptionNodes = allTestAssumptionNodes.filter(_.sourceInfo.toString.contains("@" + dependencyKeyword + "(\"Explicit\")")) + val resRelevant: Seq[String] = checkDependenciesAndGetErrorMsgs(relevantAssumptionNodes, dependencies, isDependencyExpected = true, "Missing explicit dependency") - val dependencies = assumptionAnalysisInterpreter.getAllNonInternalDependencies(assertionNodes.map(_.id)).map(_.id) + val irrelevantNodes = allTestAssumptionNodes.filterNot(_.sourceInfo.toString.contains("@" + dependencyKeyword + "(\"Explicit\")")) + val resIrrelevant = checkDependenciesAndGetErrorMsgs(irrelevantNodes, dependencies, isDependencyExpected = false, "Unexpected explicit dependency") - assumptionsPerSource.map({ case (_, assumptions) => + resRelevant ++ resIrrelevant + } + + protected def checkDependenciesAndGetErrorMsgs(relevantAssumptionNodes: Set[AssumptionAnalysisNode], dependencies: Set[Int], isDependencyExpected: Boolean, errorMsg: String): Seq[String] = { + val relevantAssumptionsPerSource = relevantAssumptionNodes groupBy (n => extractSourceLine(n.sourceInfo.getPosition)) + val resRelevant = relevantAssumptionsPerSource.map({ case (_, assumptions) => val hasDependency = dependencies.intersect(assumptions.map(_.id)).nonEmpty - Option.when(hasDependency)(s"Unexpected dependency: ${assumptions.head.sourceInfo.toString}") + Option.when(!(isDependencyExpected == hasDependency))(s"$errorMsg: ${assumptions.head.sourceInfo.toString}") }).filter(_.isDefined).map(_.get).toSeq + resRelevant } protected def getTestAssertionNodes(nodes: Set[AssumptionAnalysisNode]): Set[AssumptionAnalysisNode] = @@ -515,7 +520,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { } } - class PerformanceBenchmark(name: String, program: Program, naiveProgram: Option[Program], writer: PrintWriter, programSize: Int) { + class PerformanceBenchmark(name: String, program: Program, @unused naiveProgram: Option[Program], writer: PrintWriter, programSize: Int) { private def printResult(str: String): Unit = { writer.print(str) @@ -530,13 +535,14 @@ class AssumptionAnalysisTests extends AnyFunSuite { printResult(f"$baselineDurationMs;\t$analysisDurationMs;\t${analysisDurationMs/baselineDurationMs};\t$programSize\n") - if(naiveProgram.isDefined){ - val naiveDurationMs: Double = verifyAndMeasure(naiveProgram.get, baseCommandLineArguments) - writer.println(f"naive duration (ms): $naiveDurationMs") // TODO ake - writer.println(f"diff naive-baseline (ms): ${naiveDurationMs-baselineDurationMs}") - writer.println(f"naive overhead (naive/baseline): ${naiveDurationMs/baselineDurationMs}") - println(f"naive overhead (naive/baseline): ${naiveDurationMs/baselineDurationMs}") - } + // TODO ake: rewrite if we want to support this +// if(naiveProgram.isDefined){ +// val naiveDurationMs: Double = verifyAndMeasure(naiveProgram.get, baseCommandLineArguments) +// writer.println(f"naive duration (ms): $naiveDurationMs") +// writer.println(f"diff naive-baseline (ms): ${naiveDurationMs-baselineDurationMs}") +// writer.println(f"naive overhead (naive/baseline): ${naiveDurationMs/baselineDurationMs}") +// println(f"naive overhead (naive/baseline): ${naiveDurationMs/baselineDurationMs}") +// } } From 2d3757bc0a386066382d30f0eb690c11caf5f494 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 26 Jul 2025 12:10:31 +0200 Subject: [PATCH 209/474] refactoring --- src/main/scala/Config.scala | 9 +- .../AssumptionAnalysisInterpreter.scala | 96 ++++++++++- .../AssumptionAnalyzer.scala | 2 +- src/test/scala/AssumptionAnalysisTests.scala | 153 +++++------------- 4 files changed, 143 insertions(+), 117 deletions(-) diff --git a/src/main/scala/Config.scala b/src/main/scala/Config.scala index b190bcaeb..8e1451464 100644 --- a/src/main/scala/Config.scala +++ b/src/main/scala/Config.scala @@ -904,6 +904,13 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { sys.error(s"Unexpected combination: $other") } + validateOpt(rawProverArgs, enableAssumptionAnalysis) { + case (_, Some(false)) => Right(()) + case (Some(args), Some(true)) if args.contains("proof=true") && args.contains("unsat-core=true") => Right(()) + case (_, _) => + Left(s"Option ${enableAssumptionAnalysis.name} requires ${rawProverArgs.name} with \"proof=true unsat-core=true\"") + } + validateOpt(assumptionAnalysisExportPath, enableAssumptionAnalysis) { case (None, _) => Right(()) case (Some(_), Some(true)) => Right(()) @@ -914,7 +921,7 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { validateOpt(startAssumptionAnalysisTool, enableAssumptionAnalysis) { case (Some(false), _) => Right(()) case (_, Some(true)) => Right(()) - case (Some(true), Some(false)) => + case (_, _) => Left(s"Option ${startAssumptionAnalysisTool.name} requires option ${enableAssumptionAnalysis.name}") } diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala index aee7b35c8..374147cb0 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala @@ -2,8 +2,11 @@ package viper.silicon.assumptionAnalysis import viper.silicon.verifier.Verifier import viper.silver.ast +import viper.silver.ast.utility.ViperStrategy +import viper.silver.ast.utility.rewriter.Traverse +import viper.silver.ast.{If, Stmt} -import java.io.File +import java.io.{File, PrintWriter} object AssumptionAnalysisInterpreter { private val postconditionTypes = Set(AssumptionType.ExplicitPostcondition, AssumptionType.ImplicitPostcondition) @@ -110,4 +113,95 @@ class AssumptionAnalysisInterpreter(name: String, graph: ReadOnlyAssumptionAnaly val proofCoverage = 1.0 - (uncoveredSources.size.toDouble / nodesPerSourceInfo.size.toDouble) (proofCoverage, uncoveredSources) } + + def getPrunedProgram(crucialNodes: Set[AssumptionAnalysisNode], program: ast.Program): (ast.Program, Double) = { + + def isCrucialExp(exp: ast.Exp, crucialNodesWithExpInfo: Set[AnalysisSourceInfo]): Boolean = { + crucialNodesWithExpInfo exists (n => n.getPositionString.equals(AnalysisSourceInfo.extractPositionString(exp.pos))) // TODO ake: currently we compare only lines not columns! + } + + def isCrucialStmt(stmt: ast.Stmt, crucialNodesWithStmtInfo: Set[AnalysisSourceInfo]): Boolean = { + crucialNodesWithStmtInfo exists (n => n.getPositionString.equals(AnalysisSourceInfo.extractPositionString(stmt.pos))) + } + + val crucialNodeSourceInfos = crucialNodes map (_.sourceInfo.getTopLevelSource) + var total = 0 + var removed = 0 + var nonDetermBoolCount = 0 + + def getNextNonDetermBool: String = { + nonDetermBoolCount += 1 + s"nonDetermBool_$nonDetermBoolCount" + } + + val newProgram: ast.Program = ViperStrategy.Slim({ + case s @(_: ast.Seqn | _: ast.Goto) => s + case domain@ast.Domain(name, functions, axioms, typVars, interpretations) => + val newAxioms = axioms filter (a => + crucialNodeSourceInfos exists (n => n.getPositionString.equals(AnalysisSourceInfo.extractPositionString(a.exp.pos)) || + n.getPositionString.equals(AnalysisSourceInfo.extractPositionString(a.pos)))) + ast.Domain(name, functions, newAxioms, typVars, interpretations)(domain.pos, domain.info, domain.errT) + case function@ast.Function(name, formalArgs, typ, pres, posts, body) => + val newPres = pres filter (isCrucialExp(_, crucialNodeSourceInfos)) + val newPosts = posts filter (isCrucialExp(_, crucialNodeSourceInfos)) + val newBody = body filter (isCrucialExp(_, crucialNodeSourceInfos)) + ast.Function(name, formalArgs, typ, newPres, newPosts, newBody)(function.pos, function.info, function.errT) + case meth@ast.Method(name, inVars, outVars, pres, posts, body) => + val newPres = pres filter (isCrucialExp(_, crucialNodeSourceInfos)) + val newPosts = posts filter (isCrucialExp(_, crucialNodeSourceInfos)) + total += pres.size + posts.size + removed += (pres.size - newPres.size) + (posts.size - newPosts.size) + ast.Method(name, inVars, outVars, newPres, newPosts, body)(meth.pos, meth.info, meth.errT) + case ifStmt@ast.If(cond, thenBody, elseBody) if !isCrucialExp(cond, crucialNodeSourceInfos) => + total += 1 + removed += 1 + val nonDetermBool = getNextNonDetermBool + ast.Seqn(Seq( + ast.LocalVarDeclStmt(ast.LocalVarDecl(nonDetermBool, ast.Bool)())(), + ast.If(ast.LocalVar(nonDetermBool, ast.Bool)(), thenBody, elseBody)()) + , Seq())(ifStmt.pos, ifStmt.info, ifStmt.errT) + case ifStmt: If => + total += 1 + ifStmt + case whileStmt@ast.While(cond, invs, body) if !isCrucialExp(cond, crucialNodeSourceInfos) => + val newInvs = invs filter (isCrucialExp(_, crucialNodeSourceInfos)) + total += 1 + invs.size + removed += 1 + (invs.size - newInvs.size) + val nonDetermBool = getNextNonDetermBool + ast.Seqn(Seq( + ast.LocalVarDeclStmt(ast.LocalVarDecl(nonDetermBool, ast.Bool)())(), + ast.While(ast.LocalVar(nonDetermBool, ast.Bool)(), newInvs, body)(whileStmt.pos, whileStmt.info, whileStmt.errT)) + , Seq())(whileStmt.pos, whileStmt.info, whileStmt.errT) + case whileStmt@ast.While(cond, invs, body) => + val newInvs = invs filter (isCrucialExp(_, crucialNodeSourceInfos)) + total += 1 + invs.size + removed += (invs.size - newInvs.size) + ast.While(cond, newInvs, body)(whileStmt.pos, whileStmt.info, whileStmt.errT) + case label@ast.Label(name, invs) => + val newInvs = invs filter (isCrucialExp(_, crucialNodeSourceInfos)) + total += 1 + invs.size + removed += (invs.size - newInvs.size) + ast.Label(name, newInvs)(label.pos, label.info, label.errT) + case s: ast.Package if !isCrucialStmt(s, crucialNodeSourceInfos) => + total += 1 + removed += 1 + ast.Inhale(ast.TrueLit()())() + case s: Stmt if !isCrucialStmt(s, crucialNodeSourceInfos) => + total += 1 + removed += 1 + ast.Inhale(ast.TrueLit()())() + case s: Stmt => + total += 1 + s + }, Traverse.BottomUp).execute(program) + (newProgram, removed.toDouble / total.toDouble) + } + + def pruneProgramAndExport(crucialNodes: Set[AssumptionAnalysisNode], program: ast.Program, exportFileName: String): Unit = { + val writer = new PrintWriter(exportFileName) + val (newProgram, pruningFactor) = getPrunedProgram(crucialNodes, program) + writer.println("// pruning factor: " + pruningFactor) + writer.println(newProgram.toString()) + writer.close() + } } diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 673a9bf0e..f9407af67 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -22,7 +22,7 @@ trait AssumptionAnalyzer { def getMember: Option[ast.Member] - def getNodes: Iterable[AssumptionAnalysisNode] // TODO ake: remove? + def getNodes: Iterable[AssumptionAnalysisNode] def getChunkInhaleNode(chunk: Chunk): Option[PermissionInhaleNode] def addNodes(nodes: Iterable[AssumptionAnalysisNode]): Unit diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index 35547b57a..781f4546f 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -39,7 +39,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { var baseCommandLineArguments: Seq[String] = Seq("--timeout", "300" /* seconds */) var analysisCommandLineArguments: Seq[String] = - baseCommandLineArguments ++ Seq("--enableAssumptionAnalysis", "--z3Args", "proof=true unsat-core=true") + baseCommandLineArguments ++ Seq("--enableAssumptionAnalysis", "--proverArgs", "proof=true unsat-core=true") if(EXECUTE_PRECISION_BENCHMARK) { @@ -153,7 +153,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { val assumptionAnalysisInterpreters = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].assumptionAnalysisInterpretersPerMember val joinedAssumptionAnalysisInterpreter = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].joinedAssumptionAnalysisInterpreter - new AnnotatedTest(program, assumptionAnalysisInterpreters).execute() + AnnotatedTest(program, assumptionAnalysisInterpreters).execute() PruningTest(filePrefix + "/" + fileName, program, joinedAssumptionAnalysisInterpreter.get).execute() } @@ -161,21 +161,21 @@ class AssumptionAnalysisTests extends AnyFunSuite { fileName: String, writer: PrintWriter): Unit = { resetFrontend() - println(s"Precision Benchmark for $filePrefix - $fileName started...") try{ val program: Program = tests.loadProgram(filePrefix + "/", fileName, frontend) val result = frontend.verifier.verify(program) if(result.isInstanceOf[verifier.Failure]) { - writer.println("Program does not verify. Skip") - println(f"Program does not verify. Skip.\n$result") + writer.println(f"Program $filePrefix/$fileName does not verify. Skip") + println(f"Program $filePrefix/$fileName does not verify. Skip.\n$result") return } val assumptionAnalysisInterpreters = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].assumptionAnalysisInterpretersPerMember val fullGraphInterpreter = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].joinedAssumptionAnalysisInterpreter + + println(s"Precision Benchmark for $filePrefix - $fileName started...") writer.println(s"$filePrefix - $fileName") - new PrecisionBenchmarkSoundnessTest(filePrefix + "/" + fileName, program, fullGraphInterpreter.get, writer).execute() - new AnnotatedPrecisionBenchmark(program, assumptionAnalysisInterpreters, writer).execute() + new AnnotatedPrecisionBenchmark(filePrefix + "/" + fileName, program, assumptionAnalysisInterpreters, fullGraphInterpreter.get, writer).execute() writer.println() println(s"Precision Benchmark for $filePrefix - $fileName done.") }catch{ @@ -243,7 +243,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { val dependencies = fullGraphInterpreter.getAllNonInternalDependencies(relevantNodes.map(_.id)) val crucialNodes = relevantNodes ++ dependencies - val (newProgram, pruningFactor) = getPrunedProgram(crucialNodes) + val (newProgram, pruningFactor) = fullGraphInterpreter.getPrunedProgram(crucialNodes, program) val result = frontend.verifier.verify(newProgram) exportPrunedProgram(exportFileName, newProgram, pruningFactor, result) assert(!result.isInstanceOf[verifier.Failure], s"Failed to verify new program ${newProgram.toString()}") @@ -252,108 +252,29 @@ class AssumptionAnalysisTests extends AnyFunSuite { protected def exportPrunedProgram(exportFileName: String, newProgram: Program, pruningFactor: Double, result: VerificationResult): Unit = { val writer = new PrintWriter(exportFileName) writer.println("// test result: " + !result.isInstanceOf[verifier.Failure]) - writer.println("// cleanse factor: " + pruningFactor) + writer.println("// pruning factor: " + pruningFactor) writer.println(newProgram.toString()) writer.close() } - - protected def getPrunedProgram(crucialNodes: Set[AssumptionAnalysisNode]): (ast.Program, Double) = { - val crucialNodeSourceInfos = crucialNodes map (_.sourceInfo.getTopLevelSource) - var total = 0 - var removed = 0 - var nonDetermBoolCount = 0 - - def getNextNonDetermBool: String = { - nonDetermBoolCount += 1 - s"nonDetermBool_$nonDetermBoolCount" - } - - val newProgram: ast.Program = ViperStrategy.Slim({ - case s @(_: ast.Seqn | _: ast.Goto) => s - case domain@ast.Domain(name, functions, axioms, typVars, interpretations) => - val newAxioms = axioms filter (a => - crucialNodeSourceInfos exists (n => n.getPositionString.equals(AnalysisSourceInfo.extractPositionString(a.exp.pos)) || - n.getPositionString.equals(AnalysisSourceInfo.extractPositionString(a.pos)))) - ast.Domain(name, functions, newAxioms, typVars, interpretations)(domain.pos, domain.info, domain.errT) - case function@ast.Function(name, formalArgs, typ, pres, posts, body) => - val newPres = pres filter (isCrucialExp(_, crucialNodeSourceInfos)) - val newPosts = posts filter (isCrucialExp(_, crucialNodeSourceInfos)) - val newBody = body filter (isCrucialExp(_, crucialNodeSourceInfos)) - ast.Function(name, formalArgs, typ, newPres, newPosts, newBody)(function.pos, function.info, function.errT) - case meth@ast.Method(name, inVars, outVars, pres, posts, body) => - val newPres = pres filter (isCrucialExp(_, crucialNodeSourceInfos)) - val newPosts = posts filter (isCrucialExp(_, crucialNodeSourceInfos)) - total += pres.size + posts.size - removed += (pres.size - newPres.size) + (posts.size - newPosts.size) - ast.Method(name, inVars, outVars, newPres, newPosts, body)(meth.pos, meth.info, meth.errT) - case ifStmt@ast.If(cond, thenBody, elseBody) if !isCrucialExp(cond, crucialNodeSourceInfos) => - total += 1 - removed += 1 - val nonDetermBool = getNextNonDetermBool - ast.Seqn(Seq( - ast.LocalVarDeclStmt(ast.LocalVarDecl(nonDetermBool, ast.Bool)())(), - ast.If(ast.LocalVar(nonDetermBool, ast.Bool)(), thenBody, elseBody)()) - , Seq())(ifStmt.pos, ifStmt.info, ifStmt.errT) - case ifStmt: If => - total += 1 - ifStmt - case whileStmt@ast.While(cond, invs, body) if !isCrucialExp(cond, crucialNodeSourceInfos) => - val newInvs = invs filter (isCrucialExp(_, crucialNodeSourceInfos)) - total += 1 + invs.size - removed += 1 + (invs.size - newInvs.size) - val nonDetermBool = getNextNonDetermBool - ast.Seqn(Seq( - ast.LocalVarDeclStmt(ast.LocalVarDecl(nonDetermBool, ast.Bool)())(), - ast.While(ast.LocalVar(nonDetermBool, ast.Bool)(), newInvs, body)(whileStmt.pos, whileStmt.info, whileStmt.errT)) - , Seq())(whileStmt.pos, whileStmt.info, whileStmt.errT) - case whileStmt@ast.While(cond, invs, body) => - val newInvs = invs filter (isCrucialExp(_, crucialNodeSourceInfos)) - total += 1 + invs.size - removed += (invs.size - newInvs.size) - ast.While(cond, newInvs, body)(whileStmt.pos, whileStmt.info, whileStmt.errT) - case label@ast.Label(name, invs) => - val newInvs = invs filter (isCrucialExp(_, crucialNodeSourceInfos)) - total += 1 + invs.size - removed += (invs.size - newInvs.size) - ast.Label(name, newInvs)(label.pos, label.info, label.errT) - case s: ast.Package if !isCrucialStmt(s, crucialNodeSourceInfos) => - total += 1 - removed += 1 - ast.Inhale(ast.TrueLit()())() - case s: Stmt if !isCrucialStmt(s, crucialNodeSourceInfos) => - total += 1 - removed += 1 - ast.Inhale(ast.TrueLit()())() - case s: Stmt => - total += 1 - s - }, Traverse.BottomUp).execute(program) - (newProgram, removed.toDouble / total.toDouble) - } - - protected def isCrucialExp(exp: ast.Exp, crucialNodesWithExpInfo: Set[AnalysisSourceInfo]): Boolean = { - crucialNodesWithExpInfo exists (n => n.getPositionString.equals(AnalysisSourceInfo.extractPositionString(exp.pos))) // TODO ake: currently we compare only lines not columns! - } - - protected def isCrucialStmt(stmt: ast.Stmt, crucialNodesWithStmtInfo: Set[AnalysisSourceInfo]): Boolean = { - crucialNodesWithStmtInfo exists (n => n.getPositionString.equals(AnalysisSourceInfo.extractPositionString(stmt.pos))) - } } /** * Takes a Viper program and its assumption analysis results and checks whether the analysis found the * assumptions, assertions and dependencies between them, as annotated by the user. * - * Annotations - * @dependency() -> for assumptions that should be reported as a dependency - * @irrelevant() -> for assumptions that should NOT be reported as a dependency - * @testAssertion() -> the queried assertion + * Annotations: + * + * - @dependency() -> for assumptions that should be reported as a dependency + * + * - @irrelevant() -> for assumptions that should NOT be reported as a dependency + * + * - @testAssertion() -> the queried assertion * * !!! THERE CAN ONLY BE 1 TEST ASSERTION PER METHOD, * but multiple dependency/irrelevant annotations are allowed * */ - class AnnotatedTest(program: Program, assumptionAnalysisInterpreters: List[AssumptionAnalysisInterpreter]) { + case class AnnotatedTest(program: Program, assumptionAnalysisInterpreters: List[AssumptionAnalysisInterpreter]) { def execute(): Unit = { val stmtsWithAssumptionAnnotation: Set[Infoed] = extractAnnotatedStmts({ annotationInfo => annotationInfo.values.contains(irrelevantKeyword) || annotationInfo.values.contains(dependencyKeyword) }) val allAssumptionNodes = assumptionAnalysisInterpreters.flatMap(_.getNonInternalAssumptionNodes) @@ -464,9 +385,16 @@ class AssumptionAnalysisTests extends AnyFunSuite { } - class AnnotatedPrecisionBenchmark(program: Program, assumptionAnalysisInterpreters: List[AssumptionAnalysisInterpreter], + class AnnotatedPrecisionBenchmark(fileName: String, program: Program, + assumptionAnalysisInterpreters: List[AssumptionAnalysisInterpreter], + fullGraphInterpreter: AssumptionAnalysisInterpreter, writer: PrintWriter) extends AnnotatedTest(program, assumptionAnalysisInterpreters) { override def execute(): Unit = { + if(!verifyTestSoundness()){ + writer.println(s"!!!!!!!!!!!\nFailed to verify soundness of precision test $fileName\n") + println(s"!!!!!!!!!!!\nFailed to verify soundness of precision test $fileName\n") + return + } assumptionAnalysisInterpreters foreach {a => val prec = computePrecision(a) @@ -491,13 +419,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { } } - } - - - class PrecisionBenchmarkSoundnessTest(name: String, program: Program, fullGraphInterpreter: AssumptionAnalysisInterpreter , - writer: PrintWriter) extends PruningTest(name, program, fullGraphInterpreter) { - - override def execute(): Unit = { + protected def verifyTestSoundness(): Boolean = { val irrelevantNodes = fullGraphInterpreter.getNodes.filter(node => node.sourceInfo.toString.contains("@irrelevant(")).flatMap(_.sourceInfo.getLineNumber) val relevantLines = fullGraphInterpreter.getNodes.flatMap(_.sourceInfo.getLineNumber).diff(irrelevantNodes) @@ -505,19 +427,22 @@ class AssumptionAnalysisTests extends AnyFunSuite { pruneAndVerify(relevantLines, "src/test/resources/" + fileName + s"_test.out") } - override protected def pruneAndVerify(relevantLines: Set[Int], exportFileName: String): Unit = { - val relevantNodes = relevantLines.flatMap(line => fullGraphInterpreter.getNodesByLine(line)) - - val crucialNodes = relevantNodes - val (newProgram, pruningFactor) = getPrunedProgram(crucialNodes) + protected def pruneAndVerify(relevantLines: Set[Int], exportFileName: String): Boolean = { + val crucialNodes = relevantLines.flatMap(line => fullGraphInterpreter.getNodesByLine(line)) + val (newProgram, pruningFactor) = fullGraphInterpreter.getPrunedProgram(crucialNodes, program) val result = frontend.verifier.verify(newProgram) exportPrunedProgram(exportFileName, newProgram, pruningFactor, result) - if(result.isInstanceOf[verifier.Failure]) { - writer.println(s"!!!!!!!!!!!\nFailed to verify new program $exportFileName\n") - println(s"!!!!!!!!!!!\nFailed to verify new program $exportFileName\n") - throw new Exception("Error: " + result.toString) - } + !result.isInstanceOf[verifier.Failure] } + + protected def exportPrunedProgram(exportFileName: String, newProgram: Program, pruningFactor: Double, result: VerificationResult): Unit = { + val writer = new PrintWriter(exportFileName) + writer.println("// test result: " + !result.isInstanceOf[verifier.Failure]) + writer.println("// pruning factor: " + pruningFactor) + writer.println(newProgram.toString()) + writer.close() + } + } class PerformanceBenchmark(name: String, program: Program, @unused naiveProgram: Option[Program], writer: PrintWriter, programSize: Int) { From fd2063e76b410ee6dd48fb7d6073a9ccaaeb11ba Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 26 Jul 2025 12:27:11 +0200 Subject: [PATCH 210/474] refactoring --- .../assumptionAnalysis/AnalysisInfo.scala | 13 +++-- .../AssumptionAnalysisInterpreter.scala | 43 +++------------- .../AssumptionAnalyzer.scala | 23 +++++++++ .../scala/verifier/DefaultMainVerifier.scala | 4 +- src/test/resources/andrea/quickTest.vpr | 51 +++++++++++++++---- .../dependencyAnalysisTests/quick/test.vpr | 10 ---- 6 files changed, 79 insertions(+), 65 deletions(-) delete mode 100644 src/test/resources/dependencyAnalysisTests/quick/test.vpr diff --git a/src/main/scala/assumptionAnalysis/AnalysisInfo.scala b/src/main/scala/assumptionAnalysis/AnalysisInfo.scala index 334175440..db6b20a64 100644 --- a/src/main/scala/assumptionAnalysis/AnalysisInfo.scala +++ b/src/main/scala/assumptionAnalysis/AnalysisInfo.scala @@ -5,20 +5,19 @@ object AssumptionType extends Enumeration { val Explicit, LoopInvariant, PathCondition, Rewrite, Implicit, Internal, Trigger, ExplicitPostcondition, ImplicitPostcondition = Value def fromString(s: String): Option[Value] = values.find(_.toString == s) -} -object AssertionType extends Enumeration { - type AssertionType = Value - val Explicit, Implicit = Value - def fromString(s: String): Option[Value] = values.find(_.toString == s) + def explicitAssumptionTypes: Set[AssumptionType] = Set(Explicit, ExplicitPostcondition) + def postconditionTypes: Set[AssumptionType] = Set(ImplicitPostcondition, ExplicitPostcondition) // used to join graphs via postconditions + def explicitAssertionTypes: Set[AssumptionType] = Set(Explicit) ++ postconditionTypes + def internalTypes: Set[AssumptionType] = Set(Internal) // will always be hidden from user } + import viper.silicon.assumptionAnalysis.AssumptionType._ -import viper.silicon.assumptionAnalysis.AssertionType._ import viper.silicon.decider.Decider case class AnalysisInfo(decider: Decider, assumptionAnalyzer: AssumptionAnalyzer, sourceInfo: AnalysisSourceInfo, - assumptionType: AssumptionType, assertionType: AssertionType=AssertionType.Implicit) { + assumptionType: AssumptionType) { def withAssumptionType(newAssumptionType: AssumptionType): AnalysisInfo = { copy(assumptionType=newAssumptionType) } diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala index 374147cb0..29ce5f6c4 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala @@ -8,39 +8,10 @@ import viper.silver.ast.{If, Stmt} import java.io.{File, PrintWriter} -object AssumptionAnalysisInterpreter { - private val postconditionTypes = Set(AssumptionType.ExplicitPostcondition, AssumptionType.ImplicitPostcondition) - - def joinGraphsAndGetInterpreter(name: Option[String], assumptionAnalysisInterpreters: Set[AssumptionAnalysisInterpreter]): AssumptionAnalysisInterpreter = { - val newGraph = new AssumptionAnalysisGraph - - assumptionAnalysisInterpreters foreach (interpreter => newGraph.addNodes(interpreter.getGraph.getNodes)) - assumptionAnalysisInterpreters foreach (interpreter => interpreter.getGraph.getAllEdges foreach {case (s, t) => newGraph.addEdges(s, t)}) - - // add edges between identical axioms since they were added to each interpreter // TODO ake: merge instead? - newGraph.getNodes.filter(_.isInstanceOf[AxiomAssumptionNode]).groupBy(n => (n.sourceInfo.toString, n.assumptionType)).foreach{case (_, nodes) => - newGraph.addEdges(nodes.map(_.id), nodes.map(_.id)) - } - - val types = Set(AssumptionType.Implicit, AssumptionType.Explicit) - val relevantAssumptionNodes = newGraph.nodes filter (node => node.isInstanceOf[GeneralAssumptionNode] && types.contains(node.assumptionType)) - - newGraph.nodes filter (node => postconditionTypes.contains(node.assumptionType)) foreach { node => - val nodeSourceInfoString = node.sourceInfo.getTopLevelSource.toString - val assumptionNodesForJoin = relevantAssumptionNodes filter (aNode => aNode.sourceInfo.getFineGrainedSource.toString.equals(nodeSourceInfoString)) - newGraph.addEdges(node.id, assumptionNodesForJoin map (_.id)) - } - - new AssumptionAnalysisInterpreter(name.getOrElse("joined"), newGraph) - } -} class AssumptionAnalysisInterpreter(name: String, graph: ReadOnlyAssumptionAnalysisGraph, member: Option[ast.Member]=None) { - protected val explicitAssumptionTypes = Set(AssumptionType.Explicit, AssumptionType.ExplicitPostcondition) - protected val postconditionTypes = Set(AssumptionType.ExplicitPostcondition, AssumptionType.ImplicitPostcondition) - protected val explicitAssertionTypes = Set(AssumptionType.Explicit) ++ postconditionTypes - private def getGraph: ReadOnlyAssumptionAnalysisGraph = graph + def getGraph: ReadOnlyAssumptionAnalysisGraph = graph def getName: String = name def getMember: Option[ast.Member] = member def getNodes: Set[AssumptionAnalysisNode] = graph.getNodes.toSet @@ -66,12 +37,12 @@ class AssumptionAnalysisInterpreter(name: String, graph: ReadOnlyAssumptionAnaly def getNonInternalAssumptionNodes: Set[AssumptionAnalysisNode] = getNodes filter (node => - (node.isInstanceOf[GeneralAssumptionNode] && !node.assumptionType.equals(AssumptionType.Internal)) - || postconditionTypes.contains(node.assumptionType) // postconditions act as assumptions for callers + (node.isInstanceOf[GeneralAssumptionNode] && !AssumptionType.internalTypes.contains(node.assumptionType)) + || AssumptionType.postconditionTypes.contains(node.assumptionType) // postconditions act as assumptions for callers ) def getExplicitAssumptionNodes: Set[AssumptionAnalysisNode] = getNonInternalAssumptionNodes filter (node => - explicitAssumptionTypes.contains(node.assumptionType) + AssumptionType.explicitAssumptionTypes.contains(node.assumptionType) ) private def getNonInternalAssumptionNodesPerSource: Map[String, Set[AssumptionAnalysisNode]] = @@ -79,12 +50,12 @@ class AssumptionAnalysisInterpreter(name: String, graph: ReadOnlyAssumptionAnaly def getNonInternalAssertionNodes: Set[AssumptionAnalysisNode] = getNodes filter (node => - (node.isInstanceOf[GeneralAssertionNode] && !node.assumptionType.equals(AssumptionType.Internal)) - || postconditionTypes.contains(node.assumptionType) + (node.isInstanceOf[GeneralAssertionNode] && !AssumptionType.internalTypes.contains(node.assumptionType)) + || AssumptionType.postconditionTypes.contains(node.assumptionType) ) def getExplicitAssertionNodes: Set[AssumptionAnalysisNode] = - getNonInternalAssertionNodes.filter(node => explicitAssertionTypes.contains(node.assumptionType)) + getNonInternalAssertionNodes.filter(node => AssumptionType.explicitAssertionTypes.contains(node.assumptionType)) def exportGraph(): Unit = { diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index f9407af67..97969a07b 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -93,6 +93,29 @@ object AssumptionAnalyzer { def isAssumptionLabel(label: String): Boolean = label.startsWith("assumption_") def isAxiomLabel(label: String): Boolean = label.startsWith("axiom_") + + def joinGraphsAndGetInterpreter(name: Option[String], assumptionAnalysisInterpreters: Set[AssumptionAnalysisInterpreter]): AssumptionAnalysisInterpreter = { + val newGraph = new AssumptionAnalysisGraph + + assumptionAnalysisInterpreters foreach (interpreter => newGraph.addNodes(interpreter.getGraph.getNodes)) + assumptionAnalysisInterpreters foreach (interpreter => interpreter.getGraph.getAllEdges foreach {case (s, t) => newGraph.addEdges(s, t)}) + + // add edges between identical axioms since they were added to each interpreter // TODO ake: merge instead? + newGraph.getNodes.filter(_.isInstanceOf[AxiomAssumptionNode]).groupBy(n => (n.sourceInfo.toString, n.assumptionType)).foreach{case (_, nodes) => + newGraph.addEdges(nodes.map(_.id), nodes.map(_.id)) + } + + val types = Set(AssumptionType.Implicit, AssumptionType.Explicit) + val relevantAssumptionNodes = newGraph.nodes filter (node => node.isInstanceOf[GeneralAssumptionNode] && types.contains(node.assumptionType)) + + newGraph.nodes filter (node => AssumptionType.postconditionTypes.contains(node.assumptionType)) foreach { node => + val nodeSourceInfoString = node.sourceInfo.getTopLevelSource.toString + val assumptionNodesForJoin = relevantAssumptionNodes filter (aNode => aNode.sourceInfo.getFineGrainedSource.toString.equals(nodeSourceInfoString)) + newGraph.addEdges(node.id, assumptionNodesForJoin map (_.id)) + } + + new AssumptionAnalysisInterpreter(name.getOrElse("joined"), newGraph) + } } class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index b6241bfcf..94c73d349 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -9,7 +9,7 @@ package viper.silicon.verifier import silicon.viper.assumptionAnalysis.AssumptionAnalysisUserTool import viper.silicon.Config.{ExhaleMode, JoinMode} import viper.silicon._ -import viper.silicon.assumptionAnalysis.{AssumptionAnalysisInterpreter, DependencyAnalysisReporter} +import viper.silicon.assumptionAnalysis.{AssumptionAnalyzer, DependencyAnalysisReporter} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.SiliconDebugger import viper.silicon.decider.SMTLib2PreambleReader @@ -319,7 +319,7 @@ class DefaultMainVerifier(config: Config, assumptionAnalysisInterpreters foreach (_.exportGraph()) - val joinedGraphInterpreter = AssumptionAnalysisInterpreter.joinGraphsAndGetInterpreter(inputFile.map(_.replaceAll("\\\\", "_").replaceAll(".vpr", "")), assumptionAnalysisInterpreters.toSet) + val joinedGraphInterpreter = AssumptionAnalyzer.joinGraphsAndGetInterpreter(inputFile.map(_.replaceAll("\\\\", "_").replaceAll(".vpr", "")), assumptionAnalysisInterpreters.toSet) if(Verifier.config.assumptionAnalysisExportPath.isDefined) joinedGraphInterpreter.exportGraph() diff --git a/src/test/resources/andrea/quickTest.vpr b/src/test/resources/andrea/quickTest.vpr index 191881d83..e6088140e 100644 --- a/src/test/resources/andrea/quickTest.vpr +++ b/src/test/resources/andrea/quickTest.vpr @@ -1,13 +1,44 @@ field f: Int -method infeasibleBranchNoPerm(a: Int, b: Ref) - requires a > 0 //@dependency()(a > 0) +function sum(x: Int, y: Int) : Int + requires x >= 0 && y >= 0 && y > -5 && x > -4 + ensures result == x + y && result >= 0 { - var res: Int - if(a < 0){ - //@irrelevant() TODO ake: nodes missing (infeasible path) - res := b.f + 1 - //@testAssertion() - assert false - } -} \ No newline at end of file + x + y +} + +function sumUnverified(x: Int, y: Int): Int + requires y > -5 && x > -4 + ensures result == x + y && result >= 0 + +method call2(){ + var x: Int, y: Int, z: Int + @dependency("Explicit") + assume x > 10 + @dependency("Explicit") + assume y > 0 + + @dependency("Implicit") + z := sum(x, y) + + // $PrecisionTest: $READ_ONLY=x,y,z $INVARIANT=x>0 + + assert z >= 0 +} + +method call2Unv(){ + var x: Int, y: Int, z: Int + @dependency("Explicit") + assume x > 10 + @dependency("Explicit") + assume y > 0 + + @dependency("Implicit") + z := sumUnverified(x, y) + + // $PrecisionTest: $READ_ONLY=x,y,z $INVARIANT=x>0 + + @testAssertion("Explicit") + assert z == x + y +} + diff --git a/src/test/resources/dependencyAnalysisTests/quick/test.vpr b/src/test/resources/dependencyAnalysisTests/quick/test.vpr deleted file mode 100644 index a94169f83..000000000 --- a/src/test/resources/dependencyAnalysisTests/quick/test.vpr +++ /dev/null @@ -1,10 +0,0 @@ - -method foo(a: Int, b: Int) - requires a > 0 - ensures a >= 0 -{ - @trigger() - assume b >= 0 - @trigger() - assert b >= -15 -} \ No newline at end of file From d7ff2b8fcfac710ab51ff3a1be339b994ad4fe97 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 26 Jul 2025 20:34:59 +0200 Subject: [PATCH 211/474] update test cases --- .../dependencyAnalysisTests/all/aliasing.vpr | 6 +- .../all/assume_assert.vpr | 19 ++++ .../dependencyAnalysisTests/all/branches.vpr | 22 ++--- .../dependencyAnalysisTests/all/exhale.vpr | 38 -------- .../all/imprecision-fixed.vpr | 36 ++++++++ .../all/imprecision.vpr | 45 ++++++---- .../all/method-sum.vpr | 13 ++- .../all/presentation-examples.vpr | 2 +- .../dependencyAnalysisTests/all/sum-loops.vpr | 2 +- .../dependencyAnalysisTests/all/wands.vpr | 82 ----------------- .../precision_benchmark_plotter.py | 4 +- .../precision_test_generator.py | 4 +- .../dependencyAnalysisTests/new/meeting.vpr | 15 ++++ .../unitTests/branches.vpr | 6 +- .../unitTests/domain.vpr | 29 ++++-- .../unitTests/function-call.vpr | 88 +++++++++++++++++++ .../unitTests/inhaleExhale.vpr | 14 --- .../unitTests/loops.vpr | 11 ++- .../unitTests/magicWands.vpr | 45 ++++------ .../unitTests/method-call.vpr | 36 ++++++-- .../unitTests/misc.vpr | 2 +- .../unitTests/permWildcard.vpr | 8 +- .../unitTests/permissions.vpr | 45 ++-------- .../unitTests/predicates.vpr | 11 +-- .../unitTests/quantifiedPermissions.vpr | 12 +-- src/test/scala/AssumptionAnalysisTests.scala | 3 +- 26 files changed, 312 insertions(+), 286 deletions(-) create mode 100644 src/test/resources/dependencyAnalysisTests/all/assume_assert.vpr delete mode 100644 src/test/resources/dependencyAnalysisTests/all/exhale.vpr delete mode 100644 src/test/resources/dependencyAnalysisTests/all/wands.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/unitTests/function-call.vpr diff --git a/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr b/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr index 557c2d7b9..bb2303bee 100644 --- a/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr @@ -5,9 +5,9 @@ method maybeAlias(a: Ref, b: Ref, c: Bool, n: Int) requires @dependency("Explicit")(acc(a.f, 1/2)) requires @dependency("Explicit")(acc(b.f, 1/2)) requires @dependency("Explicit")(c ==> a == b) - requires a.f > 0 && n > 0 && b.f >= 0 - requires a.f < 100 - requires !c ==> a.f < b.f + requires @irrelevant("Explicit")(a.f > 0 && n > 0 && b.f >= 0) + requires @irrelevant("Explicit")(a.f < 100) + requires @irrelevant("Explicit")(!c ==> a.f < b.f) { if(@dependency("PathCondition")(c)){ @testAssertion("Implicit") diff --git a/src/test/resources/dependencyAnalysisTests/all/assume_assert.vpr b/src/test/resources/dependencyAnalysisTests/all/assume_assert.vpr new file mode 100644 index 000000000..8985a50ac --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/all/assume_assert.vpr @@ -0,0 +1,19 @@ +field f: Int + +method assert1(a: Int) + requires @dependency("Explicit")(a > 0) +{ + @irrelevant() + assert a >= 0 + @testAssertion("Explicit") + assert a >= 0 +} + +method assume1(a: Int) + requires @dependency("Explicit")(a > 0) +{ + @irrelevant("Explicit") + assume a >= 0 + @testAssertion("Explicit") + assert a > 0 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/branches.vpr b/src/test/resources/dependencyAnalysisTests/all/branches.vpr index d54078c99..3e4ce5c8a 100644 --- a/src/test/resources/dependencyAnalysisTests/all/branches.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/branches.vpr @@ -14,7 +14,7 @@ method branches1(a: Int, b: Int) @irrelevant("Explicit") assume c ==> a > 5 - if(c){ + if(@irrelevant("PathCondition")(c)){ @dependency("Implicit") n := a + 1 }else{ @@ -40,7 +40,7 @@ method branches2(a: Int, b: Int) assume b < 50 var x: Int - if(a >= n){ + if(@dependency("PathCondition")(a >= n)){ @irrelevant("Implicit") x := a + b }else{ @@ -55,23 +55,11 @@ method branches3(a: Int, b: Int) { var n:Int, c: Bool - @irrelevant("Explicit") - assume 0 < a - @irrelevant("Explicit") - assume a < 100 @dependency("Explicit") assume 0 < b - @irrelevant("Explicit") - assume b < 50 - - if(c){ - n := a + 1 - }else{ - n := b + 1 - } var x: Int - if(a >= n){ + if(@dependency("PathCondition")(a >= n)){ @dependency("Implicit") x := a + b }else{ @@ -99,8 +87,8 @@ method nestedBranches1(a: Int, b: Int) @irrelevant("Explicit") assume c ==> a > 5 - if(c){ - if(a > b){ + if(@irrelevant("PathCondition")(c)){ + if(@irrelevant("PathCondition")(a > b)){ @dependency("Implicit") n := a - b }else{ diff --git a/src/test/resources/dependencyAnalysisTests/all/exhale.vpr b/src/test/resources/dependencyAnalysisTests/all/exhale.vpr deleted file mode 100644 index d148eee19..000000000 --- a/src/test/resources/dependencyAnalysisTests/all/exhale.vpr +++ /dev/null @@ -1,38 +0,0 @@ -field f: Int - - -method exhaleDependency(x: Ref) - requires @dependency("Explicit")(acc(x.f)) -{ - @dependency("Implicit") - exhale acc(x.f, 1/2) - @dependency("Explicit") - inhale acc(x.f, 1/2) - @testAssertion("Implicit") - x.f := 0 -} - -method exhaleIrrelevant(x: Ref) - requires acc(x.f, 1/2) // TODO ake: imprecise? @irrelevant("Explicit")(acc(x.f, 1/2)) -{ - // @irrelevant("Implicit") // TODO ake: imprecise? - exhale acc(x.f, 1/4) - @dependency("Explicit") - inhale acc(x.f, 1/2) - @testAssertion("Implicit") - inhale x.f > 0 -} - -method exhale2(){ - var x: Ref - @dependency("Explicit") - inhale acc(x.f) - @dependency("Explicit") - inhale x.f > 0 - @dependency("Implicit") - x.f := x.f + 1 - // @irrelevant("Implicit") // TODO ake - exhale acc(x.f, 1/2) - @testAssertion("Explicit") - assert x.f > 1 -} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/imprecision-fixed.vpr b/src/test/resources/dependencyAnalysisTests/all/imprecision-fixed.vpr index 15a3f225b..4c206a8c5 100644 --- a/src/test/resources/dependencyAnalysisTests/all/imprecision-fixed.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/imprecision-fixed.vpr @@ -66,4 +66,40 @@ method noAlias(a: Ref, b: Ref, c: Ref) { @testAssertion("Explicit") assert a != b +} + +method fieldAccessPrecision(){ + var x: Ref + var y: Ref + + @dependency("Explicit") + inhale acc(x.f) + @dependency("Explicit") + inhale x.f > 0 + + @irrelevant("Explicit") + inhale acc(y.f) + @irrelevant("Implicit") + y.f := y.f + 1 + + @testAssertion("Explicit") + assert x.f >= 0 +} + +method fieldAccessPrecision2(){ + var x: Ref + var y: Ref + + @dependency("Explicit") + inhale acc(x.f) + @irrelevant("Explicit") + inhale x.f > 0 + + @irrelevant("Explicit") + inhale acc(y.f) + @irrelevant("Implicit") + x.f := y.f + 1 + + @testAssertion("Explicit") + exhale acc(x.f) } \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr b/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr index 956d42063..9be3df616 100644 --- a/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr @@ -5,11 +5,11 @@ field g: Int // If the branch is not executed, the 2nd inhale is reported as a dependency // In reality, the 2nd inhale is all we need for the assertion. method nonUniqueUnsatCore(x: Ref) - requires x != null ==> acc(x.f) + requires @irrelevant("Explicit")(x != null ==> acc(x.f)) { var a: Int - if(x == null){ - // @irrelevant("Explicit") // imprecise + if(@irrelevant("PathCondition")(x == null)){ + @irrelevant("Explicit") // imprecise inhale a >= 0 } @@ -26,8 +26,7 @@ method unprovableInfeasibility(){ if (exists x: Int, y: Int, z: Int :: x > 0 && y > 0 && z > 0 && x*x*x + y*y*y == z*z*z) { // complicated condition that is equivalent to false, but can’t be proven by solver // effectively unreachable code - - // @irrelevant("Explicit") + @irrelevant("Explicit") assume a == 0 // assumption 1 } else { @dependency("Explicit") @@ -42,9 +41,9 @@ method unprovableInfeasibility(){ // If x == null is assumed, only the assignment is reported as a dependency // If x != null, the branch is infeasible, but we executed it anyways. The implication and branch condition are reported as dependencies. method infeasible2(x: Ref) - requires x != null ==> acc(x.f) // unexpected dependency + requires @irrelevant("Explicit")(x != null ==> acc(x.f)) // unexpected dependency { - if(x == null){ // unexpected dependency + if(@irrelevant("PathCondition")(x == null)){ // unexpected dependency var a: Int @dependency("Implicit") @@ -67,7 +66,7 @@ method exhaleImprecision(){ @dependency("Implicit") x.f := x.f + 1 - // @irrelevant("Implicit") // TODO ake: unexpected dependency + @irrelevant("Implicit") // unexpected dependency exhale acc(x.f, 1/2) @testAssertion("Explicit") @@ -96,7 +95,7 @@ method quantifiedPerm3(xs: Seq[Ref], ys: Seq[Ref]) { @dependency("Explicit") inhale (forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0)) - // @irrelevant("Explicit") // TODO ake: unexpected dependency (wildcards) + @irrelevant("Explicit") // unexpected dependency (wildcards) inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard) && y.f > 0) @testAssertion("Explicit") @@ -114,13 +113,13 @@ method quantifiedPerm4(xs: Seq[Ref], ys: Seq[Ref]) assume none < p1 && p1 < 1/2 var p2: Perm - // @irrelevant("Explicit") - assume none < p2 && p2 < 1/2 // TODO ake: unexpected dependency + @irrelevant("Explicit") + assume none < p2 && p2 < 1/2 // unexpected dependency @dependency("Explicit") inhale (forall x: Ref :: x in xs ==> (acc(x.f, p1) && x.f > 0)) - // @irrelevant("Explicit") // TODO ake: unexpected dependency + @irrelevant("Explicit") // unexpected dependency inhale forall y: Ref :: y in ys ==> (acc(y.f, p2) && y.f > 0) @testAssertion("Explicit") @@ -134,7 +133,7 @@ method quasihavoc1(x: Ref, y: Ref) requires @dependency("Explicit")(x.f == 3) requires @dependency("Explicit")(x != y) { - // @irrelevant() // TODO ake + @irrelevant() // unexpected dependency quasihavoc y.f // does nothing. we have no permission @testAssertion("Explicit") assert x.f == 3 @@ -154,14 +153,16 @@ method loop1(){ @dependency("Implicit") res := 0 + @irrelevant("Implicit") i := 10 while(@dependency("PathCondition")(i > 0)) invariant @irrelevant("LoopInvariant")(i <= 10) - invariant i >= 0 + invariant @irrelevant("LoopInvariant")(i >= 0) invariant @dependency("LoopInvariant")(res >= 0) { @dependency("Implicit") res := res + i + @irrelevant("Implicit") i := i - 1 } @testAssertion("Explicit") @@ -176,9 +177,23 @@ method inhaleImprecision(){ @dependency("Explicit") inhale x.f > 0 - //@irrelevant("Implicit") TODO ake: imprecision? + @irrelevant("Implicit") inhale acc(x.f, 1/2) @testAssertion("Explicit") assert x.f > 0 +} + +method packageExhale2(x: Ref) + requires @dependency("Explicit")(acc(x.f)) + { + + @irrelevant("Rewrite") + package acc(x.f, 1/2) --* acc(x.f) + + @irrelevant("Rewrite") + apply acc(x.f, 1/2) --* acc(x.f) + + @testAssertion("Explicit") + exhale acc(x.f) } \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/method-sum.vpr b/src/test/resources/dependencyAnalysisTests/all/method-sum.vpr index 90f7e38b3..a6f02efad 100644 --- a/src/test/resources/dependencyAnalysisTests/all/method-sum.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/method-sum.vpr @@ -18,13 +18,15 @@ method sumClient(x: Int, y: Int) assume y >= 0 @dependency("Explicit") assume x < y + var n: Int @dependency("Implicit") - var n: Int := sum(x, y) + n := sum(x, y) // the following stmt reports dependency on n := sum(x, y) because (x < y && n == x + y && n > 100 ==> y != 0) // although you could also prove it via (0 <= x && x < y ==> y != 0) + var n2: Int @dependency("Implicit") - var n2: Int := sum(x/y, y) + n2 := sum(x/y, y) @dependency("Implicit") n := n + n2 @@ -40,12 +42,15 @@ method sumClient2(x: Int, y: Int) assume y >= 0 @irrelevant("Explicit") assume x < y + var n: Int @irrelevant("Implicit") - var n: Int := sum(x, x) + n := sum(x, x) + @irrelevant() assert n >= 100 + var n2: Int @dependency("Implicit") - var n2: Int := sum(y, y) + n2 := sum(y, y) @testAssertion("Explicit") assert n2 == 2*y } \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/presentation-examples.vpr b/src/test/resources/dependencyAnalysisTests/all/presentation-examples.vpr index 9b2294f48..41bad87fd 100644 --- a/src/test/resources/dependencyAnalysisTests/all/presentation-examples.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/presentation-examples.vpr @@ -22,6 +22,6 @@ method foo(x: Ref, y: Ref) @dependency("Explicit") assume x.f < y.f - @testAssertion("Explicit") + @testAssertion("Explicit") assert x.f / y.f <= 1 } \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/sum-loops.vpr b/src/test/resources/dependencyAnalysisTests/all/sum-loops.vpr index f48e96ad8..0c39e9653 100644 --- a/src/test/resources/dependencyAnalysisTests/all/sum-loops.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/sum-loops.vpr @@ -34,7 +34,7 @@ method sumPerm1(n: Int, res: Ref) res.f := 0 var i: Int @dependency("Implicit") - i := 0; + i := 0 while(@dependency("PathCondition")(i <= n)) invariant @dependency("LoopInvariant")(acc(res.f)) invariant @dependency("LoopInvariant")(i <= (n + 1)) diff --git a/src/test/resources/dependencyAnalysisTests/all/wands.vpr b/src/test/resources/dependencyAnalysisTests/all/wands.vpr deleted file mode 100644 index 387440037..000000000 --- a/src/test/resources/dependencyAnalysisTests/all/wands.vpr +++ /dev/null @@ -1,82 +0,0 @@ -field next : Ref -field val : Int - -predicate list(start : Ref) -{ - acc(start.val) && acc(start.next) && - (start.next != null ==> list(start.next)) -} - -function elems(start: Ref) : Seq[Int] - requires list(start) -{ - unfolding list(start) in ( - (start.next == null ? Seq(start.val) : - Seq(start.val) ++ elems(start.next) )) -} - -method appendit_wand1(l1 : Ref, l2: Ref) - requires @dependency("Explicit")(list(l1)) - requires @dependency("Explicit")(list(l2)) - requires l2 != null - ensures list(l1) - { - @dependency("Implicit") - unfold list(l1) - if(l1.next == null) { // easy case - @dependency("Implicit") - l1.next := l2 - @testAssertion("Implicit") - fold list(l1) - } else { - @irrelevant("Explicit") - assume false - } - } - -// TODO ake -// method appendit_wand2(l1 : Ref, l2: Ref) -// requires @dependency()(list(l1)) -// requires @dependency()(list(l2)) -// requires l2 != null -// ensures list(l1) -// { -// @dependency() -// unfold list(l1) -// if(l1.next == null) { // easy case -// @irrelevant() -// l1.next := l2 -// @irrelevant() -// fold list(l1) -// } else { -// var tmp : Ref := l1.next -// var index : Int := 1 - -// package list(tmp) --* list(l1) && elems(l1) == old(elems(l1)[..index]) ++ old[lhs](elems(tmp)) -// { -// fold list(l1) -// } - -// while(unfolding list(tmp) in tmp.next != null) -// invariant index >= 0 -// invariant list(tmp) && elems(tmp) == old(elems(l1))[index..] -// invariant list(tmp) --* list(l1) && elems(l1) == old(elems(l1)[..index]) ++ old[lhs](elems(tmp)) -// { -// unfold list(tmp) -// var prev : Ref := tmp -// tmp := tmp.next -// index := index + 1 - -// package list(tmp) --* list(l1) && elems(l1) == old(elems(l1)[..index]) ++ old[lhs](elems(tmp)) -// { -// fold list(prev) -// apply list(prev) --* list(l1) && elems(l1) == old(elems(l1)[..index-1]) ++ old[lhs](elems(prev)) -// } -// } -// unfold list(tmp) -// tmp.next := l2 -// fold list(tmp) -// apply list(tmp) --* list(l1) && elems(l1) == old(elems(l1)[..index]) ++ old[lhs](elems(tmp)) - -// } -// } \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_benchmark_plotter.py b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_benchmark_plotter.py index 809a2d484..7662da5bf 100644 --- a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_benchmark_plotter.py +++ b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_benchmark_plotter.py @@ -12,8 +12,8 @@ def read_results_file(result_file_path: str) -> dict[tuple[str, str], list[tuple line = line.strip() print(line) if not line or line.startswith("!") or line.startswith("Failed") \ - or line.startswith("Program caused an exception") \ - or line.startswith("Program does not verify"): + or "Skip" in line \ + or "does not verify" in line: continue if line.startswith("dependencyAnalysisTests/precisionTests/"): # Save previous block if any diff --git a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_test_generator.py b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_test_generator.py index b6be2eb18..cc0488633 100644 --- a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_test_generator.py +++ b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_test_generator.py @@ -39,9 +39,9 @@ def extract_vars(line: str): for decl in var_decls: tmp = decl.split("=") if(tmp[0] == "$READ_ONLY"): - read_only_vars = read_only_vars + tmp[1].split(",") + read_only_vars = read_only_vars + [v.replace(";", ",") for v in tmp[1].split(",")] elif(tmp[0] == "$READ_WRITE"): - read_write_vars = read_write_vars + tmp[1].split(",") + read_write_vars = read_write_vars + [v.replace(";", ",") for v in tmp[1].split(",")] elif(tmp[0] == "$INVARIANT"): invariant = "=".join(tmp[1:]).replace("$_$", " ") elif(tmp[0] == "$ACC_INVARIANT"): diff --git a/src/test/resources/dependencyAnalysisTests/new/meeting.vpr b/src/test/resources/dependencyAnalysisTests/new/meeting.vpr index 70c73ff02..2b7235fea 100644 --- a/src/test/resources/dependencyAnalysisTests/new/meeting.vpr +++ b/src/test/resources/dependencyAnalysisTests/new/meeting.vpr @@ -1,5 +1,20 @@ field f: Int +method packageExhale2(x: Ref) + requires @dependency("Explicit")(acc(x.f)) + { + + @irrelevant("Rewrite") + package acc(x.f, 1/2) --* acc(x.f) + + @irrelevant("Rewrite") + apply acc(x.f, 1/2) --* acc(x.f) + + @testAssertion("Explicit") + exhale acc(x.f) +} + + method exhaleImprecision(){ var x: Ref diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/branches.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/branches.vpr index cc98ede27..858d92046 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/branches.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/branches.vpr @@ -9,9 +9,9 @@ method branch1(){ }else{ @dependency("Implicit") y := -x + 1 - } - // $PrecisionTest: $READ_WRITE=x $READ_ONLY=y $INVARIANT=y>0 + // $PrecisionTest: $READ_WRITE=x $READ_ONLY=y $INVARIANT=y>-1 + } @testAssertion("Explicit") assert y > 0 @@ -28,7 +28,7 @@ method branch2(){ assume y >= 10 } - // $PrecisionTest: $READ_WRITE=x $READ_ONLY=y $INVARIANT=y>0 + // $PrecisionTest: $READ_WRITE=x $READ_ONLY=y $INVARIANT=y>-1 @testAssertion("Explicit") assert y > 0 diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/domain.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/domain.vpr index ec1674987..20c8c9c76 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/domain.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/domain.vpr @@ -1,3 +1,4 @@ +field f: Int field val: Int define access(a) (forall j: Int :: 0 <= j && j < len(a) ==> acc(slot(a,j).val)) @@ -23,27 +24,39 @@ domain IArray { method domain1() { - // Create an integer array with three elements var a: IArray @irrelevant("Explicit") inhale len(a) == 3 - // $PrecisionTest: $ACC_INVARIANT=access(a) $INVARIANT=len(a)==3 + // $PrecisionTest: $INVARIANT=len(a)>0 @testAssertion("Implicit") - inhale access(a) // access to all array slots + inhale access(a) } method domain2() { - // Create an integer array with three elements + var a: IArray + @dependency("Explicit") + inhale len(a) == 3 + + @dependency("Explicit") + inhale access(a) + + // $PrecisionTest: $READ_WRITE=slot(a;0).val $INVARIANT=0= 0 && y >= 0 + ensures result >= 0 +{ + x + y +} + +function sumUnverified(x: Int, y: Int): Int + requires x >= 0 && y >= 0 + ensures result >= 0 + ensures result == x + y + +method call1(){ + var x: Int, y: Int + @dependency("Explicit") + assume x > 10 + @dependency("Explicit") + assume y > 0 + + // $PrecisionTest: $READ_ONLY=x,y $INVARIANT=y>-5 + + @testAssertion("Implicit") + x := sum(x, y) +} + +method call2(){ + var x: Int, y: Int, z: Int + @dependency("Explicit") + assume x > 10 + @dependency("Explicit") + assume y > 0 + + @dependency("Implicit") + z := sum(x, y) + + // $PrecisionTest: $READ_ONLY=x,y,z $INVARIANT=x>0 + + @testAssertion("Explicit") + assert z == x + y +} + +method call3(x: Int, y: Int) +{ + @dependency("Explicit") + assume x > 0 + @dependency("Explicit") + assume y > 0 + + var n: Int, n2: Int + @dependency("Implicit") + n := sum(x, y) + + // $PrecisionTest: $READ_WRITE=n2 $READ_ONLY=x,y,n $INVARIANT=x>0 + + @dependency("Implicit") + n2 := sum(x, y) + @dependency("Implicit") + n := n + n2 + + @testAssertion("Explicit") + assert n == 2*x + 2*y +} + + +method call4(x: Int, y: Int, z: Int) +{ + @dependency("Explicit") + assume x > 0 + @dependency("Explicit") + assume y > 0 + + @irrelevant("Explicit") + assume z > 0 + + var n: Int, n2: Int + @dependency("Explicit") + n := sumUnverified(x, y) + + // $PrecisionTest: $READ_WRITE=n2 $READ_ONLY=n,z,x,y $INVARIANT=x>0 + + @irrelevant("Explicit") + n2 := sumUnverified(x, z) + + @testAssertion("Explicit") + assert n == x + y +} diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/inhaleExhale.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/inhaleExhale.vpr index 765675104..c051d043d 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/inhaleExhale.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/inhaleExhale.vpr @@ -14,20 +14,6 @@ method exhaleInhale(a: Ref) assert perm(a.f) == write } -method exhaleInhale2(a: Ref) - requires @dependency("Explicit")(acc(a.f)) -{ - @irrelevant("Implicit") - exhale acc(a.f, 1/2) - @irrelevant("Explicit") - inhale acc(a.f, 1/2) - - // $PrecisionTest: $READ_WRITE=a.f $ACC_INVARIANT=acc(a.f) - - @testAssertion("Explicit") - assert perm(a.f) >= 1/2 -} - method exhaleImprecision(){ var x: Ref diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/loops.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/loops.vpr index d556e6b74..acac71194 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/loops.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/loops.vpr @@ -5,16 +5,16 @@ method loop1(){ var res: Int @dependency("Implicit") res := 0 - // @irrelevant("Implicit") // imprecise + @irrelevant("Implicit") i := 10 while(@dependency("PathCondition")(i > 0)) invariant @irrelevant("LoopInvariant")(i <= 10) - invariant i >= 0 + invariant @irrelevant("LoopInvariant")(i >= 0) invariant @dependency("LoopInvariant")(res >= 0) { @dependency("Implicit") res := res + i - // @irrelevant("Implicit") // imprecise + @irrelevant("Implicit") i := i - 1 } @@ -38,7 +38,7 @@ method loop2(){ { @irrelevant("Implicit") res := res + i - // $PrecisionTest: $READ_ONLY=res,i $INVARIANT=i>-1 + // $PrecisionTest: $READ_ONLY=res,i $INVARIANT=i<20 @dependency("Implicit") i := i - 1 @testAssertion("Explicit") @@ -51,7 +51,7 @@ method loop3(){ var res: Int @dependency("Implicit") res := 0 - // $PrecisionTest: $READ_ONLY=res $READ_WRITE=i $INVARIANT=res>-1 + // $PrecisionTest: $READ_ONLY=res $READ_WRITE=i $INVARIANT=res<10 @irrelevant("Implicit") i := 10 while(@dependency("PathCondition")(i > 0)) @@ -72,7 +72,6 @@ method loop4(){ var i: Int var res: Int - // $PrecisionTest: $READ_WRITE=i,res @dependency("Implicit") diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr index 070a5c0bd..22eda1362 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr @@ -20,7 +20,7 @@ method basicApply() // $PrecisionTest: $READ_ONLY=x,y $INVARIANT=x>0 - @dependency("Implicit") + @dependency("Rewrite") apply x > 0 --* y > 0 @testAssertion("Explicit") @@ -38,16 +38,16 @@ method basicPackage(l: Ref) @dependency("Implicit") tmp := l.next - @dependency("Implicit") + // $PrecisionTest: $READ_WRITE=l.val $ACC_INVARIANT=acc(l.val)&&acc(l.next) + + @dependency("Rewrite") package list(tmp) --* list(l) { @dependency() fold list(l) } - // $PrecisionTest: // TODO ake - - @dependency("Implicit") + @dependency("Rewrite") apply list(tmp) --* list(l) @testAssertion("Explicit") @@ -62,7 +62,7 @@ predicate greater0(a: Int){ method advancedPackage(a: Int, b: Int) { - @dependency("Implicit") + @dependency("Rewrite") package (greater0(a) && greater0(b)) --* greater0(a + b) { @dependency("Implicit") @@ -78,9 +78,9 @@ method advancedPackage(a: Int, b: Int) @dependency("Explicit") inhale greater0(b) - // $PrecisionTest: $READ_ONLY=a,b $INVARIANT=unfolding$_$greater0(b)$_$in$_$b>0 $ACC_INVARIANT=greater0(b) + // $PrecisionTest: $READ_ONLY=a,b $INVARIANT=(unfolding$_$greater0(b)$_$in$_$b>0) $ACC_INVARIANT=greater0(b) - @dependency("Implicit") + @dependency("Rewrite") apply (greater0(a) && greater0(b)) --* greater0(a + b) @testAssertion("Explicit") @@ -93,10 +93,10 @@ method quantifiedWand(xs: Seq[Int], b: Int) { @dependency("Explicit") inhale greater0(xs[0]) - @dependency("Implicit") + @dependency("Rewrite") apply greater0(xs[0]) --* greater0(xs[0] + b) - // $PrecisionTest: $READ_ONLY=xs[0],b,xs[1] $INVARIANT=unfolding$_$greater0(xs[0]+b)$_$in$_$xs[0]+b>0 $ACC_INVARIANT=greater0(xs[0]+b) // TODO ake: sequence update + // $PrecisionTest: $READ_ONLY=xs[0],b,xs[1] $INVARIANT=(unfolding$_$greater0(xs[0]+b)$_$in$_$xs[0]+b>0) $ACC_INVARIANT=greater0(xs[0]+b) // TODO ake: sequence update @testAssertion("Explicit") assert greater0(xs[0] + b) @@ -105,7 +105,7 @@ method quantifiedWand(xs: Seq[Int], b: Int) method packagePrecision(l: Ref) requires @dependency("Explicit")(list(l)) { - @dependency("Implicit") + @dependency("Rewrite") unfold list(l) var tmp : Ref @dependency("Implicit") @@ -113,10 +113,10 @@ method packagePrecision(l: Ref) // $PrecisionTest: $READ_WRITE=l.val $ACC_INVARIANT=acc(l.val) - @irrelevant("Implicit") + @irrelevant("Rewrite") package list(tmp) --* list(l) { - @irrelevant("Implicit") + @irrelevant("Rewrite") fold list(l) } @@ -128,28 +128,13 @@ method packageExhale(x: Ref) requires @dependency("Explicit")(acc(x.f)) { - // $PrecisionTest: $READ_WRITE=x.f $ACC_INVARIANT=acc(x.f) - @dependency("Implicit") package acc(x.f, 1/2) --* acc(x.f) + // $PrecisionTest: $READ_ONLY=x.f $ACC_INVARIANT=acc(x.f,1/2) + @testAssertion("Explicit") assert perm(x.f) == 1/2 } -method packageExhale2(x: Ref) - requires @dependency("Explicit")(acc(x.f)) - { - - @dependency("Implicit") // TODO ake: irrelevant? - package acc(x.f, 1/2) --* acc(x.f) - - // $PrecisionTest: $READ_ONLY=x.f $ACC_INVARIANT=acc(x.f,1/2) - - @dependency("Implicit") - apply acc(x.f, 1/2) --* acc(x.f) - - @testAssertion("Explicit") - exhale acc(x.f) -} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/method-call.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/method-call.vpr index 6df02906a..8a3192c00 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/method-call.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/method-call.vpr @@ -7,6 +7,21 @@ method sum(x: Int, y: Int) returns(res: Int) res := x + y } +method sumUnverified(x: Int, y: Int) returns(res: Int) + requires x >= 0 && y >= 0 + ensures res == x + y + ensures res >= 0 + +method abs(x: Ref) + requires acc(x.f) + ensures acc(x.f) + ensures x.f >= 0 +{ + if(x.f < 0){ + x.f := -x.f + } +} + method call1(){ var x: Int, y: Int @dependency("Explicit") @@ -14,7 +29,7 @@ method call1(){ @dependency("Explicit") assume y > 0 - // $PrecisionTest: $READ_ONLY=x,y $INVARIANT=x>0 + // $PrecisionTest: $READ_ONLY=x,y $INVARIANT=y>-5 @testAssertion("Implicit") x := sum(x, y) @@ -44,13 +59,13 @@ method call3(x: Int, y: Int) assume y > 0 var n: Int, n2: Int - @dependency("Implicit") - n := sum(x, y) + @dependency("Explicit") + n := sumUnverified(x, y) // $PrecisionTest: $READ_WRITE=n2 $READ_ONLY=x,y,n $INVARIANT=x>0 - @dependency("Implicit") - n2 := sum(x, y) + @dependency("Explicit") + n2 := sumUnverified(x, y) @dependency("Implicit") n := n + n2 @@ -81,3 +96,14 @@ method call4(x: Int, y: Int, z: Int) @testAssertion("Explicit") assert n == x + y } + +method absClient1(x: Ref) + requires @dependency("Explicit")(acc(x.f)) + requires @irrelevant("Explicit")(x.f > 0) +{ + @dependency("Implicit") + abs(x) + + @testAssertion("Explicit") + assert x.f >= 0 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/misc.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/misc.vpr index 219039519..b10d85a56 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/misc.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/misc.vpr @@ -7,7 +7,7 @@ method divBy0(a: Ref, n: Int) { var res: Int - // $PrecisionTest: $READ_WRITE=a.f,res $READ_ONLY=n $INVARIANT=n>0 + // $PrecisionTest: $READ_WRITE=a.f,res $READ_ONLY=n $INVARIANT=n>0 $ACC_INVARIANT=acc(a.f) @testAssertion("Implicit") res := a.f / n diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/permWildcard.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/permWildcard.vpr index 379411b50..b208441c3 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/permWildcard.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/permWildcard.vpr @@ -4,9 +4,9 @@ field g: Int method wildcardPerm(x: Ref, y: Ref) requires @dependency("Explicit")(acc(x.f, wildcard)) requires @irrelevant("Explicit")(acc(y.f, wildcard)) - requires x != y // could be a dependency but doesn't have to + requires @irrelevant("Explicit")(x != y) { - // $PrecisionTest: $READ_ONLY=x.f,y.f + // $PrecisionTest: $READ_ONLY=x.f,y.f $ACC_INVARIANT=acc(x.f,wildcard)&&acc(y.f,wildcard) @testAssertion("Implicit") inhale x.f > 0 @@ -17,7 +17,7 @@ method wildcardPermDistinctFields(x: Ref, y: Ref) requires @dependency("Explicit")(acc(x.f, wildcard)) requires @irrelevant("Explicit")(acc(y.g, wildcard)) { - // $PrecisionTest: $READ_ONLY=x.f,y.g + // $PrecisionTest: $READ_ONLY=x.f,y.g $ACC_INVARIANT=acc(x.f,wildcard)&&acc(y.g,wildcard) @testAssertion("Implicit") inhale x.f > 0 @@ -32,7 +32,7 @@ method quantifiedPermWildcard(xs: Seq[Ref], ys: Seq[Ref]) @irrelevant("Explicit") inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard)) - // $PrecisionTest: $READ_ONLY=xs[0].f,xs[1].f,ys[0].f,ys[1].f + // $PrecisionTest: $READ_ONLY=xs[0].f,ys[0].f $ACC_INVARIANT=acc(xs[0].f,wildcard)&&acc(ys[0].f,wildcard) @testAssertion("Explicit") assert xs[0].f > 0 diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr index 8ff4cb97e..f26fe6cb6 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr @@ -53,7 +53,7 @@ method perm4(){ @dependency("Implicit") x.f := x.f + 1 - // $PrecisionTest: $READ_ONLY=x.f $INVARIANT=x.f>1 $ACC_INVARIANT=acc(x.f,1/2) + // $PrecisionTest: $READ_ONLY=x.f $INVARIANT=x.f>0 $ACC_INVARIANT=acc(x.f,1/2) @testAssertion("Explicit") assert x.f > 1 @@ -72,48 +72,15 @@ method perm5(){ exhale acc(x.f) } -method fieldAccessPrecision(){ - var x: Ref - var y: Ref - - @dependency("Explicit") - inhale acc(x.f) - @dependency("Explicit") - inhale x.f > 0 - - @irrelevant("Explicit") - inhale acc(y.f) - @irrelevant("Implicit") - y.f := y.f + 1 - - @testAssertion("Explicit") - assert x.f >= 0 -} - -method fieldAccessPrecision2(){ - var x: Ref - var y: Ref - - @dependency("Explicit") - inhale acc(x.f) - @irrelevant("Explicit") - inhale x.f > 0 - - @irrelevant("Explicit") - inhale acc(y.f) - @irrelevant("Implicit") - x.f := y.f + 1 - - @testAssertion("Explicit") - exhale acc(x.f) -} - method permAmount1(x: Ref, p: Perm) requires @dependency("Explicit")(p > none) requires @dependency("Explicit")(acc(x.f, p)) { @dependency("Explicit") assume p > 1/2 + + // $PrecisionTest: $READ_ONLY=x.f $ACC_INVARIANT=p>1/2&&acc(x.f,p) + @testAssertion("Explicit") exhale acc(x.f, 1/2) } @@ -126,6 +93,10 @@ method permAmount1(x: Ref, p: Perm) inhale x.f > 0 @dependency("Explicit") assume p > 1/2 + + // $PrecisionTest: $READ_ONLY=x.f $ACC_INVARIANT=p>1/2&&acc(x.f,p/2) $INVARIANT=x.f>-10 + + @irrelevant("Implicit") exhale acc(x.f, 1/2) @testAssertion("Explicit") assert x.f > 0 diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/predicates.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/predicates.vpr index ba1da95da..5c7bbb9e2 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/predicates.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/predicates.vpr @@ -47,7 +47,7 @@ method unfoldFoldP(a: Int, b: Int) @dependency("Implicit") unfold greater5(b) - // $PrecisionTest: $READ_ONLY=a,b $INVARIANT=a>0 + // $PrecisionTest: $READ_ONLY=a,b $INVARIANT=b>0 @testAssertion("Implicit") fold greater5(a + b) @@ -60,7 +60,7 @@ method callWithPredicate(a: Int, b: Int) @dependency("Implicit") unfoldFoldP(a, b) - // $PrecisionTest: $READ_ONLY=a,b $INVARIANT=unfolding$_$greater5(a+b)$_$in$_$a+b>0 $ACC_INVARIANT=greater5(a+b) + // $PrecisionTest: $READ_ONLY=a,b $INVARIANT=(unfolding$_$greater5(a+b)$_$in$_$a+b>0) $ACC_INVARIANT=greater5(a+b) @testAssertion("Explicit") assert greater5(a + b) @@ -71,13 +71,14 @@ method unfoldPrecision(a: Int, b: Int) requires @dependency("Explicit")(greater5(b)) ensures greater0(b) { + + // $PrecisionTest: $READ_ONLY=a,b $INVARIANT=(unfolding$_$greater0(a)$_$in$_$a>0) $ACC_INVARIANT=greater0(a) + @irrelevant("Implicit") unfold greater0(a) @dependency("Implicit") unfold greater5(b) - // $PrecisionTest: $READ_ONLY=a,b $INVARIANT=b>0 - @testAssertion("Implicit") fold greater0(b) } @@ -86,7 +87,7 @@ method foldPrecision(a: Int, b: Int) requires @irrelevant("Explicit")(a > 5) requires @dependency("Explicit")(b > 5) { - // $PrecisionTest: $READ_ONLY=a,b $INVARIANT=a>0 + // $PrecisionTest: $READ_ONLY=a,b $INVARIANT=b>2 @testAssertion("Implicit") fold greater0(b) diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr index 0f13eb710..4d713976e 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr @@ -39,13 +39,14 @@ method quantifiedPerm3(xs: Seq[Ref], y: Ref) { method quantifiedExhalePartiallyTest(xs: Seq[Ref]) { var res: Int + @irrelevant("Explicit") assume |xs| > 5 inhale @dependency("Explicit")(forall x: Ref :: x in xs ==> acc(x.f)) @irrelevant("Explicit") inhale xs[0] != xs[4] - // $PrecisionTest: $READ_WRITE=xs[0].f,xs[4].f $INVARIANT=|xs|>0 $ACC_INVARIANT=forall$_$x:Ref::x$_$in$_$xs$_$==>$_$acc(x.f,1/2) + // $PrecisionTest: $READ_WRITE=xs[0].f,xs[4].f $INVARIANT=|xs|>0 $ACC_INVARIANT=forall$_$x:Ref::x$_$in$_$xs$_$==>$_$acc(x.f) @testAssertion("Explicit") exhale forall x: Ref :: x in xs ==> acc(x.f, 1/2) @@ -56,9 +57,10 @@ method quantifiedExhalePartially(xs: Seq[Ref]) { assume @dependency("Explicit")(|xs| > 5) inhale @dependency("Explicit")(forall x: Ref :: x in xs ==> acc(x.f)) + @irrelevant("Explicit") exhale forall x: Ref :: x in xs ==> acc(x.f, 1/2) - // $PrecisionTest: $READ_WRITE=res $READ_ONLY=xs[1].f,xs[0].f,xs[2].f $INVARIANT=|xs|>0 $ACC_INVARIANT=forall$_$x:Ref::x$_$in$_$xs$_$==>$_$acc(x.f) + // $PrecisionTest: $READ_WRITE=res $READ_ONLY=xs[1].f,xs[0].f,xs[2].f $INVARIANT=|xs|>0 $ACC_INVARIANT=forall$_$x:Ref::x$_$in$_$xs$_$==>$_$acc(x.f,1/2) @testAssertion("Implicit") res := xs[1].f + 1 @@ -83,7 +85,6 @@ method quantifiedPermWrite1(xs: Seq[Ref]) { @dependency("Explicit") inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) - @irrelevant("Explicit") inhale xs[0] != xs[2] @@ -102,11 +103,10 @@ method quantifiedPermWrite2(xs: Seq[Ref]) { @dependency("Implicit") xs[0].f := 0 - @irrelevant("Explicit") inhale xs[0] != xs[2] - // $PrecisionTest: $READ_WRITE=xs[2].f $READ_ONLY=xs[0].f,xs[1].f $INVARIANT=xs[0].f<3 $ACC_INVARIANT=|xs|>5&&(forall$_$x:Ref::x$_$in$_$xs$_$==>$_$acc(x.f)) + // $PrecisionTest: $READ_ONLY=xs[0].f,xs[1].f $INVARIANT=xs[0].f<3 $ACC_INVARIANT=|xs|>5&&(forall$_$x:Ref::x$_$in$_$xs$_$==>$_$acc(x.f,1/4)) @testAssertion("Explicit") assert xs[0].f == 0 @@ -121,7 +121,7 @@ method quantifiedPermWrite3(xs: Seq[Ref]) { @irrelevant("Implicit") xs[0].f := 0 - // $PrecisionTest: $READ_WRITE=xs[0].f $READ_ONLY=xs[1].f,xs[2].f $INVARIANT=xs[1].f>=0 $ACC_INVARIANT=|xs|>1&&(forall$_$x:Ref::x$_$in$_$xs$_$==>$_$acc(x.f)) + // $PrecisionTest: $READ_WRITE=xs[0].f $READ_ONLY=xs[1].f,xs[2].f $INVARIANT=xs[1].f>=0 $ACC_INVARIANT=|xs|>1&&acc(xs[0].f,3/4)&&(forall$_$x:Ref::x$_$in$_$xs$_$==>$_$acc(x.f,1/4)) @testAssertion("Explicit") assert xs[0] != xs[1] ==> xs[1].f > 0 diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index 781f4546f..02a5fb377 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -4,7 +4,6 @@ import viper.silicon.SiliconFrontend import viper.silicon.assumptionAnalysis._ import viper.silver.ast._ import viper.silver.ast.utility.ViperStrategy -import viper.silver.ast.utility.rewriter.Traverse import viper.silver.frontend.SilFrontend import viper.silver.verifier.VerificationResult import viper.silver.{ast, verifier} @@ -276,7 +275,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { */ case class AnnotatedTest(program: Program, assumptionAnalysisInterpreters: List[AssumptionAnalysisInterpreter]) { def execute(): Unit = { - val stmtsWithAssumptionAnnotation: Set[Infoed] = extractAnnotatedStmts({ annotationInfo => annotationInfo.values.contains(irrelevantKeyword) || annotationInfo.values.contains(dependencyKeyword) }) + val stmtsWithAssumptionAnnotation: Set[Infoed] = extractAnnotatedStmts({ annotationInfo => annotationInfo.values.contains(irrelevantKeyword + "(\"") || annotationInfo.values.contains(dependencyKeyword) }) val allAssumptionNodes = assumptionAnalysisInterpreters.flatMap(_.getNonInternalAssumptionNodes) var errorMsgs = stmtsWithAssumptionAnnotation.map(checkAssumptionNodeExists(allAssumptionNodes, _)).filter(_.isDefined).map(_.get).toSeq From e23fabdbcf270ced88864dd56d60435fc35e3a46 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 28 Jul 2025 12:24:48 +0200 Subject: [PATCH 212/474] performance optimization --- .../AssumptionAnalysisGraph.scala | 53 ++++++++++++------- .../AssumptionAnalysisInterpreter.scala | 19 ++++--- .../AssumptionAnalysisUserTool.scala | 4 +- .../AssumptionAnalyzer.scala | 8 +-- src/test/scala/AssumptionAnalysisTests.scala | 4 +- 5 files changed, 54 insertions(+), 34 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index 9167ee50b..20d3bc818 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -15,11 +15,12 @@ object AssumptionAnalysisGraphHelper { trait ReadOnlyAssumptionAnalysisGraph { def getNodes: Seq[AssumptionAnalysisNode] - def getDirectEdges: Map[Int, Set[Int]] - def getTransitiveEdges: Map[Int, Set[Int]] - def getAllEdges: Map[Int, Set[Int]] + def getDirectEdges: Map[Int, Set[Int]] // target -> direct dependencies + def getTransitiveEdges: Map[Int, Set[Int]] // target -> direct dependencies + def getAllEdges: Map[Int, Set[Int]] // target -> direct dependencies def existsAnyDependency(sources: Set[Int], targets: Set[Int], includeInfeasibilityNodes: Boolean): Boolean + def getAllDependencies(sources: Set[Int], includeInfeasibilityNodes: Boolean): Set[Int] def exportGraph(dirName: String): Unit } @@ -50,29 +51,29 @@ class AssumptionAnalysisGraph extends ReadOnlyAssumptionAnalysisGraph { } def addEdges(source: Int, targets: Iterable[Int]): Unit = { - val oldTargets = edges.getOrElse(source, Set.empty) - val newTargets = targets.filter(_ != source) - if(newTargets.nonEmpty) - edges.update(source, oldTargets ++ newTargets) + addEdges(Set(source), targets) } def addEdges(sources: Iterable[Int], target: Int): Unit = { - addEdges(sources, Set(target)) + val oldSources = edges.getOrElse(target, Set.empty) + val newSources = sources.filter(_ != target) + if(newSources.nonEmpty) + edges.update(target, oldSources ++ newSources) } def addEdges(sources: Iterable[Int], targets: Iterable[Int]): Unit = { - sources foreach (addEdges(_, targets)) + targets foreach (addEdges(sources, _)) } def existsAnyDependency(sources: Set[Int], targets: Set[Int], includeInfeasibilityNodes: Boolean): Boolean = { val infeasibilityNodeIds: Set[Int] = if(includeInfeasibilityNodes) Set.empty else (getNodes filter (_.isInstanceOf[InfeasibilityNode]) map (_.id)).toSet - var visited: Set[Int] = sources - var queue: List[Int] = sources.toList + var visited: Set[Int] = targets + var queue: List[Int] = targets.toList val allEdges = getAllEdges while(queue.nonEmpty){ val curr = queue.head val newVisits = allEdges.getOrElse(curr, Set()).diff(infeasibilityNodeIds) - if(newVisits.intersect(targets).nonEmpty) + if(newVisits.intersect(sources).nonEmpty) return true visited = visited ++ Set(curr) queue = queue.tail ++ (newVisits filter (!visited.contains(_))) @@ -80,14 +81,28 @@ class AssumptionAnalysisGraph extends ReadOnlyAssumptionAnalysisGraph { false } - private def addTransitiveEdges(source: AssumptionAnalysisNode, targets: Iterable[AssumptionAnalysisNode]): Unit = { - val oldTargets = transitiveEdges.getOrElse(source.id, Set.empty) - val newTargets = targets map(_.id) // filter(_ > source.id) does not work due to loop invariants - if(newTargets.nonEmpty) transitiveEdges.update(source.id, oldTargets ++ newTargets) + def getAllDependencies(targets: Set[Int], includeInfeasibilityNodes: Boolean): Set[Int] = { + val infeasibilityNodeIds: Set[Int] = if(includeInfeasibilityNodes) Set.empty else (getNodes filter (_.isInstanceOf[InfeasibilityNode]) map (_.id)).toSet + var visited: Set[Int] = targets + var queue: List[Int] = targets.toList + val allEdges = getAllEdges + while(queue.nonEmpty){ + val curr = queue.head + val newVisits = allEdges.getOrElse(curr, Set()).diff(infeasibilityNodeIds) + visited = visited ++ Set(curr) + queue = queue.tail ++ (newVisits filter (!visited.contains(_))) + } + visited // TODO ake: cache result? + } + + private def addTransitiveEdges(sources: Iterable[AssumptionAnalysisNode], target: AssumptionAnalysisNode): Unit = { + val oldSources = transitiveEdges.getOrElse(target.id, Set.empty) + val newSources = sources map(_.id) // filter(_ > target.id) does not work due to loop invariants + if(newSources.nonEmpty) transitiveEdges.update(target.id, oldSources ++ newSources) } - private def addTransitiveEdges(source: Iterable[AssumptionAnalysisNode], targets: Iterable[AssumptionAnalysisNode]): Unit = { - source foreach (s => addTransitiveEdges(s, targets)) + private def addTransitiveEdges(sources: Iterable[AssumptionAnalysisNode], targets: Iterable[AssumptionAnalysisNode]): Unit = { + targets foreach (t => addTransitiveEdges(sources, t)) } // TODO ake: maybe move to AssumptionAnalyzer? @@ -114,7 +129,7 @@ class AssumptionAnalysisGraph extends ReadOnlyAssumptionAnalysisGraph { val successors = edges.getOrElse(id, Set.empty) edges.remove(id) predecessors foreach (pid => edges.update(pid, edges.getOrElse(pid, Set.empty).filter(_ != id))) - addEdges(predecessors, successors) + addEdges(successors, predecessors) } // TODO ake: maybe move to AssumptionAnalyzer? diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala index 29ce5f6c4..8be8f64b1 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala @@ -22,15 +22,20 @@ class AssumptionAnalysisInterpreter(name: String, graph: ReadOnlyAssumptionAnaly def getNodesByPosition(file: String, line: Int): Set[AssumptionAnalysisNode] = getNodes.filter(node => node.sourceInfo.getLineNumber.isDefined && node.sourceInfo.getLineNumber.get == line && node.sourceInfo.getPositionString.startsWith(file + ".vpr")) - def getDirectDependencies(nodeIdsToAnalyze: Set[Int]): Set[AssumptionAnalysisNode] = - getNonInternalAssumptionNodes.filter(node => graph.getDirectEdges.get(node.id).exists(_.intersect(nodeIdsToAnalyze).nonEmpty)) - - def getAllNonInternalDependencies(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[AssumptionAnalysisNode] = - getNonInternalAssumptionNodes.filter(node => graph.existsAnyDependency(Set(node.id), nodeIdsToAnalyze, includeInfeasibilityNodes)) + def getDirectDependencies(nodeIdsToAnalyze: Set[Int]): Set[AssumptionAnalysisNode] = { + val directDependencyIds = nodeIdsToAnalyze flatMap (id => graph.getDirectEdges.getOrElse(id, Set.empty)) + getNonInternalAssumptionNodes.filter(node => directDependencyIds.contains(node.id)) + } - def getAllExplicitDependencies(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[AssumptionAnalysisNode] = - getExplicitAssumptionNodes.filter(node => graph.existsAnyDependency(Set(node.id), nodeIdsToAnalyze, includeInfeasibilityNodes)) + def getAllNonInternalDependencies(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[AssumptionAnalysisNode] = { + val allDependencies = graph.getAllDependencies(nodeIdsToAnalyze, includeInfeasibilityNodes) // TODO ake: cache result? + getNonInternalAssumptionNodes.filter(node => allDependencies.contains(node.id)) + } + def getAllExplicitDependencies(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[AssumptionAnalysisNode] = { + val allDependencies = graph.getAllDependencies(nodeIdsToAnalyze, includeInfeasibilityNodes) // TODO ake: cache result? + getExplicitAssumptionNodes.filter(node => allDependencies.contains(node.id)) + } def getAllNonInternalDependents(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[AssumptionAnalysisNode] = getNonInternalAssertionNodes.filter(node => graph.existsAnyDependency(nodeIdsToAnalyze, Set(node.id), includeInfeasibilityNodes)) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala index c0c62a8a8..f6629759b 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala @@ -82,7 +82,7 @@ class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpr val allDependencies = getSourceInfoString(fullGraphInterpreter.getAllNonInternalDependencies(queriedNodes.map(_.id))) val allDependenciesWithoutInfeasibility = getSourceInfoString(fullGraphInterpreter.getAllNonInternalDependencies(queriedNodes.map(_.id), includeInfeasibilityNodes=false)) val explicitDependencies = getSourceInfoString(fullGraphInterpreter.getAllExplicitDependencies(queriedNodes.map(_.id))) - val dependents = getSourceInfoString(fullGraphInterpreter.getAllNonInternalDependents(queriedNodes.map(_.id))) +// val dependents = getSourceInfoString(fullGraphInterpreter.getAllNonInternalDependents(queriedNodes.map(_.id))) println(s"Queried:\n\t${queriedNodes.map(_.sourceInfo.getTopLevelSource.toString).mkString("\n\t")}") @@ -91,7 +91,7 @@ class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpr println(s"\nDependencies without infeasibility:\n\t$allDependenciesWithoutInfeasibility") println(s"\nExplicit Dependencies:\n\t$explicitDependencies") - println(s"\nAll Dependents:\n\t$dependents") +// println(s"\nAll Dependents:\n\t$dependents") TODO ake println(s"\nDone.") } } diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 97969a07b..2a634fc6f 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -98,7 +98,7 @@ object AssumptionAnalyzer { val newGraph = new AssumptionAnalysisGraph assumptionAnalysisInterpreters foreach (interpreter => newGraph.addNodes(interpreter.getGraph.getNodes)) - assumptionAnalysisInterpreters foreach (interpreter => interpreter.getGraph.getAllEdges foreach {case (s, t) => newGraph.addEdges(s, t)}) + assumptionAnalysisInterpreters foreach (interpreter => interpreter.getGraph.getAllEdges foreach {case (t, deps) => newGraph.addEdges(deps, t)}) // add edges between identical axioms since they were added to each interpreter // TODO ake: merge instead? newGraph.getNodes.filter(_.isInstanceOf[AxiomAssumptionNode]).groupBy(n => (n.sourceInfo.toString, n.assumptionType)).foreach{case (_, nodes) => @@ -315,9 +315,9 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { } } - assumptionGraph.getAllEdges foreach { case (source, targets) => - val newSource = nodeMap(source) - mergedGraph.addEdges(newSource, targets.map(nodeMap(_))) + assumptionGraph.getAllEdges foreach { case (target, deps) => + val newTarget = nodeMap(target) + mergedGraph.addEdges(deps.map(nodeMap(_)), newTarget) } mergedGraph diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index 02a5fb377..5d7027e31 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -178,9 +178,9 @@ class AssumptionAnalysisTests extends AnyFunSuite { writer.println() println(s"Precision Benchmark for $filePrefix - $fileName done.") }catch{ - case e: Exception => + case t: Throwable => writer.println("Failed. Skip") - println(s"Exception caught: ${e.getMessage}") + println(s"Exception caught: ${t.getMessage}") } } From 0d98a972c389616e40efbc65ffca7f29eceb553f Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 28 Jul 2025 13:10:58 +0200 Subject: [PATCH 213/474] minor fix - unverified function calls --- src/main/scala/rules/Evaluator.scala | 2 +- src/main/scala/rules/Joiner.scala | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index ff413eb3e..46f0ab5a4 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -878,7 +878,7 @@ object evaluator extends EvaluationRules { * Hence, the joinedFApp will take two arguments, namely, i*i and i, * although the latter is not necessary. */ - joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s1a, v1)((s2, v2, QB) => { + joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s1a, v1, assumptionType=assumptionType)((s2, v2, QB) => { val pres = func.pres.map(_.transform { /* [Malte 2018-08-20] Two examples of the test suite, one of which is the regression * for Carbon issue #210, fail if the subsequent code that strips out triggers from diff --git a/src/main/scala/rules/Joiner.scala b/src/main/scala/rules/Joiner.scala index 006b48d31..f6e045088 100644 --- a/src/main/scala/rules/Joiner.scala +++ b/src/main/scala/rules/Joiner.scala @@ -6,6 +6,7 @@ package viper.silicon.rules +import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType, StringAnalysisSourceInfo} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp @@ -35,7 +36,7 @@ case class JoinDataEntry[D](s: State, data: D, pathConditions: RecordedPathCondi trait JoiningRules extends SymbolicExecutionRules { - def join[D, JD](s: State, v: Verifier, resetState: Boolean = true) + def join[D, JD](s: State, v: Verifier, resetState: Boolean = true, assumptionType: AssumptionType = AssumptionType.Implicit) (block: (State, Verifier, (State, D, Verifier) => VerificationResult) => VerificationResult) (merge: Seq[JoinDataEntry[D]] => (State, JD)) (Q: (State, JD, Verifier) => VerificationResult) @@ -43,7 +44,7 @@ trait JoiningRules extends SymbolicExecutionRules { } object joiner extends JoiningRules { - def join[D, JD](s: State, v: Verifier, resetState: Boolean = true) + def join[D, JD](s: State, v: Verifier, resetState: Boolean = true, assumptionType: AssumptionType = AssumptionType.Implicit) (block: (State, Verifier, (State, D, Verifier) => VerificationResult) => VerificationResult) (merge: Seq[JoinDataEntry[D]] => (State, JD)) (Q: (State, JD, Verifier) => VerificationResult) @@ -103,13 +104,13 @@ object joiner extends JoiningRules { val pcsExp = Option.when(withExp)(entry.pathConditions.conditionalizedExp) val comment = "Joined path conditions" v.decider.prover.comment(comment) - v.decider.assume(pcs, Option.when(withExp)(DebugExp.createInstance(comment, InsertionOrderedSet(pcsExp.get))), enforceAssumption = false, AssumptionType.Implicit) + v.decider.assume(pcs, Option.when(withExp)(DebugExp.createInstance(comment, InsertionOrderedSet(pcsExp.get))), enforceAssumption = false, assumptionType) feasibleBranches = And(entry.pathConditions.branchConditions) :: feasibleBranches feasibleBranchesExp = feasibleBranchesExp.map(fbe => BigAnd(entry.pathConditions.branchConditionExps.map(_._1)) :: fbe) feasibleBranchesExpNew = feasibleBranchesExpNew.map(fbe => BigAnd(entry.pathConditions.branchConditionExps.map(_._2.get)) :: fbe) }) // Assume we are in a feasible branch - v.decider.assume(Or(feasibleBranches), Option.when(withExp)(DebugExp.createInstance(Some("Feasible Branches"), feasibleBranchesExp.map(BigOr(_)), feasibleBranchesExpNew.map(BigOr(_)), InsertionOrderedSet.empty)), AssumptionType.Implicit) + v.decider.assume(Or(feasibleBranches), Option.when(withExp)(DebugExp.createInstance(Some("Feasible Branches"), feasibleBranchesExp.map(BigOr(_)), feasibleBranchesExpNew.map(BigOr(_)), InsertionOrderedSet.empty)), assumptionType) Q(sJoined, dataJoined, v) } } From 26018aeb687fd8aaf15d450aab26acf19f3cc639 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 28 Jul 2025 18:05:41 +0200 Subject: [PATCH 214/474] fixes --- .../AnalysisSourceInfo.scala | 2 +- .../AssumptionAnalysisInterpreter.scala | 2 +- .../AssumptionAnalysisUserTool.scala | 30 ++++++++++++++++++- .../all/function-sum.vpr | 2 +- .../precision_test_generator.py | 2 +- .../benchmark_scripts/snippets.txt | 3 +- .../unitTests/branches.vpr | 4 ++- src/test/scala/AssumptionAnalysisTests.scala | 3 +- 8 files changed, 40 insertions(+), 8 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala index 8c842f466..cdfb7b189 100644 --- a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala +++ b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala @@ -158,7 +158,7 @@ case class AnalysisSourceInfoStack() { def getForcedSource: Option[AnalysisSourceInfo] = forcedMainSource def setForcedSource(description: String): Unit = { - forcedMainSource = Some(StringAnalysisSourceInfo(description, getFullSourceInfo.getPosition)) + forcedMainSource = Some(StringAnalysisSourceInfo(description, NoPosition)) } def setForcedSource(source: AnalysisSourceInfo): Unit = { diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala index 8be8f64b1..a201e5d7e 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala @@ -77,7 +77,7 @@ class AssumptionAnalysisInterpreter(name: String, graph: ReadOnlyAssumptionAnaly def computeProofCoverage(assertionNodes: Set[AssumptionAnalysisNode]): (Double, Seq[String]) = { val assertionNodeIds = assertionNodes map (_.id) - val nodesPerSourceInfo = getNonInternalAssumptionNodesPerSource + val nodesPerSourceInfo = getNonInternalAssumptionNodes.filterNot(_.isInstanceOf[AxiomAssumptionNode]).groupBy(_.sourceInfo.getTopLevelSource.toString) if(nodesPerSourceInfo.isEmpty) return (Double.NaN, Seq()) val uncoveredSources = (nodesPerSourceInfo filter { case (_, nodes) => diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala index f6629759b..bf5b9afff 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala @@ -10,7 +10,8 @@ import viper.silver.ast.Method class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpreter, memberInterpreters: Seq[AssumptionAnalysisInterpreter]) { private val infoString = "Enter " + "\n\t'dep [line numbers]' to print all dependencies of the given line numbers or" + - "\n\t'cov [member names]' to print proof coverage of given member names or" + + "\n\t'cov [members]' to print proof coverage of given member or" + + "\n\t'covL member [line numbers]' to print proof coverage of given lines of given member or" + "\n\t'q' to quit" def run(): Unit = { @@ -40,6 +41,8 @@ class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpr handleDependencyQuery(inputParts.tail.toSet) } else if (inputParts(0).equalsIgnoreCase("coverage") || inputParts(0).equalsIgnoreCase("cov")) { handleProofCoverageQuery(inputParts.tail) + }else if (inputParts(0).equalsIgnoreCase("covLines") || inputParts(0).equalsIgnoreCase("covL")) { + handleProofCoverageLineQuery(inputParts.tail) } else { println("Invalid input.") println(infoString) @@ -63,6 +66,31 @@ class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpr }) } + private def handleProofCoverageLineQuery(memberNames: Seq[String]): Unit = { + if(memberNames.isEmpty) return // TODO ake: invalid input handling + + println("Proof Coverage") + val lines = memberNames.tail.flatMap(_.toIntOption) + memberInterpreters.filter(aa => aa.getMember.isDefined && aa.getMember.exists { + case meth: Method => meth.body.isDefined && meth.name.equalsIgnoreCase(memberNames.head) + case func: ast.Function => func.body.isDefined && func.name.equalsIgnoreCase(memberNames.head) + case _ => false + }) + .foreach(aa => { + val (coverage, uncoveredSources) = if(lines.nonEmpty){ + val assertions = lines flatMap aa.getNodesByLine + aa.computeProofCoverage(assertions.toSet) + }else{ + aa.computeProofCoverage() + } + println(s"${aa.getMember.map(_.name).getOrElse("")}") + println(s"coverage: $coverage") + if (!coverage.equals(1.0)) + println(s"uncovered nodes:\n\t${uncoveredSources.mkString("\n\t")}") + println() + }) + } + private def handleDependencyQuery(inputs: Set[String]): Unit = { def getSourceInfoString(nodes: Set[AssumptionAnalysisNode]) = { nodes.map(_.sourceInfo.getTopLevelSource).toList.sortBy(_.getLineNumber).mkString("\n\t") diff --git a/src/test/resources/dependencyAnalysisTests/all/function-sum.vpr b/src/test/resources/dependencyAnalysisTests/all/function-sum.vpr index fb4ae0f9d..5e8e0d89d 100644 --- a/src/test/resources/dependencyAnalysisTests/all/function-sum.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/function-sum.vpr @@ -36,7 +36,7 @@ method call2Unv(){ @dependency("Explicit") assume y > 0 - @dependency("Implicit") + @dependency("Explicit") z := sumUnverified(x, y) // $PrecisionTest: $READ_ONLY=x,y,z $INVARIANT=x>0 diff --git a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_test_generator.py b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_test_generator.py index cc0488633..01c35409e 100644 --- a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_test_generator.py +++ b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_test_generator.py @@ -86,7 +86,7 @@ def generate_from_snippet(snippet: str, line: str): generated_refs = set(gen_rw_refs + gen_ro_refs + [v.split(".")[0] for v in (gen_vars_rw_field + gen_vars_ro_field) if "." in v]) existing_refs = set([v.split(".")[0] for v in (read_write_vars+read_only_vars) if "." in v]) # snippet = f"\n//generated: {generated_refs}\n//existing: {existing_refs}\n" + snippet - snippet = "\n".join([f"@irrelevant(\"Explicit\")\ninhale {a} != {b}" for a in generated_refs for b in existing_refs if a != b]) + snippet + snippet = "\n".join([f"inhale {a} != {b}" for a in generated_refs for b in existing_refs if a != b]) + snippet gen_vars_ro_field = gen_vars_ro_field + [f"{v}.f" for v in gen_ro_refs] gen_vars_rw_field = gen_vars_rw_field + [f"{v}.f" for v in gen_rw_refs] diff --git a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/snippets.txt b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/snippets.txt index 79437f3f8..411732464 100644 --- a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/snippets.txt +++ b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/snippets.txt @@ -132,7 +132,7 @@ SNIPPET nested_branch$=${ } SNIPPET infeasible_branch$=${ - if(@dependency("PathCondition")($INVARIANT)){ + if(@irrelevant("PathCondition")($INVARIANT)){ @irrelevant("Implicit") $RW_INT_0 := 10 }else{ @@ -295,6 +295,7 @@ SNIPPET quasihavoc$=${ SNIPPET quasihavoc_disjoint_ref$=${ var gen_x: Ref + @irrelevant("Implicit") gen_x := new(f) @irrelevant("Implicit") quasihavoc gen_x.f diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/branches.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/branches.vpr index 858d92046..997949032 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/branches.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/branches.vpr @@ -43,8 +43,10 @@ method branch3(){ @dependency("Implicit") y := x + 1 }else{ + @irrelevant("Implicit") + y := 10 @irrelevant("Explicit") - assume y >= 10 + assume y > 0 } @testAssertion("Explicit") diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index 5d7027e31..87dcb07a4 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -22,7 +22,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { val EXECUTE_PRECISION_BENCHMARK = false val EXECUTE_TEST = true val EXECUTE_PERFORMANCE_BENCHMARK = false - val ignores: Seq[String] = Seq("example1", "example2", "graph-copy", "listAppend_wands", "iterativeTreeDelete") + val ignores: Seq[String] = Seq("example1", "example2", "iterativeTreeDelete") val testDirectories: Seq[String] = Seq( "dependencyAnalysisTests/all", "dependencyAnalysisTests/unitTests", @@ -30,6 +30,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { // "dependencyAnalysisTests/quick" // "dependencyAnalysisTests/fromSilver", // "dependencyAnalysisTests/performanceBenchmark" +// "dependencyAnalysisTests/precisionTests" ) val irrelevantKeyword = "irrelevant" From eb319e925cedf83803c5512235dd244f17593bf4 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 29 Jul 2025 11:23:53 +0200 Subject: [PATCH 215/474] minor fixes --- .../AssumptionAnalysisInterpreter.scala | 2 +- .../scala/assumptionAnalysis/AssumptionAnalysisNode.scala | 4 ++-- .../scala/assumptionAnalysis/AssumptionAnalyzer.scala | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala index a201e5d7e..45bce7f98 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala @@ -20,7 +20,7 @@ class AssumptionAnalysisInterpreter(name: String, graph: ReadOnlyAssumptionAnaly getNodes.filter(node => node.sourceInfo.getLineNumber.isDefined && node.sourceInfo.getLineNumber.get == line) def getNodesByPosition(file: String, line: Int): Set[AssumptionAnalysisNode] = - getNodes.filter(node => node.sourceInfo.getLineNumber.isDefined && node.sourceInfo.getLineNumber.get == line && node.sourceInfo.getPositionString.startsWith(file + ".vpr")) + getNodes.filter(node => node.sourceInfo.getLineNumber.isDefined && node.sourceInfo.getLineNumber.get == line && node.sourceInfo.getPositionString.startsWith(file + ".")) def getDirectDependencies(nodeIdsToAnalyze: Set[Int]): Set[AssumptionAnalysisNode] = { val directDependencyIds = nodeIdsToAnalyze flatMap (id => graph.getDirectEdges.getOrElse(id, Set.empty)) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisNode.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisNode.scala index a7dc52489..c3bdfff04 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisNode.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisNode.scala @@ -2,7 +2,7 @@ package viper.silicon.assumptionAnalysis import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType import viper.silicon.interfaces.state.Chunk -import viper.silicon.state.terms.{False, Term} +import viper.silicon.state.terms.{False, Term, Var} trait AssumptionAnalysisNode { val id: Int = AssumptionAnalysisGraphHelper.nextId() @@ -60,7 +60,7 @@ case class PermissionExhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSo override def getNodeString: String = "exhale " + chunk.toString } -case class LabelNode(term: Term) extends GeneralAssumptionNode { +case class LabelNode(term: Var) extends GeneralAssumptionNode { val sourceInfo: AnalysisSourceInfo = NoAnalysisSourceInfo() val assumptionType: AssumptionType = AssumptionType.Internal val isClosed: Boolean = true diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 2a634fc6f..43d579c52 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -31,7 +31,7 @@ trait AssumptionAnalyzer { def addAxiom(assumption: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, description: Option[String] = None): Option[Int] def registerInhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, labelNode: Option[LabelNode], analysisInfo: AnalysisInfo, isExhale: Boolean): CH = buildChunk(perm) def registerExhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, analysisInfo: AnalysisInfo): CH = buildChunk(perm) - def createLabelNode(labelTerm: Term, sourceChunks: Iterable[Chunk], sourceTerms: Iterable[Term]): Option[LabelNode] + def createLabelNode(label: Var, sourceChunks: Iterable[Chunk], sourceTerms: Iterable[Term]): Option[LabelNode] def createAssertOrCheckNode(term: Term, assumptionType: AssumptionType, analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] def addAssertFalseNode(isCheck: Boolean, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo): Option[Int] @@ -197,8 +197,8 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { Some(node.id) } - override def createLabelNode(labelTerm: Term, sourceChunks: Iterable[Chunk], sourceTerms: Iterable[Term]): Option[LabelNode] = { - val labelNode = LabelNode(labelTerm) + override def createLabelNode(label: Var, sourceChunks: Iterable[Chunk], sourceTerms: Iterable[Term]): Option[LabelNode] = { + val labelNode = LabelNode(label) addNode(labelNode) assumptionGraph.addEdges(getChunkNodeIds(sourceChunks.toSet) ++ getNodeIdsByTerm(sourceTerms.toSet), labelNode.id) Some(labelNode) @@ -335,7 +335,7 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { override def addNode(node: AssumptionAnalysisNode): Unit = {} override def addAssumption(assumption: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, description: Option[String] = None): Option[Int] = None override def addAxiom(assumption: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, description: Option[String]): Option[Int] = None - override def createLabelNode(labelTerm: Term, sourceChunks: Iterable[Chunk], sourceTerms: Iterable[Term]): Option[LabelNode] = None + override def createLabelNode(labelTerm: Var, sourceChunks: Iterable[Chunk], sourceTerms: Iterable[Term]): Option[LabelNode] = None override def createAssertOrCheckNode(term: Term, assumptionType: AssumptionType, analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] = None override def addAssertFalseNode(isCheck: Boolean, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo): Option[Int] = None From 7ce3861debe88b01478f4cdb2cc53bb2d881d992 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 29 Jul 2025 12:06:59 +0200 Subject: [PATCH 216/474] make user tool reports prettier --- .../AssumptionAnalysisInterpreter.scala | 25 ++++++++++++------- .../AssumptionAnalysisUserTool.scala | 10 ++++---- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala index 45bce7f98..dbd144f40 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala @@ -37,6 +37,9 @@ class AssumptionAnalysisInterpreter(name: String, graph: ReadOnlyAssumptionAnaly getExplicitAssumptionNodes.filter(node => allDependencies.contains(node.id)) } + def filterOutNodesBySourceInfo(nodes: Set[AssumptionAnalysisNode], excludeInfos: Set[AnalysisSourceInfo]): Set[AssumptionAnalysisNode] = + nodes filterNot (node => excludeInfos.exists(i => i.getTopLevelSource.toString.equals(node.sourceInfo.getTopLevelSource.toString))) + def getAllNonInternalDependents(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[AssumptionAnalysisNode] = getNonInternalAssertionNodes.filter(node => graph.existsAnyDependency(nodeIdsToAnalyze, Set(node.id), includeInfeasibilityNodes)) @@ -70,22 +73,26 @@ class AssumptionAnalysisInterpreter(name: String, graph: ReadOnlyAssumptionAnaly graph.exportGraph(Verifier.config.assumptionAnalysisExportPath() + "/" + name) } - def computeProofCoverage(): (Double, Seq[String]) = { - val explicitAssertionNodes = getExplicitAssertionNodes + private def getNodesWithIdenticalSource(nodes: Set[AssumptionAnalysisNode]): Set[AssumptionAnalysisNode] = { + val sourceInfos = nodes map (_.sourceInfo.getTopLevelSource.toString) + getNodes filter (node => sourceInfos.contains(node.sourceInfo.getTopLevelSource.toString)) + } + + def computeProofCoverage(): (Double, Set[String]) = { + val explicitAssertionNodes = getNodesWithIdenticalSource(getExplicitAssertionNodes) computeProofCoverage(explicitAssertionNodes) } - def computeProofCoverage(assertionNodes: Set[AssumptionAnalysisNode]): (Double, Seq[String]) = { + def computeProofCoverage(assertionNodes: Set[AssumptionAnalysisNode]): (Double, Set[String]) = { val assertionNodeIds = assertionNodes map (_.id) + val dependencies = graph.getAllDependencies(assertionNodeIds, includeInfeasibilityNodes=true) + val coveredNodes = dependencies ++ assertionNodeIds val nodesPerSourceInfo = getNonInternalAssumptionNodes.filterNot(_.isInstanceOf[AxiomAssumptionNode]).groupBy(_.sourceInfo.getTopLevelSource.toString) - if(nodesPerSourceInfo.isEmpty) return (Double.NaN, Seq()) + if(nodesPerSourceInfo.isEmpty) return (Double.NaN, Set()) val uncoveredSources = (nodesPerSourceInfo filter { case (_, nodes) => - val nodeIds = nodes map (_.id) - // it is not an explicit assertion itself and has no dependency to an explicit assertion - nodeIds.intersect(assertionNodeIds).isEmpty && - !graph.existsAnyDependency(nodeIds, assertionNodeIds, includeInfeasibilityNodes=true) - }).keys.toSeq + coveredNodes.intersect(nodes map (_.id)).isEmpty + }).keys.toSet val proofCoverage = 1.0 - (uncoveredSources.size.toDouble / nodesPerSourceInfo.size.toDouble) (proofCoverage, uncoveredSources) } diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala index bf5b9afff..eb386899c 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala @@ -93,7 +93,7 @@ class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpr private def handleDependencyQuery(inputs: Set[String]): Unit = { def getSourceInfoString(nodes: Set[AssumptionAnalysisNode]) = { - nodes.map(_.sourceInfo.getTopLevelSource).toList.sortBy(_.getLineNumber).mkString("\n\t") + nodes.groupBy(node => node.sourceInfo.getTopLevelSource.toString).map{case (_, nodes) => nodes.head.sourceInfo.getTopLevelSource}.toList.sortBy(_.getLineNumber).mkString("\n\t") } val queriedNodes = inputs flatMap (input => { @@ -106,10 +106,10 @@ class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpr Set.empty } }) - val directDependencies = getSourceInfoString(fullGraphInterpreter.getDirectDependencies(queriedNodes.map(_.id))) - val allDependencies = getSourceInfoString(fullGraphInterpreter.getAllNonInternalDependencies(queriedNodes.map(_.id))) - val allDependenciesWithoutInfeasibility = getSourceInfoString(fullGraphInterpreter.getAllNonInternalDependencies(queriedNodes.map(_.id), includeInfeasibilityNodes=false)) - val explicitDependencies = getSourceInfoString(fullGraphInterpreter.getAllExplicitDependencies(queriedNodes.map(_.id))) + val directDependencies = getSourceInfoString(fullGraphInterpreter.filterOutNodesBySourceInfo(fullGraphInterpreter.getDirectDependencies(queriedNodes.map(_.id)), queriedNodes.map(_.sourceInfo))) + val allDependencies = getSourceInfoString(fullGraphInterpreter.filterOutNodesBySourceInfo(fullGraphInterpreter.getAllNonInternalDependencies(queriedNodes.map(_.id)), queriedNodes.map(_.sourceInfo))) + val allDependenciesWithoutInfeasibility = getSourceInfoString(fullGraphInterpreter.filterOutNodesBySourceInfo(fullGraphInterpreter.getAllNonInternalDependencies(queriedNodes.map(_.id), includeInfeasibilityNodes=false), queriedNodes.map(_.sourceInfo))) + val explicitDependencies = getSourceInfoString(fullGraphInterpreter.filterOutNodesBySourceInfo(fullGraphInterpreter.getAllExplicitDependencies(queriedNodes.map(_.id)), queriedNodes.map(_.sourceInfo))) // val dependents = getSourceInfoString(fullGraphInterpreter.getAllNonInternalDependents(queriedNodes.map(_.id))) println(s"Queried:\n\t${queriedNodes.map(_.sourceInfo.getTopLevelSource.toString).mkString("\n\t")}") From cba7b3aebe7cb315278e818114731ae85c464374 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 1 Aug 2025 09:29:32 +0200 Subject: [PATCH 217/474] add more queries to command line tool --- .../AnalysisSourceInfo.scala | 6 +- .../AssumptionAnalysisGraph.scala | 20 +++- .../AssumptionAnalysisInterpreter.scala | 18 +++- .../AssumptionAnalysisUserTool.scala | 97 ++++++++++++++----- 4 files changed, 105 insertions(+), 36 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala index cdfb7b189..79fa66ac7 100644 --- a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala +++ b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala @@ -52,7 +52,7 @@ case class NoAnalysisSourceInfo() extends AnalysisSourceInfo { } case class ExpAnalysisSourceInfo(source: ast.Exp) extends AnalysisSourceInfo { - override def toString: String = (if(source.info.getSourceString.isEmpty) source.toString else source.info.getSourceString) + + override def toString: String = (if(source.info.getSourceString.isEmpty) source.toString else source.info.getSourceString).replaceAll("\n", "\t") + " (" + super.toString + ")" override def getPosition: Position = source.pos @@ -69,7 +69,7 @@ case class ExpAnalysisSourceInfo(source: ast.Exp) extends AnalysisSourceInfo { } case class StmtAnalysisSourceInfo(source: ast.Stmt) extends AnalysisSourceInfo { - override def toString: String = (if(source.info.getSourceString.isEmpty) source.toString() else source.info.getSourceString) + + override def toString: String = (if(source.info.getSourceString.isEmpty) source.toString() else source.info.getSourceString).replaceAll("\n", "\t") + " (" + super.toString + ")" override def getPosition: Position = source.pos @@ -85,7 +85,7 @@ case class StmtAnalysisSourceInfo(source: ast.Stmt) extends AnalysisSourceInfo { } case class StringAnalysisSourceInfo(description: String, position: Position) extends AnalysisSourceInfo { - override def toString: String = description + " (" + super.toString + ")" + override def toString: String = description.replaceAll("\n", "\t") + " (" + super.toString + ")" override def getPosition: Position = position } diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index 20d3bc818..1e548204e 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -21,6 +21,7 @@ trait ReadOnlyAssumptionAnalysisGraph { def existsAnyDependency(sources: Set[Int], targets: Set[Int], includeInfeasibilityNodes: Boolean): Boolean def getAllDependencies(sources: Set[Int], includeInfeasibilityNodes: Boolean): Set[Int] + def getAllDependents(sources: Set[Int], includeInfeasibilityNodes: Boolean): Set[Int] def exportGraph(dirName: String): Unit } @@ -67,7 +68,7 @@ class AssumptionAnalysisGraph extends ReadOnlyAssumptionAnalysisGraph { def existsAnyDependency(sources: Set[Int], targets: Set[Int], includeInfeasibilityNodes: Boolean): Boolean = { val infeasibilityNodeIds: Set[Int] = if(includeInfeasibilityNodes) Set.empty else (getNodes filter (_.isInstanceOf[InfeasibilityNode]) map (_.id)).toSet - var visited: Set[Int] = targets + var visited: Set[Int] = Set.empty var queue: List[Int] = targets.toList val allEdges = getAllEdges while(queue.nonEmpty){ @@ -83,7 +84,7 @@ class AssumptionAnalysisGraph extends ReadOnlyAssumptionAnalysisGraph { def getAllDependencies(targets: Set[Int], includeInfeasibilityNodes: Boolean): Set[Int] = { val infeasibilityNodeIds: Set[Int] = if(includeInfeasibilityNodes) Set.empty else (getNodes filter (_.isInstanceOf[InfeasibilityNode]) map (_.id)).toSet - var visited: Set[Int] = targets + var visited: Set[Int] = Set.empty var queue: List[Int] = targets.toList val allEdges = getAllEdges while(queue.nonEmpty){ @@ -92,7 +93,20 @@ class AssumptionAnalysisGraph extends ReadOnlyAssumptionAnalysisGraph { visited = visited ++ Set(curr) queue = queue.tail ++ (newVisits filter (!visited.contains(_))) } - visited // TODO ake: cache result? + visited + } + + def getAllDependents(sources: Set[Int], includeInfeasibilityNodes: Boolean): Set[Int] = { + val infeasibilityNodeIds: Set[Int] = if(includeInfeasibilityNodes) Set.empty else (getNodes filter (_.isInstanceOf[InfeasibilityNode]) map (_.id)).toSet + var visited: Set[Int] = Set.empty + var queue: Set[Int] = sources + val allEdges = getAllEdges + while(queue.nonEmpty){ + val newVisits = allEdges.filter{case (t, s) => s.intersect(queue).nonEmpty && !infeasibilityNodeIds.contains(t)}.keys.toSet + visited = visited ++ queue + queue = newVisits diff visited + } + visited } private def addTransitiveEdges(sources: Iterable[AssumptionAnalysisNode], target: AssumptionAnalysisNode): Unit = { diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala index 45bce7f98..20ba79b64 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala @@ -28,18 +28,24 @@ class AssumptionAnalysisInterpreter(name: String, graph: ReadOnlyAssumptionAnaly } def getAllNonInternalDependencies(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[AssumptionAnalysisNode] = { - val allDependencies = graph.getAllDependencies(nodeIdsToAnalyze, includeInfeasibilityNodes) // TODO ake: cache result? + val allDependencies = graph.getAllDependencies(nodeIdsToAnalyze, includeInfeasibilityNodes).diff(nodeIdsToAnalyze) getNonInternalAssumptionNodes.filter(node => allDependencies.contains(node.id)) } def getAllExplicitDependencies(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[AssumptionAnalysisNode] = { - val allDependencies = graph.getAllDependencies(nodeIdsToAnalyze, includeInfeasibilityNodes) // TODO ake: cache result? + val allDependencies = graph.getAllDependencies(nodeIdsToAnalyze, includeInfeasibilityNodes).diff(nodeIdsToAnalyze) getExplicitAssumptionNodes.filter(node => allDependencies.contains(node.id)) } - def getAllNonInternalDependents(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[AssumptionAnalysisNode] = - getNonInternalAssertionNodes.filter(node => graph.existsAnyDependency(nodeIdsToAnalyze, Set(node.id), includeInfeasibilityNodes)) + def getAllNonInternalDependents(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[AssumptionAnalysisNode] = { + val allDependents = graph.getAllDependents(nodeIdsToAnalyze, includeInfeasibilityNodes).diff(nodeIdsToAnalyze) + getNonInternalAssumptionNodes.filter(node => allDependents.contains(node.id)) + } + def getAllExplicitDependents(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[AssumptionAnalysisNode] = { + val allDependents = graph.getAllDependents(nodeIdsToAnalyze, includeInfeasibilityNodes).diff(nodeIdsToAnalyze) + getExplicitAssertionNodes.filter(node => allDependents.contains(node.id)) + } def getNonInternalAssumptionNodes: Set[AssumptionAnalysisNode] = getNodes filter (node => (node.isInstanceOf[GeneralAssumptionNode] && !AssumptionType.internalTypes.contains(node.assumptionType)) @@ -50,6 +56,10 @@ class AssumptionAnalysisInterpreter(name: String, graph: ReadOnlyAssumptionAnaly AssumptionType.explicitAssumptionTypes.contains(node.assumptionType) ) + def hasAnyDependency(nodesToAnalyze: Set[AssumptionAnalysisNode], includeInfeasibilityNodes: Boolean = true): Boolean = + nodesToAnalyze.intersect(getNonInternalAssumptionNodes) + .exists(node => graph.existsAnyDependency(Set(node.id), nodesToAnalyze map (_.id) filter (_ != node.id), includeInfeasibilityNodes)) + private def getNonInternalAssumptionNodesPerSource: Map[String, Set[AssumptionAnalysisNode]] = getNonInternalAssumptionNodes.groupBy(_.sourceInfo.getTopLevelSource.toString) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala index bf5b9afff..a639f1485 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala @@ -10,6 +10,8 @@ import viper.silver.ast.Method class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpreter, memberInterpreters: Seq[AssumptionAnalysisInterpreter]) { private val infoString = "Enter " + "\n\t'dep [line numbers]' to print all dependencies of the given line numbers or" + + "\n\t'downDep [line numbers]' to print all dependents of the given line numbers or" + + "\n\t'hasDep [line numbers]' to print whether there exists any dependency between any pair of the given lines or" + "\n\t'cov [members]' to print proof coverage of given member or" + "\n\t'covL member [line numbers]' to print proof coverage of given lines of given member or" + "\n\t'q' to quit" @@ -36,12 +38,16 @@ class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpr } private def handleUserInput(userInput: String): Unit = { - val inputParts = userInput.split(" ") - if (inputParts(0).equalsIgnoreCase("d") || inputParts(0).equalsIgnoreCase("dep")) { + val inputParts = userInput.split(" ").toSeq + if (inputParts.head.equalsIgnoreCase("dep")) { handleDependencyQuery(inputParts.tail.toSet) - } else if (inputParts(0).equalsIgnoreCase("coverage") || inputParts(0).equalsIgnoreCase("cov")) { + } else if (inputParts.head.equalsIgnoreCase("downDep")) { + handleDependentsQuery(inputParts.tail.toSet) + } else if (inputParts.head.equalsIgnoreCase("hasDep")) { + handleHasDependencyQuery(inputParts.tail.toSet) + } else if (inputParts.head.equalsIgnoreCase("coverage") || inputParts.head.equalsIgnoreCase("cov")) { handleProofCoverageQuery(inputParts.tail) - }else if (inputParts(0).equalsIgnoreCase("covLines") || inputParts(0).equalsIgnoreCase("covL")) { + }else if (inputParts.head.equalsIgnoreCase("covLines") || inputParts.head.equalsIgnoreCase("covL")) { handleProofCoverageLineQuery(inputParts.tail) } else { println("Invalid input.") @@ -57,8 +63,8 @@ class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpr case _ => false }) .foreach(aa => { - val (coverage, uncoveredSources) = aa.computeProofCoverage() - println(s"${aa.getMember.map(_.name).getOrElse("")}") + val ((coverage, uncoveredSources), time) = measureTime(aa.computeProofCoverage()) + println(s"${aa.getMember.map(_.name).getOrElse("")} (${time}ms)") println(s"coverage: $coverage") if (!coverage.equals(1.0)) println(s"uncovered nodes:\n\t${uncoveredSources.mkString("\n\t")}") @@ -77,13 +83,13 @@ class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpr case _ => false }) .foreach(aa => { - val (coverage, uncoveredSources) = if(lines.nonEmpty){ + val ((coverage, uncoveredSources), time) = if(lines.nonEmpty){ val assertions = lines flatMap aa.getNodesByLine - aa.computeProofCoverage(assertions.toSet) + measureTime(aa.computeProofCoverage(assertions.toSet)) }else{ - aa.computeProofCoverage() + measureTime(aa.computeProofCoverage()) } - println(s"${aa.getMember.map(_.name).getOrElse("")}") + println(s"${aa.getMember.map(_.name).getOrElse("")} (${time}ms)") println(s"coverage: $coverage") if (!coverage.equals(1.0)) println(s"uncovered nodes:\n\t${uncoveredSources.mkString("\n\t")}") @@ -91,12 +97,12 @@ class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpr }) } - private def handleDependencyQuery(inputs: Set[String]): Unit = { - def getSourceInfoString(nodes: Set[AssumptionAnalysisNode]) = { - nodes.map(_.sourceInfo.getTopLevelSource).toList.sortBy(_.getLineNumber).mkString("\n\t") - } + private def getSourceInfoString(nodes: Set[AssumptionAnalysisNode]) = { + nodes.map(_.sourceInfo.getTopLevelSource).toList.sortBy(_.getLineNumber).map(_.toString).mkString("\n\t") + } - val queriedNodes = inputs flatMap (input => { + private def getQueriedNodesFromInput(inputs: Set[String])= { + inputs flatMap (input => { val parts = input.split("@") if(parts.size == 2) parts(1).toIntOption.map(fullGraphInterpreter.getNodesByPosition(parts(0), _)).getOrElse(Set.empty) @@ -106,20 +112,59 @@ class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpr Set.empty } }) - val directDependencies = getSourceInfoString(fullGraphInterpreter.getDirectDependencies(queriedNodes.map(_.id))) - val allDependencies = getSourceInfoString(fullGraphInterpreter.getAllNonInternalDependencies(queriedNodes.map(_.id))) - val allDependenciesWithoutInfeasibility = getSourceInfoString(fullGraphInterpreter.getAllNonInternalDependencies(queriedNodes.map(_.id), includeInfeasibilityNodes=false)) - val explicitDependencies = getSourceInfoString(fullGraphInterpreter.getAllExplicitDependencies(queriedNodes.map(_.id))) -// val dependents = getSourceInfoString(fullGraphInterpreter.getAllNonInternalDependents(queriedNodes.map(_.id))) + } + + private def handleDependencyQuery(inputs: Set[String]): Unit = { + + val queriedNodes = getQueriedNodesFromInput(inputs) + + val (directDependencies, timeDirect) = measureTime[Set[AssumptionAnalysisNode]](fullGraphInterpreter.getDirectDependencies(queriedNodes.map(_.id))) + val (allDependencies, timeAll) = measureTime[Set[AssumptionAnalysisNode]](fullGraphInterpreter.getAllNonInternalDependencies(queriedNodes.map(_.id))) + val (allDependenciesWithoutInfeasibility, timeWithoutInfeasibility) = measureTime[Set[AssumptionAnalysisNode]](fullGraphInterpreter.getAllNonInternalDependencies(queriedNodes.map(_.id), includeInfeasibilityNodes=false)) + val (explicitDependencies, timeExplicit) = measureTime[Set[AssumptionAnalysisNode]](fullGraphInterpreter.getAllExplicitDependencies(queriedNodes.map(_.id))) + + println(s"Queried:\n\t${getSourceInfoString(queriedNodes)}") + + println(s"\nDirect Dependencies (${timeDirect}ms):\n\t${getSourceInfoString(directDependencies)}") + println(s"\nAll Dependencies (${timeAll}ms):\n\t${getSourceInfoString(allDependencies)}") + println(s"\nDependencies without infeasibility (${timeWithoutInfeasibility}ms):\n\t${getSourceInfoString(allDependenciesWithoutInfeasibility)}") + println(s"\nExplicit Dependencies (${timeExplicit}ms):\n\t${getSourceInfoString(explicitDependencies)}") + + println(s"\nDone.") + } + + private def handleDependentsQuery(inputs: Set[String]): Unit = { - println(s"Queried:\n\t${queriedNodes.map(_.sourceInfo.getTopLevelSource.toString).mkString("\n\t")}") + val queriedNodes = getQueriedNodesFromInput(inputs) - println(s"\nDirect Dependencies:\n\t$directDependencies") - println(s"\nAll Dependencies:\n\t$allDependencies") - println(s"\nDependencies without infeasibility:\n\t$allDependenciesWithoutInfeasibility") - println(s"\nExplicit Dependencies:\n\t$explicitDependencies") + val (allDependents, timeAll) = measureTime[Set[AssumptionAnalysisNode]](fullGraphInterpreter.getAllNonInternalDependents(queriedNodes.map(_.id))) + val (dependentsWithoutInfeasibility, timeWithoutInfeasibility) = measureTime[Set[AssumptionAnalysisNode]](fullGraphInterpreter.getAllNonInternalDependents(queriedNodes.map(_.id), includeInfeasibilityNodes=false)) + val (explicitDependents, timeExplicit) = measureTime[Set[AssumptionAnalysisNode]](fullGraphInterpreter.getAllExplicitDependents(queriedNodes.map(_.id))) + + println(s"Queried:\n\t${getSourceInfoString(queriedNodes)}") + + println(s"\nAll Dependents (${timeAll}ms):\n\t${getSourceInfoString(allDependents)}") + println(s"\nDependents without infeasibility (${timeWithoutInfeasibility}ms):\n\t${getSourceInfoString(dependentsWithoutInfeasibility)}") + println(s"\nExplicit Dependents (${timeExplicit}ms):\n\t${getSourceInfoString(explicitDependents)}") -// println(s"\nAll Dependents:\n\t$dependents") TODO ake println(s"\nDone.") } + + private def handleHasDependencyQuery(inputs: Set[String]): Unit = { + val queriedNodes = getQueriedNodesFromInput(inputs) + + val (depExists, time) = measureTime[Boolean](fullGraphInterpreter.hasAnyDependency(queriedNodes)) + + println(s"Queried:\n\t${getSourceInfoString(queriedNodes)}") + println(s"Dependency exists? $depExists") + println(s"\nDone in ${time}ms.") + } + + private def measureTime[T](function: => T): (T, Double) = { + val startAnalysis = System.nanoTime() + val res = function + val endAnalysis = System.nanoTime() + val durationMs = (endAnalysis - startAnalysis) / 1e6 + (res, durationMs) + } } From d00dcb8d44642bd8639edd4f31effe19dedc82df Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 1 Aug 2025 09:29:48 +0200 Subject: [PATCH 218/474] fix performance plotter --- .../benchmark_scripts/plotter.py | 18 ++++++++++-------- ..._2025-07-24_09-53-19_absolute_runtimes.png | Bin 2398 -> 71405 bytes .../result_2025-07-24_09-53-19_overhead.png | Bin 2398 -> 68691 bytes ...t_2025-07-24_09-53-19_overhead_vs_size.png | Bin 2398 -> 27767 bytes 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/plotter.py b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/plotter.py index cf3b3e6eb..bfa6fd62b 100644 --- a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/plotter.py +++ b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/plotter.py @@ -18,14 +18,14 @@ def plot_absolute_runtimes(header: list[str], test_results: dict[str, list[float result1 = [] result2 = [] for name, result in test_results.items(): - names.append(name) + names.append(name.removeprefix("dependencyAnalysisTests/")) result1.append(result[0]) result2.append(result[1]) x = np.arange(len(names)) width = 0.35 - fig, ax = plt.subplots() + fig, ax = plt.subplots(figsize=(12, 6)) ax.bar(x - width/2, result1, width, label=header[1]) ax.bar(x + width/2, result2, width, label=header[2]) @@ -36,18 +36,19 @@ def plot_absolute_runtimes(header: list[str], test_results: dict[str, list[float ax.legend() plt.tight_layout() - plt.show() + # plt.show() plt.savefig(out_file.replace(".out", "_absolute_runtimes.png")) + # plt.show() def plot_overhead(header: list[str], test_results: dict[str, list[float]], out_file: str): names = [] result1 = [] for name, result in test_results.items(): - names.append(name) + names.append(name.removeprefix("dependencyAnalysisTests/")) result1.append(result[2]) - fig, ax = plt.subplots() - ax.bar(names, result1, label=header[3]) + fig, ax = plt.subplots(figsize=(12, 6)) + ax.bar(names, result1) ax.set_ylabel('analysis runtime/baseline runtime') ax.set_title('Overhead of the Analysis') @@ -55,8 +56,8 @@ def plot_overhead(header: list[str], test_results: dict[str, list[float]], out_f ax.legend() plt.tight_layout() - plt.show() plt.savefig(out_file.replace(".out", "_overhead.png")) + # plt.show() def plot_overhead_vs_program_size(header: list[str], test_results: dict[str, list[float]], out_file: str): names = [] @@ -67,6 +68,7 @@ def plot_overhead_vs_program_size(header: list[str], test_results: dict[str, lis result1.append(result[2]) result2.append(result[3]) + plt.figure(figsize=(12,6)) plt.scatter(result2, result1) plt.ylabel('analysis runtime/baseline runtime') @@ -76,7 +78,7 @@ def plot_overhead_vs_program_size(header: list[str], test_results: dict[str, lis plt.grid(True) plt.tight_layout() - plt.show() + # plt.show() plt.savefig(out_file.replace(".out", "_overhead_vs_size.png")) input_file = input("Input file: ") diff --git a/src/test/resources/dependencyAnalysisTests/performanceBenchmark/result_2025-07-24_09-53-19_absolute_runtimes.png b/src/test/resources/dependencyAnalysisTests/performanceBenchmark/result_2025-07-24_09-53-19_absolute_runtimes.png index 38abef5e34b8fcf56df76f3cd3b5199766662e23..ed180ed940d83d84689cef7b5a0e5570ea19ac82 100644 GIT binary patch literal 71405 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 z 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 k!1>>4|Ns95$uYlsbDlCjFttX m#o=g;Fj^_nvt1B&npLfmea};?BU6D576wmOKbLh*2~7YkC!hKN diff --git a/src/test/resources/dependencyAnalysisTests/performanceBenchmark/result_2025-07-24_09-53-19_overhead.png b/src/test/resources/dependencyAnalysisTests/performanceBenchmark/result_2025-07-24_09-53-19_overhead.png index 38abef5e34b8fcf56df76f3cd3b5199766662e23..c529bc84b9e64c1be6102806e6cd4d9bf1294460 100644 GIT binary patch literal 68691 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 Z8LY&^#I}0-$e=Gk<&>IImZF*O{{R)oy|@4X literal 2398 zcmeAS@N?(olHy`uVBq!ia0y~yU}|7sV0^&A1{5*9c;^X_vMOsbDlCjFttX m#o=g;Fj^_nvt1B&npLfmea};?BU6D576wmOKbLh*2~7YkC!hKN diff --git a/src/test/resources/dependencyAnalysisTests/performanceBenchmark/result_2025-07-24_09-53-19_overhead_vs_size.png b/src/test/resources/dependencyAnalysisTests/performanceBenchmark/result_2025-07-24_09-53-19_overhead_vs_size.png index 38abef5e34b8fcf56df76f3cd3b5199766662e23..8fe299098edefdb025f261fe4b334be692ff79b2 100644 GIT binary patch literal 27767 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 literal 2398 zcmeAS@N?(olHy`uVBq!ia0y~yU}|7sV0^&A1{5*9c;^X_vMOsbDlCjFttX m#o=g;Fj^_nvt1B&npLfmea};?BU6D576wmOKbLh*2~7YkC!hKN From e2b2a7380246ff7e24389d3bfe975883d52cd124 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 1 Aug 2025 10:34:47 +0200 Subject: [PATCH 219/474] add disableInfeasibilityChecks config flag --- src/main/scala/Config.scala | 6 ++++++ src/main/scala/rules/Brancher.scala | 6 +++--- src/main/scala/rules/ChunkSupporter.scala | 4 ++-- src/main/scala/rules/Executor.scala | 4 ++-- src/main/scala/rules/MoreCompleteExhaleSupporter.scala | 2 +- src/test/scala/AssumptionAnalysisTests.scala | 2 +- 6 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/main/scala/Config.scala b/src/main/scala/Config.scala index 8e1451464..3c0df05d5 100644 --- a/src/main/scala/Config.scala +++ b/src/main/scala/Config.scala @@ -836,6 +836,12 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { noshort = true ) + val disableInfeasibilityChecks: ScallopOption[Boolean] = opt[Boolean]("disableInfeasibilityChecks", + descr = "Disable infeasibility checks. As a consequence all paths will be explored to the end. (Potentially) huge performance overhead!", + default = Some(false), + noshort = true + ) + val assumptionAnalysisExportPath: ScallopOption[String] = opt[String]("assumptionAnalysisExportPath", descr = "Path to the directory where the assumption analysis graphs should be exported to", default = None, diff --git a/src/main/scala/rules/Brancher.scala b/src/main/scala/rules/Brancher.scala index b9457947e..d2c3430f0 100644 --- a/src/main/scala/rules/Brancher.scala +++ b/src/main/scala/rules/Brancher.scala @@ -112,7 +112,7 @@ object brancher extends BranchingRules { val currentAnalysisSourceInfos = v.decider.analysisSourceInfoStack.getAnalysisSourceInfos val elseBranchVerificationTask: Verifier => VerificationResult = - if (executeElseBranch || Verifier.config.enableAssumptionAnalysis()) { + if (executeElseBranch || Verifier.config.disableInfeasibilityChecks()) { /* [BRANCH-PARALLELISATION] */ /* Compute the following sets * 1. only if the else-branch needs to be explored @@ -193,7 +193,7 @@ object brancher extends BranchingRules { } val elseBranchFuture: Future[Seq[VerificationResult]] = - if (executeElseBranch || Verifier.config.enableAssumptionAnalysis()) { + if (executeElseBranch || Verifier.config.disableInfeasibilityChecks()) { if (parallelizeElseBranch) { /* [BRANCH-PARALLELISATION] */ v.verificationPoolManager.queueVerificationTask(v0 => { @@ -209,7 +209,7 @@ object brancher extends BranchingRules { } val res = { - val thenRes = if (executeThenBranch || Verifier.config.enableAssumptionAnalysis()) { + val thenRes = if (executeThenBranch || Verifier.config.disableInfeasibilityChecks()) { v.symbExLog.markReachable(uidBranchPoint) v.decider.analysisSourceInfoStack.setAnalysisSourceInfo(currentAnalysisSourceInfos) executionFlowController.locally(s, v)((s1, v1) => { diff --git a/src/main/scala/rules/ChunkSupporter.scala b/src/main/scala/rules/ChunkSupporter.scala index 20b624f7f..a0df606d1 100644 --- a/src/main/scala/rules/ChunkSupporter.scala +++ b/src/main/scala/rules/ChunkSupporter.scala @@ -146,7 +146,7 @@ object chunkSupporter extends ChunkSupportRules { } QS(s2.copy(h = s.h), h2, snap, v1) case (_, s2, h2, _) if v1.decider.checkSmoke(isAssert=true, assumptionType) => - if(Verifier.config.enableAssumptionAnalysis()) + if(Verifier.config.disableInfeasibilityChecks()) QS(s2.copy(h = s.h), h2, None, v1) else Success() // TODO: Mark branch as dead? @@ -268,7 +268,7 @@ object chunkSupporter extends ChunkSupportRules { case Some(ch) if v.decider.check(IsPositive(ch.perm), Verifier.config.checkTimeout(), assumptionType) => Q(s, ch.snap, v) case _ if v.decider.checkSmoke(isAssert=true, assumptionType) => - if (s.isInPackage || Verifier.config.enableAssumptionAnalysis()) { + if (s.isInPackage || Verifier.config.disableInfeasibilityChecks()) { val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) Q(s, snap, v) } else { diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index d2954aafa..b0b15dab4 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -281,7 +281,7 @@ object executor extends ExecutionRules { v2.decider.declareAndRecordAsFreshFunctions(ff1 -- v2.decider.freshFunctions) /* [BRANCH-PARALLELISATION] */ v2.decider.assume(pcs.assumptions map (t => v.decider.wrapWithAssumptionAnalysisLabel(t, Set.empty, Set(t))), Some(pcs.assumptionExps), "Loop invariant", enforceAssumption=false, assumptionType=AssumptionType.LoopInvariant) v2.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) - if (v2.decider.checkSmoke() && !Verifier.config.enableAssumptionAnalysis()) + if (v2.decider.checkSmoke() && !Verifier.config.disableInfeasibilityChecks()) Success() else { execs(s3, stmts, v2)((s4, v3) => { @@ -500,7 +500,7 @@ object executor extends ExecutionRules { Q(s2, v) case inhale @ ast.Inhale(a) => a match { - case _: ast.FalseLit if !Verifier.config.enableAssumptionAnalysis() => + case _: ast.FalseLit if !Verifier.config.disableInfeasibilityChecks() => /* We're done */ Success() case _ => diff --git a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala index 95caab5b3..23ecfac46 100644 --- a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala +++ b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala @@ -207,7 +207,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { if (relevantChunks.isEmpty) { if (v.decider.checkSmoke(true)) { - if (s.isInPackage || Verifier.config.enableAssumptionAnalysis()) { + if (s.isInPackage || Verifier.config.disableInfeasibilityChecks()) { val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) Q(s, snap, v) } else { diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index 87dcb07a4..efc0d95e9 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -39,7 +39,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { var baseCommandLineArguments: Seq[String] = Seq("--timeout", "300" /* seconds */) var analysisCommandLineArguments: Seq[String] = - baseCommandLineArguments ++ Seq("--enableAssumptionAnalysis", "--proverArgs", "proof=true unsat-core=true") + baseCommandLineArguments ++ Seq("--enableAssumptionAnalysis", "--disableInfeasibilityChecks", "--proverArgs", "proof=true unsat-core=true") if(EXECUTE_PRECISION_BENCHMARK) { From b46fd8b486524f3aab2ebc2ea4550422ef4ed1f3 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 2 Aug 2025 17:08:19 +0200 Subject: [PATCH 220/474] remove analysis labels from function axioms --- .../AssumptionAnalysisUserTool.scala | 4 ++-- .../functions/FunctionVerificationUnit.scala | 10 ++++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala index bc4919394..eacc3c473 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala @@ -68,7 +68,7 @@ class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpr println(s"coverage: $coverage") if (!coverage.equals(1.0)) println(s"uncovered nodes:\n\t${uncoveredSources.mkString("\n\t")}") - println() + println("Done.") }) } @@ -93,7 +93,7 @@ class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpr println(s"coverage: $coverage") if (!coverage.equals(1.0)) println(s"uncovered nodes:\n\t${uncoveredSources.mkString("\n\t")}") - println() + println("Done.") }) } diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index 91051aaca..b00fe44be 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -292,8 +292,14 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver } private def emitAndRecordFunctionAxioms(axiom: (Term, Option[(AnalysisSourceInfo, AssumptionType)])*): Unit = { - decider.prover.assumeAxiomsWithAnalysisInfo(InsertionOrderedSet(axiom), "Function axioms") - emittedFunctionAxioms = emittedFunctionAxioms ++ axiom + val cleanAxiom = + if(!Verifier.config.enableAssumptionAnalysis()) axiom + else axiom.map(a => (a._1.transform{ + case Var(name, _, _) if name.name.startsWith("analysisLabel") => True + }(), a._2)) + decider.prover.assumeAxiomsWithAnalysisInfo(InsertionOrderedSet(cleanAxiom), "Function axioms") + + emittedFunctionAxioms = emittedFunctionAxioms ++ cleanAxiom } private def generateFunctionSymbolsAfterVerification: Iterable[Either[String, Decl]] = { From da2df738f747bc52210ce63648a1686da71c916f Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 4 Aug 2025 18:19:18 +0200 Subject: [PATCH 221/474] minor fixes --- .../AnalysisSourceInfo.scala | 61 ------------- .../AssumptionAnalysisInterpreter.scala | 2 +- .../AssumptionAnalysisUserTool.scala | 16 ++-- .../assumptionAnalysis/SourceInfoStack.scala | 86 ++++++++++++++++++ src/main/scala/decider/Decider.scala | 10 +- ...ter.py => viper_perf_benchmark_plotter.py} | 4 +- ..._2025-07-24_15-48-37_absolute_runtimes.png | Bin 0 -> 40630 bytes .../result_2025-07-24_15-48-37_overhead.png | Bin 0 -> 36132 bytes ...t_2025-07-24_15-48-37_overhead_vs_size.png | Bin 0 -> 24058 bytes src/test/scala/AssumptionAnalysisTests.scala | 6 +- 10 files changed, 105 insertions(+), 80 deletions(-) create mode 100644 src/main/scala/assumptionAnalysis/SourceInfoStack.scala rename src/test/resources/dependencyAnalysisTests/benchmark_scripts/{plotter.py => viper_perf_benchmark_plotter.py} (96%) create mode 100644 src/test/resources/dependencyAnalysisTests/performanceBenchmark/result_2025-07-24_15-48-37_absolute_runtimes.png create mode 100644 src/test/resources/dependencyAnalysisTests/performanceBenchmark/result_2025-07-24_15-48-37_overhead.png create mode 100644 src/test/resources/dependencyAnalysisTests/performanceBenchmark/result_2025-07-24_15-48-37_overhead_vs_size.png diff --git a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala index 79fa66ac7..f92654ab4 100644 --- a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala +++ b/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala @@ -1,6 +1,5 @@ package viper.silicon.assumptionAnalysis -import viper.silicon.verifier.Verifier import viper.silver.ast import viper.silver.ast._ @@ -113,63 +112,3 @@ case class CompositeAnalysisSourceInfo(coarseGrainedSource: AnalysisSourceInfo, override def isAnalysisEnabled: Boolean = coarseGrainedSource.isAnalysisEnabled && fineGrainedSource.isAnalysisEnabled } - - -case class AnalysisSourceInfoStack() { - private var sourceInfos: List[AnalysisSourceInfo] = List.empty - private var forcedMainSource: Option[AnalysisSourceInfo] = None - - def getAnalysisSourceInfos: List[AnalysisSourceInfo] = sourceInfos - - def getFullSourceInfo: AnalysisSourceInfo = { - if(!Verifier.config.enableAssumptionAnalysis()) return NoAnalysisSourceInfo() - if(forcedMainSource.isDefined) - return forcedMainSource.get - if(sourceInfos.size <= 1){ - sourceInfos.headOption.getOrElse(NoAnalysisSourceInfo()) - } else{ - CompositeAnalysisSourceInfo(sourceInfos.last, sourceInfos.head) - } - } - - def addAnalysisSourceInfo(analysisSourceInfo: AnalysisSourceInfo): Unit = { - if(!Verifier.config.enableAssumptionAnalysis()) return - sourceInfos = analysisSourceInfo +: sourceInfos - } - - def setAnalysisSourceInfo(analysisSourceInfo: List[AnalysisSourceInfo]): Unit = { - if(!Verifier.config.enableAssumptionAnalysis()) return - sourceInfos = analysisSourceInfo - } - - def popAnalysisSourceInfo(analysisSourceInfo: AnalysisSourceInfo): Unit = { - if(!Verifier.config.enableAssumptionAnalysis()) return - - var currSourceInfo = sourceInfos - // popping just one source info might not be enough since infeasible branches might return without popping the source info - while(currSourceInfo.nonEmpty && !currSourceInfo.head.equals(analysisSourceInfo)) { - currSourceInfo = currSourceInfo.tail - } - if(currSourceInfo.isEmpty || !currSourceInfo.head.equals(analysisSourceInfo)) - throw new RuntimeException("unexpected source info") - sourceInfos = currSourceInfo.tail - } - - def getForcedSource: Option[AnalysisSourceInfo] = forcedMainSource - - def setForcedSource(description: String): Unit = { - forcedMainSource = Some(StringAnalysisSourceInfo(description, NoPosition)) - } - - def setForcedSource(source: AnalysisSourceInfo): Unit = { - forcedMainSource = Some(source) - } - - def setForcedSource(sourceOpt: Option[AnalysisSourceInfo]): Unit = { - forcedMainSource = sourceOpt - } - - def removeForcedSource(): Unit = { - forcedMainSource = None - } -} \ No newline at end of file diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala index 54269fdc9..4944a895f 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala @@ -39,7 +39,7 @@ class AssumptionAnalysisInterpreter(name: String, graph: ReadOnlyAssumptionAnaly def getAllNonInternalDependents(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[AssumptionAnalysisNode] = { val allDependents = graph.getAllDependents(nodeIdsToAnalyze, includeInfeasibilityNodes).diff(nodeIdsToAnalyze) - getNonInternalAssumptionNodes.filter(node => allDependents.contains(node.id)) + getNonInternalAssertionNodes.filter(node => allDependents.contains(node.id)) } def getAllExplicitDependents(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[AssumptionAnalysisNode] = { diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala index eacc3c473..2148ae7e0 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala @@ -1,11 +1,11 @@ package silicon.viper.assumptionAnalysis -import viper.silicon.assumptionAnalysis.{AssumptionAnalysisGraph, AssumptionAnalysisInterpreter, AssumptionAnalysisNode, AssumptionAnalyzer, AssumptionType} +import viper.silicon.assumptionAnalysis.{AssumptionAnalysisInterpreter, AssumptionAnalysisNode} +import viper.silver.ast +import viper.silver.ast.Method import scala.annotation.tailrec import scala.io.StdIn.readLine -import viper.silver.ast -import viper.silver.ast.Method class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpreter, memberInterpreters: Seq[AssumptionAnalysisInterpreter]) { private val infoString = "Enter " + @@ -68,8 +68,8 @@ class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpr println(s"coverage: $coverage") if (!coverage.equals(1.0)) println(s"uncovered nodes:\n\t${uncoveredSources.mkString("\n\t")}") - println("Done.") }) + println("Done.") } private def handleProofCoverageLineQuery(memberNames: Seq[String]): Unit = { @@ -93,8 +93,8 @@ class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpr println(s"coverage: $coverage") if (!coverage.equals(1.0)) println(s"uncovered nodes:\n\t${uncoveredSources.mkString("\n\t")}") - println("Done.") }) + println("Done.") } private def getSourceInfoString(nodes: Set[AssumptionAnalysisNode]) = { @@ -128,8 +128,7 @@ class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpr println(s"\nAll Dependencies (${timeAll}ms):\n\t${getSourceInfoString(allDependencies)}") println(s"\nDependencies without infeasibility (${timeWithoutInfeasibility}ms):\n\t${getSourceInfoString(allDependenciesWithoutInfeasibility)}") println(s"\nExplicit Dependencies (${timeExplicit}ms):\n\t${getSourceInfoString(explicitDependencies)}") - - println(s"\nDone.") + println("Done.") } private def handleDependentsQuery(inputs: Set[String]): Unit = { @@ -145,8 +144,7 @@ class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpr println(s"\nAll Dependents (${timeAll}ms):\n\t${getSourceInfoString(allDependents)}") println(s"\nDependents without infeasibility (${timeWithoutInfeasibility}ms):\n\t${getSourceInfoString(dependentsWithoutInfeasibility)}") println(s"\nExplicit Dependents (${timeExplicit}ms):\n\t${getSourceInfoString(explicitDependents)}") - - println(s"\nDone.") + println("Done.") } private def handleHasDependencyQuery(inputs: Set[String]): Unit = { diff --git a/src/main/scala/assumptionAnalysis/SourceInfoStack.scala b/src/main/scala/assumptionAnalysis/SourceInfoStack.scala new file mode 100644 index 000000000..c3a2cd283 --- /dev/null +++ b/src/main/scala/assumptionAnalysis/SourceInfoStack.scala @@ -0,0 +1,86 @@ +package viper.silicon.assumptionAnalysis + +import viper.silicon.verifier.Verifier +import viper.silver.ast.NoPosition + +trait SourceInfoStack { + + def getAnalysisSourceInfos: List[AnalysisSourceInfo] + + def getFullSourceInfo: AnalysisSourceInfo + + def addAnalysisSourceInfo(analysisSourceInfo: AnalysisSourceInfo): Unit + + def setAnalysisSourceInfo(analysisSourceInfo: List[AnalysisSourceInfo]): Unit + + def popAnalysisSourceInfo(analysisSourceInfo: AnalysisSourceInfo): Unit + + def getForcedSource: _root_.scala.Option[AnalysisSourceInfo] + + def setForcedSource(description: _root_.java.lang.String): Unit + + def setForcedSource(source: AnalysisSourceInfo): Unit + + def setForcedSource(sourceOpt: _root_.scala.Option[AnalysisSourceInfo]): Unit + + def removeForcedSource(): Unit +} + +case class AnalysisSourceInfoStack() extends SourceInfoStack { + private var sourceInfos: List[AnalysisSourceInfo] = List.empty + private var forcedMainSource: Option[AnalysisSourceInfo] = None + + override def getAnalysisSourceInfos: List[AnalysisSourceInfo] = sourceInfos + + override def getFullSourceInfo: AnalysisSourceInfo = { + if(!Verifier.config.enableAssumptionAnalysis()) return NoAnalysisSourceInfo() + if(forcedMainSource.isDefined) + return forcedMainSource.get + if(sourceInfos.size <= 1){ + sourceInfos.headOption.getOrElse(NoAnalysisSourceInfo()) + } else{ + CompositeAnalysisSourceInfo(sourceInfos.last, sourceInfos.head) + } + } + + override def addAnalysisSourceInfo(analysisSourceInfo: AnalysisSourceInfo): Unit = { + if(!Verifier.config.enableAssumptionAnalysis()) return + sourceInfos = analysisSourceInfo +: sourceInfos + } + + override def setAnalysisSourceInfo(analysisSourceInfo: List[AnalysisSourceInfo]): Unit = { + if(!Verifier.config.enableAssumptionAnalysis()) return + sourceInfos = analysisSourceInfo + } + + override def popAnalysisSourceInfo(analysisSourceInfo: AnalysisSourceInfo): Unit = { + if(!Verifier.config.enableAssumptionAnalysis()) return + + var currSourceInfo = sourceInfos + // popping just one source info might not be enough since infeasible branches might return without popping the source info + while(currSourceInfo.nonEmpty && !currSourceInfo.head.equals(analysisSourceInfo)) { + currSourceInfo = currSourceInfo.tail + } + if(currSourceInfo.isEmpty || !currSourceInfo.head.equals(analysisSourceInfo)) + throw new RuntimeException("unexpected source info") + sourceInfos = currSourceInfo.tail + } + + override def getForcedSource: Option[AnalysisSourceInfo] = forcedMainSource + + override def setForcedSource(description: String): Unit = { + forcedMainSource = Some(StringAnalysisSourceInfo(description, NoPosition)) + } + + override def setForcedSource(source: AnalysisSourceInfo): Unit = { + forcedMainSource = Some(source) + } + + override def setForcedSource(sourceOpt: Option[AnalysisSourceInfo]): Unit = { + forcedMainSource = sourceOpt + } + + override def removeForcedSource(): Unit = { + forcedMainSource = None + } +} \ No newline at end of file diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index cc8237728..aebb73bc8 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -61,8 +61,8 @@ trait Decider { def startDebugSubExp(): Unit - def registerChunk[CH <: GeneralChunk](buildChunk: (Term => CH), perm: Term, analysisInfo: AnalysisInfo, isExhale: Boolean): CH - def registerDerivedChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: (Term => CH), perm: Term, analysisInfo: AnalysisInfo, isExhale: Boolean, createLabel: Boolean=true): CH + def registerChunk[CH <: GeneralChunk](buildChunk: Term => CH, perm: Term, analysisInfo: AnalysisInfo, isExhale: Boolean): CH + def registerDerivedChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, analysisInfo: AnalysisInfo, isExhale: Boolean, createLabel: Boolean=true): CH def wrapWithAssumptionAnalysisLabel(term: Term, sourceChunks: Iterable[Chunk] = Set.empty, sourceTerms: Iterable[Term] = Set.empty): Term def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp], assumptionType: AssumptionType): Unit @@ -155,10 +155,12 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => }else{ removeAssumptionAnalyzer() } + analysisSourceInfoStack = AnalysisSourceInfoStack() } override def removeAssumptionAnalyzer(): Unit = { assumptionAnalyzer = new NoAssumptionAnalyzer + analysisSourceInfoStack = AnalysisSourceInfoStack() } def getAnalysisInfo: AnalysisInfo = getAnalysisInfo(AssumptionType.Implicit) @@ -305,11 +307,11 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } } - def registerChunk[CH <: GeneralChunk](buildChunk: (Term => CH), perm: Term, analysisInfo: AnalysisInfo, isExhale: Boolean): CH = { + def registerChunk[CH <: GeneralChunk](buildChunk: Term => CH, perm: Term, analysisInfo: AnalysisInfo, isExhale: Boolean): CH = { registerDerivedChunk[CH](Set.empty, buildChunk, perm, analysisInfo, isExhale) } - def registerDerivedChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: (Term => CH), perm: Term, analysisInfo: AnalysisInfo, isExhale: Boolean, createLabel: Boolean=true): CH = { + def registerDerivedChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, analysisInfo: AnalysisInfo, isExhale: Boolean, createLabel: Boolean=true): CH = { if(!Verifier.config.enableAssumptionAnalysis()) return buildChunk(perm) diff --git a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/plotter.py b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/viper_perf_benchmark_plotter.py similarity index 96% rename from src/test/resources/dependencyAnalysisTests/benchmark_scripts/plotter.py rename to src/test/resources/dependencyAnalysisTests/benchmark_scripts/viper_perf_benchmark_plotter.py index bfa6fd62b..f02468bff 100644 --- a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/plotter.py +++ b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/viper_perf_benchmark_plotter.py @@ -4,10 +4,10 @@ def import_result(file: str): with open(file, mode="r") as f: contents = f.readlines() - header = [c.strip() for c in contents[0].split(";")] + header = [c.strip() for c in contents[0].split(",")] results = {} for line in contents[1:]: - parts = line.split(';') + parts = line.split(',') test_name = parts[0].strip() runtimes = [float(x.strip()) for x in parts[1:]] results[test_name] = runtimes diff --git a/src/test/resources/dependencyAnalysisTests/performanceBenchmark/result_2025-07-24_15-48-37_absolute_runtimes.png b/src/test/resources/dependencyAnalysisTests/performanceBenchmark/result_2025-07-24_15-48-37_absolute_runtimes.png new file mode 100644 index 0000000000000000000000000000000000000000..95239ab753b522f5072e75176e78e3ae3757dc16 GIT binary patch literal 40630 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 MZZx`W;2iaT06q)49smFU literal 0 HcmV?d00001 diff --git a/src/test/resources/dependencyAnalysisTests/performanceBenchmark/result_2025-07-24_15-48-37_overhead.png b/src/test/resources/dependencyAnalysisTests/performanceBenchmark/result_2025-07-24_15-48-37_overhead.png new file mode 100644 index 0000000000000000000000000000000000000000..75f56d4872cc85b0ddaf5370664b61e15d27eb5b GIT binary patch literal 36132 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 z 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 literal 0 HcmV?d00001 diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index efc0d95e9..15bdb65d7 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -64,7 +64,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss") val writer = new PrintWriter(s"$basePath/result_${now.format(formatter)}.out") val proofCoverageWriter = new PrintWriter(s"$basePath/proofCoverage_${now.format(formatter)}.out") - writer.println(f"test name;\tbaseline (ms);\tanalysis (ms);\tanalysis/baseline\tprogram size") + writer.println(f"test name,baseline (ms),analysis (ms),analysis/baseline,program size") testDirectories foreach (dir => visitFiles(dir, executePerformanceBenchmark(_, _, writer, proofCoverageWriter))) writer.close() proofCoverageWriter.close() @@ -453,12 +453,12 @@ class AssumptionAnalysisTests extends AnyFunSuite { } def execute(): Unit = { - printResult(f"$name;\t") + printResult(f"$name,") val analysisDurationMs: Double = verifyAndMeasure(program, analysisCommandLineArguments) val baselineDurationMs: Double = verifyAndMeasure(program, baseCommandLineArguments) - printResult(f"$baselineDurationMs;\t$analysisDurationMs;\t${analysisDurationMs/baselineDurationMs};\t$programSize\n") + printResult(f"$baselineDurationMs,$analysisDurationMs,${analysisDurationMs/baselineDurationMs},$programSize\n") // TODO ake: rewrite if we want to support this // if(naiveProgram.isDefined){ From b2381b6fe5c19d2b1f7f8380b6a081ed07c536a7 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 5 Aug 2025 13:34:37 +0200 Subject: [PATCH 222/474] add infeasibility bug example --- .../new/infeasibilityBug.vpr | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/test/resources/dependencyAnalysisTests/new/infeasibilityBug.vpr diff --git a/src/test/resources/dependencyAnalysisTests/new/infeasibilityBug.vpr b/src/test/resources/dependencyAnalysisTests/new/infeasibilityBug.vpr new file mode 100644 index 000000000..e3ff14876 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/new/infeasibilityBug.vpr @@ -0,0 +1,28 @@ +field Value: Int +field Left: Ref +field Right: Ref + +predicate tree(self: Ref) { + acc(self.Left) && acc(self.Value) && acc(self.Right) && + (self.Left != null ==> tree(self.Left)) && + (self.Right != null ==> tree(self.Right)) +} + + +function Contains(self: Ref, v: Int): Bool + requires self != null ==> tree(self) +{ + self != null && (unfolding tree(self) in (Contains(self.Left, v) || Contains(self.Right, v))) +} + + +method Insert(self: Ref, v: Int) returns (res: Ref) +{ + inhale acc(self.Left) && acc(self.Value) && acc(self.Right) + inhale self.Left == null + inhale tree(self.Right) + inhale Contains(self.Right, v) // self.Right := Insert(self.Right, v) + fold tree(self) + assert self != null + assert Contains(self, v) +} \ No newline at end of file From 9c7fb10c5ec324a279a7085e1c4a1bd4c46d96db Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 5 Aug 2025 16:36:27 +0200 Subject: [PATCH 223/474] add precision tests - branches --- .../new/custom_precision_test.vpr | 209 ++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100644 src/test/resources/dependencyAnalysisTests/new/custom_precision_test.vpr diff --git a/src/test/resources/dependencyAnalysisTests/new/custom_precision_test.vpr b/src/test/resources/dependencyAnalysisTests/new/custom_precision_test.vpr new file mode 100644 index 000000000..da09e4cc6 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/new/custom_precision_test.vpr @@ -0,0 +1,209 @@ +field f: Int +field g: Int + + +method branching(x: Ref, a: Int, b: Bool, n: Int) + requires acc(x.f) && x.f > 0 + ensures acc(x.f) +{ + var res: Int + + if(a > 0){ + res := a + }else{ + res := x.f + } + + if(b){ + x.f := 1 + res := res + 1 + }else{ + x.f := 2 + res := res + 2 + } + + if(a > 0){ + assert res > a + }else{ + assert x.f >= 1 + assert b ==> res > a + assert !b ==> res > 2 + } +} + +method branching2(x: Ref, a: Int, b: Bool, n: Int) + requires acc(x.f) && x.f > 0 + ensures acc(x.f) +{ + var res: Ref + res := new(f) + + if(a > 0){ + res.f := a + }else{ + res.f := x.f + } + + if(b){ + x.f := 1 + res.f := res.f + 1 + }else{ + x.f := 2 + res.f := res.f + 2 + } + + if(a > 0){ + assert res.f > a + }else{ + assert x.f >= 1 + assert b ==> res.f > a + assert !b ==> res.f > 2 + } +} + +method assign(x: Ref, val: Int) + requires acc(x.f) + requires val > 0 + ensures acc(x.f) + ensures x.f == val +{ + x.f := val +} + +method branchingMethod(x: Ref, a: Int, b: Bool, n: Int) + requires acc(x.f) && x.f > 0 + ensures acc(x.f) +{ + var res: Ref + res := new(f) + + if(a > 0){ + res.f := a + }else{ + res.f := x.f + } + + if(b){ + x.f := 1 + assign(res, res.f + 1) + }else{ + x.f := 2 + assign(res, res.f + 2) + } + + if(a > 0){ + assert res.f > a + }else{ + assert x.f >= 1 + assert b ==> res.f > a + assert !b ==> res.f > 2 + } +} + +function retVal(val: Int): Int + requires val > 0 +{ + val +} + +method branchingFunction(x: Ref, a: Int, b: Bool, n: Int) + requires acc(x.f) && x.f > 0 + ensures acc(x.f) +{ + var res: Ref + res := new(f) + + if(a > 0){ + res.f := a + }else{ + res.f := x.f + } + + if(b){ + x.f := 1 + res.f := retVal(res.f + 1) + }else{ + x.f := 2 + res.f := retVal(res.f + 2) + } + + if(a > 0){ + assert res.f > a + }else{ + assert x.f >= 1 + assert b ==> res.f > a + assert !b ==> res.f > 2 + } +} + +method branchingDiv0(x: Ref, a: Int, b: Bool, n: Int) + requires acc(x.f) && x.f > 0 + ensures acc(x.f) +{ + var res: Ref + res := new(f) + + if(a > 0){ + res.f := a + }else{ + res.f := x.f + } + + if(b){ + x.f := 1 + res.f := (res.f*res.f+res.f)/res.f + }else{ + x.f := 2 + res.f := (res.f*res.f + 2*res.f)/res.f + } + + if(a > 0){ + assert res.f > a + }else{ + assert x.f >= 1 + assert b ==> res.f > a + assert !b ==> res.f > 2 + } +} + +method branchNLoops(x: Ref, a: Int, b: Bool, n: Int) + requires acc(x.f) && x.f > 0 + ensures acc(x.f) +{ + var res: Ref + res := new(f) + var whatever: Bool := b + + if(a > 0){ + res.f := a + }else{ + res.f := x.f + } + + while(whatever) + invariant acc(x.f) + invariant acc(res.f) + invariant x != res + invariant res.f > 0 + invariant x.f >= 1 + { + if(a > 0){ + x.f := x.f + 4 + res.f := res.f + a + }else{ + x.f := x.f + 1 + res.f := res.f + 2 + } + if(res.f > 5){ + whatever := false + } + } + + if(a > 0){ + assert res.f >= 0 + }else{ + assert x.f >= 1 + assert b ==> res.f > a + assert !b ==> res.f > 0 + } +} From 572657893726727914e97b7295a670031fa7fba1 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 6 Aug 2025 08:59:12 +0200 Subject: [PATCH 224/474] fix graph export --- .../scala/assumptionAnalysis/AssumptionAnalysisGraph.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala index 1e548204e..8387d4522 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala @@ -162,8 +162,8 @@ class AssumptionAnalysisGraph extends ReadOnlyAssumptionAnalysisGraph { private def exportEdges(fileName: String): Unit = { val writer = new PrintWriter(fileName) writer.println("source,target,label") - edges foreach (e => e._2 foreach (t => writer.println(e._1.toString + "," + t + ",direct"))) - transitiveEdges foreach (e => e._2 foreach (t => writer.println(e._1.toString + "," + t + ",transitive"))) + edges foreach (e => e._2 foreach (s => writer.println(s.toString + "," + e._1.toString + ",direct"))) + transitiveEdges foreach (e => e._2 foreach (s => writer.println(s.toString + "," + e._1.toString + ",transitive"))) writer.close() } From 97ec24f03eb8b568c8e76afc476239daadd620b7 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 6 Aug 2025 09:00:39 +0200 Subject: [PATCH 225/474] optimize infeasibility case --- .../assumptionAnalysis/AssumptionAnalyzer.scala | 15 +++++++++++++++ src/main/scala/rules/Executor.scala | 15 ++++++++++++--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 43d579c52..9f1baa972 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -44,6 +44,8 @@ trait AssumptionAnalyzer { def addCustomExpDependency(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp]): Unit def addFunctionAxiomEdges(): Unit + def addInfeasibilityDepToStmt(infeasNodeId: Option[Int], analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Unit = {} + def buildFinalGraph(): Option[AssumptionAnalysisGraph] } @@ -211,6 +213,12 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { else Some(SimpleAssertionNode(term, assumptionType, analysisSourceInfo, isClosed_)) } + + def addAssertNode(term: Term, assumptionType: AssumptionType, analysisSourceInfo: AnalysisSourceInfo): Option[Int] = { + val node = createAssertOrCheckNode(term, assumptionType, analysisSourceInfo, isCheck=false) + node foreach addNode + node map (_.id) + } override def addAssertFalseNode(isCheck: Boolean, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo): Option[Int] = { val node = createAssertOrCheckNode(False, assumptionType, sourceInfo, isCheck) @@ -322,6 +330,13 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { mergedGraph } + + override def addInfeasibilityDepToStmt(infeasNodeId: Option[Int], analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Unit = { + val newAssertionNodeId = addAssertNode(False, assumptionType, analysisSourceInfo) + val newAssumptionNodeId = addAssumption(False, analysisSourceInfo, assumptionType) + addDependency(infeasNodeId, newAssumptionNodeId) + addDependency(infeasNodeId, newAssertionNodeId) + } } class NoAssumptionAnalyzer extends AssumptionAnalyzer { diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index b0b15dab4..3cfabb1fa 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -327,10 +327,19 @@ object executor extends ExecutionRules { val sepIdentifier = v.symbExLog.openScope(new ExecuteRecord(stmt, s, v.decider.pcs)) val sourceInfo = StmtAnalysisSourceInfo(stmt) v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo) - exec2(s, stmt, v)((s1, v1) => { + if(Verifier.config.disableInfeasibilityChecks() && Verifier.config.enableAssumptionAnalysis() && + v.decider.pcs.getCurrentInfeasibilityNode.nonEmpty){ + v.decider.assumptionAnalyzer.addInfeasibilityDepToStmt(v.decider.pcs.getCurrentInfeasibilityNode, + v.decider.analysisSourceInfoStack.getFullSourceInfo, AssumptionType.Implicit) v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) - v1.symbExLog.closeScope(sepIdentifier) - Q(s1, v1)}) + Q(s, v) + }else { + exec2(s, stmt, v)((s1, v1) => { + v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) + v1.symbExLog.closeScope(sepIdentifier) + Q(s1, v1) + }) + } } def exec2(state: State, stmt: ast.Stmt, v: Verifier) From ad5d00bff94c36d93aff3acbb013d9777c24b23c Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 6 Aug 2025 09:48:42 +0200 Subject: [PATCH 226/474] Revert "optimize infeasibility case" This reverts commit 97ec24f03eb8b568c8e76afc476239daadd620b7. --- .../assumptionAnalysis/AssumptionAnalyzer.scala | 15 --------------- src/main/scala/rules/Executor.scala | 15 +++------------ 2 files changed, 3 insertions(+), 27 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 9f1baa972..43d579c52 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -44,8 +44,6 @@ trait AssumptionAnalyzer { def addCustomExpDependency(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp]): Unit def addFunctionAxiomEdges(): Unit - def addInfeasibilityDepToStmt(infeasNodeId: Option[Int], analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Unit = {} - def buildFinalGraph(): Option[AssumptionAnalysisGraph] } @@ -213,12 +211,6 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { else Some(SimpleAssertionNode(term, assumptionType, analysisSourceInfo, isClosed_)) } - - def addAssertNode(term: Term, assumptionType: AssumptionType, analysisSourceInfo: AnalysisSourceInfo): Option[Int] = { - val node = createAssertOrCheckNode(term, assumptionType, analysisSourceInfo, isCheck=false) - node foreach addNode - node map (_.id) - } override def addAssertFalseNode(isCheck: Boolean, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo): Option[Int] = { val node = createAssertOrCheckNode(False, assumptionType, sourceInfo, isCheck) @@ -330,13 +322,6 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { mergedGraph } - - override def addInfeasibilityDepToStmt(infeasNodeId: Option[Int], analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Unit = { - val newAssertionNodeId = addAssertNode(False, assumptionType, analysisSourceInfo) - val newAssumptionNodeId = addAssumption(False, analysisSourceInfo, assumptionType) - addDependency(infeasNodeId, newAssumptionNodeId) - addDependency(infeasNodeId, newAssertionNodeId) - } } class NoAssumptionAnalyzer extends AssumptionAnalyzer { diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 3cfabb1fa..b0b15dab4 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -327,19 +327,10 @@ object executor extends ExecutionRules { val sepIdentifier = v.symbExLog.openScope(new ExecuteRecord(stmt, s, v.decider.pcs)) val sourceInfo = StmtAnalysisSourceInfo(stmt) v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo) - if(Verifier.config.disableInfeasibilityChecks() && Verifier.config.enableAssumptionAnalysis() && - v.decider.pcs.getCurrentInfeasibilityNode.nonEmpty){ - v.decider.assumptionAnalyzer.addInfeasibilityDepToStmt(v.decider.pcs.getCurrentInfeasibilityNode, - v.decider.analysisSourceInfoStack.getFullSourceInfo, AssumptionType.Implicit) + exec2(s, stmt, v)((s1, v1) => { v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) - Q(s, v) - }else { - exec2(s, stmt, v)((s1, v1) => { - v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) - v1.symbExLog.closeScope(sepIdentifier) - Q(s1, v1) - }) - } + v1.symbExLog.closeScope(sepIdentifier) + Q(s1, v1)}) } def exec2(state: State, stmt: ast.Stmt, v: Verifier) From 37530fb2bce768f9ac71726a21d04c236feebb77 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 6 Aug 2025 11:05:39 +0200 Subject: [PATCH 227/474] add test cases (applying, unfolding) --- .../unitTests/magicWands.vpr | 15 ++++++ .../unitTests/predicates.vpr | 47 ++++++++++++++++++- 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr index 22eda1362..c1a5c5f33 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr @@ -138,3 +138,18 @@ method packageExhale(x: Ref) assert perm(x.f) == 1/2 } +method applying1() +{ + var x: Int + var y: Int + @dependency("Explicit") + inhale x > 0 + @dependency("Explicit") + inhale x > 0 --* y > 0 + + // $PrecisionTest: $READ_ONLY=x,y $INVARIANT=x>0 + + @testAssertion("Explicit") + assert applying (x > 0 --* y > 0) in y > 0 +} + diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/predicates.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/predicates.vpr index 5c7bbb9e2..5196e48f3 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/predicates.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/predicates.vpr @@ -91,4 +91,49 @@ method foldPrecision(a: Int, b: Int) @testAssertion("Implicit") fold greater0(b) -} \ No newline at end of file +} + +method unfolding1(n: Int) + requires @dependency("Explicit")(greater5(n)) +{ + + // $PrecisionTest: $READ_ONLY=n $INVARIANT=(unfolding$_$greater5(n)$_$in$_$n>0) $ACC_INVARIANT=greater5(n) + + @testAssertion("Explicit") + assert unfolding greater5(n) in n > 0 +} + +predicate implPred(a: Int, x: Ref){ + a > 0 ==> acc(x.f) +} + +method unfoldWithImpl(a: Int, x: Ref) + requires @dependency("Explicit")(implPred(a, x)) +{ + @dependency("Rewrite") + unfold implPred(a, x) + if(@dependency("PathCondition")(a > 5)){ + @irrelevant("Implicit") + x.f := a + + // $PrecisionTest: $READ_ONLY=a $READ_WRITE=x.f $INVARIANT=a>=0 $ACC_INVARIANT=acc(x.f) + } + @testAssertion("Implicit") + fold implPred(a, x) +} + +method unfoldingWithImpl(a: Int, x: Ref) + requires @dependency("Explicit")(implPred(a, x)) +{ + var res: Int + if(@dependency("PathCondition")(a > 5)){ + + // $PrecisionTest: $READ_ONLY=a $READ_WRITE=res $INVARIANT=a>0 + + @testAssertion("Implicit") + res := unfolding implPred(a, x) in x.f + }else{ + @irrelevant("Implicit") + res := a + } +} From 161cad0ca508164468282fcbde63c2e3f2091da6 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 6 Aug 2025 11:30:04 +0200 Subject: [PATCH 228/474] cleanup --- .../scala/assumptionAnalysis/AssumptionAnalyzer.scala | 1 + src/main/scala/decider/Decider.scala | 2 +- src/main/scala/interfaces/state/Chunks.scala | 6 +++++- src/main/scala/rules/ChunkSupporter.scala | 8 ++++---- src/main/scala/rules/Executor.scala | 2 +- src/main/scala/rules/MoreCompleteExhaleSupporter.scala | 3 ++- .../supporters/functions/FunctionVerificationUnit.scala | 2 +- .../resources/dependencyAnalysisTests/mce/mce_tests.vpr | 2 +- .../real-world-examples/listAppend.vpr | 6 +++--- .../dependencyAnalysisTests/unitTests/quasihavoc.vpr | 2 +- 10 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 43d579c52..6aaab5436 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -48,6 +48,7 @@ trait AssumptionAnalyzer { } object AssumptionAnalyzer { + val analysisLabelName: String = "$$analysisLabel$$" private val assumptionTypeAnnotationKey = "assumptionType" private val enableAssumptionAnalysisAnnotationKey = "enableAssumptionAnalysis" diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index aebb73bc8..2848df6a6 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -331,7 +331,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => val chunkInhaleNode = assumptionAnalyzer.getChunkInhaleNode(sourceChunks.head) return chunkInhaleNode.map(_.labelNode) } - val (label, _) = fresh(ast.LocalVar("analysisLabel", ast.Bool)()) + val (label, _) = fresh(ast.LocalVar(AssumptionAnalyzer.analysisLabelName, ast.Bool)()) val labelNode = assumptionAnalyzer.createLabelNode(label, sourceChunks, sourceTerms) val smtLabel = AssumptionAnalyzer.createAssumptionLabel(labelNode.map(_.id)) assumeLabel(label, smtLabel) diff --git a/src/main/scala/interfaces/state/Chunks.scala b/src/main/scala/interfaces/state/Chunks.scala index 7e88d6ead..623b05f78 100644 --- a/src/main/scala/interfaces/state/Chunks.scala +++ b/src/main/scala/interfaces/state/Chunks.scala @@ -41,7 +41,7 @@ object GeneralChunk { val newChunk = analysisInfo.decider.registerDerivedChunk[GeneralChunk](Set(chunk), {finalPerm => chunk.permMinus(finalPerm, newPermExp)}, newPerm, analysisInfo.withAssumptionType(AssumptionType.Internal), isExhale=false, createLabel=false) // TODO ake: assumption type? maybe for exhale we want to have Implicit? - @unused // we still need to register the chunk to have a sound analysis + @unused // we need to register the chunk to have a sound analysis val exhaledChunk = analysisInfo.decider.registerDerivedChunk[GeneralChunk](Set(chunk), { finalPerm => chunk.withPerm(finalPerm, newPermExp)}, newPerm, analysisInfo, isExhale=true, createLabel=false) @@ -78,6 +78,10 @@ object NonQuantifiedChunk { chunk.withSnap(snap, snapExp)}, chunk.perm, analysisInfo, isExhale=false, createLabel=false) } + + def withPerm(chunk: GeneralChunk, newPerm: Term, newPermExp: Option[ast.Exp], analysisInfo: AnalysisInfo, isExhale: Boolean=false): NonQuantifiedChunk = { + GeneralChunk.withPerm(chunk, newPerm, newPermExp, analysisInfo, isExhale).asInstanceOf[NonQuantifiedChunk] + } } trait QuantifiedChunk extends GeneralChunk { diff --git a/src/main/scala/rules/ChunkSupporter.scala b/src/main/scala/rules/ChunkSupporter.scala index a0df606d1..dcc8154bf 100644 --- a/src/main/scala/rules/ChunkSupporter.scala +++ b/src/main/scala/rules/ChunkSupporter.scala @@ -189,8 +189,8 @@ object chunkSupporter extends ChunkSupportRules { val toTake = PermMin(ch.perm, perms) val toTakeExp = permsExp.map(pe => buildMinExp(Seq(ch.permExp.get, pe), ast.Perm)) val newPermExp = permsExp.map(pe => ast.PermSub(ch.permExp.get, toTakeExp.get)(pe.pos, pe.info, pe.errT)) - val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, toTake), newPermExp, v.decider.getAnalysisInfo(AssumptionType.Implicit)).asInstanceOf[NonQuantifiedChunk] - val takenChunk = Some(GeneralChunk.withPerm(ch, toTake, toTakeExp, v.decider.getAnalysisInfo(assumptionType), isExhale=true).asInstanceOf[NonQuantifiedChunk]) // TODO ake: casting! + val newChunk = NonQuantifiedChunk.withPerm(ch, PermMinus(ch.perm, toTake), newPermExp, v.decider.getAnalysisInfo(AssumptionType.Implicit)) + val takenChunk = Some(NonQuantifiedChunk.withPerm(ch, toTake, toTakeExp, v.decider.getAnalysisInfo(assumptionType), isExhale=true)) var newHeap = h - ch if (!v.decider.check(newChunk.perm === NoPerm, Verifier.config.checkTimeout(), AssumptionType.Internal)) { newHeap = newHeap + newChunk @@ -203,8 +203,8 @@ object chunkSupporter extends ChunkSupportRules { val constraintExp = permsExp.map(pe => ast.PermLtCmp(pe, ch.permExp.get)(pe.pos, pe.info, pe.errT)) v.decider.assume(PermLess(perms, ch.perm), Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp)), AssumptionType.Implicit) val newPermExp = permsExp.map(pe => ast.PermSub(ch.permExp.get, pe)(pe.pos, pe.info, pe.errT)) - val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, perms), newPermExp, v.decider.getAnalysisInfo(AssumptionType.Implicit)).asInstanceOf[NonQuantifiedChunk] - val takenChunk = GeneralChunk.withPerm(ch, perms, permsExp, v.decider.getAnalysisInfo(assumptionType), isExhale=true).asInstanceOf[NonQuantifiedChunk] // TODO ake: casting! + val newChunk = NonQuantifiedChunk.withPerm(ch, PermMinus(ch.perm, perms), newPermExp, v.decider.getAnalysisInfo(AssumptionType.Implicit)) + val takenChunk = NonQuantifiedChunk.withPerm(ch, perms, permsExp, v.decider.getAnalysisInfo(assumptionType), isExhale=true) val newHeap = h - ch + newChunk assumeProperties(newChunk, newHeap) (Complete(), s, newHeap, Some(takenChunk)) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index b0b15dab4..117007dd8 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -578,7 +578,7 @@ object executor extends ExecutionRules { // Calling hack510() triggers a state consolidation. // See also Silicon issue #510. case ast.MethodCall(`hack510_method_name`, _, _) => - val s1 = v.stateConsolidator(s).consolidate(s, v) // TODO ake: pass assumption Type + val s1 = v.stateConsolidator(s).consolidate(s, v) // TODO ake: pass assumption Type? Q(s1, v) case call @ ast.MethodCall(methodName, eArgs, lhs) => diff --git a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala index 23ecfac46..39487301a 100644 --- a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala +++ b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala @@ -480,8 +480,9 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { newFr = newFr.recordPathSymbol(permTaken.applicable.asInstanceOf[Function]).recordConstraint(constraint) + @unused // required in order to ensure a sound assumption analysis val _ = GeneralChunk.withPerm(ch, permTaken, None, v.decider.getAnalysisInfo(assumptionType), isExhale=true) - GeneralChunk.withPerm(ch, PermMinus(ch.perm, permTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, permTakenExp.get)(pe.pos, pe.info, pe.errT)), v.decider.getAnalysisInfo(AssumptionType.Implicit)).asInstanceOf[NonQuantifiedChunk] // TODO ake: casting + NonQuantifiedChunk.withPerm(ch, PermMinus(ch.perm, permTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, permTakenExp.get)(pe.pos, pe.info, pe.errT)), v.decider.getAnalysisInfo(AssumptionType.Implicit)) }) val totalTakenBounds = diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index b00fe44be..cd48cc1e9 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -295,7 +295,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver val cleanAxiom = if(!Verifier.config.enableAssumptionAnalysis()) axiom else axiom.map(a => (a._1.transform{ - case Var(name, _, _) if name.name.startsWith("analysisLabel") => True + case Var(name, _, _) if name.name.startsWith(AssumptionAnalyzer.analysisLabelName) => True }(), a._2)) decider.prover.assumeAxiomsWithAnalysisInfo(InsertionOrderedSet(cleanAxiom), "Function axioms") diff --git a/src/test/resources/dependencyAnalysisTests/mce/mce_tests.vpr b/src/test/resources/dependencyAnalysisTests/mce/mce_tests.vpr index 6676c66c9..009297bf8 100644 --- a/src/test/resources/dependencyAnalysisTests/mce/mce_tests.vpr +++ b/src/test/resources/dependencyAnalysisTests/mce/mce_tests.vpr @@ -7,7 +7,7 @@ method mce_test_1(a: Ref, b: Ref, c: Ref) @dependency("Explicit") inhale acc(a.f) // assumption 1 - // @irrelevant("Explicit") // TODO ake: imprecise + @irrelevant("Explicit") inhale acc(b.f) // assumption 2 if (@dependency("PathCondition")(c == a || c == b)) { diff --git a/src/test/resources/dependencyAnalysisTests/real-world-examples/listAppend.vpr b/src/test/resources/dependencyAnalysisTests/real-world-examples/listAppend.vpr index 8819d6ae3..46f4014e1 100644 --- a/src/test/resources/dependencyAnalysisTests/real-world-examples/listAppend.vpr +++ b/src/test/resources/dependencyAnalysisTests/real-world-examples/listAppend.vpr @@ -14,16 +14,16 @@ function listLength(l:Ref) : Int } method appendList1(this: Ref, e: Int) - requires list(this) // TODO ake: imprecise @irrelevant("Explicit")(list(this)) + requires @irrelevant("Explicit")(list(this)) requires @irrelevant("Explicit")(0 <= e && e < 100) ensures list(this) { - // @irrelevant("Implicit") TODO ake: imprecise + @irrelevant("Implicit") unfold list(this) @irrelevant("Explicit") assume 0 <= this.elem && this.elem < 100 - if (this.next == null) { // TODO ake: imprecise @irrelevant("PathCondition")(this.next == null) + if (@irrelevant("PathCondition")(this.next == null)) { var n: Ref @dependency("Implicit") diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/quasihavoc.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/quasihavoc.vpr index ebe5746f0..b42a9db5b 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/quasihavoc.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/quasihavoc.vpr @@ -5,7 +5,7 @@ method quasihavoc1(x: Ref, y: Ref) requires @dependency("Explicit")(x.f == 3) requires @dependency("Explicit")(x != y) { - @irrelevant() // TODO ake + @irrelevant() quasihavoc y.f // does nothing. we have no permission @testAssertion("Explicit") assert x.f == 3 From 2900206977dd17e87e74b5bb4f95d3f35874fde7 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 8 Aug 2025 09:07:05 +0200 Subject: [PATCH 229/474] optimize infeasibility case --- .../AssumptionAnalyzer.scala | 15 +++++++++++++ src/main/scala/decider/Decider.scala | 13 ++++++++---- src/main/scala/rules/Executor.scala | 21 ++++++++++++++++--- 3 files changed, 42 insertions(+), 7 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 6aaab5436..b0505908e 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -44,6 +44,8 @@ trait AssumptionAnalyzer { def addCustomExpDependency(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp]): Unit def addFunctionAxiomEdges(): Unit + def addInfeasibilityDepToStmt(infeasNodeId: Option[Int], analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Unit = {} + def buildFinalGraph(): Option[AssumptionAnalysisGraph] } @@ -212,6 +214,12 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { else Some(SimpleAssertionNode(term, assumptionType, analysisSourceInfo, isClosed_)) } + + def addAssertNode(term: Term, assumptionType: AssumptionType, analysisSourceInfo: AnalysisSourceInfo): Option[Int] = { + val node = createAssertOrCheckNode(term, assumptionType, analysisSourceInfo, isCheck=false) + node foreach addNode + node map (_.id) + } override def addAssertFalseNode(isCheck: Boolean, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo): Option[Int] = { val node = createAssertOrCheckNode(False, assumptionType, sourceInfo, isCheck) @@ -323,6 +331,13 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { mergedGraph } + + override def addInfeasibilityDepToStmt(infeasNodeId: Option[Int], analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Unit = { + val newAssertionNodeId = addAssertNode(False, assumptionType, analysisSourceInfo) + val newAssumptionNodeId = addAssumption(False, analysisSourceInfo, assumptionType) + addDependency(infeasNodeId, newAssumptionNodeId) + addDependency(infeasNodeId, newAssertionNodeId) + } } class NoAssumptionAnalyzer extends AssumptionAnalyzer { diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 2848df6a6..69ae6a260 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -444,7 +444,9 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => }) } - private def assumeWithoutSmokeChecks(termsWithLabel: InsertionOrderedSet[(Term, String)], isDefinition: Boolean = false) = { + private def assumeWithoutSmokeChecks(termsWithLabel: InsertionOrderedSet[(Term, String)], isDefinition: Boolean = false): None.type = { + if (Verifier.config.disableInfeasibilityChecks() && pcs.getCurrentInfeasibilityNode.isDefined) return None + val terms = termsWithLabel map (_._1) val assumeRecord = new DeciderAssumeRecord(terms) val sepIdentifier = symbExLog.openScope(assumeRecord) @@ -457,7 +459,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } /* Add terms to the prover's assumptions */ - termsWithLabel foreach{case (t, label) => prover.assume(t, label)} + termsWithLabel foreach { case (t, label) => prover.assume(t, label) } symbExLog.closeScope(sepIdentifier) None @@ -477,7 +479,8 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => }else{ ("", None) } val timeout = if (isAssert) Verifier.config.assertTimeout.toOption else Verifier.config.checkTimeout.toOption - val result = prover.check(timeout, label) == Unsat + val isInfeasiblePath = Verifier.config.disableInfeasibilityChecks() && pcs.getCurrentInfeasibilityNode.isDefined + val result = isInfeasiblePath || prover.check(timeout, label) == Unsat if(result) { if(pcs.getCurrentInfeasibilityNode.isDefined){ assumptionAnalyzer.addDependency(pcs.getCurrentInfeasibilityNode, checkNodeId) @@ -525,6 +528,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => val sepIdentifier = symbExLog.openScope(assertRecord) val asserted = if(Verifier.config.enableAssumptionAnalysis()) t.equals(True) else isKnownToBeTrue(t) + val assertNode = if(!asserted) assumptionAnalyzer.createAssertOrCheckNode(t, assumptionType, decider.analysisSourceInfoStack.getFullSourceInfo, isCheck) else None val result = asserted || proverAssert(t, timeout, AssumptionAnalyzer.createAssertionLabel(assertNode map (_.id))) @@ -547,7 +551,8 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => val assertRecord = new ProverAssertRecord(t, timeout) val sepIdentifier = symbExLog.openScope(assertRecord) - val result = prover.assert(t, timeout, label) + val isInfeasiblePath = Verifier.config.disableInfeasibilityChecks() && pcs.getCurrentInfeasibilityNode.isDefined + val result = isInfeasiblePath || prover.assert(t, timeout, label) if(result) if(pcs.getCurrentInfeasibilityNode.isDefined) { diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 117007dd8..6e171122a 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -321,16 +321,31 @@ object executor extends ExecutionRules { else Q(s, v) + // TODO ake: skipping some statements (even in infeasible paths) resulted in variable not found errors + private def alwaysExecute(stmt: ast.Stmt): Boolean = stmt match { + case ast.NewStmt(_, _) | ast.LocalVarDeclStmt(_) | ast.Label(_, _) => true + case _ => false + } + def exec(s: State, stmt: ast.Stmt, v: Verifier) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { val sepIdentifier = v.symbExLog.openScope(new ExecuteRecord(stmt, s, v.decider.pcs)) val sourceInfo = StmtAnalysisSourceInfo(stmt) v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo) - exec2(s, stmt, v)((s1, v1) => { + if(Verifier.config.disableInfeasibilityChecks() && Verifier.config.enableAssumptionAnalysis() && + v.decider.pcs.getCurrentInfeasibilityNode.isDefined && !alwaysExecute(stmt)){ + v.decider.assumptionAnalyzer.addInfeasibilityDepToStmt(v.decider.pcs.getCurrentInfeasibilityNode, + v.decider.analysisSourceInfoStack.getFullSourceInfo, AssumptionType.Implicit) v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) - v1.symbExLog.closeScope(sepIdentifier) - Q(s1, v1)}) + Q(s, v) + }else { + exec2(s, stmt, v)((s1, v1) => { + v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) + v1.symbExLog.closeScope(sepIdentifier) + Q(s1, v1) + }) + } } def exec2(state: State, stmt: ast.Stmt, v: Verifier) From 4594cf6138a1a21f7de37b80bed527d591989bf5 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 8 Aug 2025 10:23:31 +0200 Subject: [PATCH 230/474] fix verification errors occuring when disabling infeasibility checks --- src/main/scala/decider/Decider.scala | 11 ++++++----- src/main/scala/decider/PathConditions.scala | 7 +++++++ src/main/scala/rules/Evaluator.scala | 9 ++++++++- src/main/scala/rules/Joiner.scala | 3 ++- 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 69ae6a260..e8547fa29 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -64,6 +64,7 @@ trait Decider { def registerChunk[CH <: GeneralChunk](buildChunk: Term => CH, perm: Term, analysisInfo: AnalysisInfo, isExhale: Boolean): CH def registerDerivedChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, analysisInfo: AnalysisInfo, isExhale: Boolean, createLabel: Boolean=true): CH def wrapWithAssumptionAnalysisLabel(term: Term, sourceChunks: Iterable[Chunk] = Set.empty, sourceTerms: Iterable[Term] = Set.empty): Term + def isPathInfeasible(): Boolean def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp], assumptionType: AssumptionType): Unit def assume(t: Term, debugExp: Option[DebugExp], assumptionType: AssumptionType): Unit @@ -346,6 +347,8 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => labelNode.map(n => Implies(n.term, term)).getOrElse(term) } + def isPathInfeasible(): Boolean = Verifier.config.disableInfeasibilityChecks() && pcs.getCurrentInfeasibilityNode.isDefined + def addDebugExp(e: DebugExp): Unit = { if (debugMode) { pathConditions.addDebugExp(e) @@ -445,7 +448,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } private def assumeWithoutSmokeChecks(termsWithLabel: InsertionOrderedSet[(Term, String)], isDefinition: Boolean = false): None.type = { - if (Verifier.config.disableInfeasibilityChecks() && pcs.getCurrentInfeasibilityNode.isDefined) return None + if (isPathInfeasible()) return None val terms = termsWithLabel map (_._1) val assumeRecord = new DeciderAssumeRecord(terms) @@ -479,8 +482,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => }else{ ("", None) } val timeout = if (isAssert) Verifier.config.assertTimeout.toOption else Verifier.config.checkTimeout.toOption - val isInfeasiblePath = Verifier.config.disableInfeasibilityChecks() && pcs.getCurrentInfeasibilityNode.isDefined - val result = isInfeasiblePath || prover.check(timeout, label) == Unsat + val result = isPathInfeasible() || prover.check(timeout, label) == Unsat if(result) { if(pcs.getCurrentInfeasibilityNode.isDefined){ assumptionAnalyzer.addDependency(pcs.getCurrentInfeasibilityNode, checkNodeId) @@ -551,8 +553,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => val assertRecord = new ProverAssertRecord(t, timeout) val sepIdentifier = symbExLog.openScope(assertRecord) - val isInfeasiblePath = Verifier.config.disableInfeasibilityChecks() && pcs.getCurrentInfeasibilityNode.isDefined - val result = isInfeasiblePath || prover.assert(t, timeout, label) + val result = isPathInfeasible() || prover.assert(t, timeout, label) if(result) if(pcs.getCurrentInfeasibilityNode.isDefined) { diff --git a/src/main/scala/decider/PathConditions.scala b/src/main/scala/decider/PathConditions.scala index e155541a9..2fccfa17d 100644 --- a/src/main/scala/decider/PathConditions.scala +++ b/src/main/scala/decider/PathConditions.scala @@ -32,6 +32,7 @@ trait RecordedPathConditions { def definingAssumptionExps: InsertionOrderedSet[DebugExp] def declarations: InsertionOrderedSet[Decl] def analysisLabels: InsertionOrderedSet[Term] + def infeasibilityNodeId: Option[Int] def definitionsOnly: RecordedPathConditions @@ -282,6 +283,9 @@ private trait LayeredPathConditionStackLike { protected def analysisLabels(layers: Stack[PathConditionStackLayer]): InsertionOrderedSet[Term] = InsertionOrderedSet(layers.flatMap(_.analysisLabels)) + protected def infeasibilityNodeId(layers: Stack[PathConditionStackLayer]): Option[Int] = + layers.flatMap(_.infeasibilityNodeId).headOption + protected def contains(layers: Stack[PathConditionStackLayer], assumption: Term): Boolean = layers exists (_.contains(assumption)) @@ -428,6 +432,7 @@ private class DefaultRecordedPathConditions(from: Stack[PathConditionStackLayer] val definingAssumptionExps: InsertionOrderedSet[DebugExp] = definingAssumptionExps(from) val declarations: InsertionOrderedSet[Decl] = declarations(from) val analysisLabels: InsertionOrderedSet[Term] = analysisLabels(from) + val infeasibilityNodeId: Option[Int] = infeasibilityNodeId(from) def contains(assumption: Term): Boolean = contains(from, assumption) @@ -605,6 +610,8 @@ private[decider] class LayeredPathConditionStack def analysisLabels: InsertionOrderedSet[Term] = InsertionOrderedSet(layers.flatMap(_.analysisLabels)) + override def infeasibilityNodeId: Option[Mark] = layers.flatMap(_.infeasibilityNodeId).headOption + def definingAssumptions: InsertionOrderedSet[Term] = InsertionOrderedSet(layers.flatMap(_.globalDefiningAssumptions) ++ layers.flatMap(_.nonGlobalDefiningAssumptions)) // Note: Performance? diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 46f0ab5a4..00eb751a5 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -1688,7 +1688,14 @@ object evaluator extends EvaluationRules { val joinSort = v.symbolConverter.toSort(joinType) assert(entries.nonEmpty, "Expected at least one join data entry") - entries match { + // join entries coming from feasible paths only! + val feasibleEntries = entries filter (!Verifier.config.disableInfeasibilityChecks() || _.pathConditions.infeasibilityNodeId.isEmpty) + + feasibleEntries match { + case Seq() => + /* feasibility checks are disabled and all entries are infeasible, we can return any state and data */ + v.decider.pcs.setCurrentInfeasibilityNode(entries.head.pathConditions.infeasibilityNodeId) + (entries.head.s, entries.head.data) case Seq(entry) => /* If there is only one entry, i.e. one branch to join, it is assumed that the other * branch was infeasible, and the branch conditions are therefore ignored. diff --git a/src/main/scala/rules/Joiner.scala b/src/main/scala/rules/Joiner.scala index f6e045088..31f4b41c8 100644 --- a/src/main/scala/rules/Joiner.scala +++ b/src/main/scala/rules/Joiner.scala @@ -99,7 +99,8 @@ object joiner extends JoiningRules { var feasibleBranchesExp: Option[List[ast.Exp]] = Option.when(withExp)(Nil) var feasibleBranchesExpNew: Option[List[ast.Exp]] = Option.when(withExp)(Nil) - entries foreach (entry => { + // infeasible branches can be skipped + entries filter (entry => !Verifier.config.disableInfeasibilityChecks() || entry.pathConditions.infeasibilityNodeId.isEmpty) foreach (entry => { val pcs = entry.pathConditions.conditionalized val pcsExp = Option.when(withExp)(entry.pathConditions.conditionalizedExp) val comment = "Joined path conditions" From db164e57836296cad46856e47c1251301b4d3b67 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 8 Aug 2025 10:23:31 +0200 Subject: [PATCH 231/474] fix verification errors occuring when disabling infeasibility checks --- src/main/scala/rules/Consumer.scala | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index 920161d6a..c6054870d 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -204,6 +204,11 @@ object consumer extends ConsumptionRules { * time permissions have been consumed. */ + if(v.decider.isPathInfeasible()){ + v.decider.assumptionAnalyzer.addInfeasibilityDepToStmt(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, assumptionType) + return Q(s, h, Option.when(returnSnap)(Unit), v) + } + v.logger.debug(s"\nCONSUME ${viper.silicon.utils.ast.sourceLineColumn(a)}: $a") v.logger.debug(v.stateFormatter.format(s, v.decider.pcs)) v.logger.debug("h = " + v.stateFormatter.format(h)) From a25c90a90af2bf5c061d5c861645bd4483effd44 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 12 Aug 2025 08:12:49 +0200 Subject: [PATCH 232/474] check pipeline --- src/main/scala/Config.scala | 4 ++-- src/test/scala/AssumptionAnalysisTests.scala | 18 ++++-------------- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/src/main/scala/Config.scala b/src/main/scala/Config.scala index 3c0df05d5..ca1b68b40 100644 --- a/src/main/scala/Config.scala +++ b/src/main/scala/Config.scala @@ -832,13 +832,13 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { val enableAssumptionAnalysis: ScallopOption[Boolean] = opt[Boolean]("enableAssumptionAnalysis", descr = "Enable assumption analysis mode", - default = Some(false), + default = Some(true), noshort = true ) val disableInfeasibilityChecks: ScallopOption[Boolean] = opt[Boolean]("disableInfeasibilityChecks", descr = "Disable infeasibility checks. As a consequence all paths will be explored to the end. (Potentially) huge performance overhead!", - default = Some(false), + default = Some(true), noshort = true ) diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index 15bdb65d7..4bac66670 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -20,13 +20,13 @@ class AssumptionAnalysisTests extends AnyFunSuite { val CHECK_PRECISION = false val EXECUTE_PRECISION_BENCHMARK = false - val EXECUTE_TEST = true + val EXECUTE_TEST = false val EXECUTE_PERFORMANCE_BENCHMARK = false val ignores: Seq[String] = Seq("example1", "example2", "iterativeTreeDelete") val testDirectories: Seq[String] = Seq( "dependencyAnalysisTests/all", "dependencyAnalysisTests/unitTests", -// "dependencyAnalysisTests/real-world-examples", + "dependencyAnalysisTests/real-world-examples", // "dependencyAnalysisTests/quick" // "dependencyAnalysisTests/fromSilver", // "dependencyAnalysisTests/performanceBenchmark" @@ -70,7 +70,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { proofCoverageWriter.close() } -// createSingleTest("dependencyAnalysisTests/quick", "test") +// createSingleTest("dependencyAnalysisTests/real-world-examples", "listAppend") if(EXECUTE_TEST) testDirectories foreach (dir => visitFiles(dir, createSingleTest)) @@ -413,7 +413,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { if(dependenciesPerSource.nonEmpty){ val wrongDependencies = assumptionsPerSource.filter({ case (_, assumptions) => dependencyIds.intersect(assumptions.map(_.id)).nonEmpty }) - 1.0 - (wrongDependencies.size.toDouble / dependenciesPerSource.size.toDouble) // TODO ake: or / assumptionsPerSource.size.toDouble? + 1.0 - (wrongDependencies.size.toDouble / dependenciesPerSource.size.toDouble) }else{ 1.0 } @@ -459,16 +459,6 @@ class AssumptionAnalysisTests extends AnyFunSuite { val baselineDurationMs: Double = verifyAndMeasure(program, baseCommandLineArguments) printResult(f"$baselineDurationMs,$analysisDurationMs,${analysisDurationMs/baselineDurationMs},$programSize\n") - - // TODO ake: rewrite if we want to support this -// if(naiveProgram.isDefined){ -// val naiveDurationMs: Double = verifyAndMeasure(naiveProgram.get, baseCommandLineArguments) -// writer.println(f"naive duration (ms): $naiveDurationMs") -// writer.println(f"diff naive-baseline (ms): ${naiveDurationMs-baselineDurationMs}") -// writer.println(f"naive overhead (naive/baseline): ${naiveDurationMs/baselineDurationMs}") -// println(f"naive overhead (naive/baseline): ${naiveDurationMs/baselineDurationMs}") -// } - } private def verifyAndMeasure(program_ : Program, commandLineArgs: Seq[String]) = { From 1dc6c42523be1ee234cffc5439757b5d1a0a6ace Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 16 Aug 2025 07:51:11 +0200 Subject: [PATCH 233/474] add pruning requests to command line tool --- .../AssumptionAnalysisUserTool.scala | 19 ++++++++++++++++++- .../scala/verifier/DefaultMainVerifier.scala | 2 +- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala index 2148ae7e0..475570a68 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala @@ -7,13 +7,15 @@ import viper.silver.ast.Method import scala.annotation.tailrec import scala.io.StdIn.readLine -class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpreter, memberInterpreters: Seq[AssumptionAnalysisInterpreter]) { +class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpreter, memberInterpreters: Seq[AssumptionAnalysisInterpreter], + program: ast.Program) { private val infoString = "Enter " + "\n\t'dep [line numbers]' to print all dependencies of the given line numbers or" + "\n\t'downDep [line numbers]' to print all dependents of the given line numbers or" + "\n\t'hasDep [line numbers]' to print whether there exists any dependency between any pair of the given lines or" + "\n\t'cov [members]' to print proof coverage of given member or" + "\n\t'covL member [line numbers]' to print proof coverage of given lines of given member or" + + "\n\t'prune [line numbers]' to prune the program with respect to the given line numbers and export the new program or" + "\n\t'q' to quit" def run(): Unit = { @@ -49,6 +51,8 @@ class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpr handleProofCoverageQuery(inputParts.tail) }else if (inputParts.head.equalsIgnoreCase("covLines") || inputParts.head.equalsIgnoreCase("covL")) { handleProofCoverageLineQuery(inputParts.tail) + }else if(inputParts.head.equalsIgnoreCase("prune")){ + handlePruningRequest(inputParts.tail) } else { println("Invalid input.") println(infoString) @@ -164,4 +168,17 @@ class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpr val durationMs = (endAnalysis - startAnalysis) / 1e6 (res, durationMs) } + + private def handlePruningRequest(inputs: Seq[String]): Unit = { + println("exportFileName: ") + val exportFileName = readLine() + + val queriedNodes = getQueriedNodesFromInput(inputs.toSet) + val dependencies = fullGraphInterpreter.getAllNonInternalDependencies(queriedNodes.map(_.id)) + val crucialNodes = queriedNodes ++ dependencies + println(s"Found ${crucialNodes.size} crucial nodes. Pruning...") + + fullGraphInterpreter.pruneProgramAndExport(crucialNodes, program, exportFileName) + println("Done.") + } } diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index 94c73d349..68e13ddc3 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -324,7 +324,7 @@ class DefaultMainVerifier(config: Config, joinedGraphInterpreter.exportGraph() if(Verifier.config.startAssumptionAnalysisTool()){ - val commandLineTool = new AssumptionAnalysisUserTool(joinedGraphInterpreter, assumptionAnalysisInterpreters) + val commandLineTool = new AssumptionAnalysisUserTool(joinedGraphInterpreter, assumptionAnalysisInterpreters, originalProgram) commandLineTool.run() } From b011e39d5f970214020650298a1d81a2000a6040 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 16 Aug 2025 14:01:59 +0200 Subject: [PATCH 234/474] fix precision benchmark --- src/main/scala/Config.scala | 4 +- .../precision_test_generator.py | 5 +- .../benchmark_scripts/snippets.txt | 114 ++++++++---------- src/test/scala/AssumptionAnalysisTests.scala | 5 +- 4 files changed, 59 insertions(+), 69 deletions(-) diff --git a/src/main/scala/Config.scala b/src/main/scala/Config.scala index ca1b68b40..3c0df05d5 100644 --- a/src/main/scala/Config.scala +++ b/src/main/scala/Config.scala @@ -832,13 +832,13 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { val enableAssumptionAnalysis: ScallopOption[Boolean] = opt[Boolean]("enableAssumptionAnalysis", descr = "Enable assumption analysis mode", - default = Some(true), + default = Some(false), noshort = true ) val disableInfeasibilityChecks: ScallopOption[Boolean] = opt[Boolean]("disableInfeasibilityChecks", descr = "Disable infeasibility checks. As a consequence all paths will be explored to the end. (Potentially) huge performance overhead!", - default = Some(true), + default = Some(false), noshort = true ) diff --git a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_test_generator.py b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_test_generator.py index 01c35409e..a207868c1 100644 --- a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_test_generator.py +++ b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_test_generator.py @@ -92,9 +92,10 @@ def generate_from_snippet(snippet: str, line: str): gen_vars_rw_field = gen_vars_rw_field + [f"{v}.f" for v in gen_rw_refs] snippet = snippet.replace("$INVARIANT", invariant if invariant != "" else "true") - acc_invariants = ([inv for inv in acc_invariant.split("&&") if inv != ""] ) + [f"acc({v}, 1/2)" for v in gen_vars_ro_field] + [f"acc({v})" for v in gen_vars_rw_field] - acc_invariant = " && ".join([f"@irrelevant(\"LoopInvariant\")({v})" for v in acc_invariants]) + gen_acc_invariants = [f"acc({v}, 1/2)" for v in gen_vars_ro_field] + [f"acc({v})" for v in gen_vars_rw_field] + gen_acc_invariant = " && ".join([f"@irrelevant(\"LoopInvariant\")({v})" for v in gen_acc_invariants]) snippet = snippet.replace("$ACC_INVARIANT", acc_invariant if acc_invariant != "" else "true") + snippet = snippet.replace("$GEN_ACC_INVARIANT", gen_acc_invariant if gen_acc_invariant != "" else "true") # declare and initialize newly generated vars snippet = "".join([f"var {v.split(".")[0]}: Ref\n@irrelevant(\"Explicit\")\ninhale acc({v}, 1/2)\n" for v in gen_vars_ro_field]) + snippet diff --git a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/snippets.txt b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/snippets.txt index 411732464..e4e3f4668 100644 --- a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/snippets.txt +++ b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/snippets.txt @@ -1,11 +1,11 @@ field gen_f: Int -predicate gen_confuse(i: Int, inv: Bool){ - i >= 0 && inv +predicate gen_confuse(i: Int){ + i >= 0 } -predicate gen_confuse_with_impl(i: Int, inv: Bool){ - i >= 0 ==> inv +predicate gen_confuse_with_impl(i: Int, x: Ref){ + i >= 0 ==> acc(x.f) } function gen_add(a: Int, b: Int): Int @@ -24,6 +24,12 @@ method incr(a: Ref) a.f := a.f + 1 } +method gen_incr_pure(a: Int) returns (res: Int) + ensures res == a + 1 +{ + res := a + 1 +} + SNIPPET baseline$=${} SNIPPET assignment_pure$=${ @@ -36,7 +42,7 @@ SNIPPET assignment_field$=${ $RW_INT_FIELD_0 := $RO_INT_0 + $RO_INT_1 } -SNIPPET div_by_0$=${ +SNIPPET branch_div0$=${ if(@irrelevant("PathCondition")($RO_INT_0 > 0)){ @irrelevant("Implicit") $RW_INT_0 := $RO_INT_1 / $RO_INT_0 @@ -47,45 +53,31 @@ SNIPPET div_by_0$=${ } SNIPPET inhale_invariant$=${ + var gen_i: Int @irrelevant("Explicit") - inhale $INVARIANT + inhale $INVARIANT && gen_i > 0 } SNIPPET assert$=${ + var gen_i: Int + @irrelevant("Explicit") + inhale gen_i > 0 @irrelevant() - assert $INVARIANT + assert $INVARIANT && gen_i > 0 } -SNIPPET exhale_inhale_perm$=${ - @irrelevant("Implicit") - exhale acc($RW_INT_FIELD_0, 1/2) - +// fold-unfold with implication +SNIPPET fold_unfold_with_implication$=${ + var gen_x: Ref @irrelevant("Explicit") - inhale acc($RW_INT_FIELD_0, 1/2) + inhale acc(gen_x.f) + @irrelevant("Implicit") + fold gen_confuse_with_impl($RO_INT_0, gen_x) + @irrelevant("Implicit") + unfold gen_confuse_with_impl($RO_INT_0, gen_x) } -// TODO ake -// fold-unfold -// SNIPPET fold_unfold_basic$=${ -// var gen_i: Int -// @irrelevant("Implicit") -// inhale gen_i > 10 -// @irrelevant("Implicit") -// fold gen_confuse(gen_i, $ACC_INVARIANT && $INVARIANT) -// @irrelevant("Implicit") -// unfold gen_confuse(gen_i, $ACC_INVARIANT && $INVARIANT) -// } - -// fold-unfold with implication -// SNIPPET fold_unfold_with_implication$=${ -// var gen_i: Int -// @irrelevant("Implicit") -// fold gen_confuse_with_impl(gen_i, $ACC_INVARIANT && $INVARIANT) -// @irrelevant("Implicit") -// unfold gen_confuse_with_impl(gen_i, $ACC_INVARIANT && $INVARIANT) -// } - // function call SNIPPET function_add$=${ @irrelevant("Implicit") @@ -94,9 +86,18 @@ SNIPPET function_add$=${ $RW_INT_1 := gen_add($RW_INT_0, $RO_INT_2) } -SNIPPET method_call$=${ + +SNIPPET method_call_impure$=${ + var gen_ref_0: Ref + @irrelevant("Implicit") + gen_ref_0 := new(f) @irrelevant("Implicit") - incr($RW_REF_F_0) + incr(gen_ref_0) +} + +SNIPPET method_call_pure$=${ + @irrelevant("Implicit") + $RW_INT_0 := gen_incr_pure($RO_INT_0) } SNIPPET branch$=${ @@ -230,8 +231,8 @@ SNIPPET while_pure$=${ @irrelevant("Implicit") $RW_INT_PURE_1 := 0 while(@irrelevant("PathCondition")($RW_INT_PURE_0 > 0)) - invariant @irrelevant("LoopInvariant")($ACC_INVARIANT) - invariant @irrelevant("LoopInvariant")($INVARIANT) + invariant $ACC_INVARIANT + invariant @irrelevant("LoopInvariant")($GEN_ACC_INVARIANT) invariant @irrelevant("LoopInvariant")($RW_INT_PURE_0 <= gen_start) invariant @irrelevant("LoopInvariant")($RW_INT_PURE_1 == (gen_start-$RW_INT_PURE_0)*$RO_INT_PURE_0) { @@ -248,10 +249,9 @@ SNIPPET while_perm$=${ gen_start := $RW_INT_FIELD_0 @irrelevant("Implicit") $RW_INT_FIELD_1 := 0 - @irrelevant("Explicit") while(@irrelevant("PathCondition")($RW_INT_FIELD_0 > 0)) - invariant @irrelevant("LoopInvariant")($ACC_INVARIANT) - invariant @irrelevant("LoopInvariant")($INVARIANT) + invariant $ACC_INVARIANT + invariant @irrelevant("LoopInvariant")($GEN_ACC_INVARIANT) invariant @irrelevant("LoopInvariant")($RW_INT_FIELD_0 <= gen_start) invariant @irrelevant("LoopInvariant")($RW_INT_FIELD_1 == (gen_start-$RW_INT_FIELD_0)*10) { @@ -262,29 +262,17 @@ SNIPPET while_perm$=${ } } -SNIPPET magic_wand_1$=${ - var gen_i: Int - @irrelevant("Explicit") - inhale gen_i > 0 - @irrelevant("Implicit") - package gen_i >= 0 --* $ACC_INVARIANT && $INVARIANT - @irrelevant("Implicit") - apply gen_i >= 0 --* $ACC_INVARIANT && $INVARIANT -} - -SNIPPET magic_wand_2$=${ - var gen_i: Int - @irrelevant("Explicit") - inhale gen_i > 0 - @irrelevant("Implicit") - package $INVARIANT --* gen_i >= 0 -} - -SNIPPET magic_wand_perm$=${ - @irrelevant("Implicit") - package acc($RW_INT_FIELD_0, 1/2) --* acc($RW_INT_FIELD_0, write) - @irrelevant("Implicit") - apply acc($RW_INT_FIELD_0, 1/2) --* acc($RW_INT_FIELD_0, write) +SNIPPET magic_wand$=${ + var gen_i: Int + var gen_x: Ref + @irrelevant("Explicit") + inhale gen_i > 0 + @irrelevant("Explicit") + inhale acc(gen_x.f) && gen_x.f > 0 + @irrelevant("Implicit") + package gen_i >= 0 --* acc(gen_x.f) && gen_x.f > 0 + @irrelevant("Implicit") + apply gen_i >= 0 --* acc(gen_x.f) && gen_x.f > 0 } diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index 4bac66670..7eee26d44 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -20,7 +20,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { val CHECK_PRECISION = false val EXECUTE_PRECISION_BENCHMARK = false - val EXECUTE_TEST = false + val EXECUTE_TEST = true val EXECUTE_PERFORMANCE_BENCHMARK = false val ignores: Seq[String] = Seq("example1", "example2", "iterativeTreeDelete") val testDirectories: Seq[String] = Seq( @@ -72,10 +72,11 @@ class AssumptionAnalysisTests extends AnyFunSuite { // createSingleTest("dependencyAnalysisTests/real-world-examples", "listAppend") - if(EXECUTE_TEST) + if(EXECUTE_TEST) { testDirectories foreach (dir => visitFiles(dir, createSingleTest)) analysisCommandLineArguments = Seq("--enableMoreCompleteExhale") ++ analysisCommandLineArguments visitFiles("dependencyAnalysisTests/mce", createSingleTest) + } def visitFiles(dirName: String, function: (String, String) => Unit): Unit = { From 5559971af1ec30e0d7160ffa07190c28527467ad Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 16 Aug 2025 14:25:31 +0200 Subject: [PATCH 235/474] add precision benchmark results --- .../results/result_2025-08-16_13-51-18.out | 3584 +++++++++++++++++ .../precisionTests/results/result_table.out | 14 + src/test/scala/AssumptionAnalysisTests.scala | 4 +- 3 files changed, 3601 insertions(+), 1 deletion(-) create mode 100644 src/test/resources/dependencyAnalysisTests/precisionTests/results/result_2025-08-16_13-51-18.out create mode 100644 src/test/resources/dependencyAnalysisTests/precisionTests/results/result_table.out diff --git a/src/test/resources/dependencyAnalysisTests/precisionTests/results/result_2025-08-16_13-51-18.out b/src/test/resources/dependencyAnalysisTests/precisionTests/results/result_2025-08-16_13-51-18.out new file mode 100644 index 000000000..9759fc7f6 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/precisionTests/results/result_2025-08-16_13-51-18.out @@ -0,0 +1,3584 @@ +dependencyAnalysisTests/precisionTests/branches - assert +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +branch1: 1.0 +branch2: 1.0 +branch3: 1.0 +branch4: 1.0 +branch5: 1.0 + +dependencyAnalysisTests/precisionTests/branches - assignment_field +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +branch1: 1.0 +branch2: 1.0 +branch3: 1.0 +branch4: 1.0 +branch5: 1.0 + +dependencyAnalysisTests/precisionTests/branches - assignment_pure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +branch1: 1.0 +branch2: 1.0 +branch3: 1.0 +branch4: 1.0 +branch5: 1.0 + +dependencyAnalysisTests/precisionTests/branches - baseline +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +branch1: 1.0 +branch2: 1.0 +branch3: 1.0 +branch4: 1.0 +branch5: 1.0 + +dependencyAnalysisTests/precisionTests/branches - branch +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +branch1: 0.75 +branch2: 0.5 +branch3: 0.6666666666666667 +branch4: 1.0 +branch5: 0.33333333333333337 + +dependencyAnalysisTests/precisionTests/branches - branch_div0 +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +branch1: 0.75 +branch2: 0.6666666666666667 +branch3: 0.6666666666666667 +branch4: 1.0 +branch5: 0.33333333333333337 + +dependencyAnalysisTests/precisionTests/branches - branch_function +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +branch1: 0.6 +branch2: 0.4 +branch3: 0.4 +branch4: 1.0 +branch5: 0.25 + +dependencyAnalysisTests/precisionTests/branches - disjoint_ref +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +branch1: 1.0 +branch2: 1.0 +branch3: 1.0 +branch4: 1.0 +branch5: 1.0 + +dependencyAnalysisTests/precisionTests/branches - disjoint_ref_disjoint_field +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +branch1: 1.0 +branch2: 1.0 +branch3: 1.0 +branch4: 1.0 +branch5: 1.0 + +dependencyAnalysisTests/precisionTests/branches - disjoint_ref_disjoint_field_quantified +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +branch1: 1.0 +branch2: 1.0 +branch3: 1.0 +branch4: 1.0 +branch5: 1.0 + +dependencyAnalysisTests/precisionTests/branches - disjoint_ref_quantified +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +branch1: 1.0 +branch2: 1.0 +branch3: 1.0 +branch4: 1.0 +branch5: 1.0 + +dependencyAnalysisTests/precisionTests/branches - fold_unfold_with_implication +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +branch1: 0.5 +branch2: 0.4 +branch3: 0.4 +branch4: 1.0 +branch5: 0.19999999999999996 + +dependencyAnalysisTests/precisionTests/branches - function_add +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +branch1: 1.0 +branch2: 1.0 +branch3: 1.0 +branch4: 1.0 +branch5: 1.0 + +dependencyAnalysisTests/precisionTests/branches - goto_pure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +branch1: 1.0 +branch2: 1.0 +branch3: 1.0 +branch4: 1.0 +branch5: 1.0 + +dependencyAnalysisTests/precisionTests/branches - infeasible_branch +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +branch1: 0.75 +branch2: 0.6666666666666667 +branch3: 0.6666666666666667 +branch4: 1.0 +branch5: 0.33333333333333337 + +dependencyAnalysisTests/precisionTests/branches - inhale_invariant +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +branch1: 1.0 +branch2: 1.0 +branch3: 1.0 +branch4: 1.0 +branch5: 1.0 + +dependencyAnalysisTests/precisionTests/branches - magic_wand +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +branch1: 1.0 +branch2: 1.0 +branch3: 1.0 +branch4: 1.0 +branch5: 1.0 + +dependencyAnalysisTests/precisionTests/branches - method_call_impure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +branch1: 1.0 +branch2: 1.0 +branch3: 1.0 +branch4: 1.0 +branch5: 1.0 + +dependencyAnalysisTests/precisionTests/branches - method_call_pure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +branch1: 1.0 +branch2: 1.0 +branch3: 1.0 +branch4: 1.0 +branch5: 1.0 + +dependencyAnalysisTests/precisionTests/branches - nested_branch +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +branch1: 0.6 +branch2: 0.4 +branch3: 0.5 +branch4: 1.0 +branch5: 0.25 + +dependencyAnalysisTests/precisionTests/branches - quasihavoc +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +branch1: 1.0 +branch2: 1.0 +branch3: 1.0 +branch4: 1.0 +branch5: 1.0 + +dependencyAnalysisTests/precisionTests/branches - quasihavoc_disjoint_ref +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +branch1: 1.0 +branch2: 1.0 +branch3: 1.0 +branch4: 1.0 +branch5: 1.0 + +dependencyAnalysisTests/precisionTests/branches - quasihavoc_disjoint_ref_disjoint_field +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +branch1: 1.0 +branch2: 1.0 +branch3: 1.0 +branch4: 1.0 +branch5: 1.0 + +dependencyAnalysisTests/precisionTests/branches - while_perm +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +branch1: 1.0 +branch2: 1.0 +branch3: 1.0 +branch4: 1.0 +branch5: 1.0 + +dependencyAnalysisTests/precisionTests/branches - while_pure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +branch1: 1.0 +branch2: 1.0 +branch3: 1.0 +branch4: 1.0 +branch5: 1.0 + +dependencyAnalysisTests/precisionTests/domain - assert +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +domain1: 1.0 +domain2: 0.75 +domain3: 1.0 + +dependencyAnalysisTests/precisionTests/domain - assignment_field +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +domain1: 1.0 +domain2: 1.0 +domain3: 1.0 + +dependencyAnalysisTests/precisionTests/domain - assignment_pure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +domain1: 1.0 +domain2: 1.0 +domain3: 1.0 + +dependencyAnalysisTests/precisionTests/domain - baseline +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +domain1: 1.0 +domain2: 1.0 +domain3: 1.0 + +dependencyAnalysisTests/precisionTests/domain - branch +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +domain1: 1.0 +domain2: 1.0 +domain3: 1.0 + +dependencyAnalysisTests/precisionTests/domain - branch_div0 +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +domain1: 1.0 +domain2: 1.0 +domain3: 0.8 + +dependencyAnalysisTests/precisionTests/domain - branch_function +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +domain1: 1.0 +domain2: 1.0 +domain3: 0.6923076923076923 + +dependencyAnalysisTests/precisionTests/domain - disjoint_ref +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +domain1: 1.0 +domain2: 1.0 +domain3: 1.0 + +dependencyAnalysisTests/precisionTests/domain - disjoint_ref_disjoint_field +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +domain1: 1.0 +domain2: 1.0 +domain3: 1.0 + +dependencyAnalysisTests/precisionTests/domain - disjoint_ref_disjoint_field_quantified +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +domain1: 1.0 +domain2: 1.0 +domain3: 1.0 + +dependencyAnalysisTests/precisionTests/domain - disjoint_ref_quantified +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +domain1: 1.0 +domain2: 1.0 +domain3: 1.0 + +dependencyAnalysisTests/precisionTests/domain - fold_unfold_with_implication +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +domain1: 0.25 +domain2: 0.5 +domain3: 0.7272727272727273 + +dependencyAnalysisTests/precisionTests/domain - function_add +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +domain1: 1.0 +domain2: 1.0 +domain3: 1.0 + +dependencyAnalysisTests/precisionTests/domain - goto_pure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +domain1: 1.0 +domain2: 1.0 +domain3: 1.0 + +dependencyAnalysisTests/precisionTests/domain - infeasible_branch +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +domain1: 0.33333333333333337 +domain2: 0.75 +domain3: 0.8888888888888888 + +dependencyAnalysisTests/precisionTests/domain - inhale_invariant +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +domain1: 1.0 +domain2: 0.6666666666666667 +domain3: 1.0 + +dependencyAnalysisTests/precisionTests/domain - magic_wand +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +domain1: 1.0 +domain2: 1.0 +domain3: 1.0 + +dependencyAnalysisTests/precisionTests/domain - method_call_impure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +domain1: 1.0 +domain2: 1.0 +domain3: 1.0 + +dependencyAnalysisTests/precisionTests/domain - method_call_pure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +domain1: 1.0 +domain2: 1.0 +domain3: 1.0 + +dependencyAnalysisTests/precisionTests/domain - nested_branch +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +domain1: 1.0 +domain2: 1.0 +domain3: 0.8 + +dependencyAnalysisTests/precisionTests/domain - quasihavoc +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +domain1: 1.0 +domain2: 0.75 +domain3: 0.8888888888888888 + +dependencyAnalysisTests/precisionTests/domain - quasihavoc_disjoint_ref +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +domain1: 1.0 +domain2: 1.0 +domain3: 1.0 + +dependencyAnalysisTests/precisionTests/domain - quasihavoc_disjoint_ref_disjoint_field +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +domain1: 1.0 +domain2: 1.0 +domain3: 1.0 + +dependencyAnalysisTests/precisionTests/domain - while_perm +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +domain1: 1.0 +domain2: 1.0 +domain3: 1.0 + +dependencyAnalysisTests/precisionTests/domain - while_pure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +domain1: 1.0 +domain2: 1.0 +domain3: 1.0 + +dependencyAnalysisTests/precisionTests/function-call - assert +gen_add: 1.0 +gen_add_positive: 1.0 +sum: 1.0 +sumUnverified: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +call1: 1.0 +call2: 1.0 +call3: 1.0 +call4: 1.0 + +dependencyAnalysisTests/precisionTests/function-call - assignment_field +gen_add: 1.0 +gen_add_positive: 1.0 +sum: 1.0 +sumUnverified: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +call1: 1.0 +call2: 1.0 +call3: 1.0 +call4: 1.0 + +dependencyAnalysisTests/precisionTests/function-call - assignment_pure +gen_add: 1.0 +gen_add_positive: 1.0 +sum: 1.0 +sumUnverified: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +call1: 1.0 +call2: 1.0 +call3: 1.0 +call4: 1.0 + +dependencyAnalysisTests/precisionTests/function-call - baseline +gen_add: 1.0 +gen_add_positive: 1.0 +sum: 1.0 +sumUnverified: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +call1: 1.0 +call2: 1.0 +call3: 1.0 +call4: 1.0 + +dependencyAnalysisTests/precisionTests/function-call - branch +gen_add: 1.0 +gen_add_positive: 1.0 +sum: 1.0 +sumUnverified: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +call1: 1.0 +call2: 1.0 +call3: 1.0 +call4: 1.0 + +dependencyAnalysisTests/precisionTests/function-call - branch_div0 +gen_add: 1.0 +gen_add_positive: 1.0 +sum: 1.0 +sumUnverified: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +call1: 0.6666666666666667 +call2: 0.8 +call3: 0.8571428571428572 +call4: 0.8 + +dependencyAnalysisTests/precisionTests/function-call - branch_function +gen_add: 1.0 +gen_add_positive: 1.0 +sum: 1.0 +sumUnverified: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +call1: 0.5 +call2: 0.6666666666666667 +call3: 0.75 +call4: 0.5714285714285714 + +dependencyAnalysisTests/precisionTests/function-call - disjoint_ref +gen_add: 1.0 +gen_add_positive: 1.0 +sum: 1.0 +sumUnverified: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +call1: 1.0 +call2: 1.0 +call3: 1.0 +call4: 1.0 + +dependencyAnalysisTests/precisionTests/function-call - disjoint_ref_disjoint_field +gen_add: 1.0 +gen_add_positive: 1.0 +sum: 1.0 +sumUnverified: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +call1: 1.0 +call2: 1.0 +call3: 1.0 +call4: 1.0 + +dependencyAnalysisTests/precisionTests/function-call - disjoint_ref_disjoint_field_quantified +gen_add: 1.0 +gen_add_positive: 1.0 +sum: 1.0 +sumUnverified: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +call1: 1.0 +call2: 1.0 +call3: 1.0 +call4: 1.0 + +dependencyAnalysisTests/precisionTests/function-call - disjoint_ref_quantified +gen_add: 1.0 +gen_add_positive: 1.0 +sum: 1.0 +sumUnverified: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +call1: 1.0 +call2: 1.0 +call3: 1.0 +call4: 1.0 + +dependencyAnalysisTests/precisionTests/function-call - fold_unfold_with_implication +gen_add: 1.0 +gen_add_positive: 1.0 +sum: 1.0 +sumUnverified: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +call1: 0.4 +call2: 0.6666666666666667 +call3: 0.75 +call4: 0.5714285714285714 + +dependencyAnalysisTests/precisionTests/function-call - function_add +gen_add: 1.0 +gen_add_positive: 1.0 +sum: 1.0 +sumUnverified: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +call1: 1.0 +call2: 1.0 +call3: 1.0 +call4: 1.0 + +dependencyAnalysisTests/precisionTests/function-call - goto_pure +gen_add: 1.0 +gen_add_positive: 1.0 +sum: 1.0 +sumUnverified: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +call1: 1.0 +call2: 1.0 +call3: 1.0 +call4: 1.0 + +dependencyAnalysisTests/precisionTests/function-call - infeasible_branch +gen_add: 1.0 +gen_add_positive: 1.0 +sum: 1.0 +sumUnverified: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +call1: 0.6666666666666667 +call2: 0.8 +call3: 0.8571428571428572 +call4: 0.8 + +dependencyAnalysisTests/precisionTests/function-call - inhale_invariant +gen_add: 1.0 +gen_add_positive: 1.0 +sum: 1.0 +sumUnverified: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +call1: 1.0 +call2: 1.0 +call3: 1.0 +call4: 1.0 + +dependencyAnalysisTests/precisionTests/function-call - magic_wand +gen_add: 1.0 +gen_add_positive: 1.0 +sum: 1.0 +sumUnverified: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +call1: 1.0 +call2: 1.0 +call3: 1.0 +call4: 1.0 + +dependencyAnalysisTests/precisionTests/function-call - method_call_impure +gen_add: 1.0 +gen_add_positive: 1.0 +sum: 1.0 +sumUnverified: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +call1: 1.0 +call2: 1.0 +call3: 1.0 +call4: 1.0 + +dependencyAnalysisTests/precisionTests/function-call - method_call_pure +gen_add: 1.0 +gen_add_positive: 1.0 +sum: 1.0 +sumUnverified: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +call1: 1.0 +call2: 1.0 +call3: 1.0 +call4: 1.0 + +dependencyAnalysisTests/precisionTests/function-call - nested_branch +gen_add: 1.0 +gen_add_positive: 1.0 +sum: 1.0 +sumUnverified: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +call1: 0.6666666666666667 +call2: 0.8 +call3: 0.8571428571428572 +call4: 0.8 + +dependencyAnalysisTests/precisionTests/function-call - quasihavoc +gen_add: 1.0 +gen_add_positive: 1.0 +sum: 1.0 +sumUnverified: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +call1: 1.0 +call2: 1.0 +call3: 1.0 +call4: 1.0 + +dependencyAnalysisTests/precisionTests/function-call - quasihavoc_disjoint_ref +gen_add: 1.0 +gen_add_positive: 1.0 +sum: 1.0 +sumUnverified: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +call1: 1.0 +call2: 1.0 +call3: 1.0 +call4: 1.0 + +dependencyAnalysisTests/precisionTests/function-call - quasihavoc_disjoint_ref_disjoint_field +gen_add: 1.0 +gen_add_positive: 1.0 +sum: 1.0 +sumUnverified: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +call1: 1.0 +call2: 1.0 +call3: 1.0 +call4: 1.0 + +dependencyAnalysisTests/precisionTests/function-call - while_perm +gen_add: 1.0 +gen_add_positive: 1.0 +sum: 1.0 +sumUnverified: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +call1: 1.0 +call2: 1.0 +call3: 1.0 +call4: 1.0 + +dependencyAnalysisTests/precisionTests/function-call - while_pure +gen_add: 1.0 +gen_add_positive: 1.0 +sum: 1.0 +sumUnverified: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +call1: 1.0 +call2: 1.0 +call3: 1.0 +call4: 1.0 + +dependencyAnalysisTests/precisionTests/inhaleExhale - assert +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +exhaleInhale: 0.33333333333333337 +exhaleImprecision: 0.6666666666666667 +inhaleImprecision: 0.5 + +dependencyAnalysisTests/precisionTests/inhaleExhale - assignment_field +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +exhaleInhale: 0.33333333333333337 +exhaleImprecision: 0.6666666666666667 +inhaleImprecision: 0.6666666666666667 + +dependencyAnalysisTests/precisionTests/inhaleExhale - assignment_pure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +exhaleInhale: 0.33333333333333337 +exhaleImprecision: 0.6666666666666667 +inhaleImprecision: 0.6666666666666667 + +dependencyAnalysisTests/precisionTests/inhaleExhale - baseline +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +exhaleInhale: 0.33333333333333337 +exhaleImprecision: 0.6666666666666667 +inhaleImprecision: 0.6666666666666667 + +dependencyAnalysisTests/precisionTests/inhaleExhale - branch +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +exhaleInhale: 0.33333333333333337 +exhaleImprecision: 0.6666666666666667 +inhaleImprecision: 0.5 + +dependencyAnalysisTests/precisionTests/inhaleExhale - branch_div0 +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +exhaleInhale: 0.33333333333333337 +exhaleImprecision: 0.5 +inhaleImprecision: 0.5 + +dependencyAnalysisTests/precisionTests/inhaleExhale - branch_function +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +exhaleInhale: 0.33333333333333337 +exhaleImprecision: 0.5 +inhaleImprecision: 0.4 + +dependencyAnalysisTests/precisionTests/inhaleExhale - disjoint_ref +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +exhaleInhale: 0.25 +exhaleImprecision: 0.6666666666666667 +inhaleImprecision: 0.6666666666666667 + +dependencyAnalysisTests/precisionTests/inhaleExhale - disjoint_ref_disjoint_field +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +exhaleInhale: 0.33333333333333337 +exhaleImprecision: 0.6666666666666667 +inhaleImprecision: 0.6666666666666667 + +dependencyAnalysisTests/precisionTests/inhaleExhale - disjoint_ref_disjoint_field_quantified +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +exhaleInhale: 0.33333333333333337 +exhaleImprecision: 0.6666666666666667 +inhaleImprecision: 0.6666666666666667 + +dependencyAnalysisTests/precisionTests/inhaleExhale - disjoint_ref_quantified +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +exhaleInhale: 0.25 +exhaleImprecision: 0.4 +inhaleImprecision: 0.4 + +dependencyAnalysisTests/precisionTests/inhaleExhale - fold_unfold_with_implication +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +exhaleInhale: 0.16666666666666663 +exhaleImprecision: 0.33333333333333337 +inhaleImprecision: 0.33333333333333337 + +dependencyAnalysisTests/precisionTests/inhaleExhale - function_add +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +exhaleInhale: 0.33333333333333337 +exhaleImprecision: 0.6666666666666667 +inhaleImprecision: 0.6666666666666667 + +dependencyAnalysisTests/precisionTests/inhaleExhale - goto_pure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +exhaleInhale: 0.33333333333333337 +exhaleImprecision: 0.6666666666666667 +inhaleImprecision: 0.6666666666666667 + +dependencyAnalysisTests/precisionTests/inhaleExhale - infeasible_branch +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +exhaleInhale: 0.25 +exhaleImprecision: 0.5 +inhaleImprecision: 0.5 + +dependencyAnalysisTests/precisionTests/inhaleExhale - inhale_invariant +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +exhaleInhale: 0.33333333333333337 +exhaleImprecision: 0.6666666666666667 +inhaleImprecision: 0.33333333333333337 + +dependencyAnalysisTests/precisionTests/inhaleExhale - magic_wand +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +exhaleInhale: 0.1428571428571429 +exhaleImprecision: 0.6666666666666667 +inhaleImprecision: 0.6666666666666667 + +dependencyAnalysisTests/precisionTests/inhaleExhale - method_call_impure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +exhaleInhale: 0.25 +exhaleImprecision: 0.6666666666666667 +inhaleImprecision: 0.6666666666666667 + +dependencyAnalysisTests/precisionTests/inhaleExhale - method_call_pure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +exhaleInhale: 0.5 +exhaleImprecision: 0.6666666666666667 +inhaleImprecision: 0.6666666666666667 + +dependencyAnalysisTests/precisionTests/inhaleExhale - nested_branch +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +exhaleInhale: 0.33333333333333337 +exhaleImprecision: 0.5 +inhaleImprecision: 0.4 + +dependencyAnalysisTests/precisionTests/inhaleExhale - quasihavoc +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +exhaleInhale: 0.25 +exhaleImprecision: 0.5 +inhaleImprecision: 0.5 + +dependencyAnalysisTests/precisionTests/inhaleExhale - quasihavoc_disjoint_ref +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +exhaleInhale: 0.19999999999999996 +exhaleImprecision: 0.4 +inhaleImprecision: 0.4 + +dependencyAnalysisTests/precisionTests/inhaleExhale - quasihavoc_disjoint_ref_disjoint_field +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +exhaleInhale: 0.33333333333333337 +exhaleImprecision: 0.6666666666666667 +inhaleImprecision: 0.6666666666666667 + +Program dependencyAnalysisTests/precisionTests/inhaleExhale/while_perm does not verify. Skip +Program dependencyAnalysisTests/precisionTests/inhaleExhale/while_pure does not verify. Skip +dependencyAnalysisTests/precisionTests/loops - assert +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +loop1: 0.7142857142857143 +loop2: 1.0 +loop3: 1.0 +loop4: 1.0 + +dependencyAnalysisTests/precisionTests/loops - assignment_field +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +loop1: 0.7142857142857143 +loop2: 1.0 +loop3: 1.0 +loop4: 1.0 + +dependencyAnalysisTests/precisionTests/loops - assignment_pure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +loop1: 0.7142857142857143 +loop2: 1.0 +loop3: 1.0 +loop4: 1.0 + +dependencyAnalysisTests/precisionTests/loops - baseline +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +loop1: 0.7142857142857143 +loop2: 1.0 +loop3: 1.0 +loop4: 1.0 + +dependencyAnalysisTests/precisionTests/loops - branch +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +loop1: 0.625 +loop2: 1.0 +loop3: 0.8333333333333334 +loop4: 1.0 + +dependencyAnalysisTests/precisionTests/loops - branch_div0 +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +loop1: 0.7142857142857143 +loop2: 0.4285714285714286 +loop3: 0.8333333333333334 +loop4: 1.0 + +dependencyAnalysisTests/precisionTests/loops - branch_function +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +loop1: 0.625 +loop2: 0.2727272727272727 +loop3: 0.7142857142857143 +loop4: 1.0 + +dependencyAnalysisTests/precisionTests/loops - disjoint_ref +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +loop1: 0.7142857142857143 +loop2: 1.0 +loop3: 1.0 +loop4: 1.0 + +dependencyAnalysisTests/precisionTests/loops - disjoint_ref_disjoint_field +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +loop1: 0.7142857142857143 +loop2: 1.0 +loop3: 1.0 +loop4: 1.0 + +dependencyAnalysisTests/precisionTests/loops - disjoint_ref_disjoint_field_quantified +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +loop1: 0.7142857142857143 +loop2: 1.0 +loop3: 1.0 +loop4: 1.0 + +dependencyAnalysisTests/precisionTests/loops - disjoint_ref_quantified +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +loop1: 0.7142857142857143 +loop2: 1.0 +loop3: 1.0 +loop4: 1.0 + +dependencyAnalysisTests/precisionTests/loops - fold_unfold_with_implication +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +loop1: 0.5 +loop2: 0.2727272727272727 +loop3: 0.625 +loop4: 0.5 + +dependencyAnalysisTests/precisionTests/loops - function_add +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +loop1: 0.7142857142857143 +loop2: 1.0 +loop3: 1.0 +loop4: 1.0 + +Program dependencyAnalysisTests/precisionTests/loops/goto_pure does not verify. Skip +dependencyAnalysisTests/precisionTests/loops - infeasible_branch +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +loop1: 0.5555555555555556 +loop2: 0.5 +loop3: 0.8333333333333334 +loop4: 0.6666666666666667 + +dependencyAnalysisTests/precisionTests/loops - inhale_invariant +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +loop1: 0.7142857142857143 +loop2: 1.0 +loop3: 1.0 +loop4: 1.0 + +dependencyAnalysisTests/precisionTests/loops - magic_wand +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +loop1: 0.7142857142857143 +loop2: 1.0 +loop3: 1.0 +loop4: 1.0 + +dependencyAnalysisTests/precisionTests/loops - method_call_impure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +loop1: 0.7142857142857143 +loop2: 1.0 +loop3: 1.0 +loop4: 1.0 + +dependencyAnalysisTests/precisionTests/loops - method_call_pure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +loop1: 0.7142857142857143 +loop2: 1.0 +loop3: 1.0 +loop4: 1.0 + +dependencyAnalysisTests/precisionTests/loops - nested_branch +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +loop1: 0.625 +loop2: 0.375 +loop3: 0.7142857142857143 +loop4: 1.0 + +dependencyAnalysisTests/precisionTests/loops - quasihavoc +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +loop1: 0.7142857142857143 +loop2: 1.0 +loop3: 1.0 +loop4: 1.0 + +dependencyAnalysisTests/precisionTests/loops - quasihavoc_disjoint_ref +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +loop1: 0.7142857142857143 +loop2: 1.0 +loop3: 1.0 +loop4: 1.0 + +dependencyAnalysisTests/precisionTests/loops - quasihavoc_disjoint_ref_disjoint_field +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +loop1: 0.7142857142857143 +loop2: 1.0 +loop3: 1.0 +loop4: 1.0 + +dependencyAnalysisTests/precisionTests/loops - while_perm +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +loop1: 0.7142857142857143 +loop2: 1.0 +loop3: 1.0 +loop4: 1.0 + +Program dependencyAnalysisTests/precisionTests/loops/while_pure does not verify. Skip +dependencyAnalysisTests/precisionTests/magicWands - assert +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +basicApply: 1.0 +basicPackage: 1.0 +advancedPackage: 1.0 +quantifiedWand: 1.0 +packagePrecision: 1.0 +packageExhale: 1.0 +applying1: 1.0 + +dependencyAnalysisTests/precisionTests/magicWands - assignment_field +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +basicApply: 1.0 +basicPackage: 1.0 +advancedPackage: 1.0 +quantifiedWand: 1.0 +packagePrecision: 1.0 +packageExhale: 1.0 +applying1: 1.0 + +dependencyAnalysisTests/precisionTests/magicWands - assignment_pure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +basicApply: 1.0 +basicPackage: 1.0 +advancedPackage: 1.0 +quantifiedWand: 1.0 +packagePrecision: 1.0 +packageExhale: 1.0 +applying1: 1.0 + +dependencyAnalysisTests/precisionTests/magicWands - baseline +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +basicApply: 1.0 +basicPackage: 1.0 +advancedPackage: 1.0 +quantifiedWand: 1.0 +packagePrecision: 1.0 +packageExhale: 1.0 +applying1: 1.0 + +dependencyAnalysisTests/precisionTests/magicWands - branch +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +basicApply: 0.75 +basicPackage: 1.0 +advancedPackage: 1.0 +quantifiedWand: 1.0 +packagePrecision: 1.0 +packageExhale: 1.0 +applying1: 0.75 + +dependencyAnalysisTests/precisionTests/magicWands - branch_div0 +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +basicApply: 0.75 +basicPackage: 1.0 +advancedPackage: 1.0 +quantifiedWand: 1.0 +packagePrecision: 1.0 +packageExhale: 1.0 +applying1: 0.75 + +dependencyAnalysisTests/precisionTests/magicWands - branch_function +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +basicApply: 0.6 +basicPackage: 1.0 +advancedPackage: 1.0 +quantifiedWand: 1.0 +packagePrecision: 1.0 +packageExhale: 1.0 +applying1: 0.6 + +dependencyAnalysisTests/precisionTests/magicWands - disjoint_ref +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +basicApply: 1.0 +basicPackage: 1.0 +advancedPackage: 1.0 +quantifiedWand: 1.0 +packagePrecision: 1.0 +packageExhale: 0.6666666666666667 +applying1: 1.0 + +dependencyAnalysisTests/precisionTests/magicWands - disjoint_ref_disjoint_field +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +basicApply: 1.0 +basicPackage: 1.0 +advancedPackage: 1.0 +quantifiedWand: 1.0 +packagePrecision: 1.0 +packageExhale: 1.0 +applying1: 1.0 + +dependencyAnalysisTests/precisionTests/magicWands - disjoint_ref_disjoint_field_quantified +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +basicApply: 1.0 +basicPackage: 1.0 +advancedPackage: 1.0 +quantifiedWand: 1.0 +packagePrecision: 1.0 +packageExhale: 1.0 +applying1: 1.0 + +dependencyAnalysisTests/precisionTests/magicWands - disjoint_ref_quantified +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +basicApply: 1.0 +basicPackage: 1.0 +advancedPackage: 1.0 +quantifiedWand: 1.0 +packagePrecision: 1.0 +packageExhale: 0.5 +applying1: 1.0 + +dependencyAnalysisTests/precisionTests/magicWands - fold_unfold_with_implication +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +basicApply: 0.5 +basicPackage: 0.6666666666666667 +advancedPackage: 0.7 +quantifiedWand: 0.5714285714285714 +packagePrecision: 0.5 +packageExhale: 0.4 +applying1: 0.5 + +dependencyAnalysisTests/precisionTests/magicWands - function_add +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +basicApply: 1.0 +basicPackage: 1.0 +advancedPackage: 1.0 +quantifiedWand: 1.0 +packagePrecision: 1.0 +packageExhale: 1.0 +applying1: 1.0 + +dependencyAnalysisTests/precisionTests/magicWands - goto_pure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +basicApply: 1.0 +basicPackage: 1.0 +advancedPackage: 1.0 +quantifiedWand: 1.0 +packagePrecision: 1.0 +packageExhale: 1.0 +applying1: 1.0 + +dependencyAnalysisTests/precisionTests/magicWands - infeasible_branch +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +basicApply: 0.75 +basicPackage: 0.8571428571428572 +advancedPackage: 0.875 +quantifiedWand: 0.8 +packagePrecision: 0.75 +packageExhale: 0.6666666666666667 +applying1: 0.75 + +dependencyAnalysisTests/precisionTests/magicWands - inhale_invariant +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +basicApply: 1.0 +basicPackage: 1.0 +advancedPackage: 1.0 +quantifiedWand: 1.0 +packagePrecision: 1.0 +packageExhale: 1.0 +applying1: 1.0 + +dependencyAnalysisTests/precisionTests/magicWands - magic_wand +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +basicApply: 1.0 +basicPackage: 1.0 +advancedPackage: 1.0 +quantifiedWand: 1.0 +packagePrecision: 1.0 +packageExhale: 0.33333333333333337 +applying1: 1.0 + +dependencyAnalysisTests/precisionTests/magicWands - method_call_impure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +basicApply: 1.0 +basicPackage: 1.0 +advancedPackage: 1.0 +quantifiedWand: 1.0 +packagePrecision: 1.0 +packageExhale: 0.6666666666666667 +applying1: 1.0 + +dependencyAnalysisTests/precisionTests/magicWands - method_call_pure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +basicApply: 1.0 +basicPackage: 1.0 +advancedPackage: 1.0 +quantifiedWand: 1.0 +packagePrecision: 1.0 +packageExhale: 1.0 +applying1: 1.0 + +dependencyAnalysisTests/precisionTests/magicWands - nested_branch +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +basicApply: 0.6 +basicPackage: 1.0 +advancedPackage: 1.0 +quantifiedWand: 1.0 +packagePrecision: 1.0 +packageExhale: 1.0 +applying1: 0.6 + +dependencyAnalysisTests/precisionTests/magicWands - quasihavoc +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +basicApply: 1.0 +basicPackage: 0.8571428571428572 +advancedPackage: 1.0 +quantifiedWand: 1.0 +packagePrecision: 1.0 +packageExhale: 0.6 +applying1: 1.0 + +dependencyAnalysisTests/precisionTests/magicWands - quasihavoc_disjoint_ref +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +basicApply: 1.0 +basicPackage: 1.0 +advancedPackage: 1.0 +quantifiedWand: 1.0 +packagePrecision: 1.0 +packageExhale: 0.5 +applying1: 1.0 + +dependencyAnalysisTests/precisionTests/magicWands - quasihavoc_disjoint_ref_disjoint_field +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +basicApply: 1.0 +basicPackage: 1.0 +advancedPackage: 1.0 +quantifiedWand: 1.0 +packagePrecision: 1.0 +packageExhale: 1.0 +applying1: 1.0 + +Failed. Skip +Failed. Skip +dependencyAnalysisTests/precisionTests/method-call - assert +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +sum: 1.0 +sumUnverified: 1.0 +abs: 1.0 +call1: 1.0 +call2: 1.0 +call3: 1.0 +call4: 1.0 +absClient1: 1.0 + +dependencyAnalysisTests/precisionTests/method-call - assignment_field +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +sum: 1.0 +sumUnverified: 1.0 +abs: 1.0 +call1: 1.0 +call2: 1.0 +call3: 1.0 +call4: 1.0 +absClient1: 1.0 + +dependencyAnalysisTests/precisionTests/method-call - assignment_pure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +sum: 1.0 +sumUnverified: 1.0 +abs: 1.0 +call1: 1.0 +call2: 1.0 +call3: 1.0 +call4: 1.0 +absClient1: 1.0 + +dependencyAnalysisTests/precisionTests/method-call - baseline +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +sum: 1.0 +sumUnverified: 1.0 +abs: 1.0 +call1: 1.0 +call2: 1.0 +call3: 1.0 +call4: 1.0 +absClient1: 1.0 + +dependencyAnalysisTests/precisionTests/method-call - branch +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +sum: 1.0 +sumUnverified: 1.0 +abs: 1.0 +call1: 1.0 +call2: 1.0 +call3: 1.0 +call4: 1.0 +absClient1: 1.0 + +dependencyAnalysisTests/precisionTests/method-call - branch_div0 +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +sum: 1.0 +sumUnverified: 1.0 +abs: 1.0 +call1: 0.6666666666666667 +call2: 0.75 +call3: 0.8333333333333334 +call4: 0.75 +absClient1: 1.0 + +dependencyAnalysisTests/precisionTests/method-call - branch_function +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +sum: 1.0 +sumUnverified: 1.0 +abs: 1.0 +call1: 0.5 +call2: 0.6 +call3: 0.7142857142857143 +call4: 0.5 +absClient1: 1.0 + +dependencyAnalysisTests/precisionTests/method-call - disjoint_ref +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +sum: 1.0 +sumUnverified: 1.0 +abs: 1.0 +call1: 1.0 +call2: 1.0 +call3: 1.0 +call4: 1.0 +absClient1: 1.0 + +dependencyAnalysisTests/precisionTests/method-call - disjoint_ref_disjoint_field +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +sum: 1.0 +sumUnverified: 1.0 +abs: 1.0 +call1: 1.0 +call2: 1.0 +call3: 1.0 +call4: 1.0 +absClient1: 1.0 + +dependencyAnalysisTests/precisionTests/method-call - disjoint_ref_disjoint_field_quantified +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +sum: 1.0 +sumUnverified: 1.0 +abs: 1.0 +call1: 1.0 +call2: 1.0 +call3: 1.0 +call4: 1.0 +absClient1: 1.0 + +dependencyAnalysisTests/precisionTests/method-call - disjoint_ref_quantified +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +sum: 1.0 +sumUnverified: 1.0 +abs: 1.0 +call1: 1.0 +call2: 1.0 +call3: 1.0 +call4: 1.0 +absClient1: 1.0 + +dependencyAnalysisTests/precisionTests/method-call - fold_unfold_with_implication +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +sum: 1.0 +sumUnverified: 1.0 +abs: 1.0 +call1: 0.4 +call2: 0.6 +call3: 0.7142857142857143 +call4: 0.5 +absClient1: 1.0 + +dependencyAnalysisTests/precisionTests/method-call - function_add +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +sum: 1.0 +sumUnverified: 1.0 +abs: 1.0 +call1: 1.0 +call2: 1.0 +call3: 1.0 +call4: 1.0 +absClient1: 1.0 + +dependencyAnalysisTests/precisionTests/method-call - goto_pure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +sum: 1.0 +sumUnverified: 1.0 +abs: 1.0 +call1: 1.0 +call2: 1.0 +call3: 1.0 +call4: 1.0 +absClient1: 1.0 + +dependencyAnalysisTests/precisionTests/method-call - infeasible_branch +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +sum: 1.0 +sumUnverified: 1.0 +abs: 1.0 +call1: 0.6666666666666667 +call2: 0.75 +call3: 0.8333333333333334 +call4: 0.75 +absClient1: 1.0 + +dependencyAnalysisTests/precisionTests/method-call - inhale_invariant +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +sum: 1.0 +sumUnverified: 1.0 +abs: 1.0 +call1: 1.0 +call2: 1.0 +call3: 1.0 +call4: 1.0 +absClient1: 1.0 + +dependencyAnalysisTests/precisionTests/method-call - magic_wand +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +sum: 1.0 +sumUnverified: 1.0 +abs: 1.0 +call1: 1.0 +call2: 1.0 +call3: 1.0 +call4: 1.0 +absClient1: 1.0 + +dependencyAnalysisTests/precisionTests/method-call - method_call_impure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +sum: 1.0 +sumUnverified: 1.0 +abs: 1.0 +call1: 1.0 +call2: 1.0 +call3: 1.0 +call4: 1.0 +absClient1: 1.0 + +dependencyAnalysisTests/precisionTests/method-call - method_call_pure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +sum: 1.0 +sumUnverified: 1.0 +abs: 1.0 +call1: 1.0 +call2: 1.0 +call3: 1.0 +call4: 1.0 +absClient1: 1.0 + +dependencyAnalysisTests/precisionTests/method-call - nested_branch +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +sum: 1.0 +sumUnverified: 1.0 +abs: 1.0 +call1: 0.6666666666666667 +call2: 0.75 +call3: 0.8333333333333334 +call4: 0.75 +absClient1: 1.0 + +dependencyAnalysisTests/precisionTests/method-call - quasihavoc +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +sum: 1.0 +sumUnverified: 1.0 +abs: 1.0 +call1: 1.0 +call2: 1.0 +call3: 1.0 +call4: 1.0 +absClient1: 1.0 + +dependencyAnalysisTests/precisionTests/method-call - quasihavoc_disjoint_ref +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +sum: 1.0 +sumUnverified: 1.0 +abs: 1.0 +call1: 1.0 +call2: 1.0 +call3: 1.0 +call4: 1.0 +absClient1: 1.0 + +dependencyAnalysisTests/precisionTests/method-call - quasihavoc_disjoint_ref_disjoint_field +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +sum: 1.0 +sumUnverified: 1.0 +abs: 1.0 +call1: 1.0 +call2: 1.0 +call3: 1.0 +call4: 1.0 +absClient1: 1.0 + +dependencyAnalysisTests/precisionTests/method-call - while_perm +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +sum: 1.0 +sumUnverified: 1.0 +abs: 1.0 +call1: 1.0 +call2: 1.0 +call3: 1.0 +call4: 1.0 +absClient1: 1.0 + +dependencyAnalysisTests/precisionTests/method-call - while_pure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +sum: 1.0 +sumUnverified: 1.0 +abs: 1.0 +call1: 1.0 +call2: 1.0 +call3: 1.0 +call4: 1.0 +absClient1: 1.0 + +dependencyAnalysisTests/precisionTests/misc - assert +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +divBy0: 1.0 +assumeFalse: 1.0 + +dependencyAnalysisTests/precisionTests/misc - assignment_field +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +divBy0: 1.0 +assumeFalse: 1.0 + +dependencyAnalysisTests/precisionTests/misc - assignment_pure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +divBy0: 1.0 +assumeFalse: 1.0 + +dependencyAnalysisTests/precisionTests/misc - baseline +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +divBy0: 1.0 +assumeFalse: 1.0 + +dependencyAnalysisTests/precisionTests/misc - branch +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +divBy0: 1.0 +assumeFalse: 0.5 + +dependencyAnalysisTests/precisionTests/misc - branch_div0 +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +divBy0: 0.6666666666666667 +assumeFalse: 0.5 + +dependencyAnalysisTests/precisionTests/misc - branch_function +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +divBy0: 0.4 +assumeFalse: 0.33333333333333337 + +dependencyAnalysisTests/precisionTests/misc - disjoint_ref +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +divBy0: 1.0 +assumeFalse: 1.0 + +dependencyAnalysisTests/precisionTests/misc - disjoint_ref_disjoint_field +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +divBy0: 1.0 +assumeFalse: 1.0 + +dependencyAnalysisTests/precisionTests/misc - disjoint_ref_disjoint_field_quantified +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +divBy0: 1.0 +assumeFalse: 1.0 + +dependencyAnalysisTests/precisionTests/misc - disjoint_ref_quantified +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +divBy0: 0.33333333333333337 +assumeFalse: 1.0 + +dependencyAnalysisTests/precisionTests/misc - fold_unfold_with_implication +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +divBy0: 0.4 +assumeFalse: 1.0 + +dependencyAnalysisTests/precisionTests/misc - function_add +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +divBy0: 1.0 +assumeFalse: 1.0 + +dependencyAnalysisTests/precisionTests/misc - goto_pure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +divBy0: 1.0 +assumeFalse: 0.5 + +dependencyAnalysisTests/precisionTests/misc - infeasible_branch +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +divBy0: 0.6666666666666667 +assumeFalse: 0.5 + +dependencyAnalysisTests/precisionTests/misc - inhale_invariant +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +divBy0: 1.0 +assumeFalse: 1.0 + +dependencyAnalysisTests/precisionTests/misc - magic_wand +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +divBy0: 1.0 +assumeFalse: 1.0 + +dependencyAnalysisTests/precisionTests/misc - method_call_impure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +divBy0: 1.0 +assumeFalse: 1.0 + +dependencyAnalysisTests/precisionTests/misc - method_call_pure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +divBy0: 1.0 +assumeFalse: 1.0 + +dependencyAnalysisTests/precisionTests/misc - nested_branch +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +divBy0: 0.6666666666666667 +assumeFalse: 0.33333333333333337 + +dependencyAnalysisTests/precisionTests/misc - quasihavoc +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +divBy0: 0.6666666666666667 +assumeFalse: 1.0 + +dependencyAnalysisTests/precisionTests/misc - quasihavoc_disjoint_ref +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +divBy0: 0.5 +assumeFalse: 1.0 + +dependencyAnalysisTests/precisionTests/misc - quasihavoc_disjoint_ref_disjoint_field +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +divBy0: 1.0 +assumeFalse: 1.0 + +dependencyAnalysisTests/precisionTests/misc - while_perm +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +divBy0: 1.0 +assumeFalse: 0.5 + +dependencyAnalysisTests/precisionTests/misc - while_pure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +divBy0: 1.0 +assumeFalse: 0.5 + +dependencyAnalysisTests/precisionTests/permissions - assert +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +currentPerm: 1.0 +perm1: 1.0 +perm2: 1.0 +perm3: 1.0 +perm4: 1.0 +perm5: 1.0 +permAmount1: 1.0 +permAmount2: 0.8 +noAlias: 1.0 + +dependencyAnalysisTests/precisionTests/permissions - assignment_field +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +currentPerm: 1.0 +perm1: 1.0 +perm2: 1.0 +perm3: 1.0 +perm4: 1.0 +perm5: 1.0 +permAmount1: 1.0 +permAmount2: 0.8 +noAlias: 1.0 + +dependencyAnalysisTests/precisionTests/permissions - assignment_pure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +currentPerm: 1.0 +perm1: 1.0 +perm2: 1.0 +perm3: 1.0 +perm4: 1.0 +perm5: 1.0 +permAmount1: 1.0 +permAmount2: 0.8 +noAlias: 1.0 + +dependencyAnalysisTests/precisionTests/permissions - baseline +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +currentPerm: 1.0 +perm1: 1.0 +perm2: 1.0 +perm3: 1.0 +perm4: 1.0 +perm5: 1.0 +permAmount1: 1.0 +permAmount2: 0.8 +noAlias: 1.0 + +dependencyAnalysisTests/precisionTests/permissions - branch +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +currentPerm: 1.0 +perm1: 1.0 +perm2: 1.0 +perm3: 1.0 +perm4: 1.0 +perm5: 1.0 +permAmount1: 1.0 +permAmount2: 0.8 +noAlias: 1.0 + +dependencyAnalysisTests/precisionTests/permissions - branch_div0 +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +currentPerm: 1.0 +perm1: 1.0 +perm2: 1.0 +perm3: 1.0 +perm4: 0.75 +perm5: 1.0 +permAmount1: 1.0 +permAmount2: 0.6666666666666667 +noAlias: 1.0 + +dependencyAnalysisTests/precisionTests/permissions - branch_function +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +currentPerm: 1.0 +perm1: 1.0 +perm2: 1.0 +perm3: 1.0 +perm4: 0.75 +perm5: 1.0 +permAmount1: 1.0 +permAmount2: 0.6666666666666667 +noAlias: 1.0 + +dependencyAnalysisTests/precisionTests/permissions - disjoint_ref +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +currentPerm: 1.0 +perm1: 1.0 +perm2: 1.0 +perm3: 1.0 +perm4: 1.0 +perm5: 1.0 +permAmount1: 1.0 +permAmount2: 0.8 +noAlias: 1.0 + +dependencyAnalysisTests/precisionTests/permissions - disjoint_ref_disjoint_field +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +currentPerm: 1.0 +perm1: 1.0 +perm2: 1.0 +perm3: 1.0 +perm4: 1.0 +perm5: 1.0 +permAmount1: 1.0 +permAmount2: 0.8 +noAlias: 1.0 + +dependencyAnalysisTests/precisionTests/permissions - disjoint_ref_disjoint_field_quantified +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +currentPerm: 1.0 +perm1: 1.0 +perm2: 1.0 +perm3: 1.0 +perm4: 1.0 +perm5: 1.0 +permAmount1: 1.0 +permAmount2: 0.8 +noAlias: 1.0 + +dependencyAnalysisTests/precisionTests/permissions - disjoint_ref_quantified +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +currentPerm: 0.33333333333333337 +perm1: 0.33333333333333337 +perm2: 0.33333333333333337 +perm3: 1.0 +perm4: 0.5 +perm5: 1.0 +permAmount1: 1.0 +permAmount2: 0.5714285714285714 +noAlias: 1.0 + +dependencyAnalysisTests/precisionTests/permissions - fold_unfold_with_implication +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +currentPerm: 0.25 +perm1: 0.25 +perm2: 0.25 +perm3: 0.25 +perm4: 0.5 +perm5: 0.25 +permAmount1: 0.5 +permAmount2: 0.5 +noAlias: 0.4 + +dependencyAnalysisTests/precisionTests/permissions - function_add +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +currentPerm: 1.0 +perm1: 1.0 +perm2: 1.0 +perm3: 1.0 +perm4: 1.0 +perm5: 1.0 +permAmount1: 1.0 +permAmount2: 0.8 +noAlias: 1.0 + +dependencyAnalysisTests/precisionTests/permissions - goto_pure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +currentPerm: 1.0 +perm1: 1.0 +perm2: 1.0 +perm3: 1.0 +perm4: 1.0 +perm5: 1.0 +permAmount1: 1.0 +permAmount2: 0.8 +noAlias: 1.0 + +dependencyAnalysisTests/precisionTests/permissions - infeasible_branch +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +currentPerm: 0.5 +perm1: 0.5 +perm2: 0.5 +perm3: 0.5 +perm4: 0.75 +perm5: 0.5 +permAmount1: 0.75 +permAmount2: 0.6666666666666667 +noAlias: 0.6666666666666667 + +dependencyAnalysisTests/precisionTests/permissions - inhale_invariant +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +currentPerm: 1.0 +perm1: 1.0 +perm2: 1.0 +perm3: 1.0 +perm4: 1.0 +perm5: 1.0 +permAmount1: 1.0 +permAmount2: 0.8 +noAlias: 1.0 + +dependencyAnalysisTests/precisionTests/permissions - magic_wand +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +currentPerm: 0.19999999999999996 +perm1: 1.0 +perm2: 1.0 +perm3: 1.0 +perm4: 1.0 +perm5: 1.0 +permAmount1: 1.0 +permAmount2: 0.8 +noAlias: 1.0 + +dependencyAnalysisTests/precisionTests/permissions - method_call_impure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +currentPerm: 0.5 +perm1: 1.0 +perm2: 1.0 +perm3: 1.0 +perm4: 1.0 +perm5: 1.0 +permAmount1: 1.0 +permAmount2: 0.8 +noAlias: 1.0 + +dependencyAnalysisTests/precisionTests/permissions - method_call_pure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +currentPerm: 1.0 +perm1: 1.0 +perm2: 1.0 +perm3: 1.0 +perm4: 1.0 +perm5: 1.0 +permAmount1: 1.0 +permAmount2: 0.8 +noAlias: 1.0 + +dependencyAnalysisTests/precisionTests/permissions - nested_branch +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +currentPerm: 1.0 +perm1: 1.0 +perm2: 1.0 +perm3: 1.0 +perm4: 0.75 +perm5: 1.0 +permAmount1: 1.0 +permAmount2: 0.6666666666666667 +noAlias: 1.0 + +dependencyAnalysisTests/precisionTests/permissions - quasihavoc +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +currentPerm: 0.5 +perm1: 0.5 +perm2: 0.33333333333333337 +perm3: 0.5 +perm4: 0.6666666666666667 +perm5: 0.5 +permAmount1: 0.6 +permAmount2: 0.625 +noAlias: 0.5 + +dependencyAnalysisTests/precisionTests/permissions - quasihavoc_disjoint_ref +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +currentPerm: 0.33333333333333337 +perm1: 0.33333333333333337 +perm2: 0.33333333333333337 +perm3: 0.33333333333333337 +perm4: 0.6 +perm5: 0.33333333333333337 +permAmount1: 0.6 +permAmount2: 0.5714285714285714 +noAlias: 0.4 + +dependencyAnalysisTests/precisionTests/permissions - quasihavoc_disjoint_ref_disjoint_field +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +currentPerm: 1.0 +perm1: 1.0 +perm2: 1.0 +perm3: 1.0 +perm4: 1.0 +perm5: 1.0 +permAmount1: 1.0 +permAmount2: 0.8 +noAlias: 1.0 + +dependencyAnalysisTests/precisionTests/permissions - while_perm +!!!!!!!!!!! +Failed to verify soundness of precision test dependencyAnalysisTests/precisionTests/permissions/while_perm + + +dependencyAnalysisTests/precisionTests/permissions - while_pure +!!!!!!!!!!! +Failed to verify soundness of precision test dependencyAnalysisTests/precisionTests/permissions/while_pure + + +dependencyAnalysisTests/precisionTests/permWildcard - assert +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +wildcardPerm: 1.0 +wildcardPermDistinctFields: 1.0 +quantifiedPermWildcard: 1.0 + +dependencyAnalysisTests/precisionTests/permWildcard - assignment_field +!!!!!!!!!!! +Failed to verify soundness of precision test dependencyAnalysisTests/precisionTests/permWildcard/assignment_field + + +dependencyAnalysisTests/precisionTests/permWildcard - assignment_pure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +wildcardPerm: 1.0 +wildcardPermDistinctFields: 1.0 +quantifiedPermWildcard: 1.0 + +dependencyAnalysisTests/precisionTests/permWildcard - baseline +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +wildcardPerm: 1.0 +wildcardPermDistinctFields: 1.0 +quantifiedPermWildcard: 1.0 + +dependencyAnalysisTests/precisionTests/permWildcard - branch +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +wildcardPerm: 1.0 +wildcardPermDistinctFields: 1.0 +quantifiedPermWildcard: 0.4 + +dependencyAnalysisTests/precisionTests/permWildcard - branch_div0 +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +wildcardPerm: 1.0 +wildcardPermDistinctFields: 1.0 +quantifiedPermWildcard: 0.6666666666666667 + +dependencyAnalysisTests/precisionTests/permWildcard - branch_function +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +wildcardPerm: 1.0 +wildcardPermDistinctFields: 1.0 +quantifiedPermWildcard: 0.33333333333333337 + +dependencyAnalysisTests/precisionTests/permWildcard - disjoint_ref +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +wildcardPerm: 1.0 +wildcardPermDistinctFields: 1.0 +quantifiedPermWildcard: 1.0 + +dependencyAnalysisTests/precisionTests/permWildcard - disjoint_ref_disjoint_field +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +wildcardPerm: 1.0 +wildcardPermDistinctFields: 1.0 +quantifiedPermWildcard: 1.0 + +dependencyAnalysisTests/precisionTests/permWildcard - disjoint_ref_disjoint_field_quantified +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +wildcardPerm: 1.0 +wildcardPermDistinctFields: 1.0 +quantifiedPermWildcard: 1.0 + +dependencyAnalysisTests/precisionTests/permWildcard - disjoint_ref_quantified +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +wildcardPerm: 0.25 +wildcardPermDistinctFields: 0.33333333333333337 +quantifiedPermWildcard: 0.4 + +dependencyAnalysisTests/precisionTests/permWildcard - fold_unfold_with_implication +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +wildcardPerm: 0.25 +wildcardPermDistinctFields: 0.25 +quantifiedPermWildcard: 0.5 + +dependencyAnalysisTests/precisionTests/permWildcard - function_add +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +wildcardPerm: 1.0 +wildcardPermDistinctFields: 1.0 +quantifiedPermWildcard: 1.0 + +dependencyAnalysisTests/precisionTests/permWildcard - goto_pure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +wildcardPerm: 1.0 +wildcardPermDistinctFields: 1.0 +quantifiedPermWildcard: 1.0 + +dependencyAnalysisTests/precisionTests/permWildcard - infeasible_branch +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +wildcardPerm: 0.5 +wildcardPermDistinctFields: 0.5 +quantifiedPermWildcard: 0.6666666666666667 + +dependencyAnalysisTests/precisionTests/permWildcard - inhale_invariant +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +wildcardPerm: 1.0 +wildcardPermDistinctFields: 1.0 +quantifiedPermWildcard: 1.0 + +dependencyAnalysisTests/precisionTests/permWildcard - magic_wand +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +wildcardPerm: 1.0 +wildcardPermDistinctFields: 1.0 +quantifiedPermWildcard: 1.0 + +dependencyAnalysisTests/precisionTests/permWildcard - method_call_impure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +wildcardPerm: 1.0 +wildcardPermDistinctFields: 1.0 +quantifiedPermWildcard: 0.6666666666666667 + +dependencyAnalysisTests/precisionTests/permWildcard - method_call_pure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +wildcardPerm: 1.0 +wildcardPermDistinctFields: 1.0 +quantifiedPermWildcard: 1.0 + +dependencyAnalysisTests/precisionTests/permWildcard - nested_branch +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +wildcardPerm: 1.0 +wildcardPermDistinctFields: 1.0 +quantifiedPermWildcard: 0.33333333333333337 + +dependencyAnalysisTests/precisionTests/permWildcard - quasihavoc +!!!!!!!!!!! +Failed to verify soundness of precision test dependencyAnalysisTests/precisionTests/permWildcard/quasihavoc + + +dependencyAnalysisTests/precisionTests/permWildcard - quasihavoc_disjoint_ref +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +wildcardPerm: 0.25 +wildcardPermDistinctFields: 0.33333333333333337 +quantifiedPermWildcard: 0.4 + +dependencyAnalysisTests/precisionTests/permWildcard - quasihavoc_disjoint_ref_disjoint_field +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +wildcardPerm: 1.0 +wildcardPermDistinctFields: 1.0 +quantifiedPermWildcard: 1.0 + +dependencyAnalysisTests/precisionTests/permWildcard - while_perm +!!!!!!!!!!! +Failed to verify soundness of precision test dependencyAnalysisTests/precisionTests/permWildcard/while_perm + + +dependencyAnalysisTests/precisionTests/permWildcard - while_pure +!!!!!!!!!!! +Failed to verify soundness of precision test dependencyAnalysisTests/precisionTests/permWildcard/while_pure + + +dependencyAnalysisTests/precisionTests/predicates - assert +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +foldP: 0.6666666666666667 +unfoldP: 0.8 +unfoldFoldP: 1.0 +callWithPredicate: 1.0 +unfoldPrecision: 1.0 +foldPrecision: 0.5 +unfolding1: 0.33333333333333337 +unfoldWithImpl: 1.0 +unfoldingWithImpl: 0.75 + +dependencyAnalysisTests/precisionTests/predicates - assignment_field +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +foldP: 1.0 +unfoldP: 1.0 +unfoldFoldP: 1.0 +callWithPredicate: 1.0 +unfoldPrecision: 1.0 +foldPrecision: 1.0 +unfolding1: 1.0 +unfoldWithImpl: 1.0 +unfoldingWithImpl: 1.0 + +dependencyAnalysisTests/precisionTests/predicates - assignment_pure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +foldP: 1.0 +unfoldP: 1.0 +unfoldFoldP: 1.0 +callWithPredicate: 1.0 +unfoldPrecision: 1.0 +foldPrecision: 1.0 +unfolding1: 1.0 +unfoldWithImpl: 1.0 +unfoldingWithImpl: 1.0 + +dependencyAnalysisTests/precisionTests/predicates - baseline +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +foldP: 1.0 +unfoldP: 1.0 +unfoldFoldP: 1.0 +callWithPredicate: 1.0 +unfoldPrecision: 1.0 +foldPrecision: 1.0 +unfolding1: 1.0 +unfoldWithImpl: 1.0 +unfoldingWithImpl: 1.0 + +dependencyAnalysisTests/precisionTests/predicates - branch +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +foldP: 0.6666666666666667 +unfoldP: 1.0 +unfoldFoldP: 1.0 +callWithPredicate: 1.0 +unfoldPrecision: 0.6666666666666667 +foldPrecision: 1.0 +unfolding1: 0.6666666666666667 +unfoldWithImpl: 0.8 +unfoldingWithImpl: 1.0 + +dependencyAnalysisTests/precisionTests/predicates - branch_div0 +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +foldP: 0.6666666666666667 +unfoldP: 0.8 +unfoldFoldP: 0.75 +callWithPredicate: 1.0 +unfoldPrecision: 0.4 +foldPrecision: 0.33333333333333337 +unfolding1: 0.6666666666666667 +unfoldWithImpl: 0.8 +unfoldingWithImpl: 0.75 + +dependencyAnalysisTests/precisionTests/predicates - branch_function +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +foldP: 0.5 +unfoldP: 0.6666666666666667 +unfoldFoldP: 0.6 +callWithPredicate: 1.0 +unfoldPrecision: 0.33333333333333337 +foldPrecision: 0.25 +unfolding1: 0.5 +unfoldWithImpl: 0.5714285714285714 +unfoldingWithImpl: 0.75 + +dependencyAnalysisTests/precisionTests/predicates - disjoint_ref +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +foldP: 1.0 +unfoldP: 1.0 +unfoldFoldP: 1.0 +callWithPredicate: 1.0 +unfoldPrecision: 1.0 +foldPrecision: 1.0 +unfolding1: 1.0 +unfoldWithImpl: 1.0 +unfoldingWithImpl: 1.0 + +dependencyAnalysisTests/precisionTests/predicates - disjoint_ref_disjoint_field +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +foldP: 1.0 +unfoldP: 1.0 +unfoldFoldP: 1.0 +callWithPredicate: 1.0 +unfoldPrecision: 1.0 +foldPrecision: 1.0 +unfolding1: 1.0 +unfoldWithImpl: 1.0 +unfoldingWithImpl: 1.0 + +dependencyAnalysisTests/precisionTests/predicates - disjoint_ref_disjoint_field_quantified +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +foldP: 1.0 +unfoldP: 1.0 +unfoldFoldP: 1.0 +callWithPredicate: 1.0 +unfoldPrecision: 1.0 +foldPrecision: 1.0 +unfolding1: 1.0 +unfoldWithImpl: 1.0 +unfoldingWithImpl: 1.0 + +dependencyAnalysisTests/precisionTests/predicates - disjoint_ref_quantified +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +foldP: 1.0 +unfoldP: 1.0 +unfoldFoldP: 1.0 +callWithPredicate: 1.0 +unfoldPrecision: 1.0 +foldPrecision: 1.0 +unfolding1: 1.0 +unfoldWithImpl: 1.0 +unfoldingWithImpl: 0.6 + +dependencyAnalysisTests/precisionTests/predicates - fold_unfold_with_implication +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +foldP: 0.4 +unfoldP: 0.5714285714285714 +unfoldFoldP: 0.5 +callWithPredicate: 0.4 +unfoldPrecision: 0.2857142857142857 +foldPrecision: 0.19999999999999996 +unfolding1: 0.4 +unfoldWithImpl: 0.5714285714285714 +unfoldingWithImpl: 0.5 + +dependencyAnalysisTests/precisionTests/predicates - function_add +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +foldP: 1.0 +unfoldP: 1.0 +unfoldFoldP: 1.0 +callWithPredicate: 1.0 +unfoldPrecision: 1.0 +foldPrecision: 1.0 +unfolding1: 1.0 +unfoldWithImpl: 1.0 +unfoldingWithImpl: 1.0 + +dependencyAnalysisTests/precisionTests/predicates - goto_pure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +foldP: 1.0 +unfoldP: 1.0 +unfoldFoldP: 1.0 +callWithPredicate: 1.0 +unfoldPrecision: 1.0 +foldPrecision: 1.0 +unfolding1: 1.0 +unfoldWithImpl: 0.8 +unfoldingWithImpl: 1.0 + +dependencyAnalysisTests/precisionTests/predicates - infeasible_branch +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +foldP: 0.6666666666666667 +unfoldP: 0.8 +unfoldFoldP: 0.75 +callWithPredicate: 0.6666666666666667 +unfoldPrecision: 0.5 +foldPrecision: 0.5 +unfolding1: 0.5 +unfoldWithImpl: 0.8 +unfoldingWithImpl: 0.75 + +dependencyAnalysisTests/precisionTests/predicates - inhale_invariant +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +foldP: 0.5 +unfoldP: 0.6666666666666667 +unfoldFoldP: 1.0 +callWithPredicate: 1.0 +unfoldPrecision: 1.0 +foldPrecision: 0.0 +unfolding1: 0.5 +unfoldWithImpl: 1.0 +unfoldingWithImpl: 0.6666666666666667 + +dependencyAnalysisTests/precisionTests/predicates - magic_wand +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +foldP: 1.0 +unfoldP: 1.0 +unfoldFoldP: 1.0 +callWithPredicate: 1.0 +unfoldPrecision: 1.0 +foldPrecision: 1.0 +unfolding1: 1.0 +unfoldWithImpl: 1.0 +unfoldingWithImpl: 1.0 + +dependencyAnalysisTests/precisionTests/predicates - method_call_impure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +foldP: 1.0 +unfoldP: 1.0 +unfoldFoldP: 1.0 +callWithPredicate: 1.0 +unfoldPrecision: 1.0 +foldPrecision: 1.0 +unfolding1: 1.0 +unfoldWithImpl: 1.0 +unfoldingWithImpl: 1.0 + +dependencyAnalysisTests/precisionTests/predicates - method_call_pure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +foldP: 1.0 +unfoldP: 1.0 +unfoldFoldP: 1.0 +callWithPredicate: 1.0 +unfoldPrecision: 1.0 +foldPrecision: 1.0 +unfolding1: 1.0 +unfoldWithImpl: 1.0 +unfoldingWithImpl: 1.0 + +dependencyAnalysisTests/precisionTests/predicates - nested_branch +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +foldP: 0.5 +unfoldP: 0.8 +unfoldFoldP: 0.75 +callWithPredicate: 1.0 +unfoldPrecision: 0.33333333333333337 +foldPrecision: 0.33333333333333337 +unfolding1: 0.5 +unfoldWithImpl: 0.6666666666666667 +unfoldingWithImpl: 0.75 + +dependencyAnalysisTests/precisionTests/predicates - quasihavoc +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +foldP: 1.0 +unfoldP: 1.0 +unfoldFoldP: 1.0 +callWithPredicate: 1.0 +unfoldPrecision: 1.0 +foldPrecision: 1.0 +unfolding1: 1.0 +unfoldWithImpl: 0.8 +unfoldingWithImpl: 1.0 + +dependencyAnalysisTests/precisionTests/predicates - quasihavoc_disjoint_ref +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +foldP: 1.0 +unfoldP: 1.0 +unfoldFoldP: 1.0 +callWithPredicate: 1.0 +unfoldPrecision: 1.0 +foldPrecision: 1.0 +unfolding1: 1.0 +unfoldWithImpl: 0.6666666666666667 +unfoldingWithImpl: 1.0 + +dependencyAnalysisTests/precisionTests/predicates - quasihavoc_disjoint_ref_disjoint_field +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +foldP: 1.0 +unfoldP: 1.0 +unfoldFoldP: 1.0 +callWithPredicate: 1.0 +unfoldPrecision: 1.0 +foldPrecision: 1.0 +unfolding1: 1.0 +unfoldWithImpl: 1.0 +unfoldingWithImpl: 1.0 + +dependencyAnalysisTests/precisionTests/predicates - while_perm +!!!!!!!!!!! +Failed to verify soundness of precision test dependencyAnalysisTests/precisionTests/predicates/while_perm + + +dependencyAnalysisTests/precisionTests/predicates - while_pure +!!!!!!!!!!! +Failed to verify soundness of precision test dependencyAnalysisTests/precisionTests/predicates/while_pure + + +dependencyAnalysisTests/precisionTests/quantifiedPermissions - assert +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +quantifiedPerm1: 0.6666666666666667 +quantifiedPerm2: 0.6666666666666667 +quantifiedPerm3: 0.75 +quantifiedExhalePartiallyTest: 1.0 +quantifiedExhalePartially: 0.6666666666666667 +quantifiedExhaleFully: 1.0 +quantifiedPermWrite1: 1.0 +quantifiedPermWrite2: 1.0 +quantifiedPermWrite3: 1.0 +quantifiedPermWrite4: 1.0 + +dependencyAnalysisTests/precisionTests/quantifiedPermissions - assignment_field +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +quantifiedPerm1: 1.0 +quantifiedPerm2: 0.6666666666666667 +quantifiedPerm3: 1.0 +quantifiedExhalePartiallyTest: 0.5 +quantifiedExhalePartially: 0.75 +quantifiedExhaleFully: 0.5 +quantifiedPermWrite1: 0.6666666666666667 +quantifiedPermWrite2: 1.0 +quantifiedPermWrite3: 1.0 +quantifiedPermWrite4: 1.0 + +dependencyAnalysisTests/precisionTests/quantifiedPermissions - assignment_pure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +quantifiedPerm1: 1.0 +quantifiedPerm2: 1.0 +quantifiedPerm3: 1.0 +quantifiedExhalePartiallyTest: 1.0 +quantifiedExhalePartially: 0.6666666666666667 +quantifiedExhaleFully: 1.0 +quantifiedPermWrite1: 1.0 +quantifiedPermWrite2: 1.0 +quantifiedPermWrite3: 1.0 +quantifiedPermWrite4: 1.0 + +dependencyAnalysisTests/precisionTests/quantifiedPermissions - baseline +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +quantifiedPerm1: 1.0 +quantifiedPerm2: 1.0 +quantifiedPerm3: 1.0 +quantifiedExhalePartiallyTest: 1.0 +quantifiedExhalePartially: 0.6666666666666667 +quantifiedExhaleFully: 1.0 +quantifiedPermWrite1: 1.0 +quantifiedPermWrite2: 1.0 +quantifiedPermWrite3: 1.0 +quantifiedPermWrite4: 1.0 + +dependencyAnalysisTests/precisionTests/quantifiedPermissions - branch +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +quantifiedPerm1: 1.0 +quantifiedPerm2: 0.6666666666666667 +quantifiedPerm3: 1.0 +quantifiedExhalePartiallyTest: 0.5 +quantifiedExhalePartially: 0.6666666666666667 +quantifiedExhaleFully: 0.5 +quantifiedPermWrite1: 0.6666666666666667 +quantifiedPermWrite2: 0.75 +quantifiedPermWrite3: 0.6 +quantifiedPermWrite4: 1.0 + +dependencyAnalysisTests/precisionTests/quantifiedPermissions - branch_div0 +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +quantifiedPerm1: 1.0 +quantifiedPerm2: 0.6666666666666667 +quantifiedPerm3: 1.0 +quantifiedExhalePartiallyTest: 0.5 +quantifiedExhalePartially: 0.6666666666666667 +quantifiedExhaleFully: 0.5 +quantifiedPermWrite1: 0.5 +quantifiedPermWrite2: 0.75 +quantifiedPermWrite3: 0.6 +quantifiedPermWrite4: 1.0 + +dependencyAnalysisTests/precisionTests/quantifiedPermissions - branch_function +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +quantifiedPerm1: 1.0 +quantifiedPerm2: 0.6666666666666667 +quantifiedPerm3: 0.5714285714285714 +quantifiedExhalePartiallyTest: 0.5 +quantifiedExhalePartially: 0.6666666666666667 +quantifiedExhaleFully: 0.5 +quantifiedPermWrite1: 0.4 +quantifiedPermWrite2: 0.6 +quantifiedPermWrite3: 0.5 +quantifiedPermWrite4: 1.0 + +dependencyAnalysisTests/precisionTests/quantifiedPermissions - disjoint_ref +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +quantifiedPerm1: 1.0 +quantifiedPerm2: 0.6666666666666667 +quantifiedPerm3: 1.0 +quantifiedExhalePartiallyTest: 0.5 +quantifiedExhalePartially: 0.6666666666666667 +quantifiedExhaleFully: 0.5 +quantifiedPermWrite1: 0.6666666666666667 +quantifiedPermWrite2: 1.0 +quantifiedPermWrite3: 1.0 +quantifiedPermWrite4: 1.0 + +dependencyAnalysisTests/precisionTests/quantifiedPermissions - disjoint_ref_disjoint_field +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +quantifiedPerm1: 1.0 +quantifiedPerm2: 0.6666666666666667 +quantifiedPerm3: 1.0 +quantifiedExhalePartiallyTest: 0.5 +quantifiedExhalePartially: 0.6666666666666667 +quantifiedExhaleFully: 0.5 +quantifiedPermWrite1: 0.6666666666666667 +quantifiedPermWrite2: 1.0 +quantifiedPermWrite3: 1.0 +quantifiedPermWrite4: 1.0 + +dependencyAnalysisTests/precisionTests/quantifiedPermissions - disjoint_ref_disjoint_field_quantified +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +quantifiedPerm1: 1.0 +quantifiedPerm2: 0.6666666666666667 +quantifiedPerm3: 1.0 +quantifiedExhalePartiallyTest: 0.5 +quantifiedExhalePartially: 1.0 +quantifiedExhaleFully: 0.5 +quantifiedPermWrite1: 0.6666666666666667 +quantifiedPermWrite2: 1.0 +quantifiedPermWrite3: 1.0 +quantifiedPermWrite4: 1.0 + +dependencyAnalysisTests/precisionTests/quantifiedPermissions - disjoint_ref_quantified +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +quantifiedPerm1: 1.0 +quantifiedPerm2: 0.4 +quantifiedPerm3: 1.0 +quantifiedExhalePartiallyTest: 0.25 +quantifiedExhalePartially: 0.4 +quantifiedExhaleFully: 0.25 +quantifiedPermWrite1: 0.6666666666666667 +quantifiedPermWrite2: 0.5 +quantifiedPermWrite3: 0.5 +quantifiedPermWrite4: 1.0 + +dependencyAnalysisTests/precisionTests/quantifiedPermissions - fold_unfold_with_implication +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +quantifiedPerm1: 0.4 +quantifiedPerm2: 0.4 +quantifiedPerm3: 0.5 +quantifiedExhalePartiallyTest: 0.19999999999999996 +quantifiedExhalePartially: 0.33333333333333337 +quantifiedExhaleFully: 0.19999999999999996 +quantifiedPermWrite1: 0.5 +quantifiedPermWrite2: 0.6 +quantifiedPermWrite3: 0.4285714285714286 +quantifiedPermWrite4: 0.4 + +dependencyAnalysisTests/precisionTests/quantifiedPermissions - function_add +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +quantifiedPerm1: 0.6666666666666667 +quantifiedPerm2: 0.6666666666666667 +quantifiedPerm3: 1.0 +quantifiedExhalePartiallyTest: 0.33333333333333337 +quantifiedExhalePartially: 0.6666666666666667 +quantifiedExhaleFully: 0.33333333333333337 +quantifiedPermWrite1: 0.6666666666666667 +quantifiedPermWrite2: 1.0 +quantifiedPermWrite3: 1.0 +quantifiedPermWrite4: 1.0 + +Program dependencyAnalysisTests/precisionTests/quantifiedPermissions/goto_pure does not verify. Skip +dependencyAnalysisTests/precisionTests/quantifiedPermissions - infeasible_branch +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +quantifiedPerm1: 0.6666666666666667 +quantifiedPerm2: 0.5 +quantifiedPerm3: 0.75 +quantifiedExhalePartiallyTest: 0.33333333333333337 +quantifiedExhalePartially: 0.5 +quantifiedExhaleFully: 0.33333333333333337 +quantifiedPermWrite1: 0.5 +quantifiedPermWrite2: 0.75 +quantifiedPermWrite3: 0.6 +quantifiedPermWrite4: 0.5 + +dependencyAnalysisTests/precisionTests/quantifiedPermissions - inhale_invariant +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +quantifiedPerm1: 0.5 +quantifiedPerm2: 0.5 +quantifiedPerm3: 0.6666666666666667 +quantifiedExhalePartiallyTest: 1.0 +quantifiedExhalePartially: 1.0 +quantifiedExhaleFully: 1.0 +quantifiedPermWrite1: 1.0 +quantifiedPermWrite2: 1.0 +quantifiedPermWrite3: 1.0 +quantifiedPermWrite4: 1.0 + +dependencyAnalysisTests/precisionTests/quantifiedPermissions - magic_wand +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +quantifiedPerm1: 1.0 +quantifiedPerm2: 1.0 +quantifiedPerm3: 0.4285714285714286 +quantifiedExhalePartiallyTest: 1.0 +quantifiedExhalePartially: 0.6666666666666667 +quantifiedExhaleFully: 1.0 +quantifiedPermWrite1: 1.0 +quantifiedPermWrite2: 0.4285714285714286 +quantifiedPermWrite3: 1.0 +quantifiedPermWrite4: 1.0 + +dependencyAnalysisTests/precisionTests/quantifiedPermissions - method_call_impure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +quantifiedPerm1: 1.0 +quantifiedPerm2: 0.6666666666666667 +quantifiedPerm3: 0.6 +quantifiedExhalePartiallyTest: 1.0 +quantifiedExhalePartially: 0.5 +quantifiedExhaleFully: 1.0 +quantifiedPermWrite1: 1.0 +quantifiedPermWrite2: 0.75 +quantifiedPermWrite3: 0.75 +quantifiedPermWrite4: 1.0 + +dependencyAnalysisTests/precisionTests/quantifiedPermissions - method_call_pure +!!!!!!!!!!! +Failed to verify soundness of precision test dependencyAnalysisTests/precisionTests/quantifiedPermissions/method_call_pure + + +dependencyAnalysisTests/precisionTests/quantifiedPermissions - nested_branch +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +quantifiedPerm1: 1.0 +quantifiedPerm2: 0.6666666666666667 +quantifiedPerm3: 1.0 +quantifiedExhalePartiallyTest: 0.5 +quantifiedExhalePartially: 0.6666666666666667 +quantifiedExhaleFully: 0.5 +quantifiedPermWrite1: 0.5 +quantifiedPermWrite2: 0.6 +quantifiedPermWrite3: 0.5 +quantifiedPermWrite4: 1.0 + +dependencyAnalysisTests/precisionTests/quantifiedPermissions - quasihavoc +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +quantifiedPerm1: 0.6666666666666667 +quantifiedPerm2: 0.6666666666666667 +quantifiedPerm3: 0.75 +quantifiedExhalePartiallyTest: 0.33333333333333337 +quantifiedExhalePartially: 0.5 +quantifiedExhaleFully: 0.33333333333333337 +quantifiedPermWrite1: 0.6666666666666667 +quantifiedPermWrite2: 0.6666666666666667 +quantifiedPermWrite3: 0.75 +quantifiedPermWrite4: 0.4 + +dependencyAnalysisTests/precisionTests/quantifiedPermissions - quasihavoc_disjoint_ref +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +quantifiedPerm1: 0.5 +quantifiedPerm2: 0.5 +quantifiedPerm3: 0.6 +quantifiedExhalePartiallyTest: 0.33333333333333337 +quantifiedExhalePartially: 0.4 +quantifiedExhaleFully: 0.33333333333333337 +quantifiedPermWrite1: 0.5 +quantifiedPermWrite2: 0.6 +quantifiedPermWrite3: 0.6 +quantifiedPermWrite4: 0.4 + +dependencyAnalysisTests/precisionTests/quantifiedPermissions - quasihavoc_disjoint_ref_disjoint_field +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +quantifiedPerm1: 1.0 +quantifiedPerm2: 1.0 +quantifiedPerm3: 1.0 +quantifiedExhalePartiallyTest: 1.0 +quantifiedExhalePartially: 1.0 +quantifiedExhaleFully: 1.0 +quantifiedPermWrite1: 1.0 +quantifiedPermWrite2: 1.0 +quantifiedPermWrite3: 1.0 +quantifiedPermWrite4: 1.0 + +dependencyAnalysisTests/precisionTests/quantifiedPermissions - while_perm +!!!!!!!!!!! +Failed to verify soundness of precision test dependencyAnalysisTests/precisionTests/quantifiedPermissions/while_perm + + +Program dependencyAnalysisTests/precisionTests/quantifiedPermissions/while_pure does not verify. Skip +dependencyAnalysisTests/precisionTests/quasihavoc - assert +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +quasihavoc1: 0.75 +quasihavoc2: 0.5 +quasihavoc3: 0.6666666666666667 + +dependencyAnalysisTests/precisionTests/quasihavoc - assignment_field +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +quasihavoc1: 0.75 +quasihavoc2: 0.5 +quasihavoc3: 0.6666666666666667 + +dependencyAnalysisTests/precisionTests/quasihavoc - assignment_pure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +quasihavoc1: 0.75 +quasihavoc2: 0.5 +quasihavoc3: 0.6666666666666667 + +dependencyAnalysisTests/precisionTests/quasihavoc - baseline +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +quasihavoc1: 0.75 +quasihavoc2: 0.5 +quasihavoc3: 0.6666666666666667 + +dependencyAnalysisTests/precisionTests/quasihavoc - branch +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +quasihavoc1: 0.75 +quasihavoc2: 0.5 +quasihavoc3: 0.6666666666666667 + +dependencyAnalysisTests/precisionTests/quasihavoc - branch_div0 +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +quasihavoc1: 0.75 +quasihavoc2: 0.5 +quasihavoc3: 0.6666666666666667 + +dependencyAnalysisTests/precisionTests/quasihavoc - branch_function +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +quasihavoc1: 0.75 +quasihavoc2: 0.5 +quasihavoc3: 0.6666666666666667 + +dependencyAnalysisTests/precisionTests/quasihavoc - disjoint_ref +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +quasihavoc1: 0.75 +quasihavoc2: 0.5 +quasihavoc3: 0.6666666666666667 + +dependencyAnalysisTests/precisionTests/quasihavoc - disjoint_ref_disjoint_field +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +quasihavoc1: 0.75 +quasihavoc2: 0.5 +quasihavoc3: 0.6666666666666667 + +dependencyAnalysisTests/precisionTests/quasihavoc - disjoint_ref_disjoint_field_quantified +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +quasihavoc1: 0.75 +quasihavoc2: 0.5 +quasihavoc3: 0.6666666666666667 + +dependencyAnalysisTests/precisionTests/quasihavoc - disjoint_ref_quantified +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +quasihavoc1: 0.75 +quasihavoc2: 0.5 +quasihavoc3: 0.6666666666666667 + +dependencyAnalysisTests/precisionTests/quasihavoc - fold_unfold_with_implication +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +quasihavoc1: 0.75 +quasihavoc2: 0.5 +quasihavoc3: 0.6666666666666667 + +dependencyAnalysisTests/precisionTests/quasihavoc - function_add +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +quasihavoc1: 0.75 +quasihavoc2: 0.5 +quasihavoc3: 0.6666666666666667 + +dependencyAnalysisTests/precisionTests/quasihavoc - goto_pure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +quasihavoc1: 0.75 +quasihavoc2: 0.5 +quasihavoc3: 0.6666666666666667 + +dependencyAnalysisTests/precisionTests/quasihavoc - infeasible_branch +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +quasihavoc1: 0.75 +quasihavoc2: 0.5 +quasihavoc3: 0.6666666666666667 + +dependencyAnalysisTests/precisionTests/quasihavoc - inhale_invariant +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +quasihavoc1: 0.75 +quasihavoc2: 0.5 +quasihavoc3: 0.6666666666666667 + +dependencyAnalysisTests/precisionTests/quasihavoc - magic_wand +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +quasihavoc1: 0.75 +quasihavoc2: 0.5 +quasihavoc3: 0.6666666666666667 + +dependencyAnalysisTests/precisionTests/quasihavoc - method_call_impure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +quasihavoc1: 0.75 +quasihavoc2: 0.5 +quasihavoc3: 0.6666666666666667 + +dependencyAnalysisTests/precisionTests/quasihavoc - method_call_pure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +quasihavoc1: 0.75 +quasihavoc2: 0.5 +quasihavoc3: 0.6666666666666667 + +dependencyAnalysisTests/precisionTests/quasihavoc - nested_branch +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +quasihavoc1: 0.75 +quasihavoc2: 0.5 +quasihavoc3: 0.6666666666666667 + +dependencyAnalysisTests/precisionTests/quasihavoc - quasihavoc +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +quasihavoc1: 0.75 +quasihavoc2: 0.5 +quasihavoc3: 0.6666666666666667 + +dependencyAnalysisTests/precisionTests/quasihavoc - quasihavoc_disjoint_ref +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +quasihavoc1: 0.75 +quasihavoc2: 0.5 +quasihavoc3: 0.6666666666666667 + +dependencyAnalysisTests/precisionTests/quasihavoc - quasihavoc_disjoint_ref_disjoint_field +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +quasihavoc1: 0.75 +quasihavoc2: 0.5 +quasihavoc3: 0.6666666666666667 + +dependencyAnalysisTests/precisionTests/quasihavoc - while_perm +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +quasihavoc1: 0.75 +quasihavoc2: 0.5 +quasihavoc3: 0.6666666666666667 + +dependencyAnalysisTests/precisionTests/quasihavoc - while_pure +gen_add: 1.0 +gen_add_positive: 1.0 +incr: 1.0 +gen_incr_pure: 1.0 +quasihavoc1: 0.75 +quasihavoc2: 0.5 +quasihavoc3: 0.6666666666666667 + diff --git a/src/test/resources/dependencyAnalysisTests/precisionTests/results/result_table.out b/src/test/resources/dependencyAnalysisTests/precisionTests/results/result_table.out new file mode 100644 index 000000000..1b0952aa4 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/precisionTests/results/result_table.out @@ -0,0 +1,14 @@ + assert assignment_field assignment_pure baseline branch branch_div0 branch_function disjoint_ref disjoint_ref_disjoint_field disjoint_ref_disjoint_field_quantified disjoint_ref_quantified fold_unfold_with_implication function_add goto_pure infeasible_branch inhale_invariant magic_wand method_call_impure method_call_pure nested_branch quasihavoc quasihavoc_disjoint_ref quasihavoc_disjoint_ref_disjoint_field while_perm while_pure | max min avg +dependencyAnalysisTests/precisionTests/branches 1.000 1.000 1.000 1.000 0.806 0.824 0.739 1.000 1.000 1.000 1.000 0.722 1.000 1.000 0.824 1.000 1.000 1.000 1.000 0.750 1.000 1.000 1.000 1.000 1.000 | 1.000 0.722 0.947 +dependencyAnalysisTests/precisionTests/domain 0.964 1.000 1.000 1.000 1.000 0.971 0.956 1.000 1.000 1.000 1.000 0.782 1.000 1.000 0.853 0.952 1.000 1.000 1.000 0.971 0.948 1.000 1.000 1.000 1.000 | 1.000 0.782 0.976 +dependencyAnalysisTests/precisionTests/function-call 1.000 1.000 1.000 1.000 1.000 0.912 0.849 1.000 1.000 1.000 1.000 0.839 1.000 1.000 0.912 1.000 1.000 1.000 1.000 0.912 1.000 1.000 1.000 1.000 1.000 | 1.000 0.839 0.977 +dependencyAnalysisTests/precisionTests/inhaleExhale 0.786 0.810 0.810 0.810 0.786 0.762 0.748 0.798 0.810 0.810 0.721 0.690 0.810 0.810 0.750 0.762 0.782 0.798 0.833 0.748 0.750 0.714 0.810 NaN NaN | 0.833 0.690 0.778 +dependencyAnalysisTests/precisionTests/loops 0.964 0.964 0.964 0.964 0.932 0.872 0.827 0.964 0.964 0.964 0.964 0.737 0.964 NaN 0.819 0.964 0.964 0.964 0.964 0.839 0.964 0.964 0.964 0.964 NaN | 0.964 0.737 0.931 +dependencyAnalysisTests/precisionTests/magicWands 1.000 1.000 1.000 1.000 0.955 0.955 0.927 0.970 1.000 1.000 0.955 0.713 1.000 1.000 0.859 1.000 0.939 0.970 1.000 0.927 0.951 0.955 1.000 NaN NaN | 1.000 0.713 0.960 +dependencyAnalysisTests/precisionTests/method-call 1.000 1.000 1.000 1.000 1.000 0.917 0.860 1.000 1.000 1.000 1.000 0.851 1.000 1.000 0.917 1.000 1.000 1.000 1.000 0.917 1.000 1.000 1.000 1.000 1.000 | 1.000 0.851 0.978 +dependencyAnalysisTests/precisionTests/misc 1.000 1.000 1.000 1.000 0.917 0.861 0.789 1.000 1.000 1.000 0.889 0.900 1.000 0.917 0.861 1.000 1.000 1.000 1.000 0.833 0.944 0.917 1.000 0.917 0.917 | 1.000 0.789 0.946 +dependencyAnalysisTests/precisionTests/permWildcard 1.000 NaN 1.000 1.000 0.914 0.952 0.905 1.000 1.000 1.000 0.712 0.714 1.000 1.000 0.810 1.000 1.000 0.952 1.000 0.905 NaN 0.712 1.000 NaN NaN | 1.000 0.712 0.932 +dependencyAnalysisTests/precisionTests/permissions 0.985 0.985 0.985 0.985 0.985 0.955 0.955 0.985 0.985 0.985 0.775 0.550 0.985 0.985 0.718 0.985 0.923 0.946 0.985 0.955 0.671 0.603 0.985 NaN NaN | 0.985 0.550 0.907 +dependencyAnalysisTests/precisionTests/predicates 0.850 1.000 1.000 1.000 0.908 0.782 0.705 1.000 1.000 1.000 0.969 0.602 1.000 0.985 0.764 0.795 1.000 1.000 1.000 0.741 0.985 0.974 1.000 NaN NaN | 1.000 0.602 0.916 +dependencyAnalysisTests/precisionTests/quantifiedPermissions 0.911 0.863 0.976 0.976 0.811 0.799 0.743 0.857 0.857 0.881 0.712 0.569 0.810 NaN 0.674 0.905 0.895 0.876 NaN 0.781 0.695 0.626 1.000 NaN NaN | 1.000 0.569 0.820 +dependencyAnalysisTests/precisionTests/quasihavoc 0.845 0.845 0.845 0.845 0.845 0.845 0.845 0.845 0.845 0.845 0.845 0.845 0.845 0.845 0.845 0.845 0.845 0.845 0.845 0.845 0.845 0.845 0.845 0.845 0.845 | 0.845 0.845 0.845 diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index 7eee26d44..136761ba8 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -22,11 +22,13 @@ class AssumptionAnalysisTests extends AnyFunSuite { val EXECUTE_PRECISION_BENCHMARK = false val EXECUTE_TEST = true val EXECUTE_PERFORMANCE_BENCHMARK = false - val ignores: Seq[String] = Seq("example1", "example2", "iterativeTreeDelete") + val ignores: Seq[String] = Seq("example1", "example2", "iterativeTreeDelete", "listAppend_wands") val testDirectories: Seq[String] = Seq( "dependencyAnalysisTests/all", "dependencyAnalysisTests/unitTests", "dependencyAnalysisTests/real-world-examples", +// "frontends/gobra", +// "symbExLogTests", // "dependencyAnalysisTests/quick" // "dependencyAnalysisTests/fromSilver", // "dependencyAnalysisTests/performanceBenchmark" From 422e769f9fa55eec5ebcc7156d77bb35637fbd7e Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 16 Aug 2025 14:37:03 +0200 Subject: [PATCH 236/474] add sequence test --- .../dependencyAnalysisTests/all/sequences.vpr | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 src/test/resources/dependencyAnalysisTests/all/sequences.vpr diff --git a/src/test/resources/dependencyAnalysisTests/all/sequences.vpr b/src/test/resources/dependencyAnalysisTests/all/sequences.vpr new file mode 100644 index 000000000..7c0b9293e --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/all/sequences.vpr @@ -0,0 +1,48 @@ + +method sequenceUpdate(xs: Seq[Int], ys: Seq[Int]) returns (res: Seq[Int]) + requires @dependency("Explicit")(|xs| > 5) + requires @irrelevant("Explicit")(|ys| > 3) +{ + @irrelevant("Explicit") + inhale forall x: Int :: x in xs ==> x > 0 + @irrelevant("Explicit") + inhale forall y: Int :: y in ys ==> y >= 0 + + @dependency("Implicit") + res := xs[0 := 1] + + @testAssertion("Explicit") + assert res[0] == 1 +} + +method sequence1(xs: Seq[Int], ys: Seq[Int]) returns (res: Seq[Int]) + requires @dependency("Explicit")(|xs| > 5) + requires @irrelevant("Explicit")(|ys| > 3) +{ + @dependency("Explicit") + inhale forall x: Int :: x in xs ==> x > 0 + @irrelevant("Explicit") + inhale forall y: Int :: y in ys ==> y >= 0 + + @dependency("Implicit") + res := xs[0 := 1] + + @testAssertion("Explicit") + assert forall x: Int :: x in res ==> x > 0 +} + +method sequence2(xs: Seq[Int], ys: Seq[Int]) returns (res: Seq[Int]) + requires @irrelevant("Explicit")(|xs| > 5) + requires @dependency("Explicit")(|ys| > 3) +{ + @irrelevant("Explicit") + inhale forall x: Int :: x in xs ==> x > 0 + @dependency("Explicit") + inhale forall y: Int :: y in ys ==> y >= 0 + + @irrelevant("Implicit") + res := xs[0 := 1] + + @testAssertion("Explicit") + assert forall y: Int :: y in ys ==> y >= 0 +} \ No newline at end of file From e2c2090b010f8d2fcb415b10e76e9825b50e286b Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sun, 17 Aug 2025 17:56:20 +0200 Subject: [PATCH 237/474] minor fixes --- .../assumptionAnalysis/AssumptionAnalysisInterpreter.scala | 4 ++-- src/main/scala/decider/Decider.scala | 7 +++++-- src/test/scala/AssumptionAnalysisTests.scala | 5 +++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala index 4944a895f..3f5b47f87 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala @@ -17,10 +17,10 @@ class AssumptionAnalysisInterpreter(name: String, graph: ReadOnlyAssumptionAnaly def getNodes: Set[AssumptionAnalysisNode] = graph.getNodes.toSet def getNodesByLine(line: Int): Set[AssumptionAnalysisNode] = - getNodes.filter(node => node.sourceInfo.getLineNumber.isDefined && node.sourceInfo.getLineNumber.get == line) + getNodes.filter(n => !AssumptionType.internalTypes.contains(n.assumptionType)).filter(node => node.sourceInfo.getLineNumber.isDefined && node.sourceInfo.getLineNumber.get == line) def getNodesByPosition(file: String, line: Int): Set[AssumptionAnalysisNode] = - getNodes.filter(node => node.sourceInfo.getLineNumber.isDefined && node.sourceInfo.getLineNumber.get == line && node.sourceInfo.getPositionString.startsWith(file + ".")) + getNodes.filter(n => !AssumptionType.internalTypes.contains(n.assumptionType)).filter(node => node.sourceInfo.getLineNumber.isDefined && node.sourceInfo.getLineNumber.get == line && node.sourceInfo.getPositionString.startsWith(file + ".")) def getDirectDependencies(nodeIdsToAnalyze: Set[Int]): Set[AssumptionAnalysisNode] = { val directDependencyIds = nodeIdsToAnalyze flatMap (id => graph.getDirectEdges.getOrElse(id, Set.empty)) diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index e8547fa29..042ee3c04 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -501,8 +501,11 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } def checkAndGetInfeasibilityNode(t: Term, timeout: Int, assumptionType: AssumptionType=AssumptionType.Implicit): (Boolean, Option[Int]) = { + var infeasibilityNodeId: Option[Int] = pcs.getCurrentInfeasibilityNode + if(infeasibilityNodeId.isDefined){ + return (true, infeasibilityNodeId) + } val (success, checkNode) = deciderAssert(t, assumptionType, Some(timeout), isCheck=true) - var infeasibilityNodeId: Option[Int] = None if(success){ infeasibilityNodeId = assumptionAnalyzer.addInfeasibilityNode(isCheck = true, analysisSourceInfoStack.getFullSourceInfo) assumptionAnalyzer.addDependency(checkNode.map(_.id), infeasibilityNodeId) @@ -556,7 +559,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => val result = isPathInfeasible() || prover.assert(t, timeout, label) if(result) - if(pcs.getCurrentInfeasibilityNode.isDefined) { + if(pcs.getCurrentInfeasibilityNode.isDefined) { // TODO ake: should be checked before calling prover.assert assumptionAnalyzer.addDependency(pcs.getCurrentInfeasibilityNode, Some(AssumptionAnalyzer.getIdFromLabel(label))) }else{ assumptionAnalyzer.processUnsatCoreAndAddDependencies(prover.getLastUnsatCore, label) diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index 136761ba8..51d0b2a61 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -76,8 +76,8 @@ class AssumptionAnalysisTests extends AnyFunSuite { if(EXECUTE_TEST) { testDirectories foreach (dir => visitFiles(dir, createSingleTest)) - analysisCommandLineArguments = Seq("--enableMoreCompleteExhale") ++ analysisCommandLineArguments - visitFiles("dependencyAnalysisTests/mce", createSingleTest) +// analysisCommandLineArguments = Seq("--enableMoreCompleteExhale") ++ analysisCommandLineArguments +// visitFiles("dependencyAnalysisTests/mce", createSingleTest) } @@ -180,6 +180,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { writer.println(s"$filePrefix - $fileName") new AnnotatedPrecisionBenchmark(filePrefix + "/" + fileName, program, assumptionAnalysisInterpreters, fullGraphInterpreter.get, writer).execute() writer.println() + writer.flush() println(s"Precision Benchmark for $filePrefix - $fileName done.") }catch{ case t: Throwable => From 34afe0b4e75310b3ca9db9f8803adbe0f0ac9a64 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sun, 17 Aug 2025 18:15:08 +0200 Subject: [PATCH 238/474] update test examples --- .../{unitTests => all}/quasihavoc.vpr | 3 +- .../dependencyAnalysisTests/all/sequences.vpr | 2 +- .../benchmark_scripts/snippets.txt | 34 ++++++----- .../mce/imprecisions.vpr | 48 ++++++++++++++++ .../minimalExamples-precision/assert.vpr | 25 +++++++++ .../minimalExamples-precision/debug.vpr | 43 ++++++++++++++ .../minimalExamples-precision/domains.vpr | 51 +++++++++++++++++ .../infeasibility.vpr | 56 +++++++++++++++++++ .../new/infeasibilityBug.vpr | 28 ---------- .../precisionTests/results/result_table.out | 25 ++++----- .../unitTests/inhaleExhale.vpr | 8 +-- .../unitTests/loops.vpr | 4 +- .../unitTests/magicWands.vpr | 2 +- .../unitTests/permWildcard.vpr | 2 +- .../unitTests/permissions.vpr | 1 - .../unitTests/quantifiedPermissions.vpr | 13 +---- 16 files changed, 266 insertions(+), 79 deletions(-) rename src/test/resources/dependencyAnalysisTests/{unitTests => all}/quasihavoc.vpr (99%) create mode 100644 src/test/resources/dependencyAnalysisTests/mce/imprecisions.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/minimalExamples-precision/assert.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/minimalExamples-precision/debug.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/minimalExamples-precision/domains.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/minimalExamples-precision/infeasibility.vpr delete mode 100644 src/test/resources/dependencyAnalysisTests/new/infeasibilityBug.vpr diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/quasihavoc.vpr b/src/test/resources/dependencyAnalysisTests/all/quasihavoc.vpr similarity index 99% rename from src/test/resources/dependencyAnalysisTests/unitTests/quasihavoc.vpr rename to src/test/resources/dependencyAnalysisTests/all/quasihavoc.vpr index b42a9db5b..76d71491e 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/quasihavoc.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/quasihavoc.vpr @@ -32,4 +32,5 @@ method quasihavoc3(x: Ref) x.f := 3 @testAssertion("Explicit") assert x.f == 3 -} \ No newline at end of file +} + diff --git a/src/test/resources/dependencyAnalysisTests/all/sequences.vpr b/src/test/resources/dependencyAnalysisTests/all/sequences.vpr index 7c0b9293e..275cf9fa0 100644 --- a/src/test/resources/dependencyAnalysisTests/all/sequences.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/sequences.vpr @@ -33,7 +33,7 @@ method sequence1(xs: Seq[Int], ys: Seq[Int]) returns (res: Seq[Int]) method sequence2(xs: Seq[Int], ys: Seq[Int]) returns (res: Seq[Int]) requires @irrelevant("Explicit")(|xs| > 5) - requires @dependency("Explicit")(|ys| > 3) + requires @irrelevant("Explicit")(|ys| > 3) { @irrelevant("Explicit") inhale forall x: Int :: x in xs ==> x > 0 diff --git a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/snippets.txt b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/snippets.txt index e4e3f4668..af68f8369 100644 --- a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/snippets.txt +++ b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/snippets.txt @@ -101,27 +101,26 @@ SNIPPET method_call_pure$=${ } SNIPPET branch$=${ - var gen_b: Bool - if(@irrelevant("PathCondition")($RO_INT_0 > 0 && $RO_INT_1 > 0 && gen_b)){ + var gen_i: Int + if(@irrelevant("PathCondition")($RO_INT_0 > gen_i)){ @irrelevant("Implicit") $RW_INT_0 := 10 }else{ - var gen_i: Int @irrelevant("Implicit") - gen_i := $RO_INT_0 + $RO_INT_1 + $RW_INT_0 := $RO_INT_0 + $RO_INT_1 } } SNIPPET nested_branch$=${ - var gen_b: Bool - if(@irrelevant("PathCondition")($RO_INT_0 > 0 && $RO_INT_1 > 0 && gen_b)){ + var gen_c: Int + if(@irrelevant("PathCondition")($RO_INT_0 > gen_c && $RO_INT_1 > gen_c)){ var gen_i: Int @irrelevant("Implicit") gen_i := $RO_INT_0 + $RO_INT_1 @irrelevant("Implicit") $RW_INT_0 := 10 }else{ - if(@irrelevant("PathCondition")($RO_INT_0 > 0)){ + if(@irrelevant("PathCondition")($RO_INT_0 > gen_c + 5)){ @irrelevant("Implicit") $RW_INT_0 := 30 }else{ @@ -148,13 +147,16 @@ SNIPPET infeasible_branch$=${ // branches & function calls SNIPPET branch_function$=${ - if(@irrelevant("PathCondition")($RO_INT_0 > 0 && $RO_INT_1 > 0)){ + var gen_c: Int + @irrelevant("Explicit") + inhale gen_c > 0 + if(@irrelevant("PathCondition")($RO_INT_0 > gen_c && $RO_INT_1 > gen_c)){ @irrelevant("Implicit") $RW_INT_0 := gen_add_positive($RO_INT_0, $RO_INT_1) }else{ - if(@irrelevant("PathCondition")($RO_INT_0 > 0)){ + if(@irrelevant("PathCondition")($RO_INT_0 > gen_c)){ @irrelevant("Implicit") - $RW_INT_0 := gen_add_positive($RO_INT_0, -$RO_INT_1) + $RW_INT_0 := gen_add_positive($RO_INT_0, gen_c) }else{ @irrelevant("Implicit") $RW_INT_0 := 0 @@ -225,12 +227,14 @@ SNIPPET disjoint_ref_disjoint_field_quantified$=${ } SNIPPET while_pure$=${ - var gen_start: Int + var gen_start: Int, gen_c: Int + @irrelevant("Explicit") + inhale gen_c >= 0 @irrelevant("Implicit") gen_start := $RW_INT_PURE_0 @irrelevant("Implicit") $RW_INT_PURE_1 := 0 - while(@irrelevant("PathCondition")($RW_INT_PURE_0 > 0)) + while(@irrelevant("PathCondition")($RW_INT_PURE_0 > gen_c)) invariant $ACC_INVARIANT invariant @irrelevant("LoopInvariant")($GEN_ACC_INVARIANT) invariant @irrelevant("LoopInvariant")($RW_INT_PURE_0 <= gen_start) @@ -244,12 +248,14 @@ SNIPPET while_pure$=${ } SNIPPET while_perm$=${ - var gen_start: Int + var gen_start: Int, gen_c: Int + @irrelevant("Explicit") + inhale gen_c >= 0 @irrelevant("Implicit") gen_start := $RW_INT_FIELD_0 @irrelevant("Implicit") $RW_INT_FIELD_1 := 0 - while(@irrelevant("PathCondition")($RW_INT_FIELD_0 > 0)) + while(@irrelevant("PathCondition")($RW_INT_FIELD_0 > gen_c)) invariant $ACC_INVARIANT invariant @irrelevant("LoopInvariant")($GEN_ACC_INVARIANT) invariant @irrelevant("LoopInvariant")($RW_INT_FIELD_0 <= gen_start) diff --git a/src/test/resources/dependencyAnalysisTests/mce/imprecisions.vpr b/src/test/resources/dependencyAnalysisTests/mce/imprecisions.vpr new file mode 100644 index 000000000..dba7374cb --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/mce/imprecisions.vpr @@ -0,0 +1,48 @@ +field f: Int + +method exhaleInhale(a: Ref) // inhaleExhale/assignment_field + requires @dependency("Explicit")(acc(a.f)) +{ + exhale acc(a.f, 1/2) + inhale acc(a.f, 1/2) + +var gen_RO_INT_1: Int + +var gen_dummy_int: Int +{ + @irrelevant("Implicit") + a.f := a.f + gen_RO_INT_1 // reported as dependency on mce, why? +} + + @testAssertion("Explicit") + assert perm(a.f) == write +} + +method exhale1(){ // inhaleExhale/magicWand -> reports dependency to gen_x access inhale (aliasing checks?) + var x: Ref + + @dependency("Explicit") + inhale acc(x.f) + @dependency("Explicit") + inhale x.f > 0 + + exhale acc(x.f, 1/2) + + +var gen_dummy_int: Int +{ + var gen_i: Int + var gen_x: Ref + @irrelevant("Explicit") + inhale gen_i > 0 + @irrelevant("Explicit") + inhale acc(gen_x.f) && gen_x.f > 0 + @irrelevant("Implicit") + package gen_i >= 0 --* acc(gen_x.f) && gen_x.f > 0 + @irrelevant("Implicit") + apply gen_i >= 0 --* acc(gen_x.f) && gen_x.f > 0 +} + + @testAssertion("Explicit") + assert x.f > 0 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/minimalExamples-precision/assert.vpr b/src/test/resources/dependencyAnalysisTests/minimalExamples-precision/assert.vpr new file mode 100644 index 000000000..a377af179 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/minimalExamples-precision/assert.vpr @@ -0,0 +1,25 @@ +field f: Int + +method inhale1(){ + var x: Ref + + @dependency("Explicit") + inhale acc(x.f, 1/2) + @dependency("Explicit") + inhale x.f > 0 + + inhale acc(x.f, 1/2) + + +var gen_dummy_int: Int +{ + var gen_i: Int + @irrelevant("Explicit") + inhale gen_i > 0 // dependency of the assertion + @irrelevant() + assert x.f>0 && gen_i > 0 // reported as internal dependency +} + + @testAssertion("Explicit") + assert x.f > 0 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/minimalExamples-precision/debug.vpr b/src/test/resources/dependencyAnalysisTests/minimalExamples-precision/debug.vpr new file mode 100644 index 000000000..f6e77a054 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/minimalExamples-precision/debug.vpr @@ -0,0 +1,43 @@ +field f: Int + +method incr(a: Ref) + requires acc(a.f) + ensures acc(a.f) + ensures a.f == old(a.f) + 1 +{ + a.f := a.f + 1 +} +predicate implPred(a: Int, x: Ref){ + a > 0 ==> acc(x.f) +} + + +method unfoldingWithImpl(a: Int, x: Ref) + requires @dependency("Explicit")(implPred(a, x)) +{ + var res: Int + if(@dependency("PathCondition")(a > 5)){ + + +var gen_dummy_int: Int +{ + var gen_xs: Seq[Ref] + @irrelevant("Explicit") + inhale |gen_xs| > 2 + @irrelevant("Explicit") + inhale forall gen_x: Ref :: gen_x in gen_xs ==> acc(gen_x.f) + @irrelevant("Explicit") + inhale forall gen_x: Ref :: gen_x in gen_xs ==> gen_x.f < 0 + @irrelevant("Implicit") + gen_xs[0].f := gen_xs[1].f + a - res + @irrelevant("Implicit") + res := gen_xs[0].f + gen_xs[1].f +} + + @testAssertion("Implicit") + res := unfolding implPred(a, x) in x.f + }else{ + @irrelevant("Implicit") + res := a + } +} diff --git a/src/test/resources/dependencyAnalysisTests/minimalExamples-precision/domains.vpr b/src/test/resources/dependencyAnalysisTests/minimalExamples-precision/domains.vpr new file mode 100644 index 000000000..1342ec134 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/minimalExamples-precision/domains.vpr @@ -0,0 +1,51 @@ +field f: Int +field val: Int +define access(a) + (forall j: Int :: 0 <= j && j < len(a) ==> acc(slot(a,j).val)) + + +domain IArray { + function slot(a: IArray, i: Int): Ref + function len(a: IArray): Int + function first(r: Ref): IArray + function second(r: Ref): Int + + + axiom all_diff { + forall a: IArray, i: Int :: { slot(a,i) } + first(slot(a,i)) == a && second(slot(a,i)) == i + } + + axiom len_nonneg { + forall a: IArray :: { len(a) } + len(a) >= 0 + } +} + +function gen_add_positive(a: Int, b: Int): Int + requires a >= 0 && b >= 0 + ensures result == a + b + ensures result >= 0 + +method domain2a() // assert +{ + var a: IArray + @dependency("Explicit") + inhale len(a) == 3 + + @dependency("Explicit") + inhale access(a) + + +var gen_dummy_int: Int +{ + var gen_i: Int + @irrelevant("Explicit") + inhale gen_i > 0 // dependency via the assertion + @irrelevant() + assert 0 0 +} + + @testAssertion("Implicit") + slot(a,0).val := 0 +} diff --git a/src/test/resources/dependencyAnalysisTests/minimalExamples-precision/infeasibility.vpr b/src/test/resources/dependencyAnalysisTests/minimalExamples-precision/infeasibility.vpr new file mode 100644 index 000000000..3d031f49a --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/minimalExamples-precision/infeasibility.vpr @@ -0,0 +1,56 @@ +field f: Int + +predicate gen_confuse_with_impl(i: Int, x: Ref){ + i >= 0 ==> acc(x.f) +} +function gen_add_positive(a: Int, b: Int): Int + requires a >= 0 && b >= 0 + ensures result == a + b + ensures result >= 0 + +method branch1(){ + var x: Int, y: Int + + x := 10 + + inhale y > 0 + + var gen_dummy_int: Int + var gen_x: Ref + inhale acc(gen_x.f) + + fold gen_confuse_with_impl(y, gen_x) + unfold gen_confuse_with_impl(y, gen_x) // causes branching, one branch is infeasible, reported as dependency + + assert x > 0 +} + +method branch2(){ + var x: Int, y: Int + + x := 10 + + // inhale y > 0 // ONLY DIFFERENCE!!! + + var gen_dummy_int: Int + var gen_x: Ref + inhale acc(gen_x.f) + + inhale gen_confuse_with_impl(y, gen_x) + unfold gen_confuse_with_impl(y, gen_x) // causes branching, all branches feasible, not reported as dependency + + assert x > 0 +} + +method branch_implication(){ + var x: Int, y: Int + + if(x > 0){ + inhale y > 0 + }else{ + inhale y > 0 + } + + assert y > 0 + assert x > 0 ==> y > 0 // precise +} diff --git a/src/test/resources/dependencyAnalysisTests/new/infeasibilityBug.vpr b/src/test/resources/dependencyAnalysisTests/new/infeasibilityBug.vpr deleted file mode 100644 index e3ff14876..000000000 --- a/src/test/resources/dependencyAnalysisTests/new/infeasibilityBug.vpr +++ /dev/null @@ -1,28 +0,0 @@ -field Value: Int -field Left: Ref -field Right: Ref - -predicate tree(self: Ref) { - acc(self.Left) && acc(self.Value) && acc(self.Right) && - (self.Left != null ==> tree(self.Left)) && - (self.Right != null ==> tree(self.Right)) -} - - -function Contains(self: Ref, v: Int): Bool - requires self != null ==> tree(self) -{ - self != null && (unfolding tree(self) in (Contains(self.Left, v) || Contains(self.Right, v))) -} - - -method Insert(self: Ref, v: Int) returns (res: Ref) -{ - inhale acc(self.Left) && acc(self.Value) && acc(self.Right) - inhale self.Left == null - inhale tree(self.Right) - inhale Contains(self.Right, v) // self.Right := Insert(self.Right, v) - fold tree(self) - assert self != null - assert Contains(self, v) -} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/precisionTests/results/result_table.out b/src/test/resources/dependencyAnalysisTests/precisionTests/results/result_table.out index 1b0952aa4..cafae621d 100644 --- a/src/test/resources/dependencyAnalysisTests/precisionTests/results/result_table.out +++ b/src/test/resources/dependencyAnalysisTests/precisionTests/results/result_table.out @@ -1,14 +1,13 @@ assert assignment_field assignment_pure baseline branch branch_div0 branch_function disjoint_ref disjoint_ref_disjoint_field disjoint_ref_disjoint_field_quantified disjoint_ref_quantified fold_unfold_with_implication function_add goto_pure infeasible_branch inhale_invariant magic_wand method_call_impure method_call_pure nested_branch quasihavoc quasihavoc_disjoint_ref quasihavoc_disjoint_ref_disjoint_field while_perm while_pure | max min avg -dependencyAnalysisTests/precisionTests/branches 1.000 1.000 1.000 1.000 0.806 0.824 0.739 1.000 1.000 1.000 1.000 0.722 1.000 1.000 0.824 1.000 1.000 1.000 1.000 0.750 1.000 1.000 1.000 1.000 1.000 | 1.000 0.722 0.947 -dependencyAnalysisTests/precisionTests/domain 0.964 1.000 1.000 1.000 1.000 0.971 0.956 1.000 1.000 1.000 1.000 0.782 1.000 1.000 0.853 0.952 1.000 1.000 1.000 0.971 0.948 1.000 1.000 1.000 1.000 | 1.000 0.782 0.976 -dependencyAnalysisTests/precisionTests/function-call 1.000 1.000 1.000 1.000 1.000 0.912 0.849 1.000 1.000 1.000 1.000 0.839 1.000 1.000 0.912 1.000 1.000 1.000 1.000 0.912 1.000 1.000 1.000 1.000 1.000 | 1.000 0.839 0.977 -dependencyAnalysisTests/precisionTests/inhaleExhale 0.786 0.810 0.810 0.810 0.786 0.762 0.748 0.798 0.810 0.810 0.721 0.690 0.810 0.810 0.750 0.762 0.782 0.798 0.833 0.748 0.750 0.714 0.810 NaN NaN | 0.833 0.690 0.778 -dependencyAnalysisTests/precisionTests/loops 0.964 0.964 0.964 0.964 0.932 0.872 0.827 0.964 0.964 0.964 0.964 0.737 0.964 NaN 0.819 0.964 0.964 0.964 0.964 0.839 0.964 0.964 0.964 0.964 NaN | 0.964 0.737 0.931 -dependencyAnalysisTests/precisionTests/magicWands 1.000 1.000 1.000 1.000 0.955 0.955 0.927 0.970 1.000 1.000 0.955 0.713 1.000 1.000 0.859 1.000 0.939 0.970 1.000 0.927 0.951 0.955 1.000 NaN NaN | 1.000 0.713 0.960 -dependencyAnalysisTests/precisionTests/method-call 1.000 1.000 1.000 1.000 1.000 0.917 0.860 1.000 1.000 1.000 1.000 0.851 1.000 1.000 0.917 1.000 1.000 1.000 1.000 0.917 1.000 1.000 1.000 1.000 1.000 | 1.000 0.851 0.978 -dependencyAnalysisTests/precisionTests/misc 1.000 1.000 1.000 1.000 0.917 0.861 0.789 1.000 1.000 1.000 0.889 0.900 1.000 0.917 0.861 1.000 1.000 1.000 1.000 0.833 0.944 0.917 1.000 0.917 0.917 | 1.000 0.789 0.946 -dependencyAnalysisTests/precisionTests/permWildcard 1.000 NaN 1.000 1.000 0.914 0.952 0.905 1.000 1.000 1.000 0.712 0.714 1.000 1.000 0.810 1.000 1.000 0.952 1.000 0.905 NaN 0.712 1.000 NaN NaN | 1.000 0.712 0.932 -dependencyAnalysisTests/precisionTests/permissions 0.985 0.985 0.985 0.985 0.985 0.955 0.955 0.985 0.985 0.985 0.775 0.550 0.985 0.985 0.718 0.985 0.923 0.946 0.985 0.955 0.671 0.603 0.985 NaN NaN | 0.985 0.550 0.907 -dependencyAnalysisTests/precisionTests/predicates 0.850 1.000 1.000 1.000 0.908 0.782 0.705 1.000 1.000 1.000 0.969 0.602 1.000 0.985 0.764 0.795 1.000 1.000 1.000 0.741 0.985 0.974 1.000 NaN NaN | 1.000 0.602 0.916 -dependencyAnalysisTests/precisionTests/quantifiedPermissions 0.911 0.863 0.976 0.976 0.811 0.799 0.743 0.857 0.857 0.881 0.712 0.569 0.810 NaN 0.674 0.905 0.895 0.876 NaN 0.781 0.695 0.626 1.000 NaN NaN | 1.000 0.569 0.820 -dependencyAnalysisTests/precisionTests/quasihavoc 0.845 0.845 0.845 0.845 0.845 0.845 0.845 0.845 0.845 0.845 0.845 0.845 0.845 0.845 0.845 0.845 0.845 0.845 0.845 0.845 0.845 0.845 0.845 0.845 0.845 | 0.845 0.845 0.845 +dependencyAnalysisTests/precisionTests/branches 1.000 1.000 1.000 1.000 1.000 0.824 0.710 1.000 1.000 1.000 1.000 0.722 1.000 1.000 0.824 1.000 1.000 1.000 1.000 0.917 1.000 1.000 1.000 1.000 1.000 | 1.000 0.710 0.960 +dependencyAnalysisTests/precisionTests/domain 0.964 1.000 1.000 1.000 1.000 0.971 0.949 1.000 1.000 1.000 1.000 0.782 1.000 1.000 0.853 0.952 1.000 1.000 1.000 1.000 0.948 1.000 1.000 1.000 1.000 | 1.000 0.782 0.977 +dependencyAnalysisTests/precisionTests/function-call 1.000 1.000 1.000 1.000 1.000 0.912 1.000 1.000 1.000 1.000 1.000 0.839 1.000 1.000 0.912 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 | 1.000 0.839 0.987 +dependencyAnalysisTests/precisionTests/inhaleExhale 0.964 1.000 1.000 1.000 1.000 0.929 0.929 0.964 1.000 1.000 0.786 0.786 1.000 1.000 0.893 0.952 0.918 0.964 1.000 1.000 0.869 0.829 1.000 NaN NaN | 1.000 0.786 0.947 +dependencyAnalysisTests/precisionTests/loops 1.000 1.000 1.000 1.000 1.000 0.908 0.925 1.000 1.000 1.000 1.000 0.762 1.000 NaN 0.847 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 NaN | 1.000 0.762 0.976 +dependencyAnalysisTests/precisionTests/magicWands 1.000 1.000 1.000 1.000 1.000 0.955 1.000 0.970 1.000 1.000 0.955 0.713 1.000 1.000 0.859 1.000 0.939 0.970 1.000 1.000 0.951 0.955 1.000 NaN NaN | 1.000 0.713 0.968 +dependencyAnalysisTests/precisionTests/method-call 1.000 1.000 1.000 1.000 1.000 0.917 1.000 1.000 1.000 1.000 1.000 0.851 1.000 1.000 0.917 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 | 1.000 0.851 0.987 +dependencyAnalysisTests/precisionTests/misc 1.000 1.000 1.000 1.000 0.917 0.861 0.889 1.000 1.000 1.000 0.889 0.900 1.000 0.917 0.861 1.000 1.000 1.000 1.000 0.889 0.944 0.917 1.000 0.917 0.917 | 1.000 0.861 0.953 +dependencyAnalysisTests/precisionTests/permWildcard 1.000 NaN 1.000 1.000 1.000 0.952 0.898 1.000 1.000 1.000 0.748 0.714 1.000 1.000 0.810 1.000 1.000 0.952 1.000 1.000 NaN 0.712 1.000 NaN NaN | 1.000 0.712 0.942 +dependencyAnalysisTests/precisionTests/permissions 1.000 1.000 1.000 1.000 1.000 0.968 1.000 1.000 1.000 1.000 0.782 0.560 1.000 1.000 0.731 1.000 0.938 0.962 1.000 1.000 0.681 0.614 1.000 NaN NaN | 1.000 0.560 0.923 +dependencyAnalysisTests/precisionTests/predicates 0.850 1.000 1.000 1.000 0.985 0.782 0.865 1.000 1.000 1.000 0.969 0.602 1.000 0.985 0.764 0.795 1.000 1.000 1.000 0.911 0.985 0.974 1.000 NaN NaN | 1.000 0.602 0.933 +dependencyAnalysisTests/precisionTests/quantifiedPermissions 0.935 0.881 1.000 1.000 0.881 0.823 0.769 0.881 0.881 0.881 0.726 0.581 0.833 NaN 0.692 0.905 0.918 0.894 NaN 0.788 0.707 0.640 1.000 NaN NaN | 1.000 0.581 0.839 diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/inhaleExhale.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/inhaleExhale.vpr index c051d043d..ebacec6b4 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/inhaleExhale.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/inhaleExhale.vpr @@ -3,9 +3,7 @@ field f: Int method exhaleInhale(a: Ref) requires @dependency("Explicit")(acc(a.f)) { - @irrelevant("Implicit") exhale acc(a.f, 1/2) - @irrelevant("Explicit") inhale acc(a.f, 1/2) // $PrecisionTest: $READ_WRITE=a.f $ACC_INVARIANT=acc(a.f) @@ -14,7 +12,7 @@ method exhaleInhale(a: Ref) assert perm(a.f) == write } -method exhaleImprecision(){ +method exhale1(){ var x: Ref @dependency("Explicit") @@ -22,7 +20,6 @@ method exhaleImprecision(){ @dependency("Explicit") inhale x.f > 0 - @irrelevant("Implicit") exhale acc(x.f, 1/2) // $PrecisionTest: $READ_ONLY=x.f $INVARIANT=x.f>0 $ACC_INVARIANT=acc(x.f,1/2) @@ -31,7 +28,7 @@ method exhaleImprecision(){ assert x.f > 0 } -method inhaleImprecision(){ +method inhale1(){ var x: Ref @dependency("Explicit") @@ -39,7 +36,6 @@ method inhaleImprecision(){ @dependency("Explicit") inhale x.f > 0 - @irrelevant("Implicit") inhale acc(x.f, 1/2) // $PrecisionTest: $READ_ONLY=x.f $INVARIANT=x.f>0 $ACC_INVARIANT=acc(x.f) diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/loops.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/loops.vpr index acac71194..24097a3e6 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/loops.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/loops.vpr @@ -5,7 +5,7 @@ method loop1(){ var res: Int @dependency("Implicit") res := 0 - @irrelevant("Implicit") + // @irrelevant("Implicit") i := 10 while(@dependency("PathCondition")(i > 0)) invariant @irrelevant("LoopInvariant")(i <= 10) @@ -14,7 +14,7 @@ method loop1(){ { @dependency("Implicit") res := res + i - @irrelevant("Implicit") + // @irrelevant("Implicit") i := i - 1 } diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr index c1a5c5f33..01d6d5b74 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr @@ -135,7 +135,7 @@ method packageExhale(x: Ref) @testAssertion("Explicit") - assert perm(x.f) == 1/2 + assert perm(x.f) >= 1/2 } method applying1() diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/permWildcard.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/permWildcard.vpr index b208441c3..63b0ae640 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/permWildcard.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/permWildcard.vpr @@ -4,7 +4,7 @@ field g: Int method wildcardPerm(x: Ref, y: Ref) requires @dependency("Explicit")(acc(x.f, wildcard)) requires @irrelevant("Explicit")(acc(y.f, wildcard)) - requires @irrelevant("Explicit")(x != y) + requires x != y { // $PrecisionTest: $READ_ONLY=x.f,y.f $ACC_INVARIANT=acc(x.f,wildcard)&&acc(y.f,wildcard) diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr index f26fe6cb6..83b303723 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr @@ -96,7 +96,6 @@ method permAmount1(x: Ref, p: Perm) // $PrecisionTest: $READ_ONLY=x.f $ACC_INVARIANT=p>1/2&&acc(x.f,p/2) $INVARIANT=x.f>-10 - @irrelevant("Implicit") exhale acc(x.f, 1/2) @testAssertion("Explicit") assert x.f > 0 diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr index 4d713976e..4967783b7 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr @@ -4,7 +4,6 @@ method quantifiedPerm1(xs: Seq[Ref]) { assume @dependency("Explicit")(|xs| > 5) inhale @dependency("Explicit")(forall x: Ref :: x in xs ==> acc(x.f)) - @irrelevant("Explicit") inhale xs[0] != xs[1] // $PrecisionTest: $READ_WRITE=xs[0].f,xs[1].f $INVARIANT=|xs|>0 $ACC_INVARIANT=forall$_$x:Ref::x$_$in$_$xs$_$==>$_$acc(x.f) @@ -17,7 +16,6 @@ method quantifiedPerm2(xs: Seq[Ref]) { assume @dependency("Explicit")(|xs| > 5) inhale @dependency("Explicit")(forall x: Ref :: x in xs ==> acc(x.f)) - @irrelevant("Explicit") inhale xs[0] != xs[4] // $PrecisionTest: $READ_WRITE=xs[0].f,xs[4].f $INVARIANT=|xs|>4 $ACC_INVARIANT=forall$_$x:Ref::x$_$in$_$xs$_$==>$_$acc(x.f) @@ -39,11 +37,9 @@ method quantifiedPerm3(xs: Seq[Ref], y: Ref) { method quantifiedExhalePartiallyTest(xs: Seq[Ref]) { var res: Int - @irrelevant("Explicit") assume |xs| > 5 inhale @dependency("Explicit")(forall x: Ref :: x in xs ==> acc(x.f)) - @irrelevant("Explicit") inhale xs[0] != xs[4] // $PrecisionTest: $READ_WRITE=xs[0].f,xs[4].f $INVARIANT=|xs|>0 $ACC_INVARIANT=forall$_$x:Ref::x$_$in$_$xs$_$==>$_$acc(x.f) @@ -57,7 +53,6 @@ method quantifiedExhalePartially(xs: Seq[Ref]) { assume @dependency("Explicit")(|xs| > 5) inhale @dependency("Explicit")(forall x: Ref :: x in xs ==> acc(x.f)) - @irrelevant("Explicit") exhale forall x: Ref :: x in xs ==> acc(x.f, 1/2) // $PrecisionTest: $READ_WRITE=res $READ_ONLY=xs[1].f,xs[0].f,xs[2].f $INVARIANT=|xs|>0 $ACC_INVARIANT=forall$_$x:Ref::x$_$in$_$xs$_$==>$_$acc(x.f,1/2) @@ -67,10 +62,9 @@ method quantifiedExhalePartially(xs: Seq[Ref]) { } method quantifiedExhaleFully(xs: Seq[Ref]) { - assume @irrelevant("Explicit")(|xs| > 5) + assume |xs| > 5 inhale @dependency("Explicit")(forall x: Ref :: x in xs ==> acc(x.f)) - @irrelevant("Explicit") inhale xs[0] != xs[2] // $PrecisionTest: $READ_WRITE=xs[0].f,xs[2].f $READ_ONLY=xs[1].f $INVARIANT=|xs|>0 $ACC_INVARIANT=|xs|>5&&forall$_$x:Ref::x$_$in$_$xs$_$==>$_$acc(x.f) @@ -85,7 +79,6 @@ method quantifiedPermWrite1(xs: Seq[Ref]) { @dependency("Explicit") inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) - @irrelevant("Explicit") inhale xs[0] != xs[2] // $PrecisionTest: $READ_WRITE=xs[2].f $READ_ONLY=xs[0].f,xs[1].f $INVARIANT=xs[0].f>0 $ACC_INVARIANT=|xs|>5&&(forall$_$x:Ref::x$_$in$_$xs$_$==>$_$acc(x.f)) @@ -103,7 +96,6 @@ method quantifiedPermWrite2(xs: Seq[Ref]) { @dependency("Implicit") xs[0].f := 0 - @irrelevant("Explicit") inhale xs[0] != xs[2] // $PrecisionTest: $READ_ONLY=xs[0].f,xs[1].f $INVARIANT=xs[0].f<3 $ACC_INVARIANT=|xs|>5&&(forall$_$x:Ref::x$_$in$_$xs$_$==>$_$acc(x.f,1/4)) @@ -129,14 +121,13 @@ method quantifiedPermWrite3(xs: Seq[Ref]) { method quantifiedPermWrite4(xs: Seq[Ref], ys: Seq[Ref]) requires @dependency("Explicit")(|xs| > 5) - requires @irrelevant("Explicit")(|ys| > 3) + requires |ys| > 3 { @dependency("Explicit") inhale forall x: Ref :: x in xs ==> acc(x.f) @irrelevant("Explicit") inhale forall y: Ref :: y in ys ==> acc(y.f) - @irrelevant("Explicit") inhale ys[0] != ys[1] // $PrecisionTest: $READ_WRITE=ys[0].f,ys[1].f $READ_ONLY=xs[0].f,xs[1].f $INVARIANT=|ys|>3 $ACC_INVARIANT=|xs|>5&&|ys|>3&&(forall$_$x:Ref::x$_$in$_$xs$_$==>$_$acc(x.f))&&(forall$_$y:Ref::y$_$in$_$ys$_$==>$_$acc(y.f)) From cd91caac1a2a5924dbe8155c0567f69cf9b5ee4e Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 18 Aug 2025 10:27:19 +0200 Subject: [PATCH 239/474] separate soundness tests and precision benchmark --- .../precision_benchmark_plotter.py | 17 +- .../benchmark_scripts/snippets.txt | 283 ++++++++++----- .../precisionTests/results/result_table.out | 26 +- ...AssumptionAnalysisPrecisionBenchmark.scala | 114 ++++++ .../AssumptionAnalysisTestFramework.scala | 240 ++++++++++++ src/test/scala/AssumptionAnalysisTests.scala | 341 +----------------- 6 files changed, 571 insertions(+), 450 deletions(-) create mode 100644 src/test/scala/AssumptionAnalysisPrecisionBenchmark.scala create mode 100644 src/test/scala/AssumptionAnalysisTestFramework.scala diff --git a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_benchmark_plotter.py b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_benchmark_plotter.py index 7662da5bf..323976e5b 100644 --- a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_benchmark_plotter.py +++ b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_benchmark_plotter.py @@ -35,17 +35,18 @@ def read_results_file(result_file_path: str) -> dict[tuple[str, str], list[tuple def build_table(out_file_path: str, results: dict[tuple[str, str], list[tuple[str, float]]]): f = open(out_file_path, mode="w") header = sorted(set([interference_name for (_, interference_name) in results.keys()])) - header_summary_columns = ["|", "max", "min", "avg"] + header_summary_columns = [] # /["|", "max", "min", "avg"] base_test_names = sorted(set([base_name.strip() for (base_name, _) in results.keys()])) column_1_width = max([len(h) for h in base_test_names]) + 4 - column_widths = [len(h + " ") for h in (header + header_summary_columns)] - f.write("".ljust(column_1_width) + " " + " ".join(header + header_summary_columns)) + column_widths = [len(h + " ") for h in (header + header_summary_columns)] + f.write("".ljust(column_1_width) + " & " + " & ".join(header + header_summary_columns)) f.write("\n") for base_test in base_test_names: f.write(base_test.ljust(column_1_width)) current_test_results = [] for idx, h in enumerate(header): + f.write(" & ") if not (base_test, h) in results.keys(): f.write("NaN".center(column_widths[idx])) continue @@ -55,11 +56,11 @@ def build_table(out_file_path: str, results: dict[tuple[str, str], list[tuple[st f.write(f"{avg:.3f}".center(column_widths[idx])) # print summary - f.write("|".center(column_widths[idx+1])) - f.write(f"{max(current_test_results):.3f}".center(column_widths[idx+2])) - f.write(f"{min(current_test_results):.3f}".center(column_widths[idx+3])) - f.write(f"{sum(current_test_results)/len(current_test_results):.3f}".center(column_widths[idx+4])) - f.write("\n") + # f.write("|".center(column_widths[idx+1])) + # f.write(f"{max(current_test_results):.3f}".center(column_widths[idx+2])) + # f.write(f"{min(current_test_results):.3f}".center(column_widths[idx+3])) + # f.write(f"{sum(current_test_results)/len(current_test_results):.3f}".center(column_widths[idx+4])) + f.write("\\\\ \n") result_file_name = input("file name: ") raw_results = read_results_file("silicon\\src\\test\\resources\\dependencyAnalysisTests\\precisionTests\\results\\" + result_file_name) diff --git a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/snippets.txt b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/snippets.txt index af68f8369..b53e6b354 100644 --- a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/snippets.txt +++ b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/snippets.txt @@ -8,8 +8,16 @@ predicate gen_confuse_with_impl(i: Int, x: Ref){ i >= 0 ==> acc(x.f) } +predicate gen_confuse_gen_field(i: Int, x: Ref){ + i >= 0 ==> acc(x.gen_f) +} + function gen_add(a: Int, b: Int): Int - ensures result == a + b +{ a + b } + +function gen_add_impure(x: Ref, b: Int): Int + requires acc(x.f) + ensures result == x.f + b function gen_add_positive(a: Int, b: Int): Int requires a >= 0 && b >= 0 @@ -24,51 +32,107 @@ method incr(a: Ref) a.f := a.f + 1 } +method incr_gen_f(a: Ref) + requires acc(a.gen_f) + ensures acc(a.gen_f) + ensures a.gen_f == old(a.gen_f) + 1 +{ + a.gen_f := a.gen_f + 1 +} + method gen_incr_pure(a: Int) returns (res: Int) ensures res == a + 1 { res := a + 1 } -SNIPPET baseline$=${} +SNIPPET 00_baseline$=${} + +SNIPPET 01_inhale_invariant$=${ + var gen_i: Int + @irrelevant("Explicit") + inhale $INVARIANT && gen_i > 0 +} -SNIPPET assignment_pure$=${ +SNIPPET 02_assert_invariant$=${ + var gen_i: Int + @irrelevant("Explicit") + inhale gen_i > 0 + @irrelevant() + assert $INVARIANT && gen_i > 0 +} + +SNIPPET 03_assignment_pure$=${ @irrelevant("Implicit") $RW_INT_PURE_0 := $RO_INT_0 + $RO_INT_1 } -SNIPPET assignment_field$=${ +SNIPPET 04_assignment_field$=${ @irrelevant("Implicit") $RW_INT_FIELD_0 := $RO_INT_0 + $RO_INT_1 } -SNIPPET branch_div0$=${ - if(@irrelevant("PathCondition")($RO_INT_0 > 0)){ - @irrelevant("Implicit") - $RW_INT_0 := $RO_INT_1 / $RO_INT_0 - }else{ - @irrelevant("Implicit") - $RW_INT_0 := $RO_INT_1 * $RO_INT_0 - } +SNIPPET 05_assignment_disjoint_ref$=${ + var gen_x: Ref + @irrelevant("Explicit") + inhale acc(gen_x.f) + + @irrelevant("Implicit") + gen_x.f := $RO_INT_0 + $RO_INT_1 + + @irrelevant("Implicit") + $RW_INT_0 := gen_x.f } -SNIPPET inhale_invariant$=${ - var gen_i: Int +SNIPPET 06_assignment_disjoint_ref_disjoint_field$=${ + var gen_x: Ref @irrelevant("Explicit") - inhale $INVARIANT && gen_i > 0 + inhale acc(gen_x.gen_f) + + @irrelevant("Implicit") + gen_x.gen_f := $RO_INT_0 + $RO_INT_1 + + @irrelevant("Implicit") + $RW_INT_0 := gen_x.gen_f } -SNIPPET assert$=${ - var gen_i: Int + +SNIPPET 07_assignment_disjoint_ref_quantified$=${ + var gen_xs: Seq[Ref] @irrelevant("Explicit") - inhale gen_i > 0 - @irrelevant() - assert $INVARIANT && gen_i > 0 + inhale |gen_xs| > 2 + @irrelevant("Explicit") + inhale forall gen_x: Ref :: gen_x in gen_xs ==> acc(gen_x.f) + @irrelevant("Explicit") + inhale forall gen_x: Ref :: gen_x in gen_xs ==> gen_x.f < 0 + + @irrelevant("Implicit") + gen_xs[0].f := gen_xs[1].f + $RO_INT_0 - $RO_INT_1 + + @irrelevant("Implicit") + $RW_INT_0 := gen_xs[0].f + gen_xs[1].f +} + + +SNIPPET 08_assignment_disjoint_ref_disjoint_field_quantified$=${ + var gen_xs: Seq[Ref] + @irrelevant("Explicit") + inhale |gen_xs| > 2 + @irrelevant("Explicit") + inhale forall gen_x: Ref :: gen_x in gen_xs ==> acc(gen_x.gen_f) + @irrelevant("Explicit") + inhale forall gen_x: Ref :: gen_x in gen_xs ==> gen_x.gen_f < 0 + + @irrelevant("Implicit") + gen_xs[0].gen_f := gen_xs[1].gen_f + $RO_INT_0 - $RO_INT_1 + + @irrelevant("Implicit") + $RW_INT_0 := gen_xs[0].gen_f + gen_xs[1].gen_f } // fold-unfold with implication -SNIPPET fold_unfold_with_implication$=${ +SNIPPET 09_fold_unfold_with_implication$=${ var gen_x: Ref @irrelevant("Explicit") inhale acc(gen_x.f) @@ -79,7 +143,7 @@ SNIPPET fold_unfold_with_implication$=${ } // function call -SNIPPET function_add$=${ +SNIPPET 10_function_add_pure$=${ @irrelevant("Implicit") $RW_INT_0 := gen_add($RO_INT_0, $RO_INT_1) @irrelevant("Implicit") @@ -87,20 +151,28 @@ SNIPPET function_add$=${ } -SNIPPET method_call_impure$=${ +SNIPPET 11_function_add_impure$=${ var gen_ref_0: Ref @irrelevant("Implicit") gen_ref_0 := new(f) @irrelevant("Implicit") - incr(gen_ref_0) + gen_ref_0.f := gen_add_impure(gen_ref_0, $RO_INT_0) } -SNIPPET method_call_pure$=${ +SNIPPET 12_method_call_pure$=${ @irrelevant("Implicit") $RW_INT_0 := gen_incr_pure($RO_INT_0) } -SNIPPET branch$=${ +SNIPPET 13_method_call_impure$=${ + var gen_ref_0: Ref + @irrelevant("Implicit") + gen_ref_0 := new(f) + @irrelevant("Implicit") + incr(gen_ref_0) +} + +SNIPPET 14_branch$=${ var gen_i: Int if(@irrelevant("PathCondition")($RO_INT_0 > gen_i)){ @irrelevant("Implicit") @@ -111,7 +183,7 @@ SNIPPET branch$=${ } } -SNIPPET nested_branch$=${ +SNIPPET 15_branch_nested$=${ var gen_c: Int if(@irrelevant("PathCondition")($RO_INT_0 > gen_c && $RO_INT_1 > gen_c)){ var gen_i: Int @@ -131,7 +203,7 @@ SNIPPET nested_branch$=${ } } -SNIPPET infeasible_branch$=${ +SNIPPET 16_branch_infeasible$=${ if(@irrelevant("PathCondition")($INVARIANT)){ @irrelevant("Implicit") $RW_INT_0 := 10 @@ -145,8 +217,7 @@ SNIPPET infeasible_branch$=${ } } -// branches & function calls -SNIPPET branch_function$=${ +SNIPPET 17_branch_function$=${ var gen_c: Int @irrelevant("Explicit") inhale gen_c > 0 @@ -168,65 +239,7 @@ SNIPPET branch_function$=${ } -SNIPPET disjoint_ref$=${ - var gen_x: Ref - @irrelevant("Explicit") - inhale acc(gen_x.f) - - @irrelevant("Implicit") - gen_x.f := $RO_INT_0 + $RO_INT_1 - - @irrelevant("Implicit") - $RW_INT_0 := gen_x.f -} - -SNIPPET disjoint_ref_disjoint_field$=${ - var gen_x: Ref - @irrelevant("Explicit") - inhale acc(gen_x.gen_f) - - @irrelevant("Implicit") - gen_x.gen_f := $RO_INT_0 + $RO_INT_1 - - @irrelevant("Implicit") - $RW_INT_0 := gen_x.gen_f -} - - -SNIPPET disjoint_ref_quantified$=${ - var gen_xs: Seq[Ref] - @irrelevant("Explicit") - inhale |gen_xs| > 2 - @irrelevant("Explicit") - inhale forall gen_x: Ref :: gen_x in gen_xs ==> acc(gen_x.f) - @irrelevant("Explicit") - inhale forall gen_x: Ref :: gen_x in gen_xs ==> gen_x.f < 0 - - @irrelevant("Implicit") - gen_xs[0].f := gen_xs[1].f + $RO_INT_0 - $RO_INT_1 - - @irrelevant("Implicit") - $RW_INT_0 := gen_xs[0].f + gen_xs[1].f -} - - -SNIPPET disjoint_ref_disjoint_field_quantified$=${ - var gen_xs: Seq[Ref] - @irrelevant("Explicit") - inhale |gen_xs| > 2 - @irrelevant("Explicit") - inhale forall gen_x: Ref :: gen_x in gen_xs ==> acc(gen_x.gen_f) - @irrelevant("Explicit") - inhale forall gen_x: Ref :: gen_x in gen_xs ==> gen_x.gen_f < 0 - - @irrelevant("Implicit") - gen_xs[0].gen_f := gen_xs[1].gen_f + $RO_INT_0 - $RO_INT_1 - - @irrelevant("Implicit") - $RW_INT_0 := gen_xs[0].gen_f + gen_xs[1].gen_f -} - -SNIPPET while_pure$=${ +SNIPPET 18_while_pure$=${ var gen_start: Int, gen_c: Int @irrelevant("Explicit") inhale gen_c >= 0 @@ -247,7 +260,7 @@ SNIPPET while_pure$=${ } } -SNIPPET while_perm$=${ +SNIPPET 19_while_perm$=${ var gen_start: Int, gen_c: Int @irrelevant("Explicit") inhale gen_c >= 0 @@ -268,7 +281,7 @@ SNIPPET while_perm$=${ } } -SNIPPET magic_wand$=${ +SNIPPET 20_magic_wand$=${ var gen_i: Int var gen_x: Ref @irrelevant("Explicit") @@ -282,12 +295,12 @@ SNIPPET magic_wand$=${ } -SNIPPET quasihavoc$=${ +SNIPPET 21_quasihavoc$=${ @irrelevant("Implicit") quasihavoc $RW_INT_FIELD_0 } -SNIPPET quasihavoc_disjoint_ref$=${ +SNIPPET 22_quasihavoc_disjoint_ref$=${ var gen_x: Ref @irrelevant("Implicit") gen_x := new(f) @@ -295,7 +308,7 @@ SNIPPET quasihavoc_disjoint_ref$=${ quasihavoc gen_x.f } -SNIPPET quasihavoc_disjoint_ref_disjoint_field$=${ +SNIPPET 23_quasihavoc_disjoint_ref_disjoint_field$=${ var gen_x: Ref @irrelevant("Implicit") gen_x := new(gen_f) @@ -304,7 +317,7 @@ SNIPPET quasihavoc_disjoint_ref_disjoint_field$=${ } -SNIPPET goto_pure$=${ +SNIPPET 24_goto_pure$=${ @irrelevant("Implicit") $RW_INT_PURE_0 := 0 var gen_n: Int @@ -326,3 +339,83 @@ SNIPPET goto_pure$=${ @irrelevant() assert $RW_INT_PURE_0 == gen_n * $RO_INT_PURE_0 } + +SNIPPET 25_unrelated_sync_points$=${ + var gen_i: Int, gen_j: Int, gen_n: Int, gen_p: Int + var gen_x: Ref + var gen_xs: Seq[Ref] + @irrelevant("Implicit") + gen_x := new(gen_f) + @irrelevant("Explicit") + inhale gen_x.gen_f >= 0 + + @irrelevant("Explicit") + inhale |gen_xs| > 2 + @irrelevant("Explicit") + inhale forall gen_xi: Ref :: gen_xi in gen_xs ==> acc(gen_xi.gen_f) + @irrelevant("Explicit") + inhale forall gen_xi: Ref :: gen_xi in gen_xs ==> gen_xi.gen_f < 0 + + @irrelevant("Explicit") + inhale gen_i > 0 + @irrelevant("Explicit") + inhale 0 <= gen_n && gen_n < 100 + + @irrelevant("Rewrite") + fold gen_confuse_gen_field(gen_p, gen_x) + @irrelevant("Implicit") + gen_i := gen_add_positive(gen_i, gen_n) + @irrelevant("Rewrite") + unfold gen_confuse_gen_field(gen_p, gen_x) + + while(@irrelevant("PathCondition")(gen_n > 0)) + invariant @irrelevant("Invariant")(acc(gen_x.gen_f)) + invariant @irrelevant("Invariant")(forall gen_xi: Ref :: gen_xi in gen_xs ==> acc(gen_xi.gen_f, 1/2)) + invariant @irrelevant("Invariant")(gen_x.gen_f >= 0) + invariant @irrelevant("Invariant")(gen_n >= 0) + { + @irrelevant("Implicit") + gen_x.gen_f := gen_x.gen_f + gen_n + @irrelevant("Implicit") + gen_j := gen_xs[1].gen_f + } + + @irrelevant("Implicit") + gen_j := gen_add(gen_j, gen_i) + + @irrelevant("Implicit") + incr_gen_f(gen_x) + @irrelevant("Implicit") + incr_gen_f(gen_xs[2]) + @irrelevant("Rewrite") + package gen_i >= 0 --* acc(gen_x.gen_f) + @irrelevant("Rewrite") + package gen_i >= 0 --* acc(gen_xs[0].gen_f) + + if(@irrelevant("PathCondition")(gen_j < 0)){ + @irrelevant("Implicit") + gen_j := -gen_j + } + + @irrelevant() + assert gen_j >= 0 + + @irrelevant("Rewrite") + apply gen_i >= 0 --* acc(gen_x.gen_f) + @irrelevant("Rewrite") + apply gen_i >= 0 --* acc(gen_xs[0].gen_f) + + @irrelevant("Implicit") + gen_x.gen_f := gen_i + gen_n + + @irrelevant("Implicit") + gen_xs[0].gen_f := 10 + + @irrelevant("Implicit") + gen_xs[0].gen_f := gen_xs[1].gen_f + gen_n + + @irrelevant() + assert gen_n == 0 + @irrelevant() + assert gen_xs[0] != gen_xs[1] && gen_xs[1] != gen_xs[2] ==> gen_xs[0].gen_f < 0 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/precisionTests/results/result_table.out b/src/test/resources/dependencyAnalysisTests/precisionTests/results/result_table.out index cafae621d..018d18048 100644 --- a/src/test/resources/dependencyAnalysisTests/precisionTests/results/result_table.out +++ b/src/test/resources/dependencyAnalysisTests/precisionTests/results/result_table.out @@ -1,13 +1,13 @@ - assert assignment_field assignment_pure baseline branch branch_div0 branch_function disjoint_ref disjoint_ref_disjoint_field disjoint_ref_disjoint_field_quantified disjoint_ref_quantified fold_unfold_with_implication function_add goto_pure infeasible_branch inhale_invariant magic_wand method_call_impure method_call_pure nested_branch quasihavoc quasihavoc_disjoint_ref quasihavoc_disjoint_ref_disjoint_field while_perm while_pure | max min avg -dependencyAnalysisTests/precisionTests/branches 1.000 1.000 1.000 1.000 1.000 0.824 0.710 1.000 1.000 1.000 1.000 0.722 1.000 1.000 0.824 1.000 1.000 1.000 1.000 0.917 1.000 1.000 1.000 1.000 1.000 | 1.000 0.710 0.960 -dependencyAnalysisTests/precisionTests/domain 0.964 1.000 1.000 1.000 1.000 0.971 0.949 1.000 1.000 1.000 1.000 0.782 1.000 1.000 0.853 0.952 1.000 1.000 1.000 1.000 0.948 1.000 1.000 1.000 1.000 | 1.000 0.782 0.977 -dependencyAnalysisTests/precisionTests/function-call 1.000 1.000 1.000 1.000 1.000 0.912 1.000 1.000 1.000 1.000 1.000 0.839 1.000 1.000 0.912 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 | 1.000 0.839 0.987 -dependencyAnalysisTests/precisionTests/inhaleExhale 0.964 1.000 1.000 1.000 1.000 0.929 0.929 0.964 1.000 1.000 0.786 0.786 1.000 1.000 0.893 0.952 0.918 0.964 1.000 1.000 0.869 0.829 1.000 NaN NaN | 1.000 0.786 0.947 -dependencyAnalysisTests/precisionTests/loops 1.000 1.000 1.000 1.000 1.000 0.908 0.925 1.000 1.000 1.000 1.000 0.762 1.000 NaN 0.847 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 NaN | 1.000 0.762 0.976 -dependencyAnalysisTests/precisionTests/magicWands 1.000 1.000 1.000 1.000 1.000 0.955 1.000 0.970 1.000 1.000 0.955 0.713 1.000 1.000 0.859 1.000 0.939 0.970 1.000 1.000 0.951 0.955 1.000 NaN NaN | 1.000 0.713 0.968 -dependencyAnalysisTests/precisionTests/method-call 1.000 1.000 1.000 1.000 1.000 0.917 1.000 1.000 1.000 1.000 1.000 0.851 1.000 1.000 0.917 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 | 1.000 0.851 0.987 -dependencyAnalysisTests/precisionTests/misc 1.000 1.000 1.000 1.000 0.917 0.861 0.889 1.000 1.000 1.000 0.889 0.900 1.000 0.917 0.861 1.000 1.000 1.000 1.000 0.889 0.944 0.917 1.000 0.917 0.917 | 1.000 0.861 0.953 -dependencyAnalysisTests/precisionTests/permWildcard 1.000 NaN 1.000 1.000 1.000 0.952 0.898 1.000 1.000 1.000 0.748 0.714 1.000 1.000 0.810 1.000 1.000 0.952 1.000 1.000 NaN 0.712 1.000 NaN NaN | 1.000 0.712 0.942 -dependencyAnalysisTests/precisionTests/permissions 1.000 1.000 1.000 1.000 1.000 0.968 1.000 1.000 1.000 1.000 0.782 0.560 1.000 1.000 0.731 1.000 0.938 0.962 1.000 1.000 0.681 0.614 1.000 NaN NaN | 1.000 0.560 0.923 -dependencyAnalysisTests/precisionTests/predicates 0.850 1.000 1.000 1.000 0.985 0.782 0.865 1.000 1.000 1.000 0.969 0.602 1.000 0.985 0.764 0.795 1.000 1.000 1.000 0.911 0.985 0.974 1.000 NaN NaN | 1.000 0.602 0.933 -dependencyAnalysisTests/precisionTests/quantifiedPermissions 0.935 0.881 1.000 1.000 0.881 0.823 0.769 0.881 0.881 0.881 0.726 0.581 0.833 NaN 0.692 0.905 0.918 0.894 NaN 0.788 0.707 0.640 1.000 NaN NaN | 1.000 0.581 0.839 + & 00_baseline & 01_inhale_invariant & 02_assert_invariant & 03_assignment_pure & 04_assignment_field & 05_assignment_disjoint_ref & 06_assignment_disjoint_ref_disjoint_field & 07_assignment_disjoint_ref_quantified & 08_assignment_disjoint_ref_disjoint_field_quantified & 09_fold_unfold_with_implication & 10_function_add_pure & 11_function_add_impure & 12_method_call_impure & 13_method_call_pure & 14_branch & 15_branch_nested & 16_branch_infeasible & 17_branch_function & 18_while_pure & 19_while_perm & 20_magic_wand & 21_quasihavoc & 22_quasihavoc_disjoint_ref & 23_quasihavoc_disjoint_ref_disjoint_field & 24_goto_pure & 25_unrelated_sync_points +dependencyAnalysisTests/precisionTests/branches & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.777 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.932 & 0.871 & 0.762 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.777 \\ +dependencyAnalysisTests/precisionTests/domain & 1.000 & 0.963 & 0.972 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.831 & 0.989 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.886 & 0.960 & 1.000 & 1.000 & 1.000 & 0.960 & 1.000 & 1.000 & 1.000 & 0.831 \\ +dependencyAnalysisTests/precisionTests/function-call & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.866 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.927 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.851 \\ +dependencyAnalysisTests/precisionTests/inhaleExhale & 1.000 & 0.963 & 0.972 & 1.000 & 1.000 & 0.972 & 1.000 & 0.822 & 1.000 & 0.833 & 1.000 & 0.972 & 0.972 & 1.000 & 1.000 & 1.000 & 0.917 & 0.944 & NaN & NaN & 0.937 & 0.928 & 0.867 & 1.000 & 1.000 & 0.833 \\ +dependencyAnalysisTests/precisionTests/loops & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.817 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.891 & 0.940 & NaN & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & NaN & 0.830 \\ +dependencyAnalysisTests/precisionTests/magicWands & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.962 & 1.000 & 0.757 & 1.000 & 0.974 & 0.974 & 1.000 & 1.000 & 1.000 & 0.881 & 1.000 & NaN & NaN & 0.949 & 0.970 & 0.962 & 1.000 & 1.000 & 0.757 \\ +dependencyAnalysisTests/precisionTests/method-call & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.872 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.929 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.859 \\ +dependencyAnalysisTests/precisionTests/misc & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.917 & 1.000 & 0.925 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.958 & 1.000 & 1.000 & 1.000 & 1.000 & 0.958 & 0.958 & 1.000 & 1.000 & 0.925 \\ +dependencyAnalysisTests/precisionTests/permWildcard & 1.000 & 1.000 & 1.000 & 1.000 & NaN & 1.000 & 1.000 & 0.804 & 1.000 & 0.778 & 1.000 & 0.963 & 0.963 & 1.000 & 1.000 & 1.000 & 0.852 & 0.921 & NaN & NaN & 1.000 & NaN & 0.833 & 1.000 & 1.000 & 0.767 \\ +dependencyAnalysisTests/precisionTests/permissions & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.805 & 1.000 & 0.618 & 1.000 & 0.967 & 0.967 & 1.000 & 1.000 & 1.000 & 0.767 & 1.000 & NaN & NaN & 0.947 & 0.783 & 0.738 & 1.000 & 1.000 & 0.618 \\ +dependencyAnalysisTests/precisionTests/predicates & 1.000 & 0.822 & 0.870 & 1.000 & 1.000 & 1.000 & 1.000 & 0.973 & 1.000 & 0.655 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.923 & 0.796 & 0.883 & NaN & NaN & 1.000 & 0.987 & 0.987 & 1.000 & 1.000 & 0.666 \\ +dependencyAnalysisTests/precisionTests/quantifiedPermissions & 1.000 & 0.917 & 0.943 & 1.000 & 1.000 & 1.000 & 1.000 & 0.825 & 1.000 & 0.664 & 1.000 & 0.917 & 0.907 & NaN & 1.000 & 0.919 & 0.824 & 0.867 & NaN & NaN & 0.929 & 0.829 & 0.754 & 1.000 & NaN & 0.629 \\ diff --git a/src/test/scala/AssumptionAnalysisPrecisionBenchmark.scala b/src/test/scala/AssumptionAnalysisPrecisionBenchmark.scala new file mode 100644 index 000000000..48994df42 --- /dev/null +++ b/src/test/scala/AssumptionAnalysisPrecisionBenchmark.scala @@ -0,0 +1,114 @@ +package viper.silicon.tests + +import viper.silicon.assumptionAnalysis.{AssumptionAnalysisInterpreter, DependencyAnalysisReporter} +import viper.silver.ast.Program +import viper.silver.verifier +import viper.silver.verifier.VerificationResult + +import java.io.{File, PrintWriter} +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter + + +object AssumptionAnalysisPrecisionBenchmark extends AssumptionAnalysisTestFramework { + val ignores: Seq[String] = Seq.empty + + def main(args: Array[String]): Unit = { + val basePath = "src/test/resources/dependencyAnalysisTests/precisionTests/results" + val directory = new File(basePath) + directory.mkdir() + val now: LocalDateTime = LocalDateTime.now() + val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss") + val writer = new PrintWriter(s"$basePath/result_${now.format(formatter)}.out") + visitFiles("dependencyAnalysisTests/precisionTests", executePrecisionBenchmark(_, _, writer)) + writer.close() + } + + def executePrecisionBenchmark(filePrefix: String, + fileName: String, + writer: PrintWriter): Unit = { + resetFrontend() + try{ + val program: Program = tests.loadProgram(filePrefix + "/", fileName, frontend) + val result = frontend.verifier.verify(program) + if(result.isInstanceOf[verifier.Failure]) { + writer.println(f"Program $filePrefix/$fileName does not verify. Skip") + println(f"Program $filePrefix/$fileName does not verify. Skip.\n$result") + return + } + val assumptionAnalysisInterpreters = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].assumptionAnalysisInterpretersPerMember + val fullGraphInterpreter = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].joinedAssumptionAnalysisInterpreter + + + println(s"Precision Benchmark for $filePrefix - $fileName started...") + writer.println(s"$filePrefix - $fileName") + new AnnotatedPrecisionBenchmark(filePrefix + "/" + fileName, program, assumptionAnalysisInterpreters, fullGraphInterpreter.get, writer).execute() + writer.println() + writer.flush() + println(s"Precision Benchmark for $filePrefix - $fileName done.") + }catch{ + case t: Throwable => + writer.println("Failed. Skip") + println(s"Exception caught: ${t.getMessage}") + } + } + + class AnnotatedPrecisionBenchmark(fileName: String, program: Program, + assumptionAnalysisInterpreters: List[AssumptionAnalysisInterpreter], + fullGraphInterpreter: AssumptionAnalysisInterpreter, + writer: PrintWriter) extends AnnotatedTest(program, assumptionAnalysisInterpreters, true) { + override def execute(): Unit = { + if(!verifyTestSoundness()){ + writer.println(s"!!!!!!!!!!!\nFailed to verify soundness of precision test $fileName\n") + println(s"!!!!!!!!!!!\nFailed to verify soundness of precision test $fileName\n") + return + } + + assumptionAnalysisInterpreters foreach {a => + val prec = computePrecision(a) + writer.println(s"${a.getMember.map(_.name).getOrElse("unknown")}: $prec") + } + } + + protected def computePrecision(assumptionAnalysisInterpreter: AssumptionAnalysisInterpreter): Double = { + val assumptionNodes = getTestIrrelevantAssumptionNodes(assumptionAnalysisInterpreter.getNonInternalAssumptionNodes) + val assumptionsPerSource = assumptionNodes groupBy (n => extractSourceLine(n.sourceInfo.getPosition)) + val assertionNodes = getTestAssertionNodes(assumptionAnalysisInterpreter.getNonInternalAssertionNodes) + + val dependencies = assumptionAnalysisInterpreter.getAllNonInternalDependencies(assertionNodes.map(_.id)) + val dependenciesPerSource = dependencies groupBy (n => extractSourceLine(n.sourceInfo.getPosition)) + val dependencyIds = dependencies.map(_.id) + + if(dependenciesPerSource.nonEmpty){ + val wrongDependencies = assumptionsPerSource.filter({ case (_, assumptions) => dependencyIds.intersect(assumptions.map(_.id)).nonEmpty }) + 1.0 - (wrongDependencies.size.toDouble / dependenciesPerSource.size.toDouble) + }else{ + 1.0 + } + } + + protected def verifyTestSoundness(): Boolean = { + val irrelevantNodes = fullGraphInterpreter.getNodes.filter(node => node.sourceInfo.toString.contains("@irrelevant(")).flatMap(_.sourceInfo.getLineNumber) + + val relevantLines = fullGraphInterpreter.getNodes.flatMap(_.sourceInfo.getLineNumber).diff(irrelevantNodes) + + pruneAndVerify(relevantLines, "src/test/resources/" + fileName + s"_test.out") + } + + protected def pruneAndVerify(relevantLines: Set[Int], exportFileName: String): Boolean = { + val crucialNodes = relevantLines.flatMap(line => fullGraphInterpreter.getNodesByLine(line)) + val (newProgram, pruningFactor) = fullGraphInterpreter.getPrunedProgram(crucialNodes, program) + val result = frontend.verifier.verify(newProgram) +// exportPrunedProgram(exportFileName, newProgram, pruningFactor, result) // can be used for debugging + !result.isInstanceOf[verifier.Failure] + } + + protected def exportPrunedProgram(exportFileName: String, newProgram: Program, pruningFactor: Double, result: VerificationResult): Unit = { + val writer = new PrintWriter(exportFileName) + writer.println("// test result: " + !result.isInstanceOf[verifier.Failure]) + writer.println("// pruning factor: " + pruningFactor) + writer.println(newProgram.toString()) + writer.close() + } + } +} diff --git a/src/test/scala/AssumptionAnalysisTestFramework.scala b/src/test/scala/AssumptionAnalysisTestFramework.scala new file mode 100644 index 000000000..6a1577229 --- /dev/null +++ b/src/test/scala/AssumptionAnalysisTestFramework.scala @@ -0,0 +1,240 @@ +package viper.silicon.tests + +import viper.silicon.SiliconFrontend +import viper.silicon.assumptionAnalysis.{AssumptionAnalysisInterpreter, AssumptionAnalysisNode, AssumptionType, DependencyAnalysisReporter} +import viper.silver.ast.{Infoed, Program} +import viper.silver.ast.utility.ViperStrategy +import viper.silver.{ast, verifier} +import viper.silver.verifier.VerificationResult +import scala.jdk.CollectionConverters.IterableHasAsScala + +import java.io.PrintWriter +import java.nio.file.{Files, Path, Paths} +import scala.annotation.unused + +trait AssumptionAnalysisTestFramework { + val irrelevantKeyword = "irrelevant" + val dependencyKeyword = "dependency" + val testAssertionKeyword = "testAssertion" + + val ignores: Seq[String] + var baseCommandLineArguments: Seq[String] = Seq("--timeout", "300" /* seconds */) + var analysisCommandLineArguments: Seq[String] = + baseCommandLineArguments ++ Seq("--enableAssumptionAnalysis", "--disableInfeasibilityChecks", "--proverArgs", "proof=true unsat-core=true") + + def visitFiles(dirName: String, function: (String, String) => Unit): Unit = { + val path = Paths.get(getClass.getClassLoader.getResource(dirName).toURI) + visitFiles(path, dirName, function) + } + + def visitFiles(path: Path, dirName: String, function: (String, String) => Unit): Unit = { + val directoryStream = Files.newDirectoryStream(path).asScala + val dirContent = directoryStream.toList + + for (filePath: Path <- dirContent.sorted + if Files.isReadable(filePath)) { + if(Files.isDirectory(filePath)){ + visitFiles(filePath, dirName + "/" + filePath.getFileName.toString, function) + }else{ + val rawFileName = filePath.getFileName.toString + if (rawFileName.endsWith(".vpr")) { + val fileName = rawFileName.replace(".vpr", "") + if (!ignores.contains(fileName)) + function(dirName, fileName) + } + } + } + } + + var frontend: SiliconFrontend = createFrontend(analysisCommandLineArguments) + + def createFrontend(commandLineArgs: Seq[String]): SiliconFrontend = { + val reporter = DependencyAnalysisReporter() + val fe = new SiliconFrontend(reporter) + val backend = fe.createVerifier("") + backend.parseCommandLine(commandLineArgs ++ List("--ignoreFile", "dummy.sil")) + fe.init(backend) + fe.setVerifier(backend) + backend.start() + fe + } + + def resetFrontend(): Unit = { + frontend.verifier.stop() + frontend = createFrontend(analysisCommandLineArguments) + } + + var baselineFrontend: SiliconFrontend = createFrontend(baseCommandLineArguments) + + def resetBaselineFrontend(): Unit = { + baselineFrontend.verifier.stop() + baselineFrontend = createFrontend(baseCommandLineArguments) + } + + /** + * (Almost) Fully automated test, which takes a program and its assumption analysis results and, + * for each explicit assertion, builds a new program that only contains said assertion and + * all its dependencies. The test passes if all new programs verify successfully. + * + * Statements that are only required as a trigger need to be manually annotated with @trigger() by the user. + */ + case class PruningTest(fileName: String, program: Program, fullGraphInterpreter: AssumptionAnalysisInterpreter) { + + def execute(): Unit = { + val triggerNodeLines = fullGraphInterpreter.getNodes.filter(node => node.sourceInfo.getTopLevelSource.toString.contains("@trigger()")).flatMap(_.sourceInfo.getLineNumber) + var id: Int = 0 + // TODO ake: safer would be to work with position string instead of line numbers + fullGraphInterpreter.getExplicitAssertionNodes flatMap (_.sourceInfo.getLineNumber) foreach {line => + pruneAndVerify(Set(line) ++ triggerNodeLines, "src/test/resources/" + fileName + s"_test$id.out") + id += 1 + } + } + + protected def pruneAndVerify(relevantLines: Set[Int], exportFileName: String): Unit = { + val relevantNodes = relevantLines.flatMap(line => fullGraphInterpreter.getNodesByLine(line)) + + val dependencies = fullGraphInterpreter.getAllNonInternalDependencies(relevantNodes.map(_.id)) + + val crucialNodes = relevantNodes ++ dependencies + val (newProgram, pruningFactor) = fullGraphInterpreter.getPrunedProgram(crucialNodes, program) + val result = frontend.verifier.verify(newProgram) + exportPrunedProgram(exportFileName, newProgram, pruningFactor, result) + assert(!result.isInstanceOf[verifier.Failure], s"Failed to verify new program ${newProgram.toString()}") + } + + protected def exportPrunedProgram(exportFileName: String, newProgram: Program, pruningFactor: Double, result: VerificationResult): Unit = { + val writer = new PrintWriter(exportFileName) + writer.println("// test result: " + !result.isInstanceOf[verifier.Failure]) + writer.println("// pruning factor: " + pruningFactor) + writer.println(newProgram.toString()) + writer.close() + } + } + + /** + * Takes a Viper program and its assumption analysis results and checks whether the analysis found the + * assumptions, assertions and dependencies between them, as annotated by the user. + * + * Annotations: + * + * - @dependency() -> for assumptions that should be reported as a dependency + * + * - @irrelevant() -> for assumptions that should NOT be reported as a dependency + * + * - @testAssertion() -> the queried assertion + * + * !!! THERE CAN ONLY BE 1 TEST ASSERTION PER METHOD, + * but multiple dependency/irrelevant annotations are allowed + * + */ + case class AnnotatedTest(program: Program, assumptionAnalysisInterpreters: List[AssumptionAnalysisInterpreter], checkPrecision: Boolean) { + def execute(): Unit = { + val stmtsWithAssumptionAnnotation: Set[Infoed] = extractAnnotatedStmts({ annotationInfo => annotationInfo.values.contains(irrelevantKeyword + "(\"") || annotationInfo.values.contains(dependencyKeyword) }) + val allAssumptionNodes = assumptionAnalysisInterpreters.flatMap(_.getNonInternalAssumptionNodes) + + var errorMsgs = stmtsWithAssumptionAnnotation.map(checkAssumptionNodeExists(allAssumptionNodes, _)).filter(_.isDefined).map(_.get).toSeq + errorMsgs ++= assumptionAnalysisInterpreters flatMap checkTestAssertionNodeExists + errorMsgs ++= assumptionAnalysisInterpreters flatMap checkAllDependencies + errorMsgs ++= assumptionAnalysisInterpreters flatMap checkExplicitDependencies + + val check = errorMsgs.isEmpty + assert(check, "\n" + errorMsgs.mkString("\n")) + } + + protected def extractAnnotatedStmts(annotationFilter: ast.AnnotationInfo => Boolean): Set[ast.Infoed] = { + var nodesWithAnnotation: Set[ast.Infoed] = Set.empty + @unused + val newP: ast.Program = ViperStrategy.Slim({ + case s: ast.Seqn => s + case n: ast.Infoed => + val annotationInfo = n.info.getUniqueInfo[ast.AnnotationInfo] + .filter(annotationFilter) + if (annotationInfo.isDefined) + nodesWithAnnotation += n + n + }).execute(program) + nodesWithAnnotation + } + + protected def checkAssumptionNodeExists(analysisNodes: List[AssumptionAnalysisNode], node: ast.Infoed): Option[String] = { + val pos = extractSourceLine(node.asInstanceOf[ast.Positioned].pos) + val annotationInfo = node.info.getUniqueInfo[ast.AnnotationInfo] + .map(ai => ai.values.getOrElse(irrelevantKeyword, ai.values.getOrElse(dependencyKeyword, List.empty))).getOrElse(List.empty) + val assumptionType = annotationInfo.map(AssumptionType.fromString).filter(_.isDefined).map(_.get) + val nodeExists = analysisNodes exists (analysisNode => { + extractSourceLine(analysisNode.sourceInfo.getPosition) == pos && + assumptionType.forall(_.equals(analysisNode.assumptionType)) + }) + Option.when(!nodeExists)(s"Missing analysis node:\n${node.toString}\n$pos") + } + + protected def extractSourceLine(pos: ast.Position): Int = { + pos match { + case column: ast.HasLineColumn => column.line + case _ => -1 + } + } + + protected def checkTestAssertionNodeExists(assumptionAnalysisInterpreter: AssumptionAnalysisInterpreter): Seq[String] = { + val assumptionNodes = getTestAssumptionNodes(assumptionAnalysisInterpreter.getNonInternalAssumptionNodes) ++ getTestIrrelevantAssumptionNodes(assumptionAnalysisInterpreter.getNonInternalAssumptionNodes) + val assertionNodes = getTestAssertionNodes(assumptionAnalysisInterpreter.getNonInternalAssertionNodes) + if (assumptionNodes.nonEmpty && assertionNodes.isEmpty) + Seq(s"Missing testAssertion for member: ${assumptionAnalysisInterpreter.getName}") + else + Seq.empty + } + + + protected def checkAllDependencies(assumptionAnalysisInterpreter: AssumptionAnalysisInterpreter): Seq[String] = { + val assertionNodes = getTestAssertionNodes(assumptionAnalysisInterpreter.getNonInternalAssertionNodes) + val dependencies = assumptionAnalysisInterpreter.getAllNonInternalDependencies(assertionNodes.map(_.id))map(_.id) + + val relevantAssumptionNodes = getTestAssumptionNodes(assumptionAnalysisInterpreter.getNonInternalAssumptionNodes) + val resRelevant: Seq[String] = checkDependenciesAndGetErrorMsgs(relevantAssumptionNodes, dependencies, isDependencyExpected = true, "Missing dependency") + + val resIrrelevant = if(checkPrecision){ + val irrelevantNodes = getTestIrrelevantAssumptionNodes(assumptionAnalysisInterpreter.getNonInternalAssumptionNodes) + checkDependenciesAndGetErrorMsgs(irrelevantNodes, dependencies, isDependencyExpected = false, "Unexpected dependency") + } else Seq.empty + + resRelevant ++ resIrrelevant + } + + protected def checkExplicitDependencies(assumptionAnalysisInterpreter: AssumptionAnalysisInterpreter): Seq[String] = { + val assertionNodes = getTestAssertionNodes(assumptionAnalysisInterpreter.getNonInternalAssertionNodes) + val dependencies = assumptionAnalysisInterpreter.getAllExplicitDependencies(assertionNodes.map(_.id)).map(_.id) + + val allTestAssumptionNodes = getTestAssumptionNodes(assumptionAnalysisInterpreter.getNonInternalAssumptionNodes) + + val relevantAssumptionNodes = allTestAssumptionNodes.filter(_.sourceInfo.toString.contains("@" + dependencyKeyword + "(\"Explicit\")")) + val resRelevant: Seq[String] = checkDependenciesAndGetErrorMsgs(relevantAssumptionNodes, dependencies, isDependencyExpected = true, "Missing explicit dependency") + + val irrelevantNodes = allTestAssumptionNodes.filterNot(_.sourceInfo.toString.contains("@" + dependencyKeyword + "(\"Explicit\")")) + val resIrrelevant = checkDependenciesAndGetErrorMsgs(irrelevantNodes, dependencies, isDependencyExpected = false, "Unexpected explicit dependency") + + resRelevant ++ resIrrelevant + } + + protected def checkDependenciesAndGetErrorMsgs(relevantAssumptionNodes: Set[AssumptionAnalysisNode], dependencies: Set[Int], isDependencyExpected: Boolean, errorMsg: String): Seq[String] = { + val relevantAssumptionsPerSource = relevantAssumptionNodes groupBy (n => extractSourceLine(n.sourceInfo.getPosition)) + val resRelevant = relevantAssumptionsPerSource.map({ case (_, assumptions) => + val hasDependency = dependencies.intersect(assumptions.map(_.id)).nonEmpty + Option.when(!(isDependencyExpected == hasDependency))(s"$errorMsg: ${assumptions.head.sourceInfo.toString}") + }).filter(_.isDefined).map(_.get).toSeq + resRelevant + } + + protected def getTestAssertionNodes(nodes: Set[AssumptionAnalysisNode]): Set[AssumptionAnalysisNode] = + nodes.filter(node => node.sourceInfo.toString.contains("@" + testAssertionKeyword + "(")) + + + protected def getTestAssumptionNodes(nodes: Set[AssumptionAnalysisNode]): Set[AssumptionAnalysisNode] = + nodes.filter(_.sourceInfo.toString.contains("@" + dependencyKeyword + "(")) + + + protected def getTestIrrelevantAssumptionNodes(nodes: Set[AssumptionAnalysisNode]): Set[AssumptionAnalysisNode] = + nodes.filter(_.sourceInfo.toString.contains("@" + irrelevantKeyword + "(")) + + } + +} diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index 51d0b2a61..03dae036d 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -1,25 +1,20 @@ +package viper.silicon.tests import org.scalatest.funsuite.AnyFunSuite -import viper.silicon.SiliconFrontend import viper.silicon.assumptionAnalysis._ import viper.silver.ast._ -import viper.silver.ast.utility.ViperStrategy import viper.silver.frontend.SilFrontend -import viper.silver.verifier.VerificationResult -import viper.silver.{ast, verifier} +import viper.silver.verifier import java.io.{File, PrintWriter} -import java.nio.file.{Files, Path, Paths} import java.time._ import java.time.format.DateTimeFormatter import scala.annotation.unused -import scala.jdk.CollectionConverters.IterableHasAsScala -class AssumptionAnalysisTests extends AnyFunSuite { +class AssumptionAnalysisTests extends AnyFunSuite with AssumptionAnalysisTestFramework { val CHECK_PRECISION = false - val EXECUTE_PRECISION_BENCHMARK = false val EXECUTE_TEST = true val EXECUTE_PERFORMANCE_BENCHMARK = false val ignores: Seq[String] = Seq("example1", "example2", "iterativeTreeDelete", "listAppend_wands") @@ -32,30 +27,10 @@ class AssumptionAnalysisTests extends AnyFunSuite { // "dependencyAnalysisTests/quick" // "dependencyAnalysisTests/fromSilver", // "dependencyAnalysisTests/performanceBenchmark" -// "dependencyAnalysisTests/precisionTests" +// "dependencyAnalysisTests/precisionTests/quantifiedPermissions" +// "andrea/debug" ) - val irrelevantKeyword = "irrelevant" - val dependencyKeyword = "dependency" - val testAssertionKeyword = "testAssertion" - - var baseCommandLineArguments: Seq[String] = Seq("--timeout", "300" /* seconds */) - var analysisCommandLineArguments: Seq[String] = - baseCommandLineArguments ++ Seq("--enableAssumptionAnalysis", "--disableInfeasibilityChecks", "--proverArgs", "proof=true unsat-core=true") - - - if(EXECUTE_PRECISION_BENCHMARK) { - test("precision benchmark") { - val basePath = "src/test/resources/dependencyAnalysisTests/precisionTests/results" - val directory = new File(basePath) - directory.mkdir() - val now: LocalDateTime = LocalDateTime.now() - val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss") - val writer = new PrintWriter(s"$basePath/result_${now.format(formatter)}.out") - visitFiles("dependencyAnalysisTests/precisionTests", executePrecisionBenchmark(_, _, writer)) - writer.close() - } - } if(EXECUTE_PERFORMANCE_BENCHMARK) test("performance benchmark") { @@ -81,10 +56,7 @@ class AssumptionAnalysisTests extends AnyFunSuite { } - def visitFiles(dirName: String, function: (String, String) => Unit): Unit = { - val path = Paths.get(getClass.getClassLoader.getResource(dirName).toURI) - visitFiles(path, dirName, function) - } + private def createSingleTest(dirName: String, fileName: String): Unit = { test(dirName + "/" + fileName) { @@ -97,50 +69,6 @@ class AssumptionAnalysisTests extends AnyFunSuite { } } - def visitFiles(path: Path, dirName: String, function: (String, String) => Unit): Unit = { - val directoryStream = Files.newDirectoryStream(path).asScala - val dirContent = directoryStream.toList - - for (filePath: Path <- dirContent.sorted - if Files.isReadable(filePath)) { - if(Files.isDirectory(filePath)){ - visitFiles(filePath, dirName + "/" + filePath.getFileName.toString, function) - }else{ - val rawFileName = filePath.getFileName.toString - if (rawFileName.endsWith(".vpr")) { - val fileName = rawFileName.replace(".vpr", "") - if (!ignores.contains(fileName)) - function(dirName, fileName) - } - } - } - } - - var frontend: SiliconFrontend = createFrontend(analysisCommandLineArguments) - - def createFrontend(commandLineArgs: Seq[String]): SiliconFrontend = { - val reporter = DependencyAnalysisReporter() - val fe = new SiliconFrontend(reporter) - val backend = fe.createVerifier("") - backend.parseCommandLine(commandLineArgs ++ List("--ignoreFile", "dummy.sil")) - fe.init(backend) - fe.setVerifier(backend) - backend.start() - fe - } - - def resetFrontend(): Unit = { - frontend.verifier.stop() - frontend = createFrontend(analysisCommandLineArguments) - } - - var baselineFrontend: SiliconFrontend = createFrontend(baseCommandLineArguments) - - def resetBaselineFrontend(): Unit = { - baselineFrontend.verifier.stop() - baselineFrontend = createFrontend(baseCommandLineArguments) - } - def executeTest(filePrefix: String, fileName: String, @@ -156,39 +84,10 @@ class AssumptionAnalysisTests extends AnyFunSuite { val assumptionAnalysisInterpreters = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].assumptionAnalysisInterpretersPerMember val joinedAssumptionAnalysisInterpreter = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].joinedAssumptionAnalysisInterpreter - AnnotatedTest(program, assumptionAnalysisInterpreters).execute() + AnnotatedTest(program, assumptionAnalysisInterpreters, CHECK_PRECISION).execute() PruningTest(filePrefix + "/" + fileName, program, joinedAssumptionAnalysisInterpreter.get).execute() } - def executePrecisionBenchmark(filePrefix: String, - fileName: String, - writer: PrintWriter): Unit = { - resetFrontend() - try{ - val program: Program = tests.loadProgram(filePrefix + "/", fileName, frontend) - val result = frontend.verifier.verify(program) - if(result.isInstanceOf[verifier.Failure]) { - writer.println(f"Program $filePrefix/$fileName does not verify. Skip") - println(f"Program $filePrefix/$fileName does not verify. Skip.\n$result") - return - } - val assumptionAnalysisInterpreters = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].assumptionAnalysisInterpretersPerMember - val fullGraphInterpreter = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].joinedAssumptionAnalysisInterpreter - - - println(s"Precision Benchmark for $filePrefix - $fileName started...") - writer.println(s"$filePrefix - $fileName") - new AnnotatedPrecisionBenchmark(filePrefix + "/" + fileName, program, assumptionAnalysisInterpreters, fullGraphInterpreter.get, writer).execute() - writer.println() - writer.flush() - println(s"Precision Benchmark for $filePrefix - $fileName done.") - }catch{ - case t: Throwable => - writer.println("Failed. Skip") - println(s"Exception caught: ${t.getMessage}") - } - } - def executePerformanceBenchmark(filePrefix: String, fileName: String, writer: PrintWriter, @@ -222,232 +121,6 @@ class AssumptionAnalysisTests extends AnyFunSuite { new PerformanceBenchmark(filePrefix + "/" + fileName, program, naiveProgram, writer, program.toString().split("\n").length).execute() } - /** - * (Almost) Fully automated test, which takes a program and its assumption analysis results and, - * for each explicit assertion, builds a new program that only contains said assertion and - * all its dependencies. The test passes if all new programs verify successfully. - * - * Statements that are only required as a trigger need to be manually annotated with @trigger() by the user. - */ - case class PruningTest(fileName: String, program: Program, fullGraphInterpreter: AssumptionAnalysisInterpreter) { - - def execute(): Unit = { - val triggerNodeLines = fullGraphInterpreter.getNodes.filter(node => node.sourceInfo.getTopLevelSource.toString.contains("@trigger()")).flatMap(_.sourceInfo.getLineNumber) - var id: Int = 0 - // TODO ake: safer would be to work with position string instead of line numbers - fullGraphInterpreter.getExplicitAssertionNodes flatMap (_.sourceInfo.getLineNumber) foreach {line => - pruneAndVerify(Set(line) ++ triggerNodeLines, "src/test/resources/" + fileName + s"_test$id.out") - id += 1 - } - } - - protected def pruneAndVerify(relevantLines: Set[Int], exportFileName: String): Unit = { - val relevantNodes = relevantLines.flatMap(line => fullGraphInterpreter.getNodesByLine(line)) - - val dependencies = fullGraphInterpreter.getAllNonInternalDependencies(relevantNodes.map(_.id)) - - val crucialNodes = relevantNodes ++ dependencies - val (newProgram, pruningFactor) = fullGraphInterpreter.getPrunedProgram(crucialNodes, program) - val result = frontend.verifier.verify(newProgram) - exportPrunedProgram(exportFileName, newProgram, pruningFactor, result) - assert(!result.isInstanceOf[verifier.Failure], s"Failed to verify new program ${newProgram.toString()}") - } - - protected def exportPrunedProgram(exportFileName: String, newProgram: Program, pruningFactor: Double, result: VerificationResult): Unit = { - val writer = new PrintWriter(exportFileName) - writer.println("// test result: " + !result.isInstanceOf[verifier.Failure]) - writer.println("// pruning factor: " + pruningFactor) - writer.println(newProgram.toString()) - writer.close() - } - } - - /** - * Takes a Viper program and its assumption analysis results and checks whether the analysis found the - * assumptions, assertions and dependencies between them, as annotated by the user. - * - * Annotations: - * - * - @dependency() -> for assumptions that should be reported as a dependency - * - * - @irrelevant() -> for assumptions that should NOT be reported as a dependency - * - * - @testAssertion() -> the queried assertion - * - * !!! THERE CAN ONLY BE 1 TEST ASSERTION PER METHOD, - * but multiple dependency/irrelevant annotations are allowed - * - */ - case class AnnotatedTest(program: Program, assumptionAnalysisInterpreters: List[AssumptionAnalysisInterpreter]) { - def execute(): Unit = { - val stmtsWithAssumptionAnnotation: Set[Infoed] = extractAnnotatedStmts({ annotationInfo => annotationInfo.values.contains(irrelevantKeyword + "(\"") || annotationInfo.values.contains(dependencyKeyword) }) - val allAssumptionNodes = assumptionAnalysisInterpreters.flatMap(_.getNonInternalAssumptionNodes) - - var errorMsgs = stmtsWithAssumptionAnnotation.map(checkAssumptionNodeExists(allAssumptionNodes, _)).filter(_.isDefined).map(_.get).toSeq - errorMsgs ++= assumptionAnalysisInterpreters flatMap checkTestAssertionNodeExists - errorMsgs ++= assumptionAnalysisInterpreters flatMap checkAllDependencies - errorMsgs ++= assumptionAnalysisInterpreters flatMap checkExplicitDependencies - - val check = errorMsgs.isEmpty - assert(check, "\n" + errorMsgs.mkString("\n")) - } - - protected def extractAnnotatedStmts(annotationFilter: ast.AnnotationInfo => Boolean): Set[ast.Infoed] = { - var nodesWithAnnotation: Set[ast.Infoed] = Set.empty - @unused - val newP: ast.Program = ViperStrategy.Slim({ - case s: ast.Seqn => s - case n: ast.Infoed => - val annotationInfo = n.info.getUniqueInfo[ast.AnnotationInfo] - .filter(annotationFilter) - if (annotationInfo.isDefined) - nodesWithAnnotation += n - n - }).execute(program) - nodesWithAnnotation - } - - protected def checkAssumptionNodeExists(analysisNodes: List[AssumptionAnalysisNode], node: ast.Infoed): Option[String] = { - val pos = extractSourceLine(node.asInstanceOf[ast.Positioned].pos) - val annotationInfo = node.info.getUniqueInfo[ast.AnnotationInfo] - .map(ai => ai.values.getOrElse(irrelevantKeyword, ai.values.getOrElse(dependencyKeyword, List.empty))).getOrElse(List.empty) - val assumptionType = annotationInfo.map(AssumptionType.fromString).filter(_.isDefined).map(_.get) - val nodeExists = analysisNodes exists (analysisNode => { - extractSourceLine(analysisNode.sourceInfo.getPosition) == pos && - assumptionType.forall(_.equals(analysisNode.assumptionType)) - }) - Option.when(!nodeExists)(s"Missing analysis node:\n${node.toString}\n$pos") - } - - protected def extractSourceLine(pos: ast.Position): Int = { - pos match { - case column: ast.HasLineColumn => column.line - case _ => -1 - } - } - - protected def checkTestAssertionNodeExists(assumptionAnalysisInterpreter: AssumptionAnalysisInterpreter): Seq[String] = { - val assumptionNodes = getTestAssumptionNodes(assumptionAnalysisInterpreter.getNonInternalAssumptionNodes) ++ getTestIrrelevantAssumptionNodes(assumptionAnalysisInterpreter.getNonInternalAssumptionNodes) - val assertionNodes = getTestAssertionNodes(assumptionAnalysisInterpreter.getNonInternalAssertionNodes) - if (assumptionNodes.nonEmpty && assertionNodes.isEmpty) - Seq(s"Missing testAssertion for member: ${assumptionAnalysisInterpreter.getName}") - else - Seq.empty - } - - - protected def checkAllDependencies(assumptionAnalysisInterpreter: AssumptionAnalysisInterpreter): Seq[String] = { - val assertionNodes = getTestAssertionNodes(assumptionAnalysisInterpreter.getNonInternalAssertionNodes) - val dependencies = assumptionAnalysisInterpreter.getAllNonInternalDependencies(assertionNodes.map(_.id))map(_.id) - - val relevantAssumptionNodes = getTestAssumptionNodes(assumptionAnalysisInterpreter.getNonInternalAssumptionNodes) - val resRelevant: Seq[String] = checkDependenciesAndGetErrorMsgs(relevantAssumptionNodes, dependencies, isDependencyExpected = true, "Missing dependency") - - val resIrrelevant = if(CHECK_PRECISION){ - val irrelevantNodes = getTestIrrelevantAssumptionNodes(assumptionAnalysisInterpreter.getNonInternalAssumptionNodes) - checkDependenciesAndGetErrorMsgs(irrelevantNodes, dependencies, isDependencyExpected = false, "Unexpected dependency") - } else Seq.empty - - resRelevant ++ resIrrelevant - } - - protected def checkExplicitDependencies(assumptionAnalysisInterpreter: AssumptionAnalysisInterpreter): Seq[String] = { - val assertionNodes = getTestAssertionNodes(assumptionAnalysisInterpreter.getNonInternalAssertionNodes) - val dependencies = assumptionAnalysisInterpreter.getAllExplicitDependencies(assertionNodes.map(_.id)).map(_.id) - - val allTestAssumptionNodes = getTestAssumptionNodes(assumptionAnalysisInterpreter.getNonInternalAssumptionNodes) - - val relevantAssumptionNodes = allTestAssumptionNodes.filter(_.sourceInfo.toString.contains("@" + dependencyKeyword + "(\"Explicit\")")) - val resRelevant: Seq[String] = checkDependenciesAndGetErrorMsgs(relevantAssumptionNodes, dependencies, isDependencyExpected = true, "Missing explicit dependency") - - val irrelevantNodes = allTestAssumptionNodes.filterNot(_.sourceInfo.toString.contains("@" + dependencyKeyword + "(\"Explicit\")")) - val resIrrelevant = checkDependenciesAndGetErrorMsgs(irrelevantNodes, dependencies, isDependencyExpected = false, "Unexpected explicit dependency") - - resRelevant ++ resIrrelevant - } - - protected def checkDependenciesAndGetErrorMsgs(relevantAssumptionNodes: Set[AssumptionAnalysisNode], dependencies: Set[Int], isDependencyExpected: Boolean, errorMsg: String): Seq[String] = { - val relevantAssumptionsPerSource = relevantAssumptionNodes groupBy (n => extractSourceLine(n.sourceInfo.getPosition)) - val resRelevant = relevantAssumptionsPerSource.map({ case (_, assumptions) => - val hasDependency = dependencies.intersect(assumptions.map(_.id)).nonEmpty - Option.when(!(isDependencyExpected == hasDependency))(s"$errorMsg: ${assumptions.head.sourceInfo.toString}") - }).filter(_.isDefined).map(_.get).toSeq - resRelevant - } - - protected def getTestAssertionNodes(nodes: Set[AssumptionAnalysisNode]): Set[AssumptionAnalysisNode] = - nodes.filter(node => node.sourceInfo.toString.contains("@" + testAssertionKeyword + "(")) - - - protected def getTestAssumptionNodes(nodes: Set[AssumptionAnalysisNode]): Set[AssumptionAnalysisNode] = - nodes.filter(_.sourceInfo.toString.contains("@" + dependencyKeyword + "(")) - - - protected def getTestIrrelevantAssumptionNodes(nodes: Set[AssumptionAnalysisNode]): Set[AssumptionAnalysisNode] = - nodes.filter(_.sourceInfo.toString.contains("@" + irrelevantKeyword + "(")) - - } - - - class AnnotatedPrecisionBenchmark(fileName: String, program: Program, - assumptionAnalysisInterpreters: List[AssumptionAnalysisInterpreter], - fullGraphInterpreter: AssumptionAnalysisInterpreter, - writer: PrintWriter) extends AnnotatedTest(program, assumptionAnalysisInterpreters) { - override def execute(): Unit = { - if(!verifyTestSoundness()){ - writer.println(s"!!!!!!!!!!!\nFailed to verify soundness of precision test $fileName\n") - println(s"!!!!!!!!!!!\nFailed to verify soundness of precision test $fileName\n") - return - } - - assumptionAnalysisInterpreters foreach {a => - val prec = computePrecision(a) - writer.println(s"${a.getMember.map(_.name).getOrElse("unknown")}: $prec") - } - } - - protected def computePrecision(assumptionAnalysisInterpreter: AssumptionAnalysisInterpreter): Double = { - val assumptionNodes = getTestIrrelevantAssumptionNodes(assumptionAnalysisInterpreter.getNonInternalAssumptionNodes) - val assumptionsPerSource = assumptionNodes groupBy (n => extractSourceLine(n.sourceInfo.getPosition)) - val assertionNodes = getTestAssertionNodes(assumptionAnalysisInterpreter.getNonInternalAssertionNodes) - - val dependencies = assumptionAnalysisInterpreter.getAllNonInternalDependencies(assertionNodes.map(_.id)) - val dependenciesPerSource = dependencies groupBy (n => extractSourceLine(n.sourceInfo.getPosition)) - val dependencyIds = dependencies.map(_.id) - - if(dependenciesPerSource.nonEmpty){ - val wrongDependencies = assumptionsPerSource.filter({ case (_, assumptions) => dependencyIds.intersect(assumptions.map(_.id)).nonEmpty }) - 1.0 - (wrongDependencies.size.toDouble / dependenciesPerSource.size.toDouble) - }else{ - 1.0 - } - } - - protected def verifyTestSoundness(): Boolean = { - val irrelevantNodes = fullGraphInterpreter.getNodes.filter(node => node.sourceInfo.toString.contains("@irrelevant(")).flatMap(_.sourceInfo.getLineNumber) - - val relevantLines = fullGraphInterpreter.getNodes.flatMap(_.sourceInfo.getLineNumber).diff(irrelevantNodes) - - pruneAndVerify(relevantLines, "src/test/resources/" + fileName + s"_test.out") - } - - protected def pruneAndVerify(relevantLines: Set[Int], exportFileName: String): Boolean = { - val crucialNodes = relevantLines.flatMap(line => fullGraphInterpreter.getNodesByLine(line)) - val (newProgram, pruningFactor) = fullGraphInterpreter.getPrunedProgram(crucialNodes, program) - val result = frontend.verifier.verify(newProgram) - exportPrunedProgram(exportFileName, newProgram, pruningFactor, result) - !result.isInstanceOf[verifier.Failure] - } - - protected def exportPrunedProgram(exportFileName: String, newProgram: Program, pruningFactor: Double, result: VerificationResult): Unit = { - val writer = new PrintWriter(exportFileName) - writer.println("// test result: " + !result.isInstanceOf[verifier.Failure]) - writer.println("// pruning factor: " + pruningFactor) - writer.println(newProgram.toString()) - writer.close() - } - - } class PerformanceBenchmark(name: String, program: Program, @unused naiveProgram: Option[Program], writer: PrintWriter, programSize: Int) { From 61438263bb477dbd1671c92fc21169128663d600 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 27 Aug 2025 19:01:57 +0200 Subject: [PATCH 240/474] minor fixes --- .../AssumptionAnalysisUserTool.scala | 2 +- src/test/resources/andrea/debug/debug.vpr | 89 +++++++++++++++++++ .../benchmark_scripts/snippets.txt | 28 +++--- .../minimalExamples-precision/domains.vpr | 23 +---- .../precisionTests/results/result_table.out | 26 +++--- 5 files changed, 121 insertions(+), 47 deletions(-) create mode 100644 src/test/resources/andrea/debug/debug.vpr diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala index 475570a68..3774b9781 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala @@ -137,7 +137,7 @@ class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpr private def handleDependentsQuery(inputs: Set[String]): Unit = { - val queriedNodes = getQueriedNodesFromInput(inputs) + val queriedNodes = getQueriedNodesFromInput(inputs).intersect(fullGraphInterpreter.getNonInternalAssumptionNodes) val (allDependents, timeAll) = measureTime[Set[AssumptionAnalysisNode]](fullGraphInterpreter.getAllNonInternalDependents(queriedNodes.map(_.id))) val (dependentsWithoutInfeasibility, timeWithoutInfeasibility) = measureTime[Set[AssumptionAnalysisNode]](fullGraphInterpreter.getAllNonInternalDependents(queriedNodes.map(_.id), includeInfeasibilityNodes=false)) diff --git a/src/test/resources/andrea/debug/debug.vpr b/src/test/resources/andrea/debug/debug.vpr new file mode 100644 index 000000000..79e59b75c --- /dev/null +++ b/src/test/resources/andrea/debug/debug.vpr @@ -0,0 +1,89 @@ +method quantifiedPermWrite4(xs: Seq[Ref], ys: Seq[Ref]) + requires @dependency("Explicit")(|xs| > 5) + requires |ys| > 3 +{ + @dependency("Explicit") + inhale forall x: Ref :: x in xs ==> acc(x.f) + @irrelevant("Explicit") + inhale forall y: Ref :: y in ys ==> acc(y.f) + + inhale ys[0] != ys[1] + + +var gen_dummy_int: Int +{ + @irrelevant("Implicit") + ys[0].f := gen_incr_pure(xs[0].f) +} + + @testAssertion("Implicit") + xs[0].f := 2 +} + +method quantifiedPermWrite32(xs: Seq[Ref]) { + @dependency("Explicit") + assume |xs| > 5 + @dependency("Explicit") + inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) + + @irrelevant("Implicit") + xs[0].f := 0 + + +var gen_dummy_int: Int +{ + var gen_c: Int + if(@irrelevant("PathCondition")(xs[1].f > gen_c && xs[2].f > gen_c)){ + var gen_i: Int + @irrelevant("Implicit") + gen_i := xs[1].f + xs[2].f + @irrelevant("Implicit") + xs[0].f := 10 + }else{ + if(@irrelevant("PathCondition")(xs[1].f > gen_c + 5)){ + @irrelevant("Implicit") + xs[0].f := 30 + }else{ + var gen_i: Int + @irrelevant("Implicit") + gen_i := xs[1].f + xs[2].f + } + } +} + + @testAssertion("Explicit") + assert xs[0] != xs[1] ==> xs[1].f > 0 +} + +method quantifiedPerm2(xs: Seq[Ref]) { + assume @dependency("Explicit")(|xs| > 5) + inhale @dependency("Explicit")(forall x: Ref :: x in xs ==> acc(x.f)) + + inhale xs[0] != xs[4] + + +var gen_dummy_int: Int +{ + var gen_start: Int, gen_c: Int + @irrelevant("Explicit") + inhale gen_c >= 0 + @irrelevant("Implicit") + gen_start := xs[0].f + @irrelevant("Implicit") + xs[4].f := 0 + while(@irrelevant("PathCondition")(xs[0].f > gen_c)) + invariant forall x:Ref::x in xs ==> acc(x.f) + invariant @irrelevant("LoopInvariant")(true) + invariant @irrelevant("LoopInvariant")(xs[0].f <= gen_start) + invariant @irrelevant("LoopInvariant")(xs[4].f == (gen_start-xs[0].f)*10) + { + @irrelevant("Implicit") + xs[4].f := xs[4].f + 10 + @irrelevant("Implicit") + xs[0].f := xs[0].f - 1 + } +} + + @testAssertion() + xs[0].f := xs[1].f + xs[4].f +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/snippets.txt b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/snippets.txt index b53e6b354..07b425ea5 100644 --- a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/snippets.txt +++ b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/snippets.txt @@ -1,7 +1,7 @@ field gen_f: Int -predicate gen_confuse(i: Int){ - i >= 0 +predicate gen_confuse(i: Int, x: Ref){ + i >= 0 && acc(x.gen_f) } predicate gen_confuse_with_impl(i: Int, x: Ref){ @@ -74,7 +74,7 @@ SNIPPET 04_assignment_field$=${ SNIPPET 05_assignment_disjoint_ref$=${ var gen_x: Ref - @irrelevant("Explicit") + // @irrelevant("Explicit") // TODO ake: aliasing! inhale acc(gen_x.f) @irrelevant("Implicit") @@ -101,7 +101,7 @@ SNIPPET 07_assignment_disjoint_ref_quantified$=${ var gen_xs: Seq[Ref] @irrelevant("Explicit") inhale |gen_xs| > 2 - @irrelevant("Explicit") + // @irrelevant("Explicit") // TODO ake: aliasing! inhale forall gen_x: Ref :: gen_x in gen_xs ==> acc(gen_x.f) @irrelevant("Explicit") inhale forall gen_x: Ref :: gen_x in gen_xs ==> gen_x.f < 0 @@ -134,7 +134,7 @@ SNIPPET 08_assignment_disjoint_ref_disjoint_field_quantified$=${ // fold-unfold with implication SNIPPET 09_fold_unfold_with_implication$=${ var gen_x: Ref - @irrelevant("Explicit") + // @irrelevant("Explicit") // TODO ake: aliasing! inhale acc(gen_x.f) @irrelevant("Implicit") fold gen_confuse_with_impl($RO_INT_0, gen_x) @@ -233,9 +233,6 @@ SNIPPET 17_branch_function$=${ $RW_INT_0 := 0 } } - - @irrelevant() - assert $RW_INT_0 >= 0 } @@ -286,8 +283,10 @@ SNIPPET 20_magic_wand$=${ var gen_x: Ref @irrelevant("Explicit") inhale gen_i > 0 + // @irrelevant("Explicit") // TODO ake: aliasing! + inhale acc(gen_x.f) @irrelevant("Explicit") - inhale acc(gen_x.f) && gen_x.f > 0 + inhale gen_x.f > 0 @irrelevant("Implicit") package gen_i >= 0 --* acc(gen_x.f) && gen_x.f > 0 @irrelevant("Implicit") @@ -362,11 +361,11 @@ SNIPPET 25_unrelated_sync_points$=${ inhale 0 <= gen_n && gen_n < 100 @irrelevant("Rewrite") - fold gen_confuse_gen_field(gen_p, gen_x) + fold gen_confuse(gen_n, gen_x) @irrelevant("Implicit") gen_i := gen_add_positive(gen_i, gen_n) @irrelevant("Rewrite") - unfold gen_confuse_gen_field(gen_p, gen_x) + unfold gen_confuse(gen_n, gen_x) while(@irrelevant("PathCondition")(gen_n > 0)) invariant @irrelevant("Invariant")(acc(gen_x.gen_f)) @@ -392,13 +391,14 @@ SNIPPET 25_unrelated_sync_points$=${ @irrelevant("Rewrite") package gen_i >= 0 --* acc(gen_xs[0].gen_f) - if(@irrelevant("PathCondition")(gen_j < 0)){ + var gen_bool: Bool + if(@irrelevant("PathCondition")(gen_bool)){ @irrelevant("Implicit") - gen_j := -gen_j + gen_bool := false } @irrelevant() - assert gen_j >= 0 + assert !gen_bool @irrelevant("Rewrite") apply gen_i >= 0 --* acc(gen_x.gen_f) diff --git a/src/test/resources/dependencyAnalysisTests/minimalExamples-precision/domains.vpr b/src/test/resources/dependencyAnalysisTests/minimalExamples-precision/domains.vpr index 1342ec134..4def0282f 100644 --- a/src/test/resources/dependencyAnalysisTests/minimalExamples-precision/domains.vpr +++ b/src/test/resources/dependencyAnalysisTests/minimalExamples-precision/domains.vpr @@ -10,7 +10,6 @@ domain IArray { function first(r: Ref): IArray function second(r: Ref): Int - axiom all_diff { forall a: IArray, i: Int :: { slot(a,i) } first(slot(a,i)) == a && second(slot(a,i)) == i @@ -22,30 +21,16 @@ domain IArray { } } -function gen_add_positive(a: Int, b: Int): Int - requires a >= 0 && b >= 0 - ensures result == a + b - ensures result >= 0 -method domain2a() // assert +method domain_functions() { var a: IArray - @dependency("Explicit") inhale len(a) == 3 - - @dependency("Explicit") inhale access(a) + slot(a,1).val := 1 -var gen_dummy_int: Int -{ - var gen_i: Int - @irrelevant("Explicit") - inhale gen_i > 0 // dependency via the assertion - @irrelevant() - assert 0 0 -} - - @testAssertion("Implicit") slot(a,0).val := 0 + + assert slot(a,0).val >= 0 // test assertion - precise } diff --git a/src/test/resources/dependencyAnalysisTests/precisionTests/results/result_table.out b/src/test/resources/dependencyAnalysisTests/precisionTests/results/result_table.out index 018d18048..25022c3e8 100644 --- a/src/test/resources/dependencyAnalysisTests/precisionTests/results/result_table.out +++ b/src/test/resources/dependencyAnalysisTests/precisionTests/results/result_table.out @@ -1,13 +1,13 @@ - & 00_baseline & 01_inhale_invariant & 02_assert_invariant & 03_assignment_pure & 04_assignment_field & 05_assignment_disjoint_ref & 06_assignment_disjoint_ref_disjoint_field & 07_assignment_disjoint_ref_quantified & 08_assignment_disjoint_ref_disjoint_field_quantified & 09_fold_unfold_with_implication & 10_function_add_pure & 11_function_add_impure & 12_method_call_impure & 13_method_call_pure & 14_branch & 15_branch_nested & 16_branch_infeasible & 17_branch_function & 18_while_pure & 19_while_perm & 20_magic_wand & 21_quasihavoc & 22_quasihavoc_disjoint_ref & 23_quasihavoc_disjoint_ref_disjoint_field & 24_goto_pure & 25_unrelated_sync_points -dependencyAnalysisTests/precisionTests/branches & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.777 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.932 & 0.871 & 0.762 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.777 \\ -dependencyAnalysisTests/precisionTests/domain & 1.000 & 0.963 & 0.972 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.831 & 0.989 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.886 & 0.960 & 1.000 & 1.000 & 1.000 & 0.960 & 1.000 & 1.000 & 1.000 & 0.831 \\ -dependencyAnalysisTests/precisionTests/function-call & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.866 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.927 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.851 \\ -dependencyAnalysisTests/precisionTests/inhaleExhale & 1.000 & 0.963 & 0.972 & 1.000 & 1.000 & 0.972 & 1.000 & 0.822 & 1.000 & 0.833 & 1.000 & 0.972 & 0.972 & 1.000 & 1.000 & 1.000 & 0.917 & 0.944 & NaN & NaN & 0.937 & 0.928 & 0.867 & 1.000 & 1.000 & 0.833 \\ -dependencyAnalysisTests/precisionTests/loops & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.817 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.891 & 0.940 & NaN & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & NaN & 0.830 \\ -dependencyAnalysisTests/precisionTests/magicWands & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.962 & 1.000 & 0.757 & 1.000 & 0.974 & 0.974 & 1.000 & 1.000 & 1.000 & 0.881 & 1.000 & NaN & NaN & 0.949 & 0.970 & 0.962 & 1.000 & 1.000 & 0.757 \\ -dependencyAnalysisTests/precisionTests/method-call & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.872 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.929 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.859 \\ -dependencyAnalysisTests/precisionTests/misc & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.917 & 1.000 & 0.925 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.958 & 1.000 & 1.000 & 1.000 & 1.000 & 0.958 & 0.958 & 1.000 & 1.000 & 0.925 \\ -dependencyAnalysisTests/precisionTests/permWildcard & 1.000 & 1.000 & 1.000 & 1.000 & NaN & 1.000 & 1.000 & 0.804 & 1.000 & 0.778 & 1.000 & 0.963 & 0.963 & 1.000 & 1.000 & 1.000 & 0.852 & 0.921 & NaN & NaN & 1.000 & NaN & 0.833 & 1.000 & 1.000 & 0.767 \\ -dependencyAnalysisTests/precisionTests/permissions & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.805 & 1.000 & 0.618 & 1.000 & 0.967 & 0.967 & 1.000 & 1.000 & 1.000 & 0.767 & 1.000 & NaN & NaN & 0.947 & 0.783 & 0.738 & 1.000 & 1.000 & 0.618 \\ -dependencyAnalysisTests/precisionTests/predicates & 1.000 & 0.822 & 0.870 & 1.000 & 1.000 & 1.000 & 1.000 & 0.973 & 1.000 & 0.655 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.923 & 0.796 & 0.883 & NaN & NaN & 1.000 & 0.987 & 0.987 & 1.000 & 1.000 & 0.666 \\ -dependencyAnalysisTests/precisionTests/quantifiedPermissions & 1.000 & 0.917 & 0.943 & 1.000 & 1.000 & 1.000 & 1.000 & 0.825 & 1.000 & 0.664 & 1.000 & 0.917 & 0.907 & NaN & 1.000 & 0.919 & 0.824 & 0.867 & NaN & NaN & 0.929 & 0.829 & 0.754 & 1.000 & NaN & 0.629 \\ + & 00_baseline & 01_inhale_invariant & 02_assert_invariant & 03_assignment_pure & 04_assignment_field & 05_assignment_disjoint_ref & 06_assignment_disjoint_ref_disjoint_field & 07_assignment_disjoint_ref_quantified & 08_assignment_disjoint_ref_disjoint_field_quantified & 09_fold_unfold_with_implication & 10_function_add_pure & 11_function_add_impure & 12_method_call_pure & 13_method_call_impure & 14_branch & 15_branch_nested & 16_branch_infeasible & 17_branch_function & 18_while_pure & 19_while_perm & 20_magic_wand & 21_quasihavoc & 22_quasihavoc_disjoint_ref & 23_quasihavoc_disjoint_ref_disjoint_field & 24_goto_pure & 25_unrelated_sync_points +dependencyAnalysisTests/precisionTests/branches & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.852 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.932 & 0.871 & 0.762 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 \\ +dependencyAnalysisTests/precisionTests/domain & 1.000 & 0.963 & 0.972 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.887 & 0.989 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.886 & 1.000 & 1.000 & 1.000 & 1.000 & 0.960 & 1.000 & 1.000 & 1.000 & 1.000 \\ +dependencyAnalysisTests/precisionTests/function-call & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.894 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.927 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 \\ +dependencyAnalysisTests/precisionTests/inhaleExhale & 1.000 & 0.963 & 0.972 & 1.000 & 1.000 & 1.000 & 1.000 & 0.891 & 1.000 & 0.889 & 1.000 & 0.972 & 1.000 & 0.972 & 1.000 & 1.000 & 0.917 & 0.944 & NaN & NaN & 0.944 & 0.928 & 0.867 & 1.000 & 1.000 & 1.000 \\ +dependencyAnalysisTests/precisionTests/loops & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.863 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.891 & 0.940 & NaN & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & NaN & 1.000 \\ +dependencyAnalysisTests/precisionTests/magicWands & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.981 & 1.000 & 0.838 & 1.000 & 0.974 & 1.000 & 0.974 & 1.000 & 1.000 & 0.881 & 1.000 & NaN & NaN & 0.956 & 0.970 & 0.962 & 1.000 & 1.000 & 1.000 \\ +dependencyAnalysisTests/precisionTests/method-call & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.899 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.929 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 \\ +dependencyAnalysisTests/precisionTests/misc & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.938 & 1.000 & 0.950 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.958 & 1.000 & 1.000 & 1.000 & 1.000 & 0.958 & 0.958 & 1.000 & 1.000 & 1.000 \\ +dependencyAnalysisTests/precisionTests/permWildcard & 1.000 & 1.000 & 1.000 & 1.000 & NaN & 1.000 & 1.000 & 0.907 & 1.000 & 0.833 & 1.000 & 0.963 & 1.000 & 0.963 & 1.000 & 1.000 & 0.852 & 0.921 & NaN & NaN & 1.000 & NaN & 0.833 & 1.000 & 1.000 & 1.000 \\ +dependencyAnalysisTests/precisionTests/permissions & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.892 & 1.000 & 0.746 & 1.000 & 0.967 & 1.000 & 0.967 & 1.000 & 1.000 & 0.767 & 1.000 & NaN & NaN & 0.956 & 0.783 & 0.738 & 1.000 & 1.000 & 1.000 \\ +dependencyAnalysisTests/precisionTests/predicates & 1.000 & 0.822 & 0.870 & 1.000 & 1.000 & 1.000 & 1.000 & 0.987 & 1.000 & 0.759 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.923 & 0.796 & 0.876 & NaN & NaN & 1.000 & 0.987 & 0.987 & 1.000 & 1.000 & 1.000 \\ +dependencyAnalysisTests/precisionTests/quantifiedPermissions & 1.000 & 0.917 & 0.943 & 1.000 & 1.000 & 1.000 & 1.000 & 0.902 & 1.000 & 0.754 & 1.000 & 0.917 & NaN & 0.907 & 1.000 & 0.919 & 0.824 & 0.908 & NaN & NaN & 0.938 & 0.829 & 0.754 & 1.000 & NaN & 0.957 \\ From ecc61095b575c69928a01313ad8f30dc435f250a Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 1 Sep 2025 11:58:50 +0200 Subject: [PATCH 241/474] add benchmark query to command line tool --- .../AssumptionAnalysisUserTool.scala | 41 ++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala index 3774b9781..e75231847 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala @@ -4,6 +4,7 @@ import viper.silicon.assumptionAnalysis.{AssumptionAnalysisInterpreter, Assumpti import viper.silver.ast import viper.silver.ast.Method +import java.io.PrintWriter import scala.annotation.tailrec import scala.io.StdIn.readLine @@ -51,8 +52,10 @@ class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpr handleProofCoverageQuery(inputParts.tail) }else if (inputParts.head.equalsIgnoreCase("covLines") || inputParts.head.equalsIgnoreCase("covL")) { handleProofCoverageLineQuery(inputParts.tail) - }else if(inputParts.head.equalsIgnoreCase("prune")){ + }else if(inputParts.head.equalsIgnoreCase("prune")) { handlePruningRequest(inputParts.tail) + }else if(inputParts.head.equalsIgnoreCase("benchmark")){ + handleBenchmarkQuery() } else { println("Invalid input.") println(infoString) @@ -181,4 +184,40 @@ class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpr fullGraphInterpreter.pruneProgramAndExport(crucialNodes, program, exportFileName) println("Done.") } + + private def handleBenchmarkQuery(): Unit = { + val N = 12 + var check = true + println("Result file name: ") + val exportFileName = readLine() + val writer = new PrintWriter(exportFileName) + writer.println("queried line,#deps,runtimes [ms]") + + while(check){ + println("enter line number(s) for query or 'q' to quit") + val userInput = readLine() + if(userInput.equalsIgnoreCase("q")){ + println("Quit.") + check = false + }else{ + val inputs = userInput.split(" ").toSet + + val queriedNodes = getQueriedNodesFromInput(inputs) + var allTimes = Seq.empty[Double] + var numDeps = 0 + + for (_ <- 0 to N) { + val (allDependencies, time) = measureTime[Set[AssumptionAnalysisNode]](fullGraphInterpreter.getAllNonInternalDependencies(queriedNodes.map(_.id))) + allTimes = allTimes :+ time + numDeps = allDependencies.size + } + + writer.println(s"$userInput,$numDeps,${allTimes.mkString(",")}") + println(s"Avg: ${allTimes.sum/allTimes.size}") + } + } + + writer.close() + + } } From e37df2e0de68ec5e160e42a69e61e8ff56fa276e Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 2 Sep 2025 11:35:39 +0200 Subject: [PATCH 242/474] fix benchmark query --- .../assumptionAnalysis/AssumptionAnalysisUserTool.scala | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala index e75231847..24f6d0003 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala @@ -191,7 +191,7 @@ class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpr println("Result file name: ") val exportFileName = readLine() val writer = new PrintWriter(exportFileName) - writer.println("queried line,#deps,runtimes [ms]") + writer.println("queried line,#lowLevelDeps,#deps,runtimes [ms]") while(check){ println("enter line number(s) for query or 'q' to quit") @@ -205,14 +205,16 @@ class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpr val queriedNodes = getQueriedNodesFromInput(inputs) var allTimes = Seq.empty[Double] var numDeps = 0 + var numLowLevelDeps = 0 for (_ <- 0 to N) { val (allDependencies, time) = measureTime[Set[AssumptionAnalysisNode]](fullGraphInterpreter.getAllNonInternalDependencies(queriedNodes.map(_.id))) allTimes = allTimes :+ time - numDeps = allDependencies.size + numLowLevelDeps = allDependencies.size + numDeps = allDependencies.groupBy(node => node.sourceInfo.getTopLevelSource.toString).size } - writer.println(s"$userInput,$numDeps,${allTimes.mkString(",")}") + writer.println(s"$userInput,$numLowLevelDeps,$numDeps,${allTimes.mkString(",")}") println(s"Avg: ${allTimes.sum/allTimes.size}") } } From 61d95ffd2a601b694c0c32e481ba34384d37f94f Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 3 Sep 2025 10:27:20 +0200 Subject: [PATCH 243/474] minor fix --- .../AssumptionAnalyzer.scala | 7 +- .../dependencyAnalysisTests/all/functions.vpr | 84 +++++++++++++++++++ 2 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 src/test/resources/dependencyAnalysisTests/all/functions.vpr diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index b0505908e..99e6588d9 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -103,9 +103,12 @@ object AssumptionAnalyzer { assumptionAnalysisInterpreters foreach (interpreter => newGraph.addNodes(interpreter.getGraph.getNodes)) assumptionAnalysisInterpreters foreach (interpreter => interpreter.getGraph.getAllEdges foreach {case (t, deps) => newGraph.addEdges(deps, t)}) - // add edges between identical axioms since they were added to each interpreter // TODO ake: merge instead? + // add edges for axioms since they were added to each interpreter newGraph.getNodes.filter(_.isInstanceOf[AxiomAssumptionNode]).groupBy(n => (n.sourceInfo.toString, n.assumptionType)).foreach{case (_, nodes) => - newGraph.addEdges(nodes.map(_.id), nodes.map(_.id)) + newGraph.addEdges(nodes.map(_.id), nodes.map(_.id)) // TODO ake: is this necessary; maybe nodes could be merged instead? + // add edges to assertions that were required to prove the axiom, e.g. to verify the function body/postcondition + val assertionNodes = newGraph.getNodes.filter(n => n.isInstanceOf[GeneralAssertionNode] && n.sourceInfo.getTopLevelSource.equals(nodes.head.sourceInfo.getTopLevelSource)) + newGraph.addEdges( assertionNodes.map(_.id), nodes.map(_.id)) } val types = Set(AssumptionType.Implicit, AssumptionType.Explicit) diff --git a/src/test/resources/dependencyAnalysisTests/all/functions.vpr b/src/test/resources/dependencyAnalysisTests/all/functions.vpr new file mode 100644 index 000000000..7ff9454a6 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/all/functions.vpr @@ -0,0 +1,84 @@ +field f: Int + +predicate greater0(x: Ref){ + acc(x.f) && x.f > 0 +} + + +function div100(n: Int): Int + requires n > 0 + ensures result >= 0 +{ + 100/n +} + +function div100Postcond(n: Int): Int + requires n > 0 + ensures result >= 0 + ensures result == 100/n + + +function foo(x: Ref): Int + requires greater0(x) +{ + unfolding greater0(x) in x.f +} + +function fooPostcond(x: Ref): Int + requires greater0(x) + ensures result == unfolding greater0(x) in x.f + + +method predicateClient(x: Ref) + requires acc(x.f) +{ + var a: Int + @dependency("Implicit") + x.f := 10 + @dependency("Rewrite") + fold greater0(x) + @dependency("Implicit") + a := foo(x) + + @testAssertion("Explicit") + assert a == 10 +} + +method predicateClientPostcond(x: Ref) + requires acc(x.f) +{ + var a: Int + @dependency("Implicit") + x.f := 10 + @dependency("Rewrite") + fold greater0(x) + @dependency("Explicit") + a := fooPostcond(x) + + @testAssertion("Explicit") + assert a == 10 +} + + +method callDiv(){ + var x: Int + @dependency("Explicit") + assume x > 10 + @dependency("Implicit") + x := div100(x) + + @testAssertion("Explicit") + assert x < 10 +} + +method callDivPostcond(){ + var x: Int + @dependency("Explicit") + assume x > 10 + @dependency("Explicit") + x := div100Postcond(x) + + @testAssertion("Explicit") + assert x < 10 +} + From d975eb6abdf8e86a14d7b9c1c63a72ccfd13a616 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 4 Sep 2025 14:17:24 +0200 Subject: [PATCH 244/474] add queries for benchmarks --- .../AssumptionAnalysisInterpreter.scala | 5 +++- .../AssumptionAnalysisUserTool.scala | 25 ++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala index 3f5b47f87..ae50ea006 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala @@ -63,7 +63,7 @@ class AssumptionAnalysisInterpreter(name: String, graph: ReadOnlyAssumptionAnaly nodesToAnalyze.intersect(getNonInternalAssumptionNodes) .exists(node => graph.existsAnyDependency(Set(node.id), nodesToAnalyze map (_.id) filter (_ != node.id), includeInfeasibilityNodes)) - private def getNonInternalAssumptionNodesPerSource: Map[String, Set[AssumptionAnalysisNode]] = + def getNonInternalAssumptionNodesPerSource: Map[String, Set[AssumptionAnalysisNode]] = getNonInternalAssumptionNodes.groupBy(_.sourceInfo.getTopLevelSource.toString) @@ -72,6 +72,9 @@ class AssumptionAnalysisInterpreter(name: String, graph: ReadOnlyAssumptionAnaly || AssumptionType.postconditionTypes.contains(node.assumptionType) ) + def getNonInternalAssertionNodesPerSource: Map[String, Set[AssumptionAnalysisNode]] = + getNonInternalAssertionNodes.groupBy(_.sourceInfo.getTopLevelSource.toString) + def getExplicitAssertionNodes: Set[AssumptionAnalysisNode] = getNonInternalAssertionNodes.filter(node => AssumptionType.explicitAssertionTypes.contains(node.assumptionType)) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala index 24f6d0003..d0e200367 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala @@ -54,14 +54,35 @@ class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpr handleProofCoverageLineQuery(inputParts.tail) }else if(inputParts.head.equalsIgnoreCase("prune")) { handlePruningRequest(inputParts.tail) - }else if(inputParts.head.equalsIgnoreCase("benchmark")){ + }else if(inputParts.head.equalsIgnoreCase("benchmark")) { handleBenchmarkQuery() + }else if(inputParts.head.equalsIgnoreCase("graphSize")){ + if(inputParts.tail.isEmpty) { + handleGraphSizeQuery(fullGraphInterpreter) + }else{ + memberInterpreters.filter(aa => aa.getMember.isDefined && aa.getMember.exists { + case meth: Method => meth.body.isDefined && inputParts.tail.contains(meth.name) + case func: ast.Function => func.body.isDefined && inputParts.tail.contains(func.name) + case _ => false + }) + .foreach(aa => handleGraphSizeQuery(aa)) + } } else { println("Invalid input.") println(infoString) } } + private def handleGraphSizeQuery(interpreter: AssumptionAnalysisInterpreter): Unit = { + val assumptions = interpreter.getNonInternalAssumptionNodesPerSource + val assertions = interpreter.getNonInternalAssertionNodesPerSource + val nodes = interpreter.getNonInternalAssertionNodes.union(interpreter.getNonInternalAssumptionNodes).groupBy(_.sourceInfo.getTopLevelSource.toString) + println(s"#Assumptions = ${assumptions.size}") + println(s"#Assertions = ${assertions.size}") + println(s"#Nodes = ${nodes.size}") + println("Done.") + } + private def handleProofCoverageQuery(memberNames: Seq[String]): Unit = { println("Proof Coverage") memberInterpreters.filter(aa => aa.getMember.isDefined && aa.getMember.exists { @@ -75,6 +96,7 @@ class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpr println(s"coverage: $coverage") if (!coverage.equals(1.0)) println(s"uncovered nodes:\n\t${uncoveredSources.mkString("\n\t")}") + println(s"#uncovered nodes:\n\t${uncoveredSources.size}") }) println("Done.") } @@ -100,6 +122,7 @@ class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpr println(s"coverage: $coverage") if (!coverage.equals(1.0)) println(s"uncovered nodes:\n\t${uncoveredSources.mkString("\n\t")}") + println(s"#uncovered nodes:\n\t${uncoveredSources.size}") }) println("Done.") } From d652033b921a17bee9a7a5ffcac44bb140a1aaf0 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 4 Sep 2025 17:15:20 +0200 Subject: [PATCH 245/474] minor fix --- .../assumptionAnalysis/AssumptionAnalysisUserTool.scala | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala index d0e200367..443f51ea3 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala @@ -1,6 +1,6 @@ package silicon.viper.assumptionAnalysis -import viper.silicon.assumptionAnalysis.{AssumptionAnalysisInterpreter, AssumptionAnalysisNode} +import viper.silicon.assumptionAnalysis.{AssumptionAnalysisInterpreter, AssumptionAnalysisNode, AxiomAssumptionNode} import viper.silver.ast import viper.silver.ast.Method @@ -74,9 +74,10 @@ class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpr } private def handleGraphSizeQuery(interpreter: AssumptionAnalysisInterpreter): Unit = { - val assumptions = interpreter.getNonInternalAssumptionNodesPerSource + val allAssumptions = interpreter.getNonInternalAssumptionNodes.filter(n => !n.isInstanceOf[AxiomAssumptionNode]) + val assumptions = allAssumptions.groupBy(_.sourceInfo.getTopLevelSource.toString) val assertions = interpreter.getNonInternalAssertionNodesPerSource - val nodes = interpreter.getNonInternalAssertionNodes.union(interpreter.getNonInternalAssumptionNodes).groupBy(_.sourceInfo.getTopLevelSource.toString) + val nodes = interpreter.getNonInternalAssertionNodes.union(allAssumptions).groupBy(_.sourceInfo.getTopLevelSource.toString) println(s"#Assumptions = ${assumptions.size}") println(s"#Assertions = ${assertions.size}") println(s"#Nodes = ${nodes.size}") From fc9ecde51438ea60706ab35ab72a563ae6986187 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 5 Sep 2025 17:48:39 +0200 Subject: [PATCH 246/474] fix benchmarks, add results --- .../all/implicitUpperBounds.vpr | 48 +++++++++++++++++++ .../all/imprecision.vpr | 48 +++++++------------ .../benchmark_scripts/snippets.txt | 27 ++++++++--- .../precisionTests/results/result_table.out | 26 +++++----- .../unitTests/inhaleExhale.vpr | 4 +- .../unitTests/magicWands.vpr | 4 +- .../unitTests/permissions.vpr | 10 ++-- .../unitTests/quantifiedPermissions.vpr | 4 +- 8 files changed, 111 insertions(+), 60 deletions(-) create mode 100644 src/test/resources/dependencyAnalysisTests/all/implicitUpperBounds.vpr diff --git a/src/test/resources/dependencyAnalysisTests/all/implicitUpperBounds.vpr b/src/test/resources/dependencyAnalysisTests/all/implicitUpperBounds.vpr new file mode 100644 index 000000000..eaae7afce --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/all/implicitUpperBounds.vpr @@ -0,0 +1,48 @@ +field f: Int + +method implicitUpperBounds(x: Ref, y: Ref) + requires @dependency("Explicit")(acc(x.f)) + requires @dependency("Explicit")(acc(y.f, wildcard)) +{ + var z: Ref + @irrelevant("Explicit") + inhale true --* acc(z.f) + @irrelevant("Rewrite") + apply true --* acc(z.f) + + + @testAssertion("Explicit") + assert x != y +} + +method implicitUpperBounds_quantified(gen_xs: Seq[Ref], y: Ref) + requires @dependency("Explicit")(|gen_xs|> 2) + requires @dependency("Explicit")(forall gen_xi: Ref :: gen_xi in gen_xs ==> acc(gen_xi.f)) + requires @dependency("Explicit")(acc(y.f, wildcard)) +{ + var z: Ref + @irrelevant("Explicit") + inhale true --* acc(z.f) + @irrelevant("Rewrite") + apply true --* acc(z.f) + + + @testAssertion("Explicit") + assert gen_xs[0] != y +} + +method implicitUpperBounds_quantified_2(gen_xs: Seq[Ref], y: Ref) + requires @dependency("Explicit")(|gen_xs|> 2) + requires @dependency("Explicit")(forall gen_xi: Ref :: gen_xi in gen_xs ==> acc(gen_xi.f)) + requires @dependency("Explicit")(acc(y.f, 1/2)) +{ + var z: Ref + @irrelevant("Explicit") + inhale true --* acc(z.f) + @irrelevant("Rewrite") + apply true --* acc(z.f) + + + @testAssertion("Explicit") + assert gen_xs[0] != y +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr b/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr index 9be3df616..d203bbbf9 100644 --- a/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr @@ -139,35 +139,6 @@ method quasihavoc1(x: Ref, y: Ref) assert x.f == 3 } -// imprecision due to non-uniqueness of unsat core -// in this example the loop condition and invariant res >= 0 would be enough -// to prove the invariant res >= 0. However, the SMT solver chooses to prove it -// via the invariant i>=0 -// interestingly, proving "res >= 0 is preserved by the loop body" has the following unsat core -// old(res) >= 0, res = old(res) + old(i), i = old(i) - 1, i >= 0 -// the last one comes from the asserting that invariant i >= 0 is preserved! -// the last two terms could be replaced by old(i) > 0 (the loop condition) -method loop1(){ - var i: Int - var res: Int - @dependency("Implicit") - res := 0 - - @irrelevant("Implicit") - i := 10 - while(@dependency("PathCondition")(i > 0)) - invariant @irrelevant("LoopInvariant")(i <= 10) - invariant @irrelevant("LoopInvariant")(i >= 0) - invariant @dependency("LoopInvariant")(res >= 0) - { - @dependency("Implicit") - res := res + i - @irrelevant("Implicit") - i := i - 1 - } - @testAssertion("Explicit") - assert res >= 0 -} method inhaleImprecision(){ var x: Ref @@ -196,4 +167,21 @@ method packageExhale2(x: Ref) @testAssertion("Explicit") exhale acc(x.f) -} \ No newline at end of file +} + +method quantifiedFieldAssign(){ + var a: Int + var idx: Int + var xs: Seq[Ref] + + @irrelevant("Explicit") + inhale |xs| > 2 // imprecise + @irrelevant("Implicit") + idx := 0 // imprecise + inhale forall xi: Ref :: xi in xs ==> acc(xi.f) + @irrelevant("Implicit") + xs[idx].f := a // internal dependency + + @testAssertion("Explicit") + exhale acc(xs[0].f) +} diff --git a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/snippets.txt b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/snippets.txt index 07b425ea5..a9bc8b4c1 100644 --- a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/snippets.txt +++ b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/snippets.txt @@ -62,6 +62,11 @@ SNIPPET 02_assert_invariant$=${ assert $INVARIANT && gen_i > 0 } +SNIPPET 02_2_assert_invariant$=${ + @irrelevant() + assert $INVARIANT +} + SNIPPET 03_assignment_pure$=${ @irrelevant("Implicit") $RW_INT_PURE_0 := $RO_INT_0 + $RO_INT_1 @@ -72,6 +77,13 @@ SNIPPET 04_assignment_field$=${ $RW_INT_FIELD_0 := $RO_INT_0 + $RO_INT_1 } +SNIPPET 04_2_assignment_field_disjoint_field$=${ + @irrelevant("Implicit") + inhale acc($RO_REF_F_0.gen_f) + @irrelevant("Implicit") + $RO_REF_F_0.gen_f := $RO_INT_0 + $RO_INT_1 +} + SNIPPET 05_assignment_disjoint_ref$=${ var gen_x: Ref // @irrelevant("Explicit") // TODO ake: aliasing! @@ -152,11 +164,8 @@ SNIPPET 10_function_add_pure$=${ SNIPPET 11_function_add_impure$=${ - var gen_ref_0: Ref - @irrelevant("Implicit") - gen_ref_0 := new(f) @irrelevant("Implicit") - gen_ref_0.f := gen_add_impure(gen_ref_0, $RO_INT_0) + $RW_INT_0 := gen_add_impure($RW_REF_F_0, $RO_INT_0) } SNIPPET 12_method_call_pure$=${ @@ -166,8 +175,7 @@ SNIPPET 12_method_call_pure$=${ SNIPPET 13_method_call_impure$=${ var gen_ref_0: Ref - @irrelevant("Implicit") - gen_ref_0 := new(f) + gen_ref_0 := new(f) // might be used for non-aliasing proof @irrelevant("Implicit") incr(gen_ref_0) } @@ -299,6 +307,13 @@ SNIPPET 21_quasihavoc$=${ quasihavoc $RW_INT_FIELD_0 } +SNIPPET 21_2_quasihavoc_disjoint_field$=${ + @irrelevant("Implicit") + inhale acc($RO_REF_F_0.gen_f) + @irrelevant("Implicit") + quasihavoc $RO_REF_F_0.gen_f +} + SNIPPET 22_quasihavoc_disjoint_ref$=${ var gen_x: Ref @irrelevant("Implicit") diff --git a/src/test/resources/dependencyAnalysisTests/precisionTests/results/result_table.out b/src/test/resources/dependencyAnalysisTests/precisionTests/results/result_table.out index 25022c3e8..c16ba36e6 100644 --- a/src/test/resources/dependencyAnalysisTests/precisionTests/results/result_table.out +++ b/src/test/resources/dependencyAnalysisTests/precisionTests/results/result_table.out @@ -1,13 +1,13 @@ - & 00_baseline & 01_inhale_invariant & 02_assert_invariant & 03_assignment_pure & 04_assignment_field & 05_assignment_disjoint_ref & 06_assignment_disjoint_ref_disjoint_field & 07_assignment_disjoint_ref_quantified & 08_assignment_disjoint_ref_disjoint_field_quantified & 09_fold_unfold_with_implication & 10_function_add_pure & 11_function_add_impure & 12_method_call_pure & 13_method_call_impure & 14_branch & 15_branch_nested & 16_branch_infeasible & 17_branch_function & 18_while_pure & 19_while_perm & 20_magic_wand & 21_quasihavoc & 22_quasihavoc_disjoint_ref & 23_quasihavoc_disjoint_ref_disjoint_field & 24_goto_pure & 25_unrelated_sync_points -dependencyAnalysisTests/precisionTests/branches & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.852 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.932 & 0.871 & 0.762 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 \\ -dependencyAnalysisTests/precisionTests/domain & 1.000 & 0.963 & 0.972 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.887 & 0.989 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.886 & 1.000 & 1.000 & 1.000 & 1.000 & 0.960 & 1.000 & 1.000 & 1.000 & 1.000 \\ -dependencyAnalysisTests/precisionTests/function-call & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.894 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.927 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 \\ -dependencyAnalysisTests/precisionTests/inhaleExhale & 1.000 & 0.963 & 0.972 & 1.000 & 1.000 & 1.000 & 1.000 & 0.891 & 1.000 & 0.889 & 1.000 & 0.972 & 1.000 & 0.972 & 1.000 & 1.000 & 0.917 & 0.944 & NaN & NaN & 0.944 & 0.928 & 0.867 & 1.000 & 1.000 & 1.000 \\ -dependencyAnalysisTests/precisionTests/loops & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.863 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.891 & 0.940 & NaN & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & NaN & 1.000 \\ -dependencyAnalysisTests/precisionTests/magicWands & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.981 & 1.000 & 0.838 & 1.000 & 0.974 & 1.000 & 0.974 & 1.000 & 1.000 & 0.881 & 1.000 & NaN & NaN & 0.956 & 0.970 & 0.962 & 1.000 & 1.000 & 1.000 \\ -dependencyAnalysisTests/precisionTests/method-call & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.899 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.929 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 \\ -dependencyAnalysisTests/precisionTests/misc & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.938 & 1.000 & 0.950 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.958 & 1.000 & 1.000 & 1.000 & 1.000 & 0.958 & 0.958 & 1.000 & 1.000 & 1.000 \\ -dependencyAnalysisTests/precisionTests/permWildcard & 1.000 & 1.000 & 1.000 & 1.000 & NaN & 1.000 & 1.000 & 0.907 & 1.000 & 0.833 & 1.000 & 0.963 & 1.000 & 0.963 & 1.000 & 1.000 & 0.852 & 0.921 & NaN & NaN & 1.000 & NaN & 0.833 & 1.000 & 1.000 & 1.000 \\ -dependencyAnalysisTests/precisionTests/permissions & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.892 & 1.000 & 0.746 & 1.000 & 0.967 & 1.000 & 0.967 & 1.000 & 1.000 & 0.767 & 1.000 & NaN & NaN & 0.956 & 0.783 & 0.738 & 1.000 & 1.000 & 1.000 \\ -dependencyAnalysisTests/precisionTests/predicates & 1.000 & 0.822 & 0.870 & 1.000 & 1.000 & 1.000 & 1.000 & 0.987 & 1.000 & 0.759 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.923 & 0.796 & 0.876 & NaN & NaN & 1.000 & 0.987 & 0.987 & 1.000 & 1.000 & 1.000 \\ -dependencyAnalysisTests/precisionTests/quantifiedPermissions & 1.000 & 0.917 & 0.943 & 1.000 & 1.000 & 1.000 & 1.000 & 0.902 & 1.000 & 0.754 & 1.000 & 0.917 & NaN & 0.907 & 1.000 & 0.919 & 0.824 & 0.908 & NaN & NaN & 0.938 & 0.829 & 0.754 & 1.000 & NaN & 0.957 \\ + & 00_baseline & 01_inhale_invariant & 02_2_assert_invariant & 02_assert_invariant & 03_assignment_pure & 04_2_assignment_field_disjoint_field & 04_assignment_field & 05_assignment_disjoint_ref & 06_assignment_disjoint_ref_disjoint_field & 07_assignment_disjoint_ref_quantified & 08_assignment_disjoint_ref_disjoint_field_quantified & 09_fold_unfold_with_implication & 10_function_add_pure & 11_function_add_impure & 12_method_call_pure & 13_method_call_impure & 14_branch & 15_branch_nested & 16_branch_infeasible & 17_branch_function & 18_while_pure & 19_while_perm & 20_magic_wand & 21_2_quasihavoc_disjoint_field & 21_quasihavoc & 22_quasihavoc_disjoint_ref & 23_quasihavoc_disjoint_ref_disjoint_field & 24_goto_pure & 25_unrelated_sync_points +dependencyAnalysisTests/precisionTests/branches & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.852 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.932 & 0.871 & 0.762 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 \\ +dependencyAnalysisTests/precisionTests/domain & 1.000 & 0.963 & 1.000 & 0.972 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.887 & 0.989 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.886 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.960 & 1.000 & 1.000 & 1.000 & 1.000 \\ +dependencyAnalysisTests/precisionTests/function-call & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.894 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.927 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 \\ +dependencyAnalysisTests/precisionTests/inhaleExhale & 1.000 & 0.963 & 1.000 & 0.972 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.891 & 1.000 & 0.889 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.917 & 0.944 & 1.000 & 1.000 & 0.944 & 1.000 & 0.898 & 0.867 & 1.000 & 1.000 & 1.000 \\ +dependencyAnalysisTests/precisionTests/loops & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.863 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.891 & 0.940 & NaN & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & NaN & 1.000 \\ +dependencyAnalysisTests/precisionTests/magicWands & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.981 & 1.000 & 0.838 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.881 & 1.000 & NaN & NaN & 0.956 & 1.000 & 0.958 & 0.962 & 1.000 & 1.000 & 1.000 \\ +dependencyAnalysisTests/precisionTests/method-call & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.899 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.929 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 \\ +dependencyAnalysisTests/precisionTests/misc & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.938 & 1.000 & 0.950 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.958 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.958 & 0.938 & 1.000 & 1.000 & 1.000 \\ +dependencyAnalysisTests/precisionTests/permWildcard & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & NaN & 1.000 & 1.000 & 0.891 & 1.000 & 0.833 & 1.000 & NaN & 1.000 & 1.000 & 1.000 & 1.000 & 0.852 & 0.921 & NaN & NaN & 1.000 & 1.000 & NaN & 0.776 & 1.000 & 1.000 & 1.000 \\ +dependencyAnalysisTests/precisionTests/permissions & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.892 & 1.000 & 0.746 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.767 & 1.000 & NaN & NaN & 0.956 & 1.000 & 0.723 & 0.665 & 1.000 & 1.000 & 1.000 \\ +dependencyAnalysisTests/precisionTests/predicates & 1.000 & 0.822 & 0.967 & 0.870 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.987 & 1.000 & 0.759 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.923 & 0.796 & 0.876 & NaN & NaN & 1.000 & 1.000 & 0.987 & 0.978 & 1.000 & 1.000 & 1.000 \\ +dependencyAnalysisTests/precisionTests/quantifiedPermissions & 1.000 & 0.917 & 1.000 & 0.943 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.902 & 1.000 & 0.750 & 1.000 & 1.000 & NaN & 0.988 & 1.000 & 0.919 & 0.819 & 0.908 & NaN & NaN & 0.938 & 1.000 & 0.798 & 0.685 & 1.000 & NaN & 0.957 \\ diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/inhaleExhale.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/inhaleExhale.vpr index ebacec6b4..6034d82bb 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/inhaleExhale.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/inhaleExhale.vpr @@ -22,7 +22,7 @@ method exhale1(){ exhale acc(x.f, 1/2) - // $PrecisionTest: $READ_ONLY=x.f $INVARIANT=x.f>0 $ACC_INVARIANT=acc(x.f,1/2) + // $PrecisionTest: $READ_ONLY=x.f $INVARIANT=x.f>0 $ACC_INVARIANT=acc(x.f,1/4) @testAssertion("Explicit") assert x.f > 0 @@ -38,7 +38,7 @@ method inhale1(){ inhale acc(x.f, 1/2) - // $PrecisionTest: $READ_ONLY=x.f $INVARIANT=x.f>0 $ACC_INVARIANT=acc(x.f) + // $PrecisionTest: $READ_ONLY=x.f $INVARIANT=x.f>0 $ACC_INVARIANT=acc(x.f,1/2) @testAssertion("Explicit") assert x.f > 0 diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr index 01d6d5b74..0be3f1298 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr @@ -38,7 +38,7 @@ method basicPackage(l: Ref) @dependency("Implicit") tmp := l.next - // $PrecisionTest: $READ_WRITE=l.val $ACC_INVARIANT=acc(l.val)&&acc(l.next) + // $PrecisionTest: $READ_WRITE=l.val $ACC_INVARIANT=acc(l.val)&&acc(l.next, 1/2) @dependency("Rewrite") package list(tmp) --* list(l) @@ -131,7 +131,7 @@ method packageExhale(x: Ref) @dependency("Implicit") package acc(x.f, 1/2) --* acc(x.f) - // $PrecisionTest: $READ_ONLY=x.f $ACC_INVARIANT=acc(x.f,1/2) + // $PrecisionTest: $READ_ONLY=x.f $ACC_INVARIANT=acc(x.f,1/4) @testAssertion("Explicit") diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr index 83b303723..03aa875ef 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr @@ -3,7 +3,7 @@ field f: Int method currentPerm(x: Ref) requires @dependency("Explicit")(acc(x.f, 1/2)) { - // $PrecisionTest: $READ_ONLY=x.f $ACC_INVARIANT=acc(x.f,1/2) + // $PrecisionTest: $READ_ONLY=x.f $ACC_INVARIANT=acc(x.f,1/4) @testAssertion("Explicit") assert perm(x.f) > 1/4 @@ -25,7 +25,7 @@ method perm2(){ @dependency("Explicit") inhale acc(x.f, 1/2) - // $PrecisionTest: $READ_ONLY=x.f $ACC_INVARIANT=acc(x.f,1/2) + // $PrecisionTest: $READ_ONLY=x.f $ACC_INVARIANT=acc(x.f,1/4) @testAssertion("Explicit") inhale x.f > 0 @@ -53,7 +53,7 @@ method perm4(){ @dependency("Implicit") x.f := x.f + 1 - // $PrecisionTest: $READ_ONLY=x.f $INVARIANT=x.f>0 $ACC_INVARIANT=acc(x.f,1/2) + // $PrecisionTest: $READ_ONLY=x.f $INVARIANT=x.f>0 $ACC_INVARIANT=acc(x.f,1/4) @testAssertion("Explicit") assert x.f > 1 @@ -79,7 +79,7 @@ method permAmount1(x: Ref, p: Perm) @dependency("Explicit") assume p > 1/2 - // $PrecisionTest: $READ_ONLY=x.f $ACC_INVARIANT=p>1/2&&acc(x.f,p) + // $PrecisionTest: $READ_ONLY=x.f $ACC_INVARIANT=p>1/2&&acc(x.f,p/2) @testAssertion("Explicit") exhale acc(x.f, 1/2) @@ -107,7 +107,7 @@ method noAlias(a: Ref, b: Ref, c: Ref) requires @dependency("Explicit")(acc(b.f, 1/2)) requires @irrelevant("Explicit")(acc(c.f, 1/2)) { - // $PrecisionTest: $READ_WRITE=a.f $READ_ONLY=b.f,c.f $ACC_INVARIANT=acc(a.f)&&acc(b.f,1/2)&&acc(c.f,1/2) + // $PrecisionTest: $READ_WRITE=a.f $READ_ONLY=b.f,c.f $ACC_INVARIANT=acc(a.f)&&acc(b.f,1/4)&&acc(c.f,1/4) @testAssertion("Explicit") assert a != b diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr index 4967783b7..93c4ed974 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr @@ -55,7 +55,7 @@ method quantifiedExhalePartially(xs: Seq[Ref]) { exhale forall x: Ref :: x in xs ==> acc(x.f, 1/2) - // $PrecisionTest: $READ_WRITE=res $READ_ONLY=xs[1].f,xs[0].f,xs[2].f $INVARIANT=|xs|>0 $ACC_INVARIANT=forall$_$x:Ref::x$_$in$_$xs$_$==>$_$acc(x.f,1/2) + // $PrecisionTest: $READ_WRITE=res $READ_ONLY=xs[1].f,xs[0].f,xs[2].f $INVARIANT=|xs|>0 $ACC_INVARIANT=forall$_$x:Ref::x$_$in$_$xs$_$==>$_$acc(x.f,1/4) @testAssertion("Implicit") res := xs[1].f + 1 @@ -113,7 +113,7 @@ method quantifiedPermWrite3(xs: Seq[Ref]) { @irrelevant("Implicit") xs[0].f := 0 - // $PrecisionTest: $READ_WRITE=xs[0].f $READ_ONLY=xs[1].f,xs[2].f $INVARIANT=xs[1].f>=0 $ACC_INVARIANT=|xs|>1&&acc(xs[0].f,3/4)&&(forall$_$x:Ref::x$_$in$_$xs$_$==>$_$acc(x.f,1/4)) + // $PrecisionTest: $READ_WRITE=xs[0].f $READ_ONLY=xs[1].f,xs[2].f $INVARIANT=xs[1].f>=0 $ACC_INVARIANT=|xs|>1&&acc(xs[0].f,1/2)&&(forall$_$x:Ref::x$_$in$_$xs$_$==>$_$acc(x.f,1/4)) @testAssertion("Explicit") assert xs[0] != xs[1] ==> xs[1].f > 0 From 72e7a62ecf0ec74c14ad10f724b556247a4b0049 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 5 Sep 2025 17:49:15 +0200 Subject: [PATCH 247/474] add debug config option --- src/main/scala/Config.scala | 6 ++++++ src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/scala/Config.scala b/src/main/scala/Config.scala index 3c0df05d5..725adfc16 100644 --- a/src/main/scala/Config.scala +++ b/src/main/scala/Config.scala @@ -836,6 +836,12 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { noshort = true ) + val enableAssumptionAnalysisDebugging: ScallopOption[Boolean] = opt[Boolean]("enableAssumptionAnalysisDebugging", + descr = "Enable debugging for assumption analysis mode", + default = Some(false), + noshort = true + ) + val disableInfeasibilityChecks: ScallopOption[Boolean] = opt[Boolean]("disableInfeasibilityChecks", descr = "Disable infeasibility checks. As a consequence all paths will be explored to the end. (Potentially) huge performance overhead!", default = Some(false), diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 99e6588d9..42c4f595f 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -3,6 +3,7 @@ package viper.silicon.assumptionAnalysis import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType import viper.silicon.interfaces.state.{Chunk, GeneralChunk} import viper.silicon.state.terms._ +import viper.silicon.verifier.Verifier import viper.silver.ast import scala.collection.mutable @@ -282,7 +283,7 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { override def buildFinalGraph(): Option[AssumptionAnalysisGraph] = { assumptionGraph.removeLabelNodes() - val mergedGraph = buildAndGetMergedGraph() + val mergedGraph = if(Verifier.config.enableAssumptionAnalysisDebugging()) assumptionGraph else buildAndGetMergedGraph() mergedGraph.addTransitiveEdges() Some(mergedGraph) } From 99b979654fa22700a0336ba3609850f346d99434 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 5 Sep 2025 21:05:20 +0200 Subject: [PATCH 248/474] add test cases --- .../all/function_vs_method.vpr | 45 ++++++++++++++ .../all/invariant_ordering_imprecise.vpr | 52 ++++++++++++++++ .../dependencyAnalysisTests/all/unfolding.vpr | 60 +++++++++++++++++++ 3 files changed, 157 insertions(+) create mode 100644 src/test/resources/dependencyAnalysisTests/all/function_vs_method.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/all/invariant_ordering_imprecise.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/all/unfolding.vpr diff --git a/src/test/resources/dependencyAnalysisTests/all/function_vs_method.vpr b/src/test/resources/dependencyAnalysisTests/all/function_vs_method.vpr new file mode 100644 index 000000000..34aa3099e --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/all/function_vs_method.vpr @@ -0,0 +1,45 @@ + +field f: Int + +function add(x: Ref, a: Int): Int + requires acc(x.f) + { + x.f + a + } + + method add_M(x: Ref, a: Int) returns(res: Int) + requires acc(x.f) + ensures acc(x.f) + ensures res == x.f + a + { + res := x.f + a + } + +method client_func(x: Ref, a: Int) + requires @dependency("Explicit")(acc(x.f)) + { + var b: Int + @irrelevant("Implicit") + b := add(x, a) + add(x, a) + + @irrelevant("Implicit") + x.f := 0 + + @testAssertion("Explicit") + exhale acc(x.f) +} + +method client_meth(x: Ref, a: Int) + requires @dependency("Explicit")(acc(x.f)) +{ + var b: Int + @dependency("Implicit") + b := add_M(x, a) + + @irrelevant("Implicit") + x.f := 0 + + @testAssertion("Explicit") + exhale acc(x.f) +} + diff --git a/src/test/resources/dependencyAnalysisTests/all/invariant_ordering_imprecise.vpr b/src/test/resources/dependencyAnalysisTests/all/invariant_ordering_imprecise.vpr new file mode 100644 index 000000000..81f40a18c --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/all/invariant_ordering_imprecise.vpr @@ -0,0 +1,52 @@ + +// imprecision due to non-uniqueness of unsat core +// in this example the loop condition and invariant res >= 0 would be enough +// to prove the invariant res >= 0. However, the SMT solver chooses to prove it +// via the invariant i>=0 +// interestingly, proving "res >= 0 is preserved by the loop body" has the following unsat core +// old(res) >= 0, res = old(res) + old(i), i = old(i) - 1, i >= 0 +// the last one comes from the asserting that invariant i >= 0 is preserved! +// the last two terms could be replaced by old(i) > 0 (the loop condition) +method loop1(){ + var i: Int + var res: Int + @dependency("Implicit") + res := 0 + + @irrelevant("Implicit") + i := 10 + while(@dependency("PathCondition")(i > 0)) + invariant @irrelevant("LoopInvariant")(i <= 10) + invariant @irrelevant("LoopInvariant")(i >= 0) + invariant @dependency("LoopInvariant")(res >= 0) + { + @dependency("Implicit") + res := res + i + @irrelevant("Implicit") + i := i - 1 + } + @testAssertion("Explicit") + assert res >= 0 +} + +method loop1_reordered(){ + var i: Int + var res: Int + @dependency("Implicit") + res := 0 + + @irrelevant("Implicit") + i := 10 + while(@dependency("PathCondition")(i > 0)) + invariant @dependency("LoopInvariant")(res >= 0) + invariant @irrelevant("LoopInvariant")(i <= 10) + invariant @irrelevant("LoopInvariant")(i >= 0) + { + @dependency("Implicit") + res := res + i + @irrelevant("Implicit") + i := i - 1 + } + @testAssertion("Explicit") + assert res >= 0 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/unfolding.vpr b/src/test/resources/dependencyAnalysisTests/all/unfolding.vpr new file mode 100644 index 000000000..6dc918fb7 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/all/unfolding.vpr @@ -0,0 +1,60 @@ +field f: Int + +predicate pred(x: Ref) +{ + acc(x.f) && x.f > 0 +} + + method foo(x: Ref) returns(res: Int) + requires pred(x) + ensures pred(x) + ensures res > 0 + { + unfold pred(x) + res := x.f + fold pred(x) + } + + +method client1(x: Ref) +{ + var b: Int + @dependency("Explicit") + inhale acc(x.f) + @dependency("Explicit") + inhale x.f > 0 + @dependency("Rewrite") + fold pred(x) + + @dependency("Implicit") + b := foo(x) + + @irrelevant("Implicit") + b := unfolding pred(x) in x.f + + @irrelevant() + assert b > 0 + + @testAssertion("Explicit") + exhale pred(x) + } + + method client2(x: Ref) + { + var b: Int + @dependency("Explicit") + inhale acc(x.f) + @dependency("Explicit") + inhale x.f > 0 + @dependency("Rewrite") + fold pred(x) + + @dependency("Implicit") + b := foo(x) + + @dependency("Implicit") + b := unfolding pred(x) in x.f + + @testAssertion("Explicit") + assert b > 0 + } \ No newline at end of file From ce672261b13d9aba79ebd8c1ef832a491e0eea98 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 5 Sep 2025 22:10:13 +0200 Subject: [PATCH 249/474] update plotter --- .../precision_benchmark_plotter.py | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_benchmark_plotter.py b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_benchmark_plotter.py index 323976e5b..84fe6573d 100644 --- a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_benchmark_plotter.py +++ b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_benchmark_plotter.py @@ -62,7 +62,32 @@ def build_table(out_file_path: str, results: dict[tuple[str, str], list[tuple[st # f.write(f"{sum(current_test_results)/len(current_test_results):.3f}".center(column_widths[idx+4])) f.write("\\\\ \n") +def build_table_transposed(out_file_path: str, results: dict[tuple[str, str], list[tuple[str, float]]]): + f = open(out_file_path, mode="w") + interference_names = sorted(set([interference_name for (_, interference_name) in results.keys()])) + base_test_names = sorted(set([base_name.strip() for (base_name, _) in results.keys()])) + base_test_names_striped = [n.replace("dependencyAnalysisTests/precisionTests/", "") for n in base_test_names] + column_1_width = max([len(h) for h in interference_names]) + 4 + column_widths = [len(h + " ") for h in (base_test_names_striped)] + f.write("".ljust(column_1_width) + " & " + " & ".join(base_test_names_striped)) + f.write("\n") + + for interference in interference_names: + f.write(interference.ljust(column_1_width)) + current_test_results = [] + for idx, base_test in enumerate(base_test_names): + f.write(" & ") + if not (base_test, interference) in results.keys(): + f.write("NaN".center(column_widths[idx])) + continue + result = results[(base_test, interference)] + avg = sum([prec for (_, prec) in result]) / len(result) + current_test_results.append(avg) + f.write(f"{avg:.3f}".center(column_widths[idx])) + f.write("\\\\ \n") + result_file_name = input("file name: ") raw_results = read_results_file("silicon\\src\\test\\resources\\dependencyAnalysisTests\\precisionTests\\results\\" + result_file_name) -build_table("silicon\\src\\test\\resources\\dependencyAnalysisTests\\precisionTests\\results\\result_table.out", raw_results) \ No newline at end of file +build_table("silicon\\src\\test\\resources\\dependencyAnalysisTests\\precisionTests\\results\\result_table.out", raw_results) +build_table_transposed("silicon\\src\\test\\resources\\dependencyAnalysisTests\\precisionTests\\results\\result_table_transposed.out", raw_results) \ No newline at end of file From 6e2b4d3c40682eec522a41fdda95f96fb2369fac Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 8 Sep 2025 18:09:46 +0200 Subject: [PATCH 250/474] more tests, minor fix --- .../assumptionAnalysis/SourceInfoStack.scala | 22 +++++++++++-- .../NonQuantifiedPropertyInterpreter.scala | 3 +- src/main/scala/rules/StateConsolidator.scala | 11 +++++-- .../dependencyAnalysisTests/all/aliasing.vpr | 22 +++++++++++++ .../dependencyAnalysisTests/all/functions.vpr | 14 ++++++++ .../all/state_consolidation.vpr | 33 +++++++++++++++++++ 6 files changed, 98 insertions(+), 7 deletions(-) create mode 100644 src/test/resources/dependencyAnalysisTests/all/state_consolidation.vpr diff --git a/src/main/scala/assumptionAnalysis/SourceInfoStack.scala b/src/main/scala/assumptionAnalysis/SourceInfoStack.scala index c3a2cd283..a5d79e796 100644 --- a/src/main/scala/assumptionAnalysis/SourceInfoStack.scala +++ b/src/main/scala/assumptionAnalysis/SourceInfoStack.scala @@ -3,6 +3,16 @@ package viper.silicon.assumptionAnalysis import viper.silicon.verifier.Verifier import viper.silver.ast.NoPosition +import java.util.concurrent.atomic.AtomicInteger + +object SourceInfoStackID { + private val idCounter: AtomicInteger = new AtomicInteger(0) + + def nextId(): Int = { + idCounter.getAndIncrement() + } +} + trait SourceInfoStack { def getAnalysisSourceInfos: List[AnalysisSourceInfo] @@ -15,13 +25,15 @@ trait SourceInfoStack { def popAnalysisSourceInfo(analysisSourceInfo: AnalysisSourceInfo): Unit - def getForcedSource: _root_.scala.Option[AnalysisSourceInfo] + def getForcedSource: Option[AnalysisSourceInfo] + + def setUniqueForcedSource(description: String): Unit - def setForcedSource(description: _root_.java.lang.String): Unit + def setForcedSource(description: String): Unit def setForcedSource(source: AnalysisSourceInfo): Unit - def setForcedSource(sourceOpt: _root_.scala.Option[AnalysisSourceInfo]): Unit + def setForcedSource(sourceOpt: Option[AnalysisSourceInfo]): Unit def removeForcedSource(): Unit } @@ -72,6 +84,10 @@ case class AnalysisSourceInfoStack() extends SourceInfoStack { forcedMainSource = Some(StringAnalysisSourceInfo(description, NoPosition)) } + override def setUniqueForcedSource(description: String): Unit = { + forcedMainSource = Some(StringAnalysisSourceInfo(description + SourceInfoStackID.nextId(), NoPosition)) + } + override def setForcedSource(source: AnalysisSourceInfo): Unit = { forcedMainSource = Some(source) } diff --git a/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala b/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala index 6a63bd8e7..1e2b760d2 100644 --- a/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala +++ b/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala @@ -121,7 +121,8 @@ class NonQuantifiedPropertyInterpreter(heap: Iterable[Chunk], verifier: Verifier info: Info): (Term, Option[ast.Exp]) = { val conditionTerm = buildPathCondition(condition, info)._1 if (verifier.decider.check(conditionTerm, Verifier.config.checkTimeout())) { - buildPathCondition(thenDo, info) + val (t, e) = buildPathCondition(thenDo, info) + (verifier.decider.wrapWithAssumptionAnalysisLabel(t, Set.empty, Set(conditionTerm)), e) // TODO ake: causes imprecision! } else { buildPathCondition(otherwise, info) } diff --git a/src/main/scala/rules/StateConsolidator.scala b/src/main/scala/rules/StateConsolidator.scala index 2b7377bfd..859c62820 100644 --- a/src/main/scala/rules/StateConsolidator.scala +++ b/src/main/scala/rules/StateConsolidator.scala @@ -175,10 +175,13 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol * nextChunk: current chunk from the sequence of new chunks/of chunks to merge into the * sequence of destination chunks */ - - findMatchingChunk(accMergedChunks, nextChunk, v) match { + val prevSource = v.decider.analysisSourceInfoStack.getForcedSource + v.decider.analysisSourceInfoStack.setUniqueForcedSource("state_consolidation") + val res = findMatchingChunk(accMergedChunks, nextChunk, v) match { case Some(ch) => - mergeChunks(fr1, ch, nextChunk, qvars, v) match { + val resMerge = mergeChunks(fr1, ch, nextChunk, qvars, v) + + resMerge match { case Some((fr2, newChunk, snapEq)) => (fr2, newChunk +: accMergedChunks.filterNot(_ == ch), newChunk +: accNewChunks, accSnapEqs + snapEq) case None => @@ -187,6 +190,8 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol case None => (fr1, nextChunk +: accMergedChunks, nextChunk +: accNewChunks, accSnapEqs) } + v.decider.analysisSourceInfoStack.setForcedSource(prevSource) + res } v.symbExLog.closeScope(sepIdentifier) result diff --git a/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr b/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr index bb2303bee..375e8028b 100644 --- a/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr @@ -44,3 +44,25 @@ method aliasing2(x: Ref, y: Ref, n: Int) } } +method alias1(a: Ref, b: Ref, c: Ref) + requires @dependency("Explicit")(acc(a.f)) + requires @irrelevant("Explicit")(acc(b.f)) +{ + var x: Int + if (@dependency("PathCondition")(c == a)) { + @dependency("Implicit") + x := c.f + } + if(@dependency("PathCondition")(c == b)) { + @irrelevant("Implicit") + x := c.f + } + + @dependency("Explicit") + assume a == c + + @testAssertion("Explicit") + assert x == a.f +} + + diff --git a/src/test/resources/dependencyAnalysisTests/all/functions.vpr b/src/test/resources/dependencyAnalysisTests/all/functions.vpr index 7ff9454a6..19fa28ced 100644 --- a/src/test/resources/dependencyAnalysisTests/all/functions.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/functions.vpr @@ -82,3 +82,17 @@ method callDivPostcond(){ assert x < 10 } +function fooInput(a: Int): Int + ensures a > 0 + ensures result > 0 + + +method client(a: Int) +{ + var res: Int + @dependency("Explicit") + res := fooInput(a) + + @testAssertion("Explicit") + assert a > 0 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/state_consolidation.vpr b/src/test/resources/dependencyAnalysisTests/all/state_consolidation.vpr new file mode 100644 index 000000000..55eae4b88 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/all/state_consolidation.vpr @@ -0,0 +1,33 @@ +field f: Int + +method stateConsolidation(x: Ref, y: Ref, z: Ref) + requires @dependency("Explicit")(acc(x.f, 1/2)) + requires @dependency("Explicit")(acc(y.f, 1/2)) + requires @irrelevant("Explicit")(acc(z.f, 1/2)) +{ + var a: Ref + @dependency("Explicit") + inhale acc(a.f, 1/2) + + @dependency("Explicit") + assume a == x + + @testAssertion("Explicit") + assert a != y // triggers state consolidation +} + + +method stateConsolidation2(x: Ref, y: Ref, z: Ref) + requires @dependency("Explicit")(acc(x.f, 1/2)) + requires @dependency("Explicit")(acc(y.f, 1/2)) + requires @irrelevant("Explicit")(acc(z.f, 1/2)) +{ + var a: Ref + @dependency("Explicit") + inhale acc(a.f, 1/2) + + @testAssertion("Explicit") + assert perm(a.f) == write +} + + From 9c33dbdeb2412b300a45276a71fe0f99c1fbcbc5 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 8 Sep 2025 18:11:01 +0200 Subject: [PATCH 251/474] update precision benchmark --- .../precision_benchmark_plotter.py | 8 +++--- .../benchmark_scripts/snippets.txt | 21 +++++++++++++-- .../precisionTests/results/result_table.out | 26 +++++++++---------- 3 files changed, 36 insertions(+), 19 deletions(-) diff --git a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_benchmark_plotter.py b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_benchmark_plotter.py index 84fe6573d..4ebde92dd 100644 --- a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_benchmark_plotter.py +++ b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_benchmark_plotter.py @@ -69,11 +69,11 @@ def build_table_transposed(out_file_path: str, results: dict[tuple[str, str], li base_test_names_striped = [n.replace("dependencyAnalysisTests/precisionTests/", "") for n in base_test_names] column_1_width = max([len(h) for h in interference_names]) + 4 column_widths = [len(h + " ") for h in (base_test_names_striped)] - f.write("".ljust(column_1_width) + " & " + " & ".join(base_test_names_striped)) - f.write("\n") + f.write("".ljust(column_1_width) + " & " + " & ".join(["\\rotatebox{90}{" + b + "}" for b in base_test_names_striped])) + f.write("\\\\ \\hline\n") for interference in interference_names: - f.write(interference.ljust(column_1_width)) + f.write(interference.replace("_", "\\_").ljust(column_1_width)) current_test_results = [] for idx, base_test in enumerate(base_test_names): f.write(" & ") @@ -83,7 +83,7 @@ def build_table_transposed(out_file_path: str, results: dict[tuple[str, str], li result = results[(base_test, interference)] avg = sum([prec for (_, prec) in result]) / len(result) current_test_results.append(avg) - f.write(f"{avg:.3f}".center(column_widths[idx])) + f.write(f"{avg:.2f}".center(column_widths[idx])) f.write("\\\\ \n") result_file_name = input("file name: ") diff --git a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/snippets.txt b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/snippets.txt index a9bc8b4c1..6056d8c16 100644 --- a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/snippets.txt +++ b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/snippets.txt @@ -1,9 +1,14 @@ field gen_f: Int predicate gen_confuse(i: Int, x: Ref){ + i >= 0 && acc(x.f) +} + +predicate gen_confuse_genf(i: Int, x: Ref){ i >= 0 && acc(x.gen_f) } + predicate gen_confuse_with_impl(i: Int, x: Ref){ i >= 0 ==> acc(x.f) } @@ -142,6 +147,18 @@ SNIPPET 08_assignment_disjoint_ref_disjoint_field_quantified$=${ $RW_INT_0 := gen_xs[0].gen_f + gen_xs[1].gen_f } +// fold-unfold with implication +SNIPPET 09_1_fold_unfold$=${ + var gen_x: Ref + // @irrelevant("Explicit") // TODO ake: aliasing! + inhale acc(gen_x.f) + @irrelevant("Implicit") + $RW_INT_0 := 1 + @irrelevant("Implicit") + fold gen_confuse($RW_INT_0, gen_x) + @irrelevant("Implicit") + unfold gen_confuse($RW_INT_0, gen_x) +} // fold-unfold with implication SNIPPET 09_fold_unfold_with_implication$=${ @@ -376,11 +393,11 @@ SNIPPET 25_unrelated_sync_points$=${ inhale 0 <= gen_n && gen_n < 100 @irrelevant("Rewrite") - fold gen_confuse(gen_n, gen_x) + fold gen_confuse_genf(gen_n, gen_x) @irrelevant("Implicit") gen_i := gen_add_positive(gen_i, gen_n) @irrelevant("Rewrite") - unfold gen_confuse(gen_n, gen_x) + unfold gen_confuse_genf(gen_n, gen_x) while(@irrelevant("PathCondition")(gen_n > 0)) invariant @irrelevant("Invariant")(acc(gen_x.gen_f)) diff --git a/src/test/resources/dependencyAnalysisTests/precisionTests/results/result_table.out b/src/test/resources/dependencyAnalysisTests/precisionTests/results/result_table.out index c16ba36e6..5ab72c90f 100644 --- a/src/test/resources/dependencyAnalysisTests/precisionTests/results/result_table.out +++ b/src/test/resources/dependencyAnalysisTests/precisionTests/results/result_table.out @@ -1,13 +1,13 @@ - & 00_baseline & 01_inhale_invariant & 02_2_assert_invariant & 02_assert_invariant & 03_assignment_pure & 04_2_assignment_field_disjoint_field & 04_assignment_field & 05_assignment_disjoint_ref & 06_assignment_disjoint_ref_disjoint_field & 07_assignment_disjoint_ref_quantified & 08_assignment_disjoint_ref_disjoint_field_quantified & 09_fold_unfold_with_implication & 10_function_add_pure & 11_function_add_impure & 12_method_call_pure & 13_method_call_impure & 14_branch & 15_branch_nested & 16_branch_infeasible & 17_branch_function & 18_while_pure & 19_while_perm & 20_magic_wand & 21_2_quasihavoc_disjoint_field & 21_quasihavoc & 22_quasihavoc_disjoint_ref & 23_quasihavoc_disjoint_ref_disjoint_field & 24_goto_pure & 25_unrelated_sync_points -dependencyAnalysisTests/precisionTests/branches & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.852 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.932 & 0.871 & 0.762 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 \\ -dependencyAnalysisTests/precisionTests/domain & 1.000 & 0.963 & 1.000 & 0.972 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.887 & 0.989 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.886 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.960 & 1.000 & 1.000 & 1.000 & 1.000 \\ -dependencyAnalysisTests/precisionTests/function-call & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.894 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.927 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 \\ -dependencyAnalysisTests/precisionTests/inhaleExhale & 1.000 & 0.963 & 1.000 & 0.972 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.891 & 1.000 & 0.889 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.917 & 0.944 & 1.000 & 1.000 & 0.944 & 1.000 & 0.898 & 0.867 & 1.000 & 1.000 & 1.000 \\ -dependencyAnalysisTests/precisionTests/loops & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.863 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.891 & 0.940 & NaN & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & NaN & 1.000 \\ -dependencyAnalysisTests/precisionTests/magicWands & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.981 & 1.000 & 0.838 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.881 & 1.000 & NaN & NaN & 0.956 & 1.000 & 0.958 & 0.962 & 1.000 & 1.000 & 1.000 \\ -dependencyAnalysisTests/precisionTests/method-call & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.899 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.929 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 \\ -dependencyAnalysisTests/precisionTests/misc & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.938 & 1.000 & 0.950 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.958 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.958 & 0.938 & 1.000 & 1.000 & 1.000 \\ -dependencyAnalysisTests/precisionTests/permWildcard & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & NaN & 1.000 & 1.000 & 0.891 & 1.000 & 0.833 & 1.000 & NaN & 1.000 & 1.000 & 1.000 & 1.000 & 0.852 & 0.921 & NaN & NaN & 1.000 & 1.000 & NaN & 0.776 & 1.000 & 1.000 & 1.000 \\ -dependencyAnalysisTests/precisionTests/permissions & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.892 & 1.000 & 0.746 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.767 & 1.000 & NaN & NaN & 0.956 & 1.000 & 0.723 & 0.665 & 1.000 & 1.000 & 1.000 \\ -dependencyAnalysisTests/precisionTests/predicates & 1.000 & 0.822 & 0.967 & 0.870 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.987 & 1.000 & 0.759 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.923 & 0.796 & 0.876 & NaN & NaN & 1.000 & 1.000 & 0.987 & 0.978 & 1.000 & 1.000 & 1.000 \\ -dependencyAnalysisTests/precisionTests/quantifiedPermissions & 1.000 & 0.917 & 1.000 & 0.943 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.902 & 1.000 & 0.750 & 1.000 & 1.000 & NaN & 0.988 & 1.000 & 0.919 & 0.819 & 0.908 & NaN & NaN & 0.938 & 1.000 & 0.798 & 0.685 & 1.000 & NaN & 0.957 \\ + & 00_baseline & 01_inhale_invariant & 02_2_assert_invariant & 02_assert_invariant & 03_assignment_pure & 04_2_assignment_field_disjoint_field & 04_assignment_field & 05_assignment_disjoint_ref & 06_assignment_disjoint_ref_disjoint_field & 07_assignment_disjoint_ref_quantified & 08_assignment_disjoint_ref_disjoint_field_quantified & 09_1_fold_unfold & 09_fold_unfold_with_implication & 10_function_add_pure & 11_function_add_impure & 12_method_call_pure & 13_method_call_impure & 14_branch & 15_branch_nested & 16_branch_infeasible & 17_branch_function & 18_while_pure & 19_while_perm & 20_magic_wand & 21_2_quasihavoc_disjoint_field & 21_quasihavoc & 22_quasihavoc_disjoint_ref & 23_quasihavoc_disjoint_ref_disjoint_field & 24_goto_pure & 25_unrelated_sync_points +dependencyAnalysisTests/precisionTests/branches & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.852 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.932 & 0.871 & 0.762 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 \\ +dependencyAnalysisTests/precisionTests/domain & 1.000 & 0.963 & 1.000 & 0.972 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.887 & 0.989 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.886 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.960 & 1.000 & 1.000 & 1.000 & 1.000 \\ +dependencyAnalysisTests/precisionTests/function-call & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.894 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.927 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 \\ +dependencyAnalysisTests/precisionTests/inhaleExhale & 1.000 & 0.963 & 1.000 & 0.972 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.891 & 1.000 & 0.952 & 0.889 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.917 & 0.944 & 1.000 & 1.000 & 0.944 & 1.000 & 0.898 & 0.867 & 1.000 & 1.000 & 1.000 \\ +dependencyAnalysisTests/precisionTests/loops & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.863 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.891 & 0.940 & NaN & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & NaN & 1.000 \\ +dependencyAnalysisTests/precisionTests/magicWands & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.981 & 1.000 & 1.000 & 0.838 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.881 & 1.000 & NaN & NaN & 0.956 & 1.000 & 0.958 & 0.962 & 1.000 & 1.000 & 1.000 \\ +dependencyAnalysisTests/precisionTests/method-call & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.899 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.929 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 \\ +dependencyAnalysisTests/precisionTests/misc & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.938 & 1.000 & 1.000 & 0.950 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.958 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.958 & 0.938 & 1.000 & 1.000 & 1.000 \\ +dependencyAnalysisTests/precisionTests/permWildcard & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & NaN & 1.000 & 1.000 & 0.907 & 1.000 & 1.000 & 0.833 & 1.000 & NaN & 1.000 & 1.000 & 1.000 & 1.000 & 0.852 & 0.921 & NaN & NaN & 1.000 & 1.000 & NaN & 0.776 & 1.000 & 1.000 & 1.000 \\ +dependencyAnalysisTests/precisionTests/permissions & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.892 & 1.000 & 1.000 & 0.746 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.767 & 1.000 & NaN & NaN & 0.956 & 1.000 & 0.723 & 0.665 & 1.000 & 1.000 & 1.000 \\ +dependencyAnalysisTests/precisionTests/predicates & 1.000 & 0.822 & 0.967 & 0.870 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.987 & 1.000 & 1.000 & 0.759 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.923 & 0.796 & 0.876 & NaN & NaN & 1.000 & 1.000 & 0.987 & 0.978 & 1.000 & 1.000 & 1.000 \\ +dependencyAnalysisTests/precisionTests/quantifiedPermissions & 1.000 & 0.917 & 1.000 & 0.943 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.902 & 1.000 & 1.000 & 0.750 & 1.000 & 1.000 & NaN & 0.988 & 1.000 & 0.919 & 0.819 & 0.908 & NaN & NaN & 0.938 & 1.000 & 0.798 & 0.685 & 1.000 & NaN & 0.957 \\ From d3b0ead5339aa255443635c3f022e500d3bcd261 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 8 Sep 2025 18:27:16 +0200 Subject: [PATCH 252/474] cleanup --- src/test/resources/andrea/annotations.vpr | 26 --- .../resources/andrea/assistants-tests.vpr | 84 ------- src/test/resources/andrea/axioms.vpr | 26 --- src/test/resources/andrea/branches.vpr | 221 ------------------ src/test/resources/andrea/debug/debug.vpr | 89 ------- src/test/resources/andrea/dependencyTest.vpr | 15 -- .../resources/andrea/dependencyTestDep.vpr | 8 - src/test/resources/andrea/domains.vpr | 40 ---- src/test/resources/andrea/gaussian.vpr | 66 ------ src/test/resources/andrea/impreciseness.vpr | 31 --- src/test/resources/andrea/list.vpr | 37 --- src/test/resources/andrea/lseg.vpr | 30 --- src/test/resources/andrea/magicWands.vpr | 80 ------- src/test/resources/andrea/manyMethods.vpr | 113 --------- src/test/resources/andrea/method-sum.vpr | 38 --- src/test/resources/andrea/permissions.vpr | 65 ------ src/test/resources/andrea/predicates.vpr | 52 ----- .../resources/andrea/presentation-unsat.vpr | 24 -- src/test/resources/andrea/quantified-perm.vpr | 115 --------- .../andrea/quantified-permissions.vpr | 25 -- src/test/resources/andrea/quickTest.vpr | 44 ---- src/test/resources/andrea/too-trivial.vpr | 10 - src/test/resources/andrea/transitivity.vpr | 22 -- src/test/resources/andrea/tuple.vpr | 25 -- .../dependencyAnalysisTests/all/gaussian.vpr | 75 ++++++ .../new/custom_precision_test.vpr | 209 ----------------- .../dependencyAnalysisTests/new/meeting.vpr | 165 ------------- src/test/scala/AssumptionAnalysisTests.scala | 8 +- 28 files changed, 76 insertions(+), 1667 deletions(-) delete mode 100644 src/test/resources/andrea/annotations.vpr delete mode 100644 src/test/resources/andrea/assistants-tests.vpr delete mode 100644 src/test/resources/andrea/axioms.vpr delete mode 100644 src/test/resources/andrea/branches.vpr delete mode 100644 src/test/resources/andrea/debug/debug.vpr delete mode 100644 src/test/resources/andrea/dependencyTest.vpr delete mode 100644 src/test/resources/andrea/dependencyTestDep.vpr delete mode 100644 src/test/resources/andrea/domains.vpr delete mode 100644 src/test/resources/andrea/gaussian.vpr delete mode 100644 src/test/resources/andrea/impreciseness.vpr delete mode 100644 src/test/resources/andrea/list.vpr delete mode 100644 src/test/resources/andrea/lseg.vpr delete mode 100644 src/test/resources/andrea/magicWands.vpr delete mode 100644 src/test/resources/andrea/manyMethods.vpr delete mode 100644 src/test/resources/andrea/method-sum.vpr delete mode 100644 src/test/resources/andrea/permissions.vpr delete mode 100644 src/test/resources/andrea/predicates.vpr delete mode 100644 src/test/resources/andrea/presentation-unsat.vpr delete mode 100644 src/test/resources/andrea/quantified-perm.vpr delete mode 100644 src/test/resources/andrea/quantified-permissions.vpr delete mode 100644 src/test/resources/andrea/quickTest.vpr delete mode 100644 src/test/resources/andrea/too-trivial.vpr delete mode 100644 src/test/resources/andrea/transitivity.vpr delete mode 100644 src/test/resources/andrea/tuple.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/all/gaussian.vpr delete mode 100644 src/test/resources/dependencyAnalysisTests/new/custom_precision_test.vpr delete mode 100644 src/test/resources/dependencyAnalysisTests/new/meeting.vpr diff --git a/src/test/resources/andrea/annotations.vpr b/src/test/resources/andrea/annotations.vpr deleted file mode 100644 index b498ee01f..000000000 --- a/src/test/resources/andrea/annotations.vpr +++ /dev/null @@ -1,26 +0,0 @@ - -@assumptionType("Implicit") -method annotationFunc(n: Int) returns (res: Int) - requires 0 <= n && n <= 100 - ensures res > 1000 -{ - res := 1001 -} - - -method annotationBasic(x: Int) - requires x > 0 -{ - var res2: Int - @assumptionType("Implicit") - inhale x < 100 - - var res: Int := annotationFunc(x) - assert res >= 50 - - @assumptionType("Explicit") - res2 := annotationFunc(x) - - assert 2*x <= 200 - assert res2 >= 100 -} \ No newline at end of file diff --git a/src/test/resources/andrea/assistants-tests.vpr b/src/test/resources/andrea/assistants-tests.vpr deleted file mode 100644 index dbaf2735e..000000000 --- a/src/test/resources/andrea/assistants-tests.vpr +++ /dev/null @@ -1,84 +0,0 @@ -field f: Int - -method loopAssumption() -{ - var x: Int - assume x <= 100 - - while(x <= 10) - invariant x <= 100 - { - x := x * 11 - assume x <= 100 - } - - assert x <= 100 -} - -method alias1(a: Ref, b: Ref, c: Ref) - requires acc(a.f) && acc(b.f) -{ - var x: Int - if (c == a) { - x := c.f - } - if(c == b) { - x := c.f - assume a == c - assert x == a.f - assert false - } - - - assume a == c - assert x == a.f -} - -method alias2(a: Ref, b: Ref, c: Ref) - requires acc(a.f) && acc(b.f) - requires a != b -{ - var x: Int - if (c == a) { - x := c.f - } else { - if(c == b) { - x := c.f - } - } - - assume a == c - assert x == a.f -} - -method test3(a: Int, b: Int, c: Int) -{ -var x: Int := 4 - -assume a == 0 -assume b == 1 - - -if (c == a || c == b) { - x := c -} - -assume c == a -assert x == 0 -} - - -method stupidSMT(){ - var a: Int - - - if (exists x: Int, y: Int, z: Int :: x > 0 && y > 0 && z > 0 && x*x*x + y*y*y == z*z*z) { - // complicated condition that is equivalent to false, but can’t be proven by solver - // effectively unreachable code - assume a == 0 // assumption 1 - } else { - assume a == 0 // assumption 2 - } - - assert a == 0 -} \ No newline at end of file diff --git a/src/test/resources/andrea/axioms.vpr b/src/test/resources/andrea/axioms.vpr deleted file mode 100644 index dd09d12b1..000000000 --- a/src/test/resources/andrea/axioms.vpr +++ /dev/null @@ -1,26 +0,0 @@ - -function sum(a: Int, b: Int): Int - ensures result == a + b - -function rand(): Int - ensures result > 0 - -function func(a: Int, b: Int): Int - requires a > 0 - ensures result >= a && result >= b && result > 0 -{ - a>b? a : b -} - -function useless(c: Bool): Int - requires c - ensures result == 5 - -method funcClient(a: Int, b: Int) - requires a > 0 -{ - var res: Int - res := func(a, b) - assert res >= 0 - assert rand() + rand() > 0 -} \ No newline at end of file diff --git a/src/test/resources/andrea/branches.vpr b/src/test/resources/andrea/branches.vpr deleted file mode 100644 index c0b559ec8..000000000 --- a/src/test/resources/andrea/branches.vpr +++ /dev/null @@ -1,221 +0,0 @@ - -field f: Int - - -method branches2(a: Int, b: Int) -{ - var n:Int, c: Bool - - assume 0 < a && a < 100 - assume 0 < b - assume b < 50 - assume c ==> a > 5 - - if(@assumptionType("Implicit")(c)){ - n := a + 1 - }else{ - n := b + 1 - } - - var x: Int - if(a >= n){ - x := a + b - }else{ - x := n + 1 - assert x > 1 - } - assert n > 1 - assert x > n -} - - -method branchesPerm(a: Ref, b: Ref) - requires acc(a.f) && acc(b.f) -{ - var n:Int, c: Bool - - assume 0 < a.f && a.f < 100 - assume 0 < b.f - assume b.f < 50 - assume c ==> a.f > 5 - - if(c){ - n := a.f + 1 - }else{ - n := b.f + 1 - } - - var x: Int := 0 - if(a.f > n){ - x := a.f + b.f - }else{ - x := n + 1 - assert x > 1 - } - assert n > 1 - assert x > n -} - -method nestedBranches1(a: Int, b: Int) -{ - var n:Int, c: Bool - - assume 0 < a && a < 100 - assume 0 < b - assume b < 50 - assume c ==> a > 5 - - if(c){ - if(a > b){ - n := a - b - }else{ - n := a + b - assert n > 0 - } - n := n - 1 - }else{ - n := a + b - } - - assert n >= 0 - assert n <= a + b - assert c ==> n < a + b -} - -function sum(a: Int, b: Int): Int - requires a >= 0 && b >= 0 - ensures result == a + b -{ - a + b -} - -function diff(a: Int, b: Int): Int - requires a >= 0 && b >= 0 - requires a >= b - ensures result == a - b -{ - a - b -} - -method branchesFunctions(a: Int, b: Int) -{ - var n:Int, c: Bool - - assume 0 < a && a < 100 - assume 0 < b - assume b < 50 - assume c ==> a > 5 - - if(c){ - if(a > b){ - n := diff(a, b) - }else{ - n := sum(a, b) - assert n > 0 - } - n := n - 1 - }else{ - n := sum(a, b) - } - - assert n >= 0 - assert n <= a + b - assert c ==> n < a + b -} - - -function sumPerm(a: Ref, b: Ref): Int - requires acc(a.f, wildcard) && acc(b.f, wildcard) - requires a.f >= 0 && b.f >= 0 - ensures result == a.f + b.f -{ - a.f + b.f -} - -function diffPerm(a: Ref, b: Ref): Int - requires acc(a.f, wildcard) && acc(b.f, wildcard) - requires a.f >= 0 && b.f >= 0 - requires a.f >= b.f - ensures result == a.f - b.f -{ - a.f - b.f -} - -method branchesFunctionsPerm(a: Ref, b: Ref) - requires acc(a.f) && acc(b.f) -{ - var n:Int, c: Bool - - //@assumptionType("Implicit") - assume 0 < a.f && a.f < 100 - assume 0 < b.f - assume b.f < 50 - assume c ==> a.f > 5 - - //@assumptionType("Implicit") - if(c){ - if(a.f > b.f){ - n := diffPerm(a, b) - }else{ - n := sumPerm(a, b) - assert n > 0 - } - n := n - 1 - }else{ - @assumptionType("Implicit") - n := sumPerm(a, b) - } - - assert n >= 0 - assert n <= a.f + b.f - assert c ==> n < a.f + b.f -} - - -method infeasibleBranch(a: Int, b: Int) - requires a > 0 && b > 0 -{ - var res: Int := a + 1 - - - if(a < 0){ - res := b + 1 - assert res >= 0 - } - - assert res >= 0 - assert res >= a -} - -method infeasibleBranchPerm(a: Ref, b: Ref) - requires acc(a.f) - requires a.f > 0 -{ - var res: Int := a.f + 1 - - - if(a.f < 0){ - res := b.f + 1 - assert res >= 0 - assert a.f == 0 - } - - assert res >= 0 - assert res >= a.f -} - - -method infeasibleBranchNoPerm(a: Int, b: Ref) - requires a > 0 -{ - var res: Int := a + 1 - - - if(a < 0){ - res := b.f + 1 - assert res >= 0 - } - - assert res >= 0 - assert res >= a -} \ No newline at end of file diff --git a/src/test/resources/andrea/debug/debug.vpr b/src/test/resources/andrea/debug/debug.vpr deleted file mode 100644 index 79e59b75c..000000000 --- a/src/test/resources/andrea/debug/debug.vpr +++ /dev/null @@ -1,89 +0,0 @@ -method quantifiedPermWrite4(xs: Seq[Ref], ys: Seq[Ref]) - requires @dependency("Explicit")(|xs| > 5) - requires |ys| > 3 -{ - @dependency("Explicit") - inhale forall x: Ref :: x in xs ==> acc(x.f) - @irrelevant("Explicit") - inhale forall y: Ref :: y in ys ==> acc(y.f) - - inhale ys[0] != ys[1] - - -var gen_dummy_int: Int -{ - @irrelevant("Implicit") - ys[0].f := gen_incr_pure(xs[0].f) -} - - @testAssertion("Implicit") - xs[0].f := 2 -} - -method quantifiedPermWrite32(xs: Seq[Ref]) { - @dependency("Explicit") - assume |xs| > 5 - @dependency("Explicit") - inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) - - @irrelevant("Implicit") - xs[0].f := 0 - - -var gen_dummy_int: Int -{ - var gen_c: Int - if(@irrelevant("PathCondition")(xs[1].f > gen_c && xs[2].f > gen_c)){ - var gen_i: Int - @irrelevant("Implicit") - gen_i := xs[1].f + xs[2].f - @irrelevant("Implicit") - xs[0].f := 10 - }else{ - if(@irrelevant("PathCondition")(xs[1].f > gen_c + 5)){ - @irrelevant("Implicit") - xs[0].f := 30 - }else{ - var gen_i: Int - @irrelevant("Implicit") - gen_i := xs[1].f + xs[2].f - } - } -} - - @testAssertion("Explicit") - assert xs[0] != xs[1] ==> xs[1].f > 0 -} - -method quantifiedPerm2(xs: Seq[Ref]) { - assume @dependency("Explicit")(|xs| > 5) - inhale @dependency("Explicit")(forall x: Ref :: x in xs ==> acc(x.f)) - - inhale xs[0] != xs[4] - - -var gen_dummy_int: Int -{ - var gen_start: Int, gen_c: Int - @irrelevant("Explicit") - inhale gen_c >= 0 - @irrelevant("Implicit") - gen_start := xs[0].f - @irrelevant("Implicit") - xs[4].f := 0 - while(@irrelevant("PathCondition")(xs[0].f > gen_c)) - invariant forall x:Ref::x in xs ==> acc(x.f) - invariant @irrelevant("LoopInvariant")(true) - invariant @irrelevant("LoopInvariant")(xs[0].f <= gen_start) - invariant @irrelevant("LoopInvariant")(xs[4].f == (gen_start-xs[0].f)*10) - { - @irrelevant("Implicit") - xs[4].f := xs[4].f + 10 - @irrelevant("Implicit") - xs[0].f := xs[0].f - 1 - } -} - - @testAssertion() - xs[0].f := xs[1].f + xs[4].f -} \ No newline at end of file diff --git a/src/test/resources/andrea/dependencyTest.vpr b/src/test/resources/andrea/dependencyTest.vpr deleted file mode 100644 index c03defa39..000000000 --- a/src/test/resources/andrea/dependencyTest.vpr +++ /dev/null @@ -1,15 +0,0 @@ - -import "dependencyTestDep.vpr" - -method foo() -{ - var n: Int - assume n > 0 && n < 100 - var b: Bool - var a: Int - a := 2*n - n := bar(a) - a := 2*n - - assert a == 4*n -} diff --git a/src/test/resources/andrea/dependencyTestDep.vpr b/src/test/resources/andrea/dependencyTestDep.vpr deleted file mode 100644 index f62a0cfec..000000000 --- a/src/test/resources/andrea/dependencyTestDep.vpr +++ /dev/null @@ -1,8 +0,0 @@ - -method bar(x: Int) returns(y: Int) - requires x > 0 - ensures y > 0 && y == 2 * x -{ - y := 2 * x - assert y >= 0 -} \ No newline at end of file diff --git a/src/test/resources/andrea/domains.vpr b/src/test/resources/andrea/domains.vpr deleted file mode 100644 index 8c0fd3064..000000000 --- a/src/test/resources/andrea/domains.vpr +++ /dev/null @@ -1,40 +0,0 @@ -field val: Int -define access(a) - (forall j: Int :: 0 <= j && j < len(a) ==> acc(slot(a,j).val)) - - -domain IArray { - function slot(a: IArray, i: Int): Ref - function len(a: IArray): Int - function first(r: Ref): IArray - function second(r: Ref): Int - - - axiom all_diff { - forall a: IArray, i: Int :: { slot(a,i) } - first(slot(a,i)) == a && second(slot(a,i)) == i - } - - axiom len_nonneg { - forall a: IArray :: { len(a) } - len(a) >= 0 - } -} - -method domainsClient() -{ - // Create an integer array with three elements - var a: IArray - inhale len(a) == 3 - inhale access(a) // access to all array slots - - // Initialize the elements of an array - var i: Int := 0 - while ( i < len(a) ) - invariant access(a) - invariant 0 <= i && i <= len(a) - { - slot(a,i).val := -i // models a[i] := -i - i := i + 1 - } -} \ No newline at end of file diff --git a/src/test/resources/andrea/gaussian.vpr b/src/test/resources/andrea/gaussian.vpr deleted file mode 100644 index b65f5246f..000000000 --- a/src/test/resources/andrea/gaussian.vpr +++ /dev/null @@ -1,66 +0,0 @@ -field f: Int - -method gaussianSimple(n: Int) returns (res: Int) - requires 0 <= n - requires @assumptionType("Implicit")(n <= 5) - ensures res == n * (n + 1) / 2 -{ - res := 0 - var i: Int := 0 - while(i <= n) - invariant @assumptionType("Implicit")(i <= (n + 1)) - invariant i <= 6 - invariant res == (i - 1) * i / 2 - { - @assumptionType("Explicit") - res := res + i - i := i + 1 - } -} - -method gaussianPerm(a: Ref, p: Perm) returns (res: Int) - requires none < p && p < write - requires acc(a.f, p) - requires 0 <= a.f - requires @assumptionType("Implicit")(a.f <= 5) - ensures acc(a.f, p) - ensures res == a.f * (a.f + 1) / 2 -{ - res := 0 - var i: Int := 0 - while(@assumptionType("Implicit")(i <= a.f)) - invariant acc(a.f, p) - invariant 0 <= a.f && a.f <= 5 - invariant i <= (a.f + 1) - invariant @assumptionType("Implicit")(i <= 6) - invariant @assumptionType("Explicit")(res == (i - 1) * i / 2) - { - res := res + i - i := i + 1 - } -} - -predicate gaussianEq(res: Int, n: Int){ - res == (n - 1) * n / 2 && n >= 0 -} - -method gaussianPred(n: Int) returns (res: Int) - requires 0 <= n - requires n <= 5 - ensures gaussianEq(res, n+1) -{ - res := 0 - var i: Int := 0 - fold gaussianEq(res, i) - while(i <= n) - invariant i <= (n + 1) - invariant i <= 6 - invariant gaussianEq(res, i) - { - unfold gaussianEq(res, i) - res := res + i - i := i + 1 - fold gaussianEq(res, i) - } - assert i == n+1 -} diff --git a/src/test/resources/andrea/impreciseness.vpr b/src/test/resources/andrea/impreciseness.vpr deleted file mode 100644 index 293f88c2a..000000000 --- a/src/test/resources/andrea/impreciseness.vpr +++ /dev/null @@ -1,31 +0,0 @@ -field f: Int - -method getValue(x: Ref) returns (res: Int) - requires acc(x.f) - ensures acc(x.f) - -method permTest(a: Ref, b: Ref, n: Int) - requires acc(a.f) && acc(b.f) && b.f > 0 - requires n > 0 -{ - a.f := b.f + 2 - assert a.f > 2 - a.f := n - assert a.f >= 0 // incorrectly depends on acc(b.f) -} - -method quantifiedPerm2Seqs2(xs: Seq[Ref], ys: Seq[Ref]) { - assume |xs| > 5 && |ys| > 3 - inhale forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0) - inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard) && y.f > 0) // incorrectly depends on inhale forall x in xs due to heap summary - - assert xs[0].f > 0 -} - -method quantifiedPerm2Seqs3(xs: Seq[Ref], ys: Seq[Ref]) { - assume |xs| > 5 && |ys| > 3 - inhale forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0) - inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard)) - - assert xs[0].f > 0 // incorrectly depends on inhale forall x in xs due to heap summary -} \ No newline at end of file diff --git a/src/test/resources/andrea/list.vpr b/src/test/resources/andrea/list.vpr deleted file mode 100644 index c763ab30f..000000000 --- a/src/test/resources/andrea/list.vpr +++ /dev/null @@ -1,37 +0,0 @@ -field elem: Int -field next: Ref - -predicate list(this: Ref) { - acc(this.elem) && acc(this.next) && - (this.next != null ==> list(this.next)) -} - -function listLength(l:Ref) : Int - requires list(l) - ensures result > 0 -{ - unfolding list(l) in l.next == null ? 1 : 1 + listLength(l.next) -} - -method appendList(this: Ref, e: Int) - requires list(this) - requires 0 <= e && e < 100 - ensures list(this) -{ - unfold list(this) - assume 0 <= this.elem && this.elem < 100 - - if (this.next == null) { - var n: Ref - - n := new(elem, next) - n.elem := e - n.next := null - this.next := n - - fold list(n) - } else { - appendList(this.next, e) - } - fold list(this) -} diff --git a/src/test/resources/andrea/lseg.vpr b/src/test/resources/andrea/lseg.vpr deleted file mode 100644 index b696523f7..000000000 --- a/src/test/resources/andrea/lseg.vpr +++ /dev/null @@ -1,30 +0,0 @@ -field elem: Int -field next: Ref - -predicate lseg(this: Ref, last: Ref) { - this != last ==> - acc(this.elem) && acc(this.next) && - this.next != null && - lseg(this.next, last) -} - -function values(this: Ref, last: Ref): Seq[Int] - requires lseg(this, last) -{ - unfolding lseg(this, last) in - this == last - ? Seq[Int]() - : Seq(this.elem) ++ values(this.next, last) -} - -method removeFirst(this: Ref, last: Ref) returns (first: Int, rest: Ref) - requires lseg(this, last) - requires this != last - ensures lseg(rest, last) - ensures values(rest, last) == old(values(this, last)[1..]) -{ - unfold lseg(this, last) - - first := this.elem - rest := this.next -} diff --git a/src/test/resources/andrea/magicWands.vpr b/src/test/resources/andrea/magicWands.vpr deleted file mode 100644 index 4bb87866e..000000000 --- a/src/test/resources/andrea/magicWands.vpr +++ /dev/null @@ -1,80 +0,0 @@ -field next : Ref -field val : Int - -predicate list(start : Ref) -{ - acc(start.val) && acc(start.next) && - (start.next != null && list(start.next)) -} - - -method basicApply() -{ - var x: Int - var y: Int - inhale x > 0 - inhale x > 0 --* y > 0 - apply x > 0 --* y > 0 - assert y > 0 -} - - -method basicPackage(l: Ref) - requires list(l) - ensures list(l) - { - unfold list(l) - var tmp : Ref - //@assumptionType("Explicit") - tmp := l.next - - //@assumptionType("Implicit") - package list(tmp) --* list(l) - { - fold list(l) - } - - //@assumptionType("Implicit") - apply list(tmp) --* list(l) -} - -method appendit_wand(l1 : Ref, l2: Ref) - requires list(l1) && list(l2) && l2 != null - ensures list(l1) // && elems(l1) == old(elems(l1) ++ elems(l2)) - { - unfold list(l1) - assume l1.val >= 0 - if(l1.next == null) { // easy case - l1.next := l2; fold list(l1) - } else { - var tmp : Ref := l1.next - var index : Int := 1 - - // package the magic wand required in the loop invariant below - package list(tmp) --* list(l1) - { // show how to get from list(tmp) to list(l1): - fold list(l1) // also requires acc(l1.val) && acc(l1.next) - } - - while(unfolding list(tmp) in tmp.next != null) - invariant index >= 0 - invariant list(tmp)// && elems(tmp) == old(elems(l1))[index..] - invariant list(tmp) --* list(l1) // magic wand instance - { - unfold list(tmp) - var prev : Ref := tmp - tmp := tmp.next - index := index + 1 - - package list(tmp) --* list(l1) // package new magic wand - { // we get from list(tmp) to list(l1) by ... - fold list(prev) - apply list(prev) --* list(l1) - } - } - unfold list(tmp) - tmp.next := l2 - fold list(tmp) - apply list(tmp) --* list(l1) // regain predicate for whole list - } - } \ No newline at end of file diff --git a/src/test/resources/andrea/manyMethods.vpr b/src/test/resources/andrea/manyMethods.vpr deleted file mode 100644 index 563ea86b1..000000000 --- a/src/test/resources/andrea/manyMethods.vpr +++ /dev/null @@ -1,113 +0,0 @@ - -field f: Int - -method foo1(n: Int) - requires n > 0 -{ - var a : Bool := n >= 0 - assert a -} - - -method foo2(n: Int) - requires n < 0 -{ - var a : Bool := n >= 0 - assert !a -} - -method foo3(n: Int) - requires n != 0 -{ - var a : Bool := n > 0 || n < 0 - assert a -} - -method foo4(n: Int, x: Ref) - requires n != 0 - requires acc(x.f) - ensures acc(x.f) -{ - var a : Bool := n > 0 - if(a){ - x.f := n - }else{ - x.f := -n - } - - assert x.f >= n -} - -method foo5(n: Int, x: Ref) - requires n != 0 - requires acc(x.f) - ensures acc(x.f) -{ - var a : Bool := n > 0 - if(a){ - x.f := n - }else{ - x.f := -n - } - - assert x.f > 0 -} - - -method foo6(n: Int, x: Ref) - requires acc(x.f) - ensures acc(x.f) -{ - var a : Bool := n > 0 - if(a){ - x.f := n - }else{ - x.f := -n - } - - assert x.f >= 0 -} - - -method foo7(n: Int, x: Ref) - requires acc(x.f) && x.f > 0 - ensures acc(x.f) && x.f <= 0 -{ - var a : Bool := n > 0 - if(a){ - x.f := -n - }else{ - x.f := n - } - - assert x.f <= n -} - -method foo8(n: Int, x: Ref) - requires acc(x.f) && x.f > 0 - ensures acc(x.f) && x.f < 0 -{ - var a : Bool := n > 0 - if(a){ - x.f := -n - }else{ - x.f := n - 1 - } - - assert x.f < n -} - -method foo9(n: Int, x: Ref) - requires n != 0 - requires acc(x.f) && x.f > 0 - ensures acc(x.f) && x.f < 0 -{ - var a : Bool := n > 0 - if(a){ - x.f := -n - }else{ - x.f := n - } - - assert x.f <= n -} \ No newline at end of file diff --git a/src/test/resources/andrea/method-sum.vpr b/src/test/resources/andrea/method-sum.vpr deleted file mode 100644 index 8a5c1f5f0..000000000 --- a/src/test/resources/andrea/method-sum.vpr +++ /dev/null @@ -1,38 +0,0 @@ -field f: Int - -method sum(x: Int, y: Int) returns(res: Int) - requires x >= 0 && y >= 0 - ensures res == x + y && res > 100 -{ - assume x > 100 - res := x + y -} - - -method sumClient(x: Int, y: Int) -{ - assume x >= 0 - assume y >= 0 - assume x < y - var n: Int := sum(x, y) - - // the following stmt reports dependency on n := sum(x, y) because (x < y && n == x + y && n > 100 ==> y != 0) - // although you could also prove it via (0 <= x && x < y ==> y != 0) - var n2: Int := sum(x/y, y) - n := n + n2 - - assert n >= x + 2*y - assert n >= 0 -} - -method sumClient2(x: Int, y: Int) -{ - assume x >= 0 - assume y >= 0 - assume x < y - var n: Int := sum(x, x) - assert n >= 100 - - var n2: Int := sum(y, y) - assert n2 == 2*y -} \ No newline at end of file diff --git a/src/test/resources/andrea/permissions.vpr b/src/test/resources/andrea/permissions.vpr deleted file mode 100644 index 2acea8a72..000000000 --- a/src/test/resources/andrea/permissions.vpr +++ /dev/null @@ -1,65 +0,0 @@ -field f: Int - -function id(n: Int): Int - ensures result == n - -method foo(a: Ref) - requires @assumptionType("Internal")(acc(a.f, 1/2)) && a.f >= 0 - ensures acc(a.f, 1/2) - -method call(x: Ref) - requires acc(x.f) - ensures acc(x.f) -{ - assume x.f > 0 - foo(x) - assert x.f >= 0 -} - -method permAmount(x: Ref, y: Ref, p: Perm) - requires p > none - requires acc(x.f) && acc(y.f, p) - ensures acc(x.f) && acc(y.f, p) -{ - x.f := 5 - assume p > 1/2 - assume y.f == 1 - x.f := y.f + 1 - foo(x) - assert x.f == y.f + 1 -} - -method transitivity(a: Ref, n: Int) - requires n > 0 - requires acc(a.f) && a.f > 0 -{ - var res: Int - res := a.f / n - assert res >= 0 -} - -method maybeAlias(a: Ref, b: Ref, c: Bool, n: Int) - requires acc(a.f, 1/2) && acc(b.f, 1/2) - requires c ==> a == b - requires a.f > 0 && n > 0 && b.f >= 0 - requires a.f < 100 - requires !c ==> a.f < b.f -{ - if(c){ - a.f := n + 1 - } - assert a.f >= 0 - assert !c ==> a.f <= 100 -} - -method aliasing(x: Ref, n: Int) - requires acc(x.f) && x.f < n -{ - var y: Ref := x - y.f := n + 1 - assert x.f >= n - - assume y.f < n - assert false - -} diff --git a/src/test/resources/andrea/predicates.vpr b/src/test/resources/andrea/predicates.vpr deleted file mode 100644 index b47473ed4..000000000 --- a/src/test/resources/andrea/predicates.vpr +++ /dev/null @@ -1,52 +0,0 @@ - - -predicate greater0(n: Int) -{ - n > 0 -} - -@assumptionType("Internal") -predicate greater5(n: Int) -{ - n > 5 -} - -@enableAssumptionAnalysis("True") -@assumptionType("Internal") -method unfoldP(n: Int) - requires greater0(n) -{ - var x: Int := 1 - unfold greater0(n) - x := n + x - assert x > 1 -} - -@enableAssumptionAnalysis("false") -method foldP(n: Int) - requires n > 10 -{ - var x: Int := 1 - fold greater0(x + n) -} - -method unfoldFoldP(a: Int, b: Int) - requires greater0(a) && greater5(b) - ensures greater5(a + b) -{ - assume b < 100 - @assumptionType("Implicit") - unfold greater0(a) - unfold greater5(b) - @assumptionType("Implicit") - assume a > 60 - @assumptionType("Implicit") - fold greater5(a + b) -} - -method callWithPredicate(a: Int, b: Int) - requires greater0(a) && greater5(b) - ensures greater5(a + b) -{ - unfoldFoldP(a, b) -} \ No newline at end of file diff --git a/src/test/resources/andrea/presentation-unsat.vpr b/src/test/resources/andrea/presentation-unsat.vpr deleted file mode 100644 index 82015db77..000000000 --- a/src/test/resources/andrea/presentation-unsat.vpr +++ /dev/null @@ -1,24 +0,0 @@ -field f: Int -field g: Int - -method foo(x: Ref, y: Ref) - -{ - var b1: Bool, b2: Bool, b3: Bool - inhale b1 ==> acc(x.f) - inhale b2 ==> acc(y.f) - inhale b3 ==> acc(y.g) - assume b1 - assume b2 - assume b3 - assume 0 < x.f - assume 0 < y.f - assume x.f < 100 - assume y.f < 100 - assume 0 < y.f - assume x.f < y.f - - assert x.f / y.f <= 1 -} - - diff --git a/src/test/resources/andrea/quantified-perm.vpr b/src/test/resources/andrea/quantified-perm.vpr deleted file mode 100644 index f40687468..000000000 --- a/src/test/resources/andrea/quantified-perm.vpr +++ /dev/null @@ -1,115 +0,0 @@ -field f: Int -field first : Ref -field second : Ref - -method quantifiedPerm(xs: Seq[Ref]) { - assume |xs| > 5 - inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) - - assert xs[2].f > 0 - var a : Ref := xs[0] - a.f := 0 - assert xs[0].f >= 0 - assert xs[0].f == 0 - assert xs[2].f >= 0 - a.f := -1 - assert xs[2] != a ==> xs[2].f > 0 -} - -method quantifiedWritePerm(nodes: Set[Ref], x: Ref) - requires forall n:Ref :: { n.first } n in nodes ==> - acc(n.first) && - (n.first != null ==> n.first in nodes) - requires forall n:Ref :: { n.second } n in nodes ==> - acc(n.second) && - (n.second != null ==> n.second in nodes) - requires x in nodes -{ - var y : Ref - if(x.second != null) { - y := x.second // permissions covered by preconditions - y.second := y - assert x.second.second == x.second - } -} - -method quantifiedSum(nodes: Set[Ref], x: Ref) - requires forall n:Ref :: { n.first } n in nodes ==> - acc(n.first) && - (n.first != null ==> n.first in nodes) - requires forall n:Ref :: { n.f } n in nodes ==> - acc(n.f) && 0 <= n.f && n.f <= 100 - requires x in nodes -{ - var a: Int := x.f - if(x.first != null) { - a := a + x.first.f - } - assert a >= 0 -} - - -method quantifiedPerm2Seqs(xs: Seq[Ref], ys: Seq[Ref]) { - assume |xs| > 5 && |ys| > 3 - inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) - inhale forall y: Ref :: y in ys ==> (acc(y.f) && y.f > 0) - - assert xs[0].f > 0 -} - -method quantifiedPerm2Seqs2(xs: Seq[Ref], ys: Seq[Ref]) { - assume |xs| > 5 && |ys| > 3 - inhale forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0) - inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard) && y.f > 0) - - assert xs[0].f > 0 -} - -method quantifiedPerm2Seqs3(xs: Seq[Ref], ys: Seq[Ref]) { - assume |xs| > 5 && |ys| > 3 - inhale forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0) - inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard)) - - assert xs[0].f > 0 -} - -method quantifiedExhalePartially(xs: Seq[Ref], ys: Seq[Ref]) { - assume |xs| > 5 - inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) - - xs[0].f := 10 - exhale forall x: Ref :: x in xs ==> acc(x.f, 1/2) - assert xs[1].f > 0 -} - -method quantifiedExhaleFully(xs: Seq[Ref], ys: Seq[Ref]) { - assume |xs| > 5 - inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) - - xs[0].f := 10 - exhale forall x: Ref :: x in xs ==> acc(x.f) -} - -method quantifiedPermBig(xs: Seq[Ref], ys: Seq[Ref], r: Ref) { - assume |xs| > 5 && |ys| > 5 - inhale acc(r.f, 1/2) - inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) - inhale forall y: Ref :: y in ys ==> (acc(y.f, 1/2) && y.f > 0) - - assert xs[0] != ys[0] - assert r != xs[0] - assert xs[2].f >= 0 - xs[0].f := xs[1].f + xs[2].f - xs[1].f := ys[0].f - - inhale ys[0] == r - - ys[0].f := xs[0].f - r.f := r.f + xs[0].f - assert r.f == 2*xs[0].f - - xs[3].f := r.f - - assert forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) - assert forall y: Ref :: y in ys ==> (acc(y.f, 1/2) && y.f > 0) - } \ No newline at end of file diff --git a/src/test/resources/andrea/quantified-permissions.vpr b/src/test/resources/andrea/quantified-permissions.vpr deleted file mode 100644 index d7cb6de6a..000000000 --- a/src/test/resources/andrea/quantified-permissions.vpr +++ /dev/null @@ -1,25 +0,0 @@ -field f: Int - -method foo(xs: Seq[Ref], ys: Seq[Ref]) { - assume |xs| > 5 && |ys| > 5 - inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) - inhale forall y: Ref :: y in ys ==> (acc(y.f) && y.f > 0) - - assert xs[0].f > 0 -} - -method foo2(xs: Seq[Ref], ys: Seq[Ref]) { - assume |xs| > 5 && |ys| > 5 - inhale forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0) - inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard) && y.f > 0) - - assert xs[0].f > 0 -} - -method foo3(xs: Seq[Ref], ys: Seq[Ref]) { - assume |xs| > 5 && |ys| > 5 - inhale forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0) - inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard)) - - assert xs[0].f > 0 -} \ No newline at end of file diff --git a/src/test/resources/andrea/quickTest.vpr b/src/test/resources/andrea/quickTest.vpr deleted file mode 100644 index e6088140e..000000000 --- a/src/test/resources/andrea/quickTest.vpr +++ /dev/null @@ -1,44 +0,0 @@ -field f: Int - -function sum(x: Int, y: Int) : Int - requires x >= 0 && y >= 0 && y > -5 && x > -4 - ensures result == x + y && result >= 0 -{ - x + y -} - -function sumUnverified(x: Int, y: Int): Int - requires y > -5 && x > -4 - ensures result == x + y && result >= 0 - -method call2(){ - var x: Int, y: Int, z: Int - @dependency("Explicit") - assume x > 10 - @dependency("Explicit") - assume y > 0 - - @dependency("Implicit") - z := sum(x, y) - - // $PrecisionTest: $READ_ONLY=x,y,z $INVARIANT=x>0 - - assert z >= 0 -} - -method call2Unv(){ - var x: Int, y: Int, z: Int - @dependency("Explicit") - assume x > 10 - @dependency("Explicit") - assume y > 0 - - @dependency("Implicit") - z := sumUnverified(x, y) - - // $PrecisionTest: $READ_ONLY=x,y,z $INVARIANT=x>0 - - @testAssertion("Explicit") - assert z == x + y -} - diff --git a/src/test/resources/andrea/too-trivial.vpr b/src/test/resources/andrea/too-trivial.vpr deleted file mode 100644 index 7030ec0f2..000000000 --- a/src/test/resources/andrea/too-trivial.vpr +++ /dev/null @@ -1,10 +0,0 @@ -method foo() -{ - var i: Int - var n: Int - assume i > 0 - - n := i - - assert n > 0 // no unsat core, does not even make it to the SMT-LIB file -> isKnownToBeTrue -} \ No newline at end of file diff --git a/src/test/resources/andrea/transitivity.vpr b/src/test/resources/andrea/transitivity.vpr deleted file mode 100644 index 098b71862..000000000 --- a/src/test/resources/andrea/transitivity.vpr +++ /dev/null @@ -1,22 +0,0 @@ - -method level2(n: Int) returns(a:Int) - requires n > 0 - ensures a > 0 -{ - a := 2 * n -} - -method level1(n: Int) returns(a:Int) - requires n > 0 - ensures a > 0 -{ - a := level2(n) -} - -method client(n: Int) - requires n > 0 -{ - var a: Int - a := level1(n) - assert a > 0 -} \ No newline at end of file diff --git a/src/test/resources/andrea/tuple.vpr b/src/test/resources/andrea/tuple.vpr deleted file mode 100644 index 45982c815..000000000 --- a/src/test/resources/andrea/tuple.vpr +++ /dev/null @@ -1,25 +0,0 @@ -field left: Int -field right: Int - -predicate tuple(this: Ref) { - acc(this.left) && acc(this.right) -} - -method setTuple(this: Ref, l: Int, r: Int) - requires tuple(this) - ensures tuple(this) -{ - unfold tuple(this) - this.left := l - this.right := r - fold tuple(this) -} - -method addTuple(this: Ref) returns (sum: Int) - requires acc(tuple(this), 1/2) - ensures acc(tuple(this), 1/2) -{ - unfold acc(tuple(this), 1/2) - sum := this.left + this.right - fold acc(tuple(this), 1/2) -} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/gaussian.vpr b/src/test/resources/dependencyAnalysisTests/all/gaussian.vpr new file mode 100644 index 000000000..08c4ce1e6 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/all/gaussian.vpr @@ -0,0 +1,75 @@ +field f: Int + +method gaussianSimple(n: Int) returns (res: Int) + requires @dependency("Explicit")(0 <= n) + requires @irrelevant("Explicit")(n <= 5) + ensures @testAssertion("Explicit")(res == n * (n + 1) / 2) +{ + var i: Int + @dependency("Implicit") + res := 0 + @dependency("Implicit") + i := 0 + while(@dependency("PathCondition")(i <= n)) + invariant @dependency("LoopInvariant")(i <= (n + 1)) + invariant @irrelevant("Explicit")(n <= 6) + invariant @dependency("LoopInvariant")(res == (i - 1) * i / 2)) + { + @dependency("Implicit") + res := res + i + @dependency("Implicit") + i := i + 1 + } +} + +method gaussianPerm(a: Ref, p: Perm) returns (res: Int) + requires @dependency("Explicit")(none < p) && @irrelevant("Explicit")(p < write) + requires @dependency("Explicit")(acc(a.f, p)) + requires @dependency("Explicit")(0 <= a.f) + requires @irrelevant("Explicit")(a.f <= 5) + ensures @dependency("Explicit")(acc(a.f, p)) // well-formedness requirement! + ensures @testAssertion("Explicit")(res == a.f * (a.f + 1) / 2) +{ + var i: Int + @dependency("Implicit") + res := 0 + @dependency("Implicit") + i := 0 + while(@dependency("PathCondition")(i <= a.f)) + invariant @dependency("Invariant")(acc(a.f, p)) + invariant @dependency("Invariant")(0 <= a.f) && @irrelevant("Invariant")(a.f <= 5) + invariant @dependency("Invariant")(i <= (a.f + 1)) + invariant @irrelevant("Invariant")(i <= 6) + invariant @dependency("Invariant")(res == (i - 1) * i / 2) + { + @dependency("Implicit") + res := res + i + @dependency("Implicit") + i := i + 1 + } +} + +predicate gaussianEq(res: Int, n: Int){ + res == (n - 1) * n / 2 && n >= 0 +} + +method gaussianPred(n: Int) returns (res: Int) + requires 0 <= n + requires n <= 5 + ensures gaussianEq(res, n+1) +{ + res := 0 + var i: Int := 0 + fold gaussianEq(res, i) + while(i <= n) + invariant i <= (n + 1) + invariant i <= 6 + invariant gaussianEq(res, i) + { + unfold gaussianEq(res, i) + res := res + i + i := i + 1 + fold gaussianEq(res, i) + } + assert i == n+1 +} diff --git a/src/test/resources/dependencyAnalysisTests/new/custom_precision_test.vpr b/src/test/resources/dependencyAnalysisTests/new/custom_precision_test.vpr deleted file mode 100644 index da09e4cc6..000000000 --- a/src/test/resources/dependencyAnalysisTests/new/custom_precision_test.vpr +++ /dev/null @@ -1,209 +0,0 @@ -field f: Int -field g: Int - - -method branching(x: Ref, a: Int, b: Bool, n: Int) - requires acc(x.f) && x.f > 0 - ensures acc(x.f) -{ - var res: Int - - if(a > 0){ - res := a - }else{ - res := x.f - } - - if(b){ - x.f := 1 - res := res + 1 - }else{ - x.f := 2 - res := res + 2 - } - - if(a > 0){ - assert res > a - }else{ - assert x.f >= 1 - assert b ==> res > a - assert !b ==> res > 2 - } -} - -method branching2(x: Ref, a: Int, b: Bool, n: Int) - requires acc(x.f) && x.f > 0 - ensures acc(x.f) -{ - var res: Ref - res := new(f) - - if(a > 0){ - res.f := a - }else{ - res.f := x.f - } - - if(b){ - x.f := 1 - res.f := res.f + 1 - }else{ - x.f := 2 - res.f := res.f + 2 - } - - if(a > 0){ - assert res.f > a - }else{ - assert x.f >= 1 - assert b ==> res.f > a - assert !b ==> res.f > 2 - } -} - -method assign(x: Ref, val: Int) - requires acc(x.f) - requires val > 0 - ensures acc(x.f) - ensures x.f == val -{ - x.f := val -} - -method branchingMethod(x: Ref, a: Int, b: Bool, n: Int) - requires acc(x.f) && x.f > 0 - ensures acc(x.f) -{ - var res: Ref - res := new(f) - - if(a > 0){ - res.f := a - }else{ - res.f := x.f - } - - if(b){ - x.f := 1 - assign(res, res.f + 1) - }else{ - x.f := 2 - assign(res, res.f + 2) - } - - if(a > 0){ - assert res.f > a - }else{ - assert x.f >= 1 - assert b ==> res.f > a - assert !b ==> res.f > 2 - } -} - -function retVal(val: Int): Int - requires val > 0 -{ - val -} - -method branchingFunction(x: Ref, a: Int, b: Bool, n: Int) - requires acc(x.f) && x.f > 0 - ensures acc(x.f) -{ - var res: Ref - res := new(f) - - if(a > 0){ - res.f := a - }else{ - res.f := x.f - } - - if(b){ - x.f := 1 - res.f := retVal(res.f + 1) - }else{ - x.f := 2 - res.f := retVal(res.f + 2) - } - - if(a > 0){ - assert res.f > a - }else{ - assert x.f >= 1 - assert b ==> res.f > a - assert !b ==> res.f > 2 - } -} - -method branchingDiv0(x: Ref, a: Int, b: Bool, n: Int) - requires acc(x.f) && x.f > 0 - ensures acc(x.f) -{ - var res: Ref - res := new(f) - - if(a > 0){ - res.f := a - }else{ - res.f := x.f - } - - if(b){ - x.f := 1 - res.f := (res.f*res.f+res.f)/res.f - }else{ - x.f := 2 - res.f := (res.f*res.f + 2*res.f)/res.f - } - - if(a > 0){ - assert res.f > a - }else{ - assert x.f >= 1 - assert b ==> res.f > a - assert !b ==> res.f > 2 - } -} - -method branchNLoops(x: Ref, a: Int, b: Bool, n: Int) - requires acc(x.f) && x.f > 0 - ensures acc(x.f) -{ - var res: Ref - res := new(f) - var whatever: Bool := b - - if(a > 0){ - res.f := a - }else{ - res.f := x.f - } - - while(whatever) - invariant acc(x.f) - invariant acc(res.f) - invariant x != res - invariant res.f > 0 - invariant x.f >= 1 - { - if(a > 0){ - x.f := x.f + 4 - res.f := res.f + a - }else{ - x.f := x.f + 1 - res.f := res.f + 2 - } - if(res.f > 5){ - whatever := false - } - } - - if(a > 0){ - assert res.f >= 0 - }else{ - assert x.f >= 1 - assert b ==> res.f > a - assert !b ==> res.f > 0 - } -} diff --git a/src/test/resources/dependencyAnalysisTests/new/meeting.vpr b/src/test/resources/dependencyAnalysisTests/new/meeting.vpr deleted file mode 100644 index 2b7235fea..000000000 --- a/src/test/resources/dependencyAnalysisTests/new/meeting.vpr +++ /dev/null @@ -1,165 +0,0 @@ -field f: Int - -method packageExhale2(x: Ref) - requires @dependency("Explicit")(acc(x.f)) - { - - @irrelevant("Rewrite") - package acc(x.f, 1/2) --* acc(x.f) - - @irrelevant("Rewrite") - apply acc(x.f, 1/2) --* acc(x.f) - - @testAssertion("Explicit") - exhale acc(x.f) -} - - -method exhaleImprecision(){ - var x: Ref - - inhale acc(x.f) - inhale x.f > 0 - - exhale acc(x.f, 1/2) - - assert x.f > 0 -} - -method inhaleImprecision(){ - var x: Ref - - inhale acc(x.f, 1/2) - inhale x.f > 0 - - inhale acc(x.f, 1/2) - - assert x.f > 0 -} - -// issue #01 - imprecision due to wildcards -// when accessing a qp resource, check Z < (l0? (x in xs? k1:Z): Z) + (y in ys? k2:Z) -// -> the unsat core contains dependencies to all qp perm amounts in order to assert k1, k2, ... >= Z -method quantifiedPerm3(xs: Seq[Ref], ys: Seq[Ref]) - requires |xs| > 5 - requires |ys| > 3 -{ - - inhale (forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0)) - // TODO: unexpected dependency (wildcards) - inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard) && y.f > 0) - - assert xs[0].f > 0 -} - -// havocs all resources of the given fields conditionally on whether it can prove non-aliasing or not -method quasihavoc1(x: Ref, y: Ref) - requires acc(x.f) - requires x.f == 3 - requires x != y -{ - // TODO: unexpected dependency - quasihavoc y.f // x.f --> x==y? new snap : old snap - - assert x.f == 3 -} - -method currentPerm(x: Ref) - requires acc(x.f, 1/2) -{ - assert perm(x.f) > 1/4 -} - -method infeasible1(x: Ref) - requires acc(x.f, 1/2) - requires x.f > 0 -{ - if(x.f < 0){ - x.f := x.f + 1 // missing node iff infeasible branches are skipped - assert x.f > 0 - } -} - - -method infeasible2(x: Ref) - requires x != null ==> acc(x.f) // unexpected dependency -{ - var a: Int - if(x == null){ // unexpected dependency - var a: Int - a := 0 - assert a >= 0 - } -} - - -field elem: Int -field next: Ref - -predicate list(this: Ref) { - acc(this.elem) && acc(this.next) && - (this.next != null ==> list(this.next)) -} - -method appendListFull(this: Ref, e: Int) - requires list(this) - requires 0 <= e && e < 100 - ensures list(this) -{ - unfold list(this) - assume 0 <= this.elem && this.elem < 100 - - if (this.next == null) { - var n: Ref - - n := new(elem, next) - n.elem := e - n.next := null - this.next := n - fold list(n) - } else { - appendListFull(this.next, e) - } - - fold list(this) -} - - -method automatedTest() -{ - var a: Int - a := 0 - a := 1 - a := 0 - assert a == 0 -} - - -method dependencyDefinitionViaSLP_1() -{ - var a: Int, b: Int - - // { true} - a := 1 - // { (l0 ==> a==1) } - assert a > 0 - // { (l0 ==> a==1) && (L1 ==> a > 0) } - assert a >= 0 // potentially depends only on L1? -} - -function foo(a: Int): Int { - requires a > 0 - ensures result > 0 -} - -method dependencyDefinitionViaSLP_2() -{ - var a: Int, b: Int - - // { true} - a := 1 - // { (l0 ==> a==1) } - b := foo(a) - // { (l0 ==> a==1) && (L1 ==> (a > 0 && b > 0))} - assert b > 0 // depends only on L1? -} \ No newline at end of file diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index 03dae036d..4bc449850 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -22,13 +22,7 @@ class AssumptionAnalysisTests extends AnyFunSuite with AssumptionAnalysisTestFra "dependencyAnalysisTests/all", "dependencyAnalysisTests/unitTests", "dependencyAnalysisTests/real-world-examples", -// "frontends/gobra", -// "symbExLogTests", -// "dependencyAnalysisTests/quick" -// "dependencyAnalysisTests/fromSilver", -// "dependencyAnalysisTests/performanceBenchmark" -// "dependencyAnalysisTests/precisionTests/quantifiedPermissions" -// "andrea/debug" + ) From 7e1ed2b2648288756b0fa7f85533efdfd4e91fff Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 8 Sep 2025 18:51:44 +0200 Subject: [PATCH 253/474] add neo4j import script --- .../assumptionAnalysis/neo4j_importer.py | 210 ++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 src/main/scala/assumptionAnalysis/neo4j_importer.py diff --git a/src/main/scala/assumptionAnalysis/neo4j_importer.py b/src/main/scala/assumptionAnalysis/neo4j_importer.py new file mode 100644 index 000000000..b29ea27fd --- /dev/null +++ b/src/main/scala/assumptionAnalysis/neo4j_importer.py @@ -0,0 +1,210 @@ + +from neo4j import GraphDatabase +import os +from neo4j import Transaction +import pandas as pd + +BASE_PATH = "C:/Users/Andrea/Documents/ETH/FS25/MasterThesis/silicon/graphExports/" +# BASE_PATH = "C:/Users/Andrea/Documents/ETH/FS25/MasterThesis/gobra/graphExports/" +URI = "neo4j+ssc://df418be1.databases.neo4j.io" +AUTH = ("neo4j", os.environ['NEO4J-PW']) + + +ASSUMPTION_NODE_TYPES = """["Inhale", "Assumption", "Infeasible", "Label"]""" +ASSERTION_NODE_TYPES = """["Exhale", "Assertion", "Check"]""" + +driver = GraphDatabase.driver(URI, auth=AUTH) +session = driver.session(database="neo4j") +driver.verify_connectivity() + +def create_id_uniquness_constraint(tx: Transaction, node_label: str): + tx.run(f"""CREATE CONSTRAINT `id_{node_label}_uniq` IF NOT EXISTS + FOR (n: {node_label}) + REQUIRE (n.`id`) IS UNIQUE;""") + tx.run(f"""CREATE INDEX {node_label}_pos_index IF NOT EXISTS + FOR (n:{node_label}) ON (n.position)""") + tx.run(f"""CREATE INDEX {node_label}_assumption_type_index IF NOT EXISTS + FOR (n:{node_label}) ON (n.`assumption type`)""") + tx.run(f"""CREATE INDEX {node_label}_node_type_index IF NOT EXISTS + FOR (n:{node_label}) ON (n.`node type`)""") + +def create_indices(tx: Transaction, label: str, node_label_postfix: str): + tx.run(f"""CREATE INDEX {label}{node_label_postfix}_pos_index IF NOT EXISTS + FOR (n:{label}{node_label_postfix}) ON (n.position)""") + + +def load_nodes(tx: Transaction, file_path: str, node_label: str): + with open(file_path, "r") as f: + nodes_raw = pd.read_csv(f, sep="#") + nodes = [] + for _, e in nodes_raw.iterrows(): + nodes.append(dict(e)) + tx.run(f""" + UNWIND $nodes AS row + WITH row + WHERE NOT toInteger(row.`id`) IS NULL + CALL {{ + WITH row + MERGE (n: {node_label} {{ `id`: toInteger(row.`id`) }}) + SET n.`id` = toInteger(row.`id`) + SET n.`node type` = row.`node type` + SET n.`assumption type` = row.`assumption type` + SET n.`node info` = row.`node info` + SET n.`source info` = row.`source info` + SET n.`position` = row.`position` + SET n.`fine grained source` = row.`fine grained source` + }}; + """ + , nodes=nodes + ) + +def load_edges(tx: Transaction, file_path: str, node_label: str): + with open(file_path, "r") as f: + edges_raw = pd.read_csv(f, sep=",") + edges = [] + for _, e in edges_raw.iterrows(): + edges.append(dict(e)) + # print(edges) + tx.run(f""" + UNWIND $edges AS row + WITH row + CALL {{ + WITH row + MATCH (source: {node_label} {{ `id`: toInteger(row.`source`) }}) + MATCH (target: {node_label} {{ `id`: toInteger(row.`target`) }}) + MERGE (source)-[r: `flows_into`]->(target) + SET r.`type` = row.`label` + }}; + """ + , edges=edges + ) + +def import_graph2(tx: Transaction, foldername: str, node_label: str, is_overwrite=True): + if is_overwrite: + delete_nodes_detach(tx, node_label, "") + load_nodes(tx, foldername + "/nodes.csv", node_label) + load_edges(tx, foldername + "/edges.csv", node_label) + +def import_low_level_graph(foldername: str, node_label: str): + session.execute_write(create_id_uniquness_constraint, node_label) # transaction! + session.execute_write(import_graph2, foldername, node_label) # transaction! + +def delete_nodes_detach(tx, label, node_label_postfix): + print(f"delete nodes {label}{node_label_postfix}") + tx.run(f"MATCH (n:{label}{node_label_postfix}) DETACH DELETE n;") + +def create_nodes(tx, label, node_label_postfix, assumption_node_selection): + print(f"create nodes {label}{node_label_postfix}") + tx.run(f""" + MATCH (a:{label}) + WHERE {assumption_node_selection.replace("$ID", "a")} + MERGE (aNew :{label}{node_label_postfix} {{`source info`: a.`source info`, position: a.position}}) + RETURN aNew; + """) # OR a.`node type` IN {ASSERTION_NODE_TYPES} + +def create_infeasibility_nodes(tx, label, node_label_postfix): + print(f"create infeasibility nodes") + tx.run(f""" + MATCH (c:{label})-[r:flows_into]->(a:{label}), + (c1:{label}{node_label_postfix}) + WHERE a.`node type` = "Infeasible" + AND c1.`source info` = c.`source info` AND c1.position = c.position + MERGE (c1)-[:flows_into]->(aNew :{label}{node_label_postfix} {{`source info`: a.`source info`, position: a.position, `node type`: a.`node type`}}) + RETURN aNew; + """) + tx.run(f""" + MATCH (c:{label})-[r1:flows_into]->(b:{label})-[r2:flows_into]->(a:{label}), (a1:{label}{node_label_postfix}), (c1:{label}{node_label_postfix}) + WHERE a.`node type` = "Infeasible" + AND c1.`source info` = c.`source info` AND c1.position = c.position + AND a1.`source info` = a.`source info` AND a1.position = a.position AND a1.`node type` = a.`node type` + MERGE (c1)-[:flows_into]->(a1) + RETURN a1; + """) + tx.run(f""" + MATCH (a:{label})-[r1:flows_into]->(c:{label}), (a1:{label}{node_label_postfix}), (c1:{label}{node_label_postfix}) + WHERE a.`node type` = "Infeasible" + AND c1.`source info` = c.`source info` AND c1.position = c.position + AND a1.`source info` = a.`source info` AND a1.position = a.position AND a1.`node type` = a.`node type` + MERGE (a1)-[:flows_into]->(c1) + RETURN a1; + """) + +def create_direct_edges(tx, label, node_label_postfix, assumption_node_selection): + print(f"add direct edges") + tx.run(f""" + MATCH (a:{label})-[r2:flows_into]->(c:{label}), + (a1:{label}{node_label_postfix}), (c1:{label}{node_label_postfix}) + WHERE {assumption_node_selection.replace("$ID", "a")} AND a1.`source info` = a.`source info` AND a1.position = a.position + AND a.`node type` IN {ASSUMPTION_NODE_TYPES} + AND c1.`source info` = c.`source info` AND c1.position = c.position + AND c.`node type` IN {ASSERTION_NODE_TYPES} + AND a1 <> c1 + MERGE (a1)-[n:flows_into]->(c1) + RETURN a1; + """) + +def create_indirect_edges(tx, label, node_label_postfix, assumption_node_selection): + print(f"add indirect edges") + tx.run(f""" + MATCH (a:{label})-[r:flows_into]->(b:{label}) + ((b1:{label})-[:flows_into]->(b2:{label})){{0, 5}} + (b3:{label})-[r2:flows_into]->(c:{label}), + (a1:{label}{node_label_postfix}), (c1:{label}{node_label_postfix}) + WHERE all(x in (b + b1 + b2) where NOT (x.`node type` IN {ASSUMPTION_NODE_TYPES} AND {assumption_node_selection.replace("$ID", "x")})) + AND all(x in (b + b1 + b2) where (NOT x.`node type` = "Infeasible")) + AND {assumption_node_selection.replace("$ID", "a")} AND a.`node type` IN {ASSUMPTION_NODE_TYPES} + AND c.`node type` IN {ASSERTION_NODE_TYPES} + AND a1.`source info` = a.`source info` AND a1.position = a.position + AND c1.`source info` = c.`source info` AND c1.position = c.position + AND a1 <> c1 + MERGE (a1)-[n:flows_into]->(c1) + RETURN a1; + """) + + # print("add edges across method calls") + # # add edges for joined graphs + # tx.run(f""" + # MATCH (a:{label})-[r:flows_into]->(c:{label}), + # (a1:{label}{node_label_postfix}), (c1:{label}{node_label_postfix}) + # WHERE a.`node type` IN {ASSERTION_NODE_TYPES} + # AND c.`node type` IN {ASSUMPTION_NODE_TYPES} AND NOT c.`assumption type` IN {internal_assumption_types} + # AND a1.`source info` = a.`source info` AND a1.position = a.position + # AND c1.`source info` = c.`source info` AND c1.position = c.position + # AND a1 <> c1 + # MERGE (a1)-[n:flows_into]->(c1) + # RETURN a1, n, c1; + # """) + +def import_graph_view(label, is_overwrite, node_label_postfix, assumption_node_selection): + if is_overwrite: + session.execute_write(delete_nodes_detach, label, node_label_postfix) + session.execute_write(create_indices, label, node_label_postfix) + session.execute_write(create_nodes, label, node_label_postfix, assumption_node_selection) + session.execute_write(create_infeasibility_nodes, label, node_label_postfix) + session.execute_write(create_direct_edges, label, node_label_postfix, assumption_node_selection) + session.execute_write(create_indirect_edges, label, node_label_postfix, assumption_node_selection) + +def import_graph_without_internal_nodes(label, is_overwrite=True): + node_label_postfix = "_NonInternal" + internal_assumption_types = """["Internal", "Trigger"]""" + assumption_node_selection = f"NOT $ID.`assumption type` IN {internal_assumption_types}" + import_graph_view(label, is_overwrite, node_label_postfix, assumption_node_selection) + print("non internal view done") + +def import_graph_with_explicit_nodes_only(label, is_overwrite=True): + node_label_postfix = "_Explicit" + explicit_assumption_types = """["Explicit", "ExplicitPostcondition"]""" + assumption_node_selection = f"$ID.`assumption type` IN {explicit_assumption_types}" + import_graph_view(label, is_overwrite, node_label_postfix, assumption_node_selection) + print("explicit view done") + +foldername = input("foldername: ") +node_label = input("node label: ") +import_low_level_graph(foldername, node_label) +print("Low-level graph imported successfully") +# import_graph_with_explicit_nodes_only(node_label) +# print("Explicit-only Viper graph imported successfully") +import_graph_without_internal_nodes(node_label) +print("Viper graph imported successfully") +session.close() +driver.close() \ No newline at end of file From 1620c9c0d00d5c10d26e72bcb21f854deea690a7 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 8 Sep 2025 19:09:51 +0200 Subject: [PATCH 254/474] fix --- .../dependencyAnalysisTests/all/gaussian.vpr | 75 ------------------- .../all/state_consolidation.vpr | 20 ++++- 2 files changed, 18 insertions(+), 77 deletions(-) delete mode 100644 src/test/resources/dependencyAnalysisTests/all/gaussian.vpr diff --git a/src/test/resources/dependencyAnalysisTests/all/gaussian.vpr b/src/test/resources/dependencyAnalysisTests/all/gaussian.vpr deleted file mode 100644 index 08c4ce1e6..000000000 --- a/src/test/resources/dependencyAnalysisTests/all/gaussian.vpr +++ /dev/null @@ -1,75 +0,0 @@ -field f: Int - -method gaussianSimple(n: Int) returns (res: Int) - requires @dependency("Explicit")(0 <= n) - requires @irrelevant("Explicit")(n <= 5) - ensures @testAssertion("Explicit")(res == n * (n + 1) / 2) -{ - var i: Int - @dependency("Implicit") - res := 0 - @dependency("Implicit") - i := 0 - while(@dependency("PathCondition")(i <= n)) - invariant @dependency("LoopInvariant")(i <= (n + 1)) - invariant @irrelevant("Explicit")(n <= 6) - invariant @dependency("LoopInvariant")(res == (i - 1) * i / 2)) - { - @dependency("Implicit") - res := res + i - @dependency("Implicit") - i := i + 1 - } -} - -method gaussianPerm(a: Ref, p: Perm) returns (res: Int) - requires @dependency("Explicit")(none < p) && @irrelevant("Explicit")(p < write) - requires @dependency("Explicit")(acc(a.f, p)) - requires @dependency("Explicit")(0 <= a.f) - requires @irrelevant("Explicit")(a.f <= 5) - ensures @dependency("Explicit")(acc(a.f, p)) // well-formedness requirement! - ensures @testAssertion("Explicit")(res == a.f * (a.f + 1) / 2) -{ - var i: Int - @dependency("Implicit") - res := 0 - @dependency("Implicit") - i := 0 - while(@dependency("PathCondition")(i <= a.f)) - invariant @dependency("Invariant")(acc(a.f, p)) - invariant @dependency("Invariant")(0 <= a.f) && @irrelevant("Invariant")(a.f <= 5) - invariant @dependency("Invariant")(i <= (a.f + 1)) - invariant @irrelevant("Invariant")(i <= 6) - invariant @dependency("Invariant")(res == (i - 1) * i / 2) - { - @dependency("Implicit") - res := res + i - @dependency("Implicit") - i := i + 1 - } -} - -predicate gaussianEq(res: Int, n: Int){ - res == (n - 1) * n / 2 && n >= 0 -} - -method gaussianPred(n: Int) returns (res: Int) - requires 0 <= n - requires n <= 5 - ensures gaussianEq(res, n+1) -{ - res := 0 - var i: Int := 0 - fold gaussianEq(res, i) - while(i <= n) - invariant i <= (n + 1) - invariant i <= 6 - invariant gaussianEq(res, i) - { - unfold gaussianEq(res, i) - res := res + i - i := i + 1 - fold gaussianEq(res, i) - } - assert i == n+1 -} diff --git a/src/test/resources/dependencyAnalysisTests/all/state_consolidation.vpr b/src/test/resources/dependencyAnalysisTests/all/state_consolidation.vpr index 55eae4b88..fc3d7412f 100644 --- a/src/test/resources/dependencyAnalysisTests/all/state_consolidation.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/state_consolidation.vpr @@ -16,7 +16,6 @@ method stateConsolidation(x: Ref, y: Ref, z: Ref) assert a != y // triggers state consolidation } - method stateConsolidation2(x: Ref, y: Ref, z: Ref) requires @dependency("Explicit")(acc(x.f, 1/2)) requires @dependency("Explicit")(acc(y.f, 1/2)) @@ -26,8 +25,25 @@ method stateConsolidation2(x: Ref, y: Ref, z: Ref) @dependency("Explicit") inhale acc(a.f, 1/2) + @dependency("Explicit") + assume a == x // chunks are merged somewhere + + @testAssertion("Explicit") + assert perm(x.f) == write +} + + +method stateConsolidation3(x: Ref, y: Ref, z: Ref) + requires @dependency("Explicit")(acc(x.f, 1/2)) + requires @dependency("Explicit")(acc(y.f, 1/2)) + requires @irrelevant("Explicit")(acc(z.f, 1/2)) +{ + var a: Ref + @dependency("Explicit") + inhale acc(x.f, 1/2) + @testAssertion("Explicit") - assert perm(a.f) == write + assert perm(x.f) == write } From 4ad57139b7bd967afa133a30dc0d31b73a75cc18 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 17 Sep 2025 16:59:27 +0200 Subject: [PATCH 255/474] fix and add neo4j queries --- .../AssumptionAnalyzer.scala | 2 +- .../assumptionAnalysis/neo4j_importer.py | 32 ++++++++----------- .../neo4j_query_saved_cypher_2025-9-17.csv | 22 +++++++++++++ 3 files changed, 37 insertions(+), 19 deletions(-) create mode 100644 src/main/scala/assumptionAnalysis/neo4j_query_saved_cypher_2025-9-17.csv diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index 42c4f595f..e9ff7a802 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -277,7 +277,7 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { def addCustomExpDependency(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp]): Unit = { val sourceNodeIds = sourceExps.flatMap(e => addAssumption(True, ExpAnalysisSourceInfo(e), AssumptionType.Explicit, None)) - val targetNodes = targetExps.flatMap(e => addAssumption(True, ExpAnalysisSourceInfo(e), AssumptionType.ExplicitPostcondition, None)) + val targetNodes = targetExps.flatMap(e => addAssertNode(True, AssumptionType.ExplicitPostcondition, ExpAnalysisSourceInfo(e))) assumptionGraph.addEdges(sourceNodeIds, targetNodes) } diff --git a/src/main/scala/assumptionAnalysis/neo4j_importer.py b/src/main/scala/assumptionAnalysis/neo4j_importer.py index b29ea27fd..ffb74fa6a 100644 --- a/src/main/scala/assumptionAnalysis/neo4j_importer.py +++ b/src/main/scala/assumptionAnalysis/neo4j_importer.py @@ -4,14 +4,13 @@ from neo4j import Transaction import pandas as pd -BASE_PATH = "C:/Users/Andrea/Documents/ETH/FS25/MasterThesis/silicon/graphExports/" -# BASE_PATH = "C:/Users/Andrea/Documents/ETH/FS25/MasterThesis/gobra/graphExports/" URI = "neo4j+ssc://df418be1.databases.neo4j.io" AUTH = ("neo4j", os.environ['NEO4J-PW']) ASSUMPTION_NODE_TYPES = """["Inhale", "Assumption", "Infeasible", "Label"]""" ASSERTION_NODE_TYPES = """["Exhale", "Assertion", "Check"]""" +POSTCONDITION_TYPES = """["ExplicitPostcondition", "ImplicitPostcondition"]""" driver = GraphDatabase.driver(URI, auth=AUTH) session = driver.session(database="neo4j") @@ -142,6 +141,17 @@ def create_direct_edges(tx, label, node_label_postfix, assumption_node_selection MERGE (a1)-[n:flows_into]->(c1) RETURN a1; """) + tx.run(f""" + MATCH (a:{label})-[r2:flows_into]->(c:{label}), + (a1:{label}{node_label_postfix}), (c1:{label}{node_label_postfix}) + WHERE {assumption_node_selection.replace("$ID", "a")} AND a1.`source info` = a.`source info` AND a1.position = a.position + AND a.`node type` IN {ASSERTION_NODE_TYPES} AND a.`assumption type` IN {POSTCONDITION_TYPES} + AND c1.`source info` = c.`source info` AND c1.position = c.position + AND c.`node type` IN {ASSUMPTION_NODE_TYPES} + AND a1 <> c1 + MERGE (a1)-[n:flows_into]->(c1) + RETURN a1; + """) def create_indirect_edges(tx, label, node_label_postfix, assumption_node_selection): print(f"add indirect edges") @@ -160,20 +170,6 @@ def create_indirect_edges(tx, label, node_label_postfix, assumption_node_selecti MERGE (a1)-[n:flows_into]->(c1) RETURN a1; """) - - # print("add edges across method calls") - # # add edges for joined graphs - # tx.run(f""" - # MATCH (a:{label})-[r:flows_into]->(c:{label}), - # (a1:{label}{node_label_postfix}), (c1:{label}{node_label_postfix}) - # WHERE a.`node type` IN {ASSERTION_NODE_TYPES} - # AND c.`node type` IN {ASSUMPTION_NODE_TYPES} AND NOT c.`assumption type` IN {internal_assumption_types} - # AND a1.`source info` = a.`source info` AND a1.position = a.position - # AND c1.`source info` = c.`source info` AND c1.position = c.position - # AND a1 <> c1 - # MERGE (a1)-[n:flows_into]->(c1) - # RETURN a1, n, c1; - # """) def import_graph_view(label, is_overwrite, node_label_postfix, assumption_node_selection): if is_overwrite: @@ -202,8 +198,8 @@ def import_graph_with_explicit_nodes_only(label, is_overwrite=True): node_label = input("node label: ") import_low_level_graph(foldername, node_label) print("Low-level graph imported successfully") -# import_graph_with_explicit_nodes_only(node_label) -# print("Explicit-only Viper graph imported successfully") +import_graph_with_explicit_nodes_only(node_label) +print("Explicit-only Viper graph imported successfully") import_graph_without_internal_nodes(node_label) print("Viper graph imported successfully") session.close() diff --git a/src/main/scala/assumptionAnalysis/neo4j_query_saved_cypher_2025-9-17.csv b/src/main/scala/assumptionAnalysis/neo4j_query_saved_cypher_2025-9-17.csv new file mode 100644 index 000000000..0b64c5e9c --- /dev/null +++ b/src/main/scala/assumptionAnalysis/neo4j_query_saved_cypher_2025-9-17.csv @@ -0,0 +1,22 @@ +name,description,query,id,parentId,isFolder +Queries,,,0,,true +Non Internal Dependencies,,"match (a:NODE_LABEL {`source info`: ""SOURCE INFO""})<-[r:flows_into*]-(b:NODE_LABEL) +match (a1:NODE_LABEL_NonInternal)<-[r2:flows_into*]-(b1:NODE_LABEL_NonInternal) +WHERE a.`source info`=a1.`source info` +AND b.`source info`=b1.`source info` +return a1, r2, b1",1,0,false +Explicit Dependencies,,"match (a:NODE_LABEL {`source info`: ""SOURCE INFO""})<-[r:flows_into*]-(b:NODE_LABEL) +match (a1:NODE_LABEL_Explicit)<-[r2:flows_into*]-(b1:NODE_LABEL_Explicit) +WHERE a.`source info`=a1.`source info` +AND b.`source info`=b1.`source info` +return a1, r2, b1",2,0,false +Non Internal Dependents,,"match (a:NODE_LABEL {`source info`: ""SOURCE INFO""})-[r:flows_into*]->(b:NODE_LABEL) +match (a1:NODE_LABEL_NonInternal)-[r2:flows_into*]->(b1:NODE_LABEL_NonInternal) +WHERE a.`source info`=a1.`source info` +AND b.`source info`=b1.`source info` +return a1, r2, b1",3,0,false +Explicit Dependents,,"match (a:NODE_LABEL {`source info`: ""SOURCE INFO""})-[r:flows_into*]->(b:NODE_LABEL) +match (a1:NODE_LABEL_Explicit)-[r2:flows_into*]->(b1:NODE_LABEL_Explicit) +WHERE a.`source info`=a1.`source info` +AND b.`source info`=b1.`source info` +return a1, r2, b1",4,0,false \ No newline at end of file From cc2af1ed6729574c280ef151b7ec95355e9d688f Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 22 Sep 2025 22:17:06 +0200 Subject: [PATCH 256/474] fix names of benchmarks, add latest benchmark results --- .../all/state_consolidation.vpr | 10 +++++ .../precision_benchmark_plotter.py | 5 ++- .../benchmark_scripts/snippets.txt | 38 +++++++++++++++---- .../precisionTests/results/result_table.out | 26 ++++++------- .../{inhaleExhale.vpr => A-inhaleExhale.vpr} | 0 .../{permissions.vpr => B-permissions.vpr} | 12 ------ .../{permWildcard.vpr => C-permWildcard.vpr} | 0 ...ssions.vpr => D-quantifiedPermissions.vpr} | 17 +++++++-- ...{function-call.vpr => E-function-call.vpr} | 0 .../{method-call.vpr => F-method-call.vpr} | 0 .../{predicates.vpr => G-predicates.vpr} | 0 .../{magicWands.vpr => H-magicWands.vpr} | 0 .../{branches.vpr => I-branches.vpr} | 0 .../unitTests/{loops.vpr => J-loops.vpr} | 0 .../unitTests/{domain.vpr => K-domain.vpr} | 0 .../unitTests/{misc.vpr => L-misc.vpr} | 0 16 files changed, 72 insertions(+), 36 deletions(-) rename src/test/resources/dependencyAnalysisTests/unitTests/{inhaleExhale.vpr => A-inhaleExhale.vpr} (100%) rename src/test/resources/dependencyAnalysisTests/unitTests/{permissions.vpr => B-permissions.vpr} (85%) rename src/test/resources/dependencyAnalysisTests/unitTests/{permWildcard.vpr => C-permWildcard.vpr} (100%) rename src/test/resources/dependencyAnalysisTests/unitTests/{quantifiedPermissions.vpr => D-quantifiedPermissions.vpr} (92%) rename src/test/resources/dependencyAnalysisTests/unitTests/{function-call.vpr => E-function-call.vpr} (100%) rename src/test/resources/dependencyAnalysisTests/unitTests/{method-call.vpr => F-method-call.vpr} (100%) rename src/test/resources/dependencyAnalysisTests/unitTests/{predicates.vpr => G-predicates.vpr} (100%) rename src/test/resources/dependencyAnalysisTests/unitTests/{magicWands.vpr => H-magicWands.vpr} (100%) rename src/test/resources/dependencyAnalysisTests/unitTests/{branches.vpr => I-branches.vpr} (100%) rename src/test/resources/dependencyAnalysisTests/unitTests/{loops.vpr => J-loops.vpr} (100%) rename src/test/resources/dependencyAnalysisTests/unitTests/{domain.vpr => K-domain.vpr} (100%) rename src/test/resources/dependencyAnalysisTests/unitTests/{misc.vpr => L-misc.vpr} (100%) diff --git a/src/test/resources/dependencyAnalysisTests/all/state_consolidation.vpr b/src/test/resources/dependencyAnalysisTests/all/state_consolidation.vpr index fc3d7412f..828e50f28 100644 --- a/src/test/resources/dependencyAnalysisTests/all/state_consolidation.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/state_consolidation.vpr @@ -47,3 +47,13 @@ method stateConsolidation3(x: Ref, y: Ref, z: Ref) } +method noAlias(a: Ref, b: Ref, c: Ref) + requires @dependency("Explicit")(acc(a.f)) + requires @dependency("Explicit")(acc(b.f, 1/2)) + requires @irrelevant("Explicit")(acc(c.f, 1/2)) +{ + + @testAssertion("Explicit") + assert a != b +} + diff --git a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_benchmark_plotter.py b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_benchmark_plotter.py index 4ebde92dd..c2b4ff6e2 100644 --- a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_benchmark_plotter.py +++ b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_benchmark_plotter.py @@ -83,7 +83,10 @@ def build_table_transposed(out_file_path: str, results: dict[tuple[str, str], li result = results[(base_test, interference)] avg = sum([prec for (_, prec) in result]) / len(result) current_test_results.append(avg) - f.write(f"{avg:.2f}".center(column_widths[idx])) + if avg == 1.0: + f.write(f"{avg:.2f}".center(column_widths[idx])) + else: + f.write("\\cellcolor{red!30} " + f"{avg:.2f}".center(column_widths[idx])) f.write("\\\\ \n") result_file_name = input("file name: ") diff --git a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/snippets.txt b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/snippets.txt index 6056d8c16..aa66620b8 100644 --- a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/snippets.txt +++ b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/snippets.txt @@ -18,11 +18,15 @@ predicate gen_confuse_gen_field(i: Int, x: Ref){ } function gen_add(a: Int, b: Int): Int + requires b > 0 + ensures result > a { a + b } function gen_add_impure(x: Ref, b: Int): Int requires acc(x.f) + requires b > 0 ensures result == x.f + b + ensures result > x.f function gen_add_positive(a: Int, b: Int): Int requires a >= 0 && b >= 0 @@ -31,22 +35,28 @@ function gen_add_positive(a: Int, b: Int): Int method incr(a: Ref) requires acc(a.f) + requires a.f > 0 ensures acc(a.f) ensures a.f == old(a.f) + 1 + ensures a.f > 1 { a.f := a.f + 1 } method incr_gen_f(a: Ref) requires acc(a.gen_f) + requires a.gen_f > 0 ensures acc(a.gen_f) ensures a.gen_f == old(a.gen_f) + 1 + ensures a.gen_f > 1 { a.gen_f := a.gen_f + 1 } method gen_incr_pure(a: Int) returns (res: Int) + requires a > 0 ensures res == a + 1 + ensures res > 1 { res := a + 1 } @@ -147,7 +157,7 @@ SNIPPET 08_assignment_disjoint_ref_disjoint_field_quantified$=${ $RW_INT_0 := gen_xs[0].gen_f + gen_xs[1].gen_f } -// fold-unfold with implication +// fold-unfold SNIPPET 09_1_fold_unfold$=${ var gen_x: Ref // @irrelevant("Explicit") // TODO ake: aliasing! @@ -174,25 +184,35 @@ SNIPPET 09_fold_unfold_with_implication$=${ // function call SNIPPET 10_function_add_pure$=${ @irrelevant("Implicit") - $RW_INT_0 := gen_add($RO_INT_0, $RO_INT_1) + $RW_INT_0 := 5 + @irrelevant("Implicit") + $RW_INT_0 := gen_add($RO_INT_0, $RW_INT_0) + @irrelevant("Implicit") + $RW_INT_1 := 1 @irrelevant("Implicit") - $RW_INT_1 := gen_add($RW_INT_0, $RO_INT_2) + $RW_INT_1 := gen_add($RO_INT_1, $RW_INT_1) } SNIPPET 11_function_add_impure$=${ @irrelevant("Implicit") - $RW_INT_0 := gen_add_impure($RW_REF_F_0, $RO_INT_0) + $RW_INT_0 := 5 + @irrelevant("Implicit") + $RW_INT_0 := gen_add_impure($RW_REF_F_0, $RW_INT_0) } SNIPPET 12_method_call_pure$=${ @irrelevant("Implicit") - $RW_INT_0 := gen_incr_pure($RO_INT_0) + $RW_INT_0 := 5 + @irrelevant("Implicit") + $RW_INT_0 := gen_incr_pure($RW_INT_0) } SNIPPET 13_method_call_impure$=${ var gen_ref_0: Ref gen_ref_0 := new(f) // might be used for non-aliasing proof + @irrelevant("Explicit") + inhale gen_ref_0.f > 2 @irrelevant("Implicit") incr(gen_ref_0) } @@ -270,8 +290,6 @@ SNIPPET 18_while_pure$=${ @irrelevant("Implicit") $RW_INT_PURE_1 := 0 while(@irrelevant("PathCondition")($RW_INT_PURE_0 > gen_c)) - invariant $ACC_INVARIANT - invariant @irrelevant("LoopInvariant")($GEN_ACC_INVARIANT) invariant @irrelevant("LoopInvariant")($RW_INT_PURE_0 <= gen_start) invariant @irrelevant("LoopInvariant")($RW_INT_PURE_1 == (gen_start-$RW_INT_PURE_0)*$RO_INT_PURE_0) { @@ -411,12 +429,18 @@ SNIPPET 25_unrelated_sync_points$=${ gen_j := gen_xs[1].gen_f } + @irrelevant("Implicit") + gen_i := 4 @irrelevant("Implicit") gen_j := gen_add(gen_j, gen_i) + @irrelevant("Implicit") + gen_x.gen_f := 4 @irrelevant("Implicit") incr_gen_f(gen_x) @irrelevant("Implicit") + gen_xs[2].gen_f := 4 + @irrelevant("Implicit") incr_gen_f(gen_xs[2]) @irrelevant("Rewrite") package gen_i >= 0 --* acc(gen_x.gen_f) diff --git a/src/test/resources/dependencyAnalysisTests/precisionTests/results/result_table.out b/src/test/resources/dependencyAnalysisTests/precisionTests/results/result_table.out index 5ab72c90f..4149e9be3 100644 --- a/src/test/resources/dependencyAnalysisTests/precisionTests/results/result_table.out +++ b/src/test/resources/dependencyAnalysisTests/precisionTests/results/result_table.out @@ -1,13 +1,13 @@ - & 00_baseline & 01_inhale_invariant & 02_2_assert_invariant & 02_assert_invariant & 03_assignment_pure & 04_2_assignment_field_disjoint_field & 04_assignment_field & 05_assignment_disjoint_ref & 06_assignment_disjoint_ref_disjoint_field & 07_assignment_disjoint_ref_quantified & 08_assignment_disjoint_ref_disjoint_field_quantified & 09_1_fold_unfold & 09_fold_unfold_with_implication & 10_function_add_pure & 11_function_add_impure & 12_method_call_pure & 13_method_call_impure & 14_branch & 15_branch_nested & 16_branch_infeasible & 17_branch_function & 18_while_pure & 19_while_perm & 20_magic_wand & 21_2_quasihavoc_disjoint_field & 21_quasihavoc & 22_quasihavoc_disjoint_ref & 23_quasihavoc_disjoint_ref_disjoint_field & 24_goto_pure & 25_unrelated_sync_points -dependencyAnalysisTests/precisionTests/branches & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.852 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.932 & 0.871 & 0.762 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 \\ -dependencyAnalysisTests/precisionTests/domain & 1.000 & 0.963 & 1.000 & 0.972 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.887 & 0.989 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.886 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.960 & 1.000 & 1.000 & 1.000 & 1.000 \\ -dependencyAnalysisTests/precisionTests/function-call & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.894 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.927 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 \\ -dependencyAnalysisTests/precisionTests/inhaleExhale & 1.000 & 0.963 & 1.000 & 0.972 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.891 & 1.000 & 0.952 & 0.889 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.917 & 0.944 & 1.000 & 1.000 & 0.944 & 1.000 & 0.898 & 0.867 & 1.000 & 1.000 & 1.000 \\ -dependencyAnalysisTests/precisionTests/loops & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.863 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.891 & 0.940 & NaN & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & NaN & 1.000 \\ -dependencyAnalysisTests/precisionTests/magicWands & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.981 & 1.000 & 1.000 & 0.838 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.881 & 1.000 & NaN & NaN & 0.956 & 1.000 & 0.958 & 0.962 & 1.000 & 1.000 & 1.000 \\ -dependencyAnalysisTests/precisionTests/method-call & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.899 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.929 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 \\ -dependencyAnalysisTests/precisionTests/misc & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.938 & 1.000 & 1.000 & 0.950 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.958 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.958 & 0.938 & 1.000 & 1.000 & 1.000 \\ -dependencyAnalysisTests/precisionTests/permWildcard & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & NaN & 1.000 & 1.000 & 0.907 & 1.000 & 1.000 & 0.833 & 1.000 & NaN & 1.000 & 1.000 & 1.000 & 1.000 & 0.852 & 0.921 & NaN & NaN & 1.000 & 1.000 & NaN & 0.776 & 1.000 & 1.000 & 1.000 \\ -dependencyAnalysisTests/precisionTests/permissions & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.892 & 1.000 & 1.000 & 0.746 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.767 & 1.000 & NaN & NaN & 0.956 & 1.000 & 0.723 & 0.665 & 1.000 & 1.000 & 1.000 \\ -dependencyAnalysisTests/precisionTests/predicates & 1.000 & 0.822 & 0.967 & 0.870 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.987 & 1.000 & 1.000 & 0.759 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.923 & 0.796 & 0.876 & NaN & NaN & 1.000 & 1.000 & 0.987 & 0.978 & 1.000 & 1.000 & 1.000 \\ -dependencyAnalysisTests/precisionTests/quantifiedPermissions & 1.000 & 0.917 & 1.000 & 0.943 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.902 & 1.000 & 1.000 & 0.750 & 1.000 & 1.000 & NaN & 0.988 & 1.000 & 0.919 & 0.819 & 0.908 & NaN & NaN & 0.938 & 1.000 & 0.798 & 0.685 & 1.000 & NaN & 0.957 \\ + & 00_baseline & 01_inhale_invariant & 02_2_assert_invariant & 02_assert_invariant & 03_assignment_pure & 04_2_assignment_field_disjoint_field & 04_assignment_field & 05_assignment_disjoint_ref & 06_assignment_disjoint_ref_disjoint_field & 07_assignment_disjoint_ref_quantified & 08_assignment_disjoint_ref_disjoint_field_quantified & 09_1_fold_unfold & 09_fold_unfold_with_implication & 10_function_add_pure & 11_function_add_impure & 12_method_call_pure & 13_method_call_impure & 14_branch & 15_branch_nested & 16_branch_infeasible & 17_branch_function & 18_while_pure & 19_while_perm & 20_magic_wand & 21_2_quasihavoc_disjoint_field & 21_quasihavoc & 22_quasihavoc_disjoint_ref & 23_quasihavoc_disjoint_ref_disjoint_field & 24_goto_pure & 25_unrelated_sync_points +dependencyAnalysisTests/precisionTests/A-inhaleExhale & 1.000 & 0.963 & 1.000 & 0.972 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.891 & 1.000 & 0.952 & 0.889 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.917 & 0.944 & 1.000 & 1.000 & 0.944 & 1.000 & 0.898 & 0.867 & 1.000 & 1.000 & 1.000 \\ +dependencyAnalysisTests/precisionTests/B-permissions & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.884 & 1.000 & 1.000 & 0.756 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.774 & 1.000 & 1.000 & 1.000 & 0.952 & 1.000 & 0.739 & 0.684 & 1.000 & 1.000 & 1.000 \\ +dependencyAnalysisTests/precisionTests/C-permWildcard & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & NaN & 1.000 & 1.000 & 0.907 & 1.000 & 1.000 & 0.833 & 1.000 & NaN & 1.000 & 1.000 & 1.000 & 1.000 & 0.852 & 0.921 & 1.000 & NaN & 1.000 & 1.000 & NaN & 0.776 & 1.000 & 1.000 & 1.000 \\ +dependencyAnalysisTests/precisionTests/D-quantifiedPermissions & 1.000 & 0.917 & 1.000 & 0.943 & 1.000 & 0.984 & NaN & 0.958 & 0.958 & 0.859 & 0.958 & 0.932 & 0.732 & 0.948 & NaN & NaN & 0.979 & 0.958 & 0.877 & 0.798 & 0.860 & NaN & NaN & 0.938 & 0.984 & NaN & 0.685 & 1.000 & NaN & 0.950 \\ +dependencyAnalysisTests/precisionTests/E-function-call & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.894 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.927 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 \\ +dependencyAnalysisTests/precisionTests/F-method-call & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.899 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.929 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 \\ +dependencyAnalysisTests/precisionTests/G-predicates & 1.000 & 0.822 & 0.967 & 0.870 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.987 & 1.000 & 1.000 & 0.759 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.923 & 0.796 & 0.876 & 1.000 & NaN & 1.000 & 1.000 & 0.987 & 0.978 & 1.000 & 1.000 & 1.000 \\ +dependencyAnalysisTests/precisionTests/H-magicWands & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.981 & 1.000 & 1.000 & 0.838 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.881 & 1.000 & 1.000 & NaN & 0.956 & 1.000 & 0.958 & 0.962 & 1.000 & 1.000 & 1.000 \\ +dependencyAnalysisTests/precisionTests/I-branches & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.852 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.932 & 0.871 & 0.762 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 \\ +dependencyAnalysisTests/precisionTests/J-loops & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.863 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.891 & 0.940 & NaN & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & NaN & 1.000 \\ +dependencyAnalysisTests/precisionTests/K-domain & 1.000 & 0.963 & 1.000 & 0.972 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.887 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.886 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.960 & 1.000 & 1.000 & 1.000 & 1.000 \\ +dependencyAnalysisTests/precisionTests/L-misc & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.938 & 1.000 & 1.000 & 0.950 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.958 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.958 & 0.938 & 1.000 & 1.000 & 1.000 \\ diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/inhaleExhale.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/A-inhaleExhale.vpr similarity index 100% rename from src/test/resources/dependencyAnalysisTests/unitTests/inhaleExhale.vpr rename to src/test/resources/dependencyAnalysisTests/unitTests/A-inhaleExhale.vpr diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/B-permissions.vpr similarity index 85% rename from src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr rename to src/test/resources/dependencyAnalysisTests/unitTests/B-permissions.vpr index 03aa875ef..996cabcf4 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/permissions.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/B-permissions.vpr @@ -102,16 +102,4 @@ method permAmount1(x: Ref, p: Perm) } -method noAlias(a: Ref, b: Ref, c: Ref) - requires @dependency("Explicit")(acc(a.f)) - requires @dependency("Explicit")(acc(b.f, 1/2)) - requires @irrelevant("Explicit")(acc(c.f, 1/2)) -{ - // $PrecisionTest: $READ_WRITE=a.f $READ_ONLY=b.f,c.f $ACC_INVARIANT=acc(a.f)&&acc(b.f,1/4)&&acc(c.f,1/4) - - @testAssertion("Explicit") - assert a != b -} - - diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/permWildcard.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/C-permWildcard.vpr similarity index 100% rename from src/test/resources/dependencyAnalysisTests/unitTests/permWildcard.vpr rename to src/test/resources/dependencyAnalysisTests/unitTests/C-permWildcard.vpr diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/D-quantifiedPermissions.vpr similarity index 92% rename from src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr rename to src/test/resources/dependencyAnalysisTests/unitTests/D-quantifiedPermissions.vpr index 93c4ed974..bdf2b6089 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/quantifiedPermissions.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/D-quantifiedPermissions.vpr @@ -6,7 +6,11 @@ method quantifiedPerm1(xs: Seq[Ref]) { inhale xs[0] != xs[1] - // $PrecisionTest: $READ_WRITE=xs[0].f,xs[1].f $INVARIANT=|xs|>0 $ACC_INVARIANT=forall$_$x:Ref::x$_$in$_$xs$_$==>$_$acc(x.f) + var i: Int + @irrelevant("Implicit") + i := 0 + + // $PrecisionTest: $READ_WRITE=xs[i].f,xs[1].f $INVARIANT=|xs|>0 $ACC_INVARIANT=forall$_$x:Ref::x$_$in$_$xs$_$==>$_$acc(x.f) @testAssertion() xs[0].f := 10 @@ -42,7 +46,10 @@ method quantifiedExhalePartiallyTest(xs: Seq[Ref]) { inhale xs[0] != xs[4] - // $PrecisionTest: $READ_WRITE=xs[0].f,xs[4].f $INVARIANT=|xs|>0 $ACC_INVARIANT=forall$_$x:Ref::x$_$in$_$xs$_$==>$_$acc(x.f) + var i: Int + @irrelevant("Implicit") + i := 0 + // $PrecisionTest: $READ_WRITE=xs[i].f,xs[4].f $INVARIANT=|xs|>0 $ACC_INVARIANT=forall$_$x:Ref::x$_$in$_$xs$_$==>$_$acc(x.f) @testAssertion("Explicit") exhale forall x: Ref :: x in xs ==> acc(x.f, 1/2) @@ -55,7 +62,11 @@ method quantifiedExhalePartially(xs: Seq[Ref]) { exhale forall x: Ref :: x in xs ==> acc(x.f, 1/2) - // $PrecisionTest: $READ_WRITE=res $READ_ONLY=xs[1].f,xs[0].f,xs[2].f $INVARIANT=|xs|>0 $ACC_INVARIANT=forall$_$x:Ref::x$_$in$_$xs$_$==>$_$acc(x.f,1/4) + var i: Int + @irrelevant("Implicit") + i := 1 + + // $PrecisionTest: $READ_WRITE=res $READ_ONLY=xs[i].f,xs[0].f,xs[2].f $INVARIANT=|xs|>0 $ACC_INVARIANT=forall$_$x:Ref::x$_$in$_$xs$_$==>$_$acc(x.f,1/4) @testAssertion("Implicit") res := xs[1].f + 1 diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/function-call.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/E-function-call.vpr similarity index 100% rename from src/test/resources/dependencyAnalysisTests/unitTests/function-call.vpr rename to src/test/resources/dependencyAnalysisTests/unitTests/E-function-call.vpr diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/method-call.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/F-method-call.vpr similarity index 100% rename from src/test/resources/dependencyAnalysisTests/unitTests/method-call.vpr rename to src/test/resources/dependencyAnalysisTests/unitTests/F-method-call.vpr diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/predicates.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/G-predicates.vpr similarity index 100% rename from src/test/resources/dependencyAnalysisTests/unitTests/predicates.vpr rename to src/test/resources/dependencyAnalysisTests/unitTests/G-predicates.vpr diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/H-magicWands.vpr similarity index 100% rename from src/test/resources/dependencyAnalysisTests/unitTests/magicWands.vpr rename to src/test/resources/dependencyAnalysisTests/unitTests/H-magicWands.vpr diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/branches.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/I-branches.vpr similarity index 100% rename from src/test/resources/dependencyAnalysisTests/unitTests/branches.vpr rename to src/test/resources/dependencyAnalysisTests/unitTests/I-branches.vpr diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/loops.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/J-loops.vpr similarity index 100% rename from src/test/resources/dependencyAnalysisTests/unitTests/loops.vpr rename to src/test/resources/dependencyAnalysisTests/unitTests/J-loops.vpr diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/domain.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/K-domain.vpr similarity index 100% rename from src/test/resources/dependencyAnalysisTests/unitTests/domain.vpr rename to src/test/resources/dependencyAnalysisTests/unitTests/K-domain.vpr diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/misc.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/L-misc.vpr similarity index 100% rename from src/test/resources/dependencyAnalysisTests/unitTests/misc.vpr rename to src/test/resources/dependencyAnalysisTests/unitTests/L-misc.vpr From ff17c4fa6f18e1b1d3bd350279e5627b083068bd Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 24 Sep 2025 09:17:59 +0200 Subject: [PATCH 257/474] cleanup --- src/test/scala/AssumptionAnalysisTests.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/AssumptionAnalysisTests.scala index 4bc449850..07b2a7786 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/AssumptionAnalysisTests.scala @@ -17,7 +17,7 @@ class AssumptionAnalysisTests extends AnyFunSuite with AssumptionAnalysisTestFra val CHECK_PRECISION = false val EXECUTE_TEST = true val EXECUTE_PERFORMANCE_BENCHMARK = false - val ignores: Seq[String] = Seq("example1", "example2", "iterativeTreeDelete", "listAppend_wands") + val ignores: Seq[String] = Seq() val testDirectories: Seq[String] = Seq( "dependencyAnalysisTests/all", "dependencyAnalysisTests/unitTests", From 1aae05cad68ec17c316cf3320177a2fee37f73b6 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 26 Sep 2025 10:15:30 +0200 Subject: [PATCH 258/474] add Readme --- src/main/scala/assumptionAnalysis/README.md | 72 + .../dependencyAnalysisTests/README.md | 33 + .../precision_benchmark_plotter.py | 96 - .../precision_test_generator.py | 146 - .../benchmark_scripts/snippets.txt | 477 --- .../dependencyAnalysisTests/notes.txt | 49 - .../results/result_2025-08-16_13-51-18.out | 3584 ----------------- .../precisionTests/results/result_table.out | 44 +- 8 files changed, 136 insertions(+), 4365 deletions(-) create mode 100644 src/main/scala/assumptionAnalysis/README.md create mode 100644 src/test/resources/dependencyAnalysisTests/README.md delete mode 100644 src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_benchmark_plotter.py delete mode 100644 src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_test_generator.py delete mode 100644 src/test/resources/dependencyAnalysisTests/benchmark_scripts/snippets.txt delete mode 100644 src/test/resources/dependencyAnalysisTests/notes.txt delete mode 100644 src/test/resources/dependencyAnalysisTests/precisionTests/results/result_2025-08-16_13-51-18.out diff --git a/src/main/scala/assumptionAnalysis/README.md b/src/main/scala/assumptionAnalysis/README.md new file mode 100644 index 000000000..c84b0e19d --- /dev/null +++ b/src/main/scala/assumptionAnalysis/README.md @@ -0,0 +1,72 @@ +# Dependency Analysis + + +# Running Silicon with Dependency Analysis + +Dependency Analysis is enabled thorough the following configuration options: +`--enableAssumptionAnalysis --disableInfeasibilityChecks --proverArgs "proof=true unsat-core=true"` + +Additionally, to retrieve the results and query the dependency graph, use: +- `--startAssumptionAnalysisTool` + - Automatically starts the command-line tool once verification terminates. +- `--assumptionAnalysisExportPath [PATH TO EXPORT FOLDER]` + - e.g., `--assumptionAnalysisExportPath "graphExports"` + - Exports the graph to a folder named after the verified program under the given path (e.g. `graphExports/src_test_resources_andrea_quickTest` for input program `src/test/resources/andrea/quickTest.vpr`) + +For debugging dependency analysis results, the option `--enableAssumptionAnalysisDebugging` can be used which disables the merging of nodes. +As a result, the graph used for query computation and the exported graph contain all low-level details. + + +# Command-Line Tool + +Requires `--startAssumptionAnalysisTool`. + +Example queries for program `src/test/resources/dependencyAnalysisTests/unitTests/B-permissions.vpr`: +- `dep 99` + - Returns all dependencies of assertions on line 99. +- `dep B-permissions@19` + - Uniquely identifies the given line when there are many source files. + - This notation can be used in every command. +- `dep 66 71` + - Query multiple lines. +- `downDep 14` + - Returns all dependents of assumptions on line 14. +- `hasDep 64 66 71` + - Returns true iff there is any dependency between any two queried lines and false otherwise. +- `cov` + - Prints proof coverage and uncovered nodes of each method. +- `cov perm5` + - Prints proof coverage and uncovered nodes of method `perm5`. +- `covL perm5 71` + - Prints proof coverage (and uncovered nodes) of assertions on line 71 in method `perm5`. +- `prune 66 71` + - Exports the program pruned with respect to lines 66 and 71. + - exportFileName: path and file name for the pruned program (e.g. `prunedPrograms/test.vpr` + + +# Neo4j Scripts and Usage + +Graphs exported when using `--assumptionAnalysisExportPath [PATH TO EXPORT FOLDER]` can be imported to a [Neo4j database]({https://neo4j.com/) using the `neo4j_importer.py` script. + +Importing dependency graphs to Neo4j: + +1. [Create your own Neo4j instance](https://neo4j.com/docs/aura/getting-started/create-account/). + +1. Replace `[NEO4J_URI]` with the URI to your instance (e.g., `neo4j+ssc://df123ab4.databases.neo4j.io`) + +1. Set your environment variable `NEO4J-PW` to the password of your instance. + +1. Make sure the instance is up and running. + +1. Execute `python src/main/scala/assumptionAnalysis/neo4j_importer.py` and when queried provide the following inputs: + 1. foldername: relative path to the export folder of the dependency graph (e.g. `graphExports/src_test_resources_andrea_quickTest`) + 1. node_label: label to be given to the nodes created in Neo4j + 1. Note that existing nodes with the same label are deleted! + +1. Login to Neo4j are explore the graphs. The script creates three graphs, each using a different label for its nodes: + 1. \[label\]: Graph as defined in the export files. + 1. \[label\]_NonInternal: Graph that does not contain any internal nodes and low-level nodes with identical source are already merged into one node. + 1. \[label\]_Explicit: Graph that only contains explicit assumption nodes (and all assertions) and low-level nodes with identical source are already merged into one node. + 1. For a quick start, open the Explore tool and search for `label_NonInternal-(any)-label_NonInternal`. The tool presents the graph described in (ii). + 1. Some query templates, which can be imported to Neo4j, can be found in `neo4j_query_saved_cypher_2025-9-17.csv`. + diff --git a/src/test/resources/dependencyAnalysisTests/README.md b/src/test/resources/dependencyAnalysisTests/README.md new file mode 100644 index 000000000..d9cf4ab04 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/README.md @@ -0,0 +1,33 @@ +# Dependency Analysis Test + +## Soundness Tests + +The programs for annotated tests can be found in the folders `all`, `unitTests`, and `mce`. + +Pruning tests are executed against all annotated tests and additionally the programs in folder `real-world-examples`. + +Both are executed using `src/test/scala/AssumptionAnalysisTests.scala`. + +By default, only expected dependencies are checked (`@dependency` annotations). +Setting `CHECK_PRECISION=true` additionally checks precision through `@irrelevant` annotations and reports false positives as an error. + +## Precision Benchmark + +The base programs are defined in folder `unitTests` and interferences in `precisionTests\scripts\interferences.txt`. + +Executing the benchmark: + +1. Generate test programs: `python precisionTests\scripts\precision_test_generator.py` + 1. Generates a number of subfolders in `precisionTests`. + +1. Execute precision benchmark using `src/test/scala/AssumptionAnalysisPrecisionBenchmark.scala`. + 1. Results are written to `precisionTests\results\results_{timestamp}.out` + +1. Plot the result: `python precisionTests\scripts\precision_benchmark_plotter.py` + 1. Input file name: name of the result file (e.g., `result_2025-09-22_20-07-27.out`) + 1. Each table cell represents the precision computed as `#(real dependencies) / #(reported dependencies)`. + Real dependencies are computed as `#(reported dependencies) - #(reported dependencies annotated as irrelevant)`. + + +To analyze the cause of imprecision, `src/test/scala/AssumptionAnalysisTests.scala` with `CHECK_PRECISION=true` can be executed on the program of interest. +Imprecise results are reported as `Unexpected dependency` and make the test fail. \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_benchmark_plotter.py b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_benchmark_plotter.py deleted file mode 100644 index c2b4ff6e2..000000000 --- a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_benchmark_plotter.py +++ /dev/null @@ -1,96 +0,0 @@ -import os - -def read_results_file(result_file_path: str) -> dict[tuple[str, str], list[tuple[str, float]]]: - with open(result_file_path) as f: - lines = f.readlines() - - current_test = None - current_results = [] - results = {} - - for line in lines: - line = line.strip() - print(line) - if not line or line.startswith("!") or line.startswith("Failed") \ - or "Skip" in line \ - or "does not verify" in line: - continue - if line.startswith("dependencyAnalysisTests/precisionTests/"): - # Save previous block if any - if current_test and current_results: - results[current_test] = current_results - # Start new block - parts = line.strip().split(" - ") - current_test = (parts[0], parts[1]) - current_results = [] - else: - method_name, precision = map(str.strip, line.split(":")) - current_results.append((method_name, float(precision))) - - if current_test and current_results: - results[current_test] = current_results - - return results - -def build_table(out_file_path: str, results: dict[tuple[str, str], list[tuple[str, float]]]): - f = open(out_file_path, mode="w") - header = sorted(set([interference_name for (_, interference_name) in results.keys()])) - header_summary_columns = [] # /["|", "max", "min", "avg"] - base_test_names = sorted(set([base_name.strip() for (base_name, _) in results.keys()])) - column_1_width = max([len(h) for h in base_test_names]) + 4 - column_widths = [len(h + " ") for h in (header + header_summary_columns)] - f.write("".ljust(column_1_width) + " & " + " & ".join(header + header_summary_columns)) - f.write("\n") - - for base_test in base_test_names: - f.write(base_test.ljust(column_1_width)) - current_test_results = [] - for idx, h in enumerate(header): - f.write(" & ") - if not (base_test, h) in results.keys(): - f.write("NaN".center(column_widths[idx])) - continue - result = results[(base_test, h)] - avg = sum([prec for (_, prec) in result]) / len(result) - current_test_results.append(avg) - f.write(f"{avg:.3f}".center(column_widths[idx])) - - # print summary - # f.write("|".center(column_widths[idx+1])) - # f.write(f"{max(current_test_results):.3f}".center(column_widths[idx+2])) - # f.write(f"{min(current_test_results):.3f}".center(column_widths[idx+3])) - # f.write(f"{sum(current_test_results)/len(current_test_results):.3f}".center(column_widths[idx+4])) - f.write("\\\\ \n") - -def build_table_transposed(out_file_path: str, results: dict[tuple[str, str], list[tuple[str, float]]]): - f = open(out_file_path, mode="w") - interference_names = sorted(set([interference_name for (_, interference_name) in results.keys()])) - base_test_names = sorted(set([base_name.strip() for (base_name, _) in results.keys()])) - base_test_names_striped = [n.replace("dependencyAnalysisTests/precisionTests/", "") for n in base_test_names] - column_1_width = max([len(h) for h in interference_names]) + 4 - column_widths = [len(h + " ") for h in (base_test_names_striped)] - f.write("".ljust(column_1_width) + " & " + " & ".join(["\\rotatebox{90}{" + b + "}" for b in base_test_names_striped])) - f.write("\\\\ \\hline\n") - - for interference in interference_names: - f.write(interference.replace("_", "\\_").ljust(column_1_width)) - current_test_results = [] - for idx, base_test in enumerate(base_test_names): - f.write(" & ") - if not (base_test, interference) in results.keys(): - f.write("NaN".center(column_widths[idx])) - continue - result = results[(base_test, interference)] - avg = sum([prec for (_, prec) in result]) / len(result) - current_test_results.append(avg) - if avg == 1.0: - f.write(f"{avg:.2f}".center(column_widths[idx])) - else: - f.write("\\cellcolor{red!30} " + f"{avg:.2f}".center(column_widths[idx])) - f.write("\\\\ \n") - -result_file_name = input("file name: ") -raw_results = read_results_file("silicon\\src\\test\\resources\\dependencyAnalysisTests\\precisionTests\\results\\" + result_file_name) - -build_table("silicon\\src\\test\\resources\\dependencyAnalysisTests\\precisionTests\\results\\result_table.out", raw_results) -build_table_transposed("silicon\\src\\test\\resources\\dependencyAnalysisTests\\precisionTests\\results\\result_table_transposed.out", raw_results) \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_test_generator.py b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_test_generator.py deleted file mode 100644 index a207868c1..000000000 --- a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/precision_test_generator.py +++ /dev/null @@ -1,146 +0,0 @@ - -import os - -def remove_comments(lines: list[str]): - cleansed = [] - for l in lines: - if l.strip() and not l.strip().startswith("//"): - cleansed.append(l) - return cleansed - -def read_snippets_file(path): - with open(path, mode="r") as f: - content = "".join(remove_comments(f.readlines())) - snippets = content.split("SNIPPET") - preamble = snippets[0] - snippet_dict = {} - for snippet in snippets[1:]: - decl = snippet.split("$=$") - name, definition = decl[0].strip(), decl[1].strip() - snippet_dict[name] = definition - return preamble, snippet_dict - - -def read_test_template(path): - with open(path, mode="r") as f: - content = "".join(f.readlines()) - snippets = content.split("method ") - preamble = snippets[0] - methods = snippets[1:] - return preamble, methods - -def extract_vars(line: str): - _, _, after = line.partition("$PrecisionTest:") - var_decls = after.strip().split(" ") - read_write_vars = [] - read_only_vars = [] - invariant = "" - acc_invariant = "" - for decl in var_decls: - tmp = decl.split("=") - if(tmp[0] == "$READ_ONLY"): - read_only_vars = read_only_vars + [v.replace(";", ",") for v in tmp[1].split(",")] - elif(tmp[0] == "$READ_WRITE"): - read_write_vars = read_write_vars + [v.replace(";", ",") for v in tmp[1].split(",")] - elif(tmp[0] == "$INVARIANT"): - invariant = "=".join(tmp[1:]).replace("$_$", " ") - elif(tmp[0] == "$ACC_INVARIANT"): - acc_invariant = "=".join(tmp[1:]).replace("$_$", " ") - # print(f"line: {line}") - # print(f"read only: {read_only_vars}") - # print(f"read write: {read_write_vars}") - # print() - return read_only_vars + read_write_vars, read_write_vars, invariant, acc_invariant - -def replace_vars(snippet: str, placeholder: str, vars: list[str]): - idx = 0 - gen_vars = [] - while placeholder in snippet: - if idx < len(vars): - snippet = snippet.replace(f"{placeholder}{idx}", vars[idx]) - else: - new_var = f"gen_{placeholder.replace("$", "")}{idx}{".f" if "FIELD" in placeholder else ""}" - snippet = snippet.replace(f"{placeholder}{idx}", new_var) - gen_vars.append(new_var) - idx += 1 - return snippet, gen_vars - -def generate_from_snippet(snippet: str, line: str): - read_only_vars, read_write_vars, invariant, acc_invariant = extract_vars(line) - # print(f"read_only_vars: {read_only_vars}") - # print(f"read_write_vars: {read_write_vars}") - - # replace variables, generate new ones if necessary - snippet, gen_ro_refs = replace_vars(snippet, "$RO_REF_F_", [v.split(".")[0] for v in read_only_vars if v.endswith(".f")]) - snippet, gen_rw_refs = replace_vars(snippet, "$RW_REF_F_", [v.split(".")[0] for v in read_write_vars if v.endswith(".f")]) - snippet, gen_vars_ro_field = replace_vars(snippet, "$RO_INT_FIELD_", [v for v in read_only_vars if "." in v]) - snippet, gen_vars_rw_field = replace_vars(snippet, "$RW_INT_FIELD_", [v for v in read_write_vars if "." in v]) - snippet, gen_vars_ro_pure = replace_vars(snippet, "$RO_INT_PURE_", [v for v in read_only_vars if not "." in v]) - snippet, gen_vars_rw_pure = replace_vars(snippet, "$RW_INT_PURE_", [v for v in read_write_vars if not "." in v]) - snippet, gen_vars_ro = replace_vars(snippet, "$RO_INT_", read_only_vars) - snippet, gen_vars_rw = replace_vars(snippet, "$RW_INT_", read_write_vars) - - snippet = "\nvar gen_dummy_int: Int\n" + snippet - - # assume non-aliasing of references - generated_refs = set(gen_rw_refs + gen_ro_refs + [v.split(".")[0] for v in (gen_vars_rw_field + gen_vars_ro_field) if "." in v]) - existing_refs = set([v.split(".")[0] for v in (read_write_vars+read_only_vars) if "." in v]) - # snippet = f"\n//generated: {generated_refs}\n//existing: {existing_refs}\n" + snippet - snippet = "\n".join([f"inhale {a} != {b}" for a in generated_refs for b in existing_refs if a != b]) + snippet - - gen_vars_ro_field = gen_vars_ro_field + [f"{v}.f" for v in gen_ro_refs] - gen_vars_rw_field = gen_vars_rw_field + [f"{v}.f" for v in gen_rw_refs] - - snippet = snippet.replace("$INVARIANT", invariant if invariant != "" else "true") - gen_acc_invariants = [f"acc({v}, 1/2)" for v in gen_vars_ro_field] + [f"acc({v})" for v in gen_vars_rw_field] - gen_acc_invariant = " && ".join([f"@irrelevant(\"LoopInvariant\")({v})" for v in gen_acc_invariants]) - snippet = snippet.replace("$ACC_INVARIANT", acc_invariant if acc_invariant != "" else "true") - snippet = snippet.replace("$GEN_ACC_INVARIANT", gen_acc_invariant if gen_acc_invariant != "" else "true") - - # declare and initialize newly generated vars - snippet = "".join([f"var {v.split(".")[0]}: Ref\n@irrelevant(\"Explicit\")\ninhale acc({v}, 1/2)\n" for v in gen_vars_ro_field]) + snippet - snippet = "".join([f"var {v.split(".")[0]}: Ref\n@irrelevant(\"Explicit\")\ninhale acc({v})\n" for v in gen_vars_rw_field]) + snippet - all_vars = gen_vars_ro + gen_vars_rw + gen_vars_ro_pure + gen_vars_rw_pure - if len(all_vars) > 0: - snippet = "var " + ", ".join([f"{v}: Int" for v in all_vars]) + "\n" + snippet - - return snippet - -def apply_snippet(snippet: str, method: str): - method_lines = method.splitlines() - new_method = "method " - for line in method_lines: - if not "$PrecisionTest:" in line: - new_method += line + "\n" - else: - new_method += generate_from_snippet(snippet, line) + "\n" - return new_method - - -def handle_template_file(path: str, output_path: str, snippet_preamble: str, snippets: dict[str, str]): - preamble, methods = read_test_template(path) - if not path.endswith(".vpr"): - return - program_foldername = path.replace(".vpr", "").split("\\")[-1] - os.makedirs(f"{output_path}\\{program_foldername}", exist_ok=True) - for snippet_name, snippet in snippets.items(): - f = open(f"{output_path}\\{program_foldername}\\{snippet_name}.vpr", "w") - f.write(preamble) - f.write("\n") - f.write(snippet_preamble) - f.write("\n") - for method in methods: - new_method = apply_snippet(snippet, method) - f.write(new_method) - f.close() - - -def process_folder(folder_path: str, output_path: str, snippet_preamble: str, snippets: dict[str, str]): - files = [os.path.join(folder_path, f) for f in os.listdir(folder_path) if os.path.isfile(os.path.join(folder_path, f))] - for file in files: - handle_template_file(file, output_path, snippet_preamble, snippets) - -preamble, snippet_dict = read_snippets_file("silicon\\src\\test\\resources\\dependencyAnalysisTests\\benchmark_scripts\\snippets.txt") -process_folder("silicon\\src\\test\\resources\\dependencyAnalysisTests\\unitTests", - "silicon\\src\\test\\resources\\dependencyAnalysisTests\\precisionTests", - preamble, snippet_dict) diff --git a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/snippets.txt b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/snippets.txt deleted file mode 100644 index aa66620b8..000000000 --- a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/snippets.txt +++ /dev/null @@ -1,477 +0,0 @@ -field gen_f: Int - -predicate gen_confuse(i: Int, x: Ref){ - i >= 0 && acc(x.f) -} - -predicate gen_confuse_genf(i: Int, x: Ref){ - i >= 0 && acc(x.gen_f) -} - - -predicate gen_confuse_with_impl(i: Int, x: Ref){ - i >= 0 ==> acc(x.f) -} - -predicate gen_confuse_gen_field(i: Int, x: Ref){ - i >= 0 ==> acc(x.gen_f) -} - -function gen_add(a: Int, b: Int): Int - requires b > 0 - ensures result > a -{ a + b } - -function gen_add_impure(x: Ref, b: Int): Int - requires acc(x.f) - requires b > 0 - ensures result == x.f + b - ensures result > x.f - -function gen_add_positive(a: Int, b: Int): Int - requires a >= 0 && b >= 0 - ensures result == a + b - ensures result >= 0 - -method incr(a: Ref) - requires acc(a.f) - requires a.f > 0 - ensures acc(a.f) - ensures a.f == old(a.f) + 1 - ensures a.f > 1 -{ - a.f := a.f + 1 -} - -method incr_gen_f(a: Ref) - requires acc(a.gen_f) - requires a.gen_f > 0 - ensures acc(a.gen_f) - ensures a.gen_f == old(a.gen_f) + 1 - ensures a.gen_f > 1 -{ - a.gen_f := a.gen_f + 1 -} - -method gen_incr_pure(a: Int) returns (res: Int) - requires a > 0 - ensures res == a + 1 - ensures res > 1 -{ - res := a + 1 -} - -SNIPPET 00_baseline$=${} - -SNIPPET 01_inhale_invariant$=${ - var gen_i: Int - @irrelevant("Explicit") - inhale $INVARIANT && gen_i > 0 -} - -SNIPPET 02_assert_invariant$=${ - var gen_i: Int - @irrelevant("Explicit") - inhale gen_i > 0 - @irrelevant() - assert $INVARIANT && gen_i > 0 -} - -SNIPPET 02_2_assert_invariant$=${ - @irrelevant() - assert $INVARIANT -} - -SNIPPET 03_assignment_pure$=${ - @irrelevant("Implicit") - $RW_INT_PURE_0 := $RO_INT_0 + $RO_INT_1 -} - -SNIPPET 04_assignment_field$=${ - @irrelevant("Implicit") - $RW_INT_FIELD_0 := $RO_INT_0 + $RO_INT_1 -} - -SNIPPET 04_2_assignment_field_disjoint_field$=${ - @irrelevant("Implicit") - inhale acc($RO_REF_F_0.gen_f) - @irrelevant("Implicit") - $RO_REF_F_0.gen_f := $RO_INT_0 + $RO_INT_1 -} - -SNIPPET 05_assignment_disjoint_ref$=${ - var gen_x: Ref - // @irrelevant("Explicit") // TODO ake: aliasing! - inhale acc(gen_x.f) - - @irrelevant("Implicit") - gen_x.f := $RO_INT_0 + $RO_INT_1 - - @irrelevant("Implicit") - $RW_INT_0 := gen_x.f -} - -SNIPPET 06_assignment_disjoint_ref_disjoint_field$=${ - var gen_x: Ref - @irrelevant("Explicit") - inhale acc(gen_x.gen_f) - - @irrelevant("Implicit") - gen_x.gen_f := $RO_INT_0 + $RO_INT_1 - - @irrelevant("Implicit") - $RW_INT_0 := gen_x.gen_f -} - - -SNIPPET 07_assignment_disjoint_ref_quantified$=${ - var gen_xs: Seq[Ref] - @irrelevant("Explicit") - inhale |gen_xs| > 2 - // @irrelevant("Explicit") // TODO ake: aliasing! - inhale forall gen_x: Ref :: gen_x in gen_xs ==> acc(gen_x.f) - @irrelevant("Explicit") - inhale forall gen_x: Ref :: gen_x in gen_xs ==> gen_x.f < 0 - - @irrelevant("Implicit") - gen_xs[0].f := gen_xs[1].f + $RO_INT_0 - $RO_INT_1 - - @irrelevant("Implicit") - $RW_INT_0 := gen_xs[0].f + gen_xs[1].f -} - - -SNIPPET 08_assignment_disjoint_ref_disjoint_field_quantified$=${ - var gen_xs: Seq[Ref] - @irrelevant("Explicit") - inhale |gen_xs| > 2 - @irrelevant("Explicit") - inhale forall gen_x: Ref :: gen_x in gen_xs ==> acc(gen_x.gen_f) - @irrelevant("Explicit") - inhale forall gen_x: Ref :: gen_x in gen_xs ==> gen_x.gen_f < 0 - - @irrelevant("Implicit") - gen_xs[0].gen_f := gen_xs[1].gen_f + $RO_INT_0 - $RO_INT_1 - - @irrelevant("Implicit") - $RW_INT_0 := gen_xs[0].gen_f + gen_xs[1].gen_f -} - -// fold-unfold -SNIPPET 09_1_fold_unfold$=${ - var gen_x: Ref - // @irrelevant("Explicit") // TODO ake: aliasing! - inhale acc(gen_x.f) - @irrelevant("Implicit") - $RW_INT_0 := 1 - @irrelevant("Implicit") - fold gen_confuse($RW_INT_0, gen_x) - @irrelevant("Implicit") - unfold gen_confuse($RW_INT_0, gen_x) -} - -// fold-unfold with implication -SNIPPET 09_fold_unfold_with_implication$=${ - var gen_x: Ref - // @irrelevant("Explicit") // TODO ake: aliasing! - inhale acc(gen_x.f) - @irrelevant("Implicit") - fold gen_confuse_with_impl($RO_INT_0, gen_x) - @irrelevant("Implicit") - unfold gen_confuse_with_impl($RO_INT_0, gen_x) -} - -// function call -SNIPPET 10_function_add_pure$=${ - @irrelevant("Implicit") - $RW_INT_0 := 5 - @irrelevant("Implicit") - $RW_INT_0 := gen_add($RO_INT_0, $RW_INT_0) - @irrelevant("Implicit") - $RW_INT_1 := 1 - @irrelevant("Implicit") - $RW_INT_1 := gen_add($RO_INT_1, $RW_INT_1) -} - - -SNIPPET 11_function_add_impure$=${ - @irrelevant("Implicit") - $RW_INT_0 := 5 - @irrelevant("Implicit") - $RW_INT_0 := gen_add_impure($RW_REF_F_0, $RW_INT_0) -} - -SNIPPET 12_method_call_pure$=${ - @irrelevant("Implicit") - $RW_INT_0 := 5 - @irrelevant("Implicit") - $RW_INT_0 := gen_incr_pure($RW_INT_0) -} - -SNIPPET 13_method_call_impure$=${ - var gen_ref_0: Ref - gen_ref_0 := new(f) // might be used for non-aliasing proof - @irrelevant("Explicit") - inhale gen_ref_0.f > 2 - @irrelevant("Implicit") - incr(gen_ref_0) -} - -SNIPPET 14_branch$=${ - var gen_i: Int - if(@irrelevant("PathCondition")($RO_INT_0 > gen_i)){ - @irrelevant("Implicit") - $RW_INT_0 := 10 - }else{ - @irrelevant("Implicit") - $RW_INT_0 := $RO_INT_0 + $RO_INT_1 - } -} - -SNIPPET 15_branch_nested$=${ - var gen_c: Int - if(@irrelevant("PathCondition")($RO_INT_0 > gen_c && $RO_INT_1 > gen_c)){ - var gen_i: Int - @irrelevant("Implicit") - gen_i := $RO_INT_0 + $RO_INT_1 - @irrelevant("Implicit") - $RW_INT_0 := 10 - }else{ - if(@irrelevant("PathCondition")($RO_INT_0 > gen_c + 5)){ - @irrelevant("Implicit") - $RW_INT_0 := 30 - }else{ - var gen_i: Int - @irrelevant("Implicit") - gen_i := $RO_INT_0 + $RO_INT_1 - } - } -} - -SNIPPET 16_branch_infeasible$=${ - if(@irrelevant("PathCondition")($INVARIANT)){ - @irrelevant("Implicit") - $RW_INT_0 := 10 - }else{ - // unreachable - @irrelevant("Implicit") - $RO_INT_FIELD_0 := $RO_INT_1 - $RO_INT_0 - - @irrelevant() - assert false - } -} - -SNIPPET 17_branch_function$=${ - var gen_c: Int - @irrelevant("Explicit") - inhale gen_c > 0 - if(@irrelevant("PathCondition")($RO_INT_0 > gen_c && $RO_INT_1 > gen_c)){ - @irrelevant("Implicit") - $RW_INT_0 := gen_add_positive($RO_INT_0, $RO_INT_1) - }else{ - if(@irrelevant("PathCondition")($RO_INT_0 > gen_c)){ - @irrelevant("Implicit") - $RW_INT_0 := gen_add_positive($RO_INT_0, gen_c) - }else{ - @irrelevant("Implicit") - $RW_INT_0 := 0 - } - } -} - - -SNIPPET 18_while_pure$=${ - var gen_start: Int, gen_c: Int - @irrelevant("Explicit") - inhale gen_c >= 0 - @irrelevant("Implicit") - gen_start := $RW_INT_PURE_0 - @irrelevant("Implicit") - $RW_INT_PURE_1 := 0 - while(@irrelevant("PathCondition")($RW_INT_PURE_0 > gen_c)) - invariant @irrelevant("LoopInvariant")($RW_INT_PURE_0 <= gen_start) - invariant @irrelevant("LoopInvariant")($RW_INT_PURE_1 == (gen_start-$RW_INT_PURE_0)*$RO_INT_PURE_0) - { - @irrelevant("Implicit") - $RW_INT_PURE_1 := $RW_INT_PURE_1 + $RO_INT_PURE_0 - @irrelevant("Implicit") - $RW_INT_PURE_0 := $RW_INT_PURE_0 - 1 - } -} - -SNIPPET 19_while_perm$=${ - var gen_start: Int, gen_c: Int - @irrelevant("Explicit") - inhale gen_c >= 0 - @irrelevant("Implicit") - gen_start := $RW_INT_FIELD_0 - @irrelevant("Implicit") - $RW_INT_FIELD_1 := 0 - while(@irrelevant("PathCondition")($RW_INT_FIELD_0 > gen_c)) - invariant $ACC_INVARIANT - invariant @irrelevant("LoopInvariant")($GEN_ACC_INVARIANT) - invariant @irrelevant("LoopInvariant")($RW_INT_FIELD_0 <= gen_start) - invariant @irrelevant("LoopInvariant")($RW_INT_FIELD_1 == (gen_start-$RW_INT_FIELD_0)*10) - { - @irrelevant("Implicit") - $RW_INT_FIELD_1 := $RW_INT_FIELD_1 + 10 - @irrelevant("Implicit") - $RW_INT_FIELD_0 := $RW_INT_FIELD_0 - 1 - } -} - -SNIPPET 20_magic_wand$=${ - var gen_i: Int - var gen_x: Ref - @irrelevant("Explicit") - inhale gen_i > 0 - // @irrelevant("Explicit") // TODO ake: aliasing! - inhale acc(gen_x.f) - @irrelevant("Explicit") - inhale gen_x.f > 0 - @irrelevant("Implicit") - package gen_i >= 0 --* acc(gen_x.f) && gen_x.f > 0 - @irrelevant("Implicit") - apply gen_i >= 0 --* acc(gen_x.f) && gen_x.f > 0 -} - - -SNIPPET 21_quasihavoc$=${ - @irrelevant("Implicit") - quasihavoc $RW_INT_FIELD_0 -} - -SNIPPET 21_2_quasihavoc_disjoint_field$=${ - @irrelevant("Implicit") - inhale acc($RO_REF_F_0.gen_f) - @irrelevant("Implicit") - quasihavoc $RO_REF_F_0.gen_f -} - -SNIPPET 22_quasihavoc_disjoint_ref$=${ - var gen_x: Ref - @irrelevant("Implicit") - gen_x := new(f) - @irrelevant("Implicit") - quasihavoc gen_x.f -} - -SNIPPET 23_quasihavoc_disjoint_ref_disjoint_field$=${ - var gen_x: Ref - @irrelevant("Implicit") - gen_x := new(gen_f) - @irrelevant("Implicit") - quasihavoc gen_x.gen_f -} - - -SNIPPET 24_goto_pure$=${ - @irrelevant("Implicit") - $RW_INT_PURE_0 := 0 - var gen_n: Int - @irrelevant("Implicit") - gen_n := 0 - label head - invariant @irrelevant("LoopInvariant")(gen_n >= 0) - invariant @irrelevant("LoopInvariant")($RW_INT_PURE_0 == gen_n * $RO_INT_PURE_0) - - @irrelevant("Implicit") - $RW_INT_PURE_0 := $RW_INT_PURE_0 + $RO_INT_PURE_0 - @irrelevant("Implicit") - gen_n := gen_n + 1 - - if(@irrelevant("PathCondition")($RW_INT_PURE_0 != 5 * $RO_INT_PURE_0)){ - goto head - } - - @irrelevant() - assert $RW_INT_PURE_0 == gen_n * $RO_INT_PURE_0 -} - -SNIPPET 25_unrelated_sync_points$=${ - var gen_i: Int, gen_j: Int, gen_n: Int, gen_p: Int - var gen_x: Ref - var gen_xs: Seq[Ref] - @irrelevant("Implicit") - gen_x := new(gen_f) - @irrelevant("Explicit") - inhale gen_x.gen_f >= 0 - - @irrelevant("Explicit") - inhale |gen_xs| > 2 - @irrelevant("Explicit") - inhale forall gen_xi: Ref :: gen_xi in gen_xs ==> acc(gen_xi.gen_f) - @irrelevant("Explicit") - inhale forall gen_xi: Ref :: gen_xi in gen_xs ==> gen_xi.gen_f < 0 - - @irrelevant("Explicit") - inhale gen_i > 0 - @irrelevant("Explicit") - inhale 0 <= gen_n && gen_n < 100 - - @irrelevant("Rewrite") - fold gen_confuse_genf(gen_n, gen_x) - @irrelevant("Implicit") - gen_i := gen_add_positive(gen_i, gen_n) - @irrelevant("Rewrite") - unfold gen_confuse_genf(gen_n, gen_x) - - while(@irrelevant("PathCondition")(gen_n > 0)) - invariant @irrelevant("Invariant")(acc(gen_x.gen_f)) - invariant @irrelevant("Invariant")(forall gen_xi: Ref :: gen_xi in gen_xs ==> acc(gen_xi.gen_f, 1/2)) - invariant @irrelevant("Invariant")(gen_x.gen_f >= 0) - invariant @irrelevant("Invariant")(gen_n >= 0) - { - @irrelevant("Implicit") - gen_x.gen_f := gen_x.gen_f + gen_n - @irrelevant("Implicit") - gen_j := gen_xs[1].gen_f - } - - @irrelevant("Implicit") - gen_i := 4 - @irrelevant("Implicit") - gen_j := gen_add(gen_j, gen_i) - - @irrelevant("Implicit") - gen_x.gen_f := 4 - @irrelevant("Implicit") - incr_gen_f(gen_x) - @irrelevant("Implicit") - gen_xs[2].gen_f := 4 - @irrelevant("Implicit") - incr_gen_f(gen_xs[2]) - @irrelevant("Rewrite") - package gen_i >= 0 --* acc(gen_x.gen_f) - @irrelevant("Rewrite") - package gen_i >= 0 --* acc(gen_xs[0].gen_f) - - var gen_bool: Bool - if(@irrelevant("PathCondition")(gen_bool)){ - @irrelevant("Implicit") - gen_bool := false - } - - @irrelevant() - assert !gen_bool - - @irrelevant("Rewrite") - apply gen_i >= 0 --* acc(gen_x.gen_f) - @irrelevant("Rewrite") - apply gen_i >= 0 --* acc(gen_xs[0].gen_f) - - @irrelevant("Implicit") - gen_x.gen_f := gen_i + gen_n - - @irrelevant("Implicit") - gen_xs[0].gen_f := 10 - - @irrelevant("Implicit") - gen_xs[0].gen_f := gen_xs[1].gen_f + gen_n - - @irrelevant() - assert gen_n == 0 - @irrelevant() - assert gen_xs[0] != gen_xs[1] && gen_xs[1] != gen_xs[2] ==> gen_xs[0].gen_f < 0 -} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/notes.txt b/src/test/resources/dependencyAnalysisTests/notes.txt deleted file mode 100644 index d6805dbc6..000000000 --- a/src/test/resources/dependencyAnalysisTests/notes.txt +++ /dev/null @@ -1,49 +0,0 @@ -field f: Int - -// ---------------------------------------------------------------- -// issue #01: IMPRECISION cause by WILDCARDS - -method quantifiedPerm2(xs: Seq[Ref], ys: Seq[Ref]) - requires @dependency("Explicit")(|xs| > 5) - requires @irrelevant("Explicit")(|ys| > 3) -{ - @dependency("Explicit") - inhale (forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0)) // (1) - @irrelevant("Explicit") // ERROR: unexpected dependency - inhale forall y: Ref :: y in ys ==> acc(y.f, wildcard) // (2) - - @testAssertion("Explicit") - assert xs[0].f > 0 // (3) -} - -// (1) k7 = wildcard -// (2) assume k14 > Z assume RdVar(k14) (k14=wildcard) -// (3) assert Z < (xs[0] in xs? k7: Z) + (xs[0] in ys? k14: Z) - -// when accessing a qp resource, we check Z < (x in xs? …) + (y in ys? …) the unsat core contains dependencies to all qp perm amounts (e.g. wildcard) - -// ---------------------------------------------------------------- -// issue #02 - IMPRECISION caused by write to quantified resource - - -method quantifiedPerm5(xs: Seq[Ref], ys: Seq[Ref]) - requires @dependency("Explicit")(|xs| > 5) - requires @irrelevant("Explicit")(|ys| > 3) -{ - @dependency("Explicit") - inhale forall x: Ref :: x in xs ==> acc(x.f) - @irrelevant("Explicit") - inhale forall y: Ref :: y in ys ==> acc(y.f) - - @irrelevant("Implicit") - xs[0].f := ys[0].f + 2 // (1) - @testAssertion("Implicit") - xs[0].f := 2 // (2) -} - -// (2) imprecisely depends on (1) -// issue: inhaling remaining permission of lhs was labelled as implicit -// solution: -// - inhale of remaining permissions / full permission after assignments are labelled internal -// - exhale is implicit -// - lhs and rhs are split such that transitive edges only go from lhs to rhs diff --git a/src/test/resources/dependencyAnalysisTests/precisionTests/results/result_2025-08-16_13-51-18.out b/src/test/resources/dependencyAnalysisTests/precisionTests/results/result_2025-08-16_13-51-18.out deleted file mode 100644 index 9759fc7f6..000000000 --- a/src/test/resources/dependencyAnalysisTests/precisionTests/results/result_2025-08-16_13-51-18.out +++ /dev/null @@ -1,3584 +0,0 @@ -dependencyAnalysisTests/precisionTests/branches - assert -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -branch1: 1.0 -branch2: 1.0 -branch3: 1.0 -branch4: 1.0 -branch5: 1.0 - -dependencyAnalysisTests/precisionTests/branches - assignment_field -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -branch1: 1.0 -branch2: 1.0 -branch3: 1.0 -branch4: 1.0 -branch5: 1.0 - -dependencyAnalysisTests/precisionTests/branches - assignment_pure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -branch1: 1.0 -branch2: 1.0 -branch3: 1.0 -branch4: 1.0 -branch5: 1.0 - -dependencyAnalysisTests/precisionTests/branches - baseline -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -branch1: 1.0 -branch2: 1.0 -branch3: 1.0 -branch4: 1.0 -branch5: 1.0 - -dependencyAnalysisTests/precisionTests/branches - branch -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -branch1: 0.75 -branch2: 0.5 -branch3: 0.6666666666666667 -branch4: 1.0 -branch5: 0.33333333333333337 - -dependencyAnalysisTests/precisionTests/branches - branch_div0 -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -branch1: 0.75 -branch2: 0.6666666666666667 -branch3: 0.6666666666666667 -branch4: 1.0 -branch5: 0.33333333333333337 - -dependencyAnalysisTests/precisionTests/branches - branch_function -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -branch1: 0.6 -branch2: 0.4 -branch3: 0.4 -branch4: 1.0 -branch5: 0.25 - -dependencyAnalysisTests/precisionTests/branches - disjoint_ref -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -branch1: 1.0 -branch2: 1.0 -branch3: 1.0 -branch4: 1.0 -branch5: 1.0 - -dependencyAnalysisTests/precisionTests/branches - disjoint_ref_disjoint_field -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -branch1: 1.0 -branch2: 1.0 -branch3: 1.0 -branch4: 1.0 -branch5: 1.0 - -dependencyAnalysisTests/precisionTests/branches - disjoint_ref_disjoint_field_quantified -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -branch1: 1.0 -branch2: 1.0 -branch3: 1.0 -branch4: 1.0 -branch5: 1.0 - -dependencyAnalysisTests/precisionTests/branches - disjoint_ref_quantified -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -branch1: 1.0 -branch2: 1.0 -branch3: 1.0 -branch4: 1.0 -branch5: 1.0 - -dependencyAnalysisTests/precisionTests/branches - fold_unfold_with_implication -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -branch1: 0.5 -branch2: 0.4 -branch3: 0.4 -branch4: 1.0 -branch5: 0.19999999999999996 - -dependencyAnalysisTests/precisionTests/branches - function_add -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -branch1: 1.0 -branch2: 1.0 -branch3: 1.0 -branch4: 1.0 -branch5: 1.0 - -dependencyAnalysisTests/precisionTests/branches - goto_pure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -branch1: 1.0 -branch2: 1.0 -branch3: 1.0 -branch4: 1.0 -branch5: 1.0 - -dependencyAnalysisTests/precisionTests/branches - infeasible_branch -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -branch1: 0.75 -branch2: 0.6666666666666667 -branch3: 0.6666666666666667 -branch4: 1.0 -branch5: 0.33333333333333337 - -dependencyAnalysisTests/precisionTests/branches - inhale_invariant -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -branch1: 1.0 -branch2: 1.0 -branch3: 1.0 -branch4: 1.0 -branch5: 1.0 - -dependencyAnalysisTests/precisionTests/branches - magic_wand -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -branch1: 1.0 -branch2: 1.0 -branch3: 1.0 -branch4: 1.0 -branch5: 1.0 - -dependencyAnalysisTests/precisionTests/branches - method_call_impure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -branch1: 1.0 -branch2: 1.0 -branch3: 1.0 -branch4: 1.0 -branch5: 1.0 - -dependencyAnalysisTests/precisionTests/branches - method_call_pure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -branch1: 1.0 -branch2: 1.0 -branch3: 1.0 -branch4: 1.0 -branch5: 1.0 - -dependencyAnalysisTests/precisionTests/branches - nested_branch -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -branch1: 0.6 -branch2: 0.4 -branch3: 0.5 -branch4: 1.0 -branch5: 0.25 - -dependencyAnalysisTests/precisionTests/branches - quasihavoc -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -branch1: 1.0 -branch2: 1.0 -branch3: 1.0 -branch4: 1.0 -branch5: 1.0 - -dependencyAnalysisTests/precisionTests/branches - quasihavoc_disjoint_ref -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -branch1: 1.0 -branch2: 1.0 -branch3: 1.0 -branch4: 1.0 -branch5: 1.0 - -dependencyAnalysisTests/precisionTests/branches - quasihavoc_disjoint_ref_disjoint_field -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -branch1: 1.0 -branch2: 1.0 -branch3: 1.0 -branch4: 1.0 -branch5: 1.0 - -dependencyAnalysisTests/precisionTests/branches - while_perm -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -branch1: 1.0 -branch2: 1.0 -branch3: 1.0 -branch4: 1.0 -branch5: 1.0 - -dependencyAnalysisTests/precisionTests/branches - while_pure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -branch1: 1.0 -branch2: 1.0 -branch3: 1.0 -branch4: 1.0 -branch5: 1.0 - -dependencyAnalysisTests/precisionTests/domain - assert -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -domain1: 1.0 -domain2: 0.75 -domain3: 1.0 - -dependencyAnalysisTests/precisionTests/domain - assignment_field -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -domain1: 1.0 -domain2: 1.0 -domain3: 1.0 - -dependencyAnalysisTests/precisionTests/domain - assignment_pure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -domain1: 1.0 -domain2: 1.0 -domain3: 1.0 - -dependencyAnalysisTests/precisionTests/domain - baseline -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -domain1: 1.0 -domain2: 1.0 -domain3: 1.0 - -dependencyAnalysisTests/precisionTests/domain - branch -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -domain1: 1.0 -domain2: 1.0 -domain3: 1.0 - -dependencyAnalysisTests/precisionTests/domain - branch_div0 -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -domain1: 1.0 -domain2: 1.0 -domain3: 0.8 - -dependencyAnalysisTests/precisionTests/domain - branch_function -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -domain1: 1.0 -domain2: 1.0 -domain3: 0.6923076923076923 - -dependencyAnalysisTests/precisionTests/domain - disjoint_ref -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -domain1: 1.0 -domain2: 1.0 -domain3: 1.0 - -dependencyAnalysisTests/precisionTests/domain - disjoint_ref_disjoint_field -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -domain1: 1.0 -domain2: 1.0 -domain3: 1.0 - -dependencyAnalysisTests/precisionTests/domain - disjoint_ref_disjoint_field_quantified -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -domain1: 1.0 -domain2: 1.0 -domain3: 1.0 - -dependencyAnalysisTests/precisionTests/domain - disjoint_ref_quantified -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -domain1: 1.0 -domain2: 1.0 -domain3: 1.0 - -dependencyAnalysisTests/precisionTests/domain - fold_unfold_with_implication -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -domain1: 0.25 -domain2: 0.5 -domain3: 0.7272727272727273 - -dependencyAnalysisTests/precisionTests/domain - function_add -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -domain1: 1.0 -domain2: 1.0 -domain3: 1.0 - -dependencyAnalysisTests/precisionTests/domain - goto_pure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -domain1: 1.0 -domain2: 1.0 -domain3: 1.0 - -dependencyAnalysisTests/precisionTests/domain - infeasible_branch -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -domain1: 0.33333333333333337 -domain2: 0.75 -domain3: 0.8888888888888888 - -dependencyAnalysisTests/precisionTests/domain - inhale_invariant -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -domain1: 1.0 -domain2: 0.6666666666666667 -domain3: 1.0 - -dependencyAnalysisTests/precisionTests/domain - magic_wand -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -domain1: 1.0 -domain2: 1.0 -domain3: 1.0 - -dependencyAnalysisTests/precisionTests/domain - method_call_impure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -domain1: 1.0 -domain2: 1.0 -domain3: 1.0 - -dependencyAnalysisTests/precisionTests/domain - method_call_pure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -domain1: 1.0 -domain2: 1.0 -domain3: 1.0 - -dependencyAnalysisTests/precisionTests/domain - nested_branch -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -domain1: 1.0 -domain2: 1.0 -domain3: 0.8 - -dependencyAnalysisTests/precisionTests/domain - quasihavoc -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -domain1: 1.0 -domain2: 0.75 -domain3: 0.8888888888888888 - -dependencyAnalysisTests/precisionTests/domain - quasihavoc_disjoint_ref -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -domain1: 1.0 -domain2: 1.0 -domain3: 1.0 - -dependencyAnalysisTests/precisionTests/domain - quasihavoc_disjoint_ref_disjoint_field -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -domain1: 1.0 -domain2: 1.0 -domain3: 1.0 - -dependencyAnalysisTests/precisionTests/domain - while_perm -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -domain1: 1.0 -domain2: 1.0 -domain3: 1.0 - -dependencyAnalysisTests/precisionTests/domain - while_pure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -domain1: 1.0 -domain2: 1.0 -domain3: 1.0 - -dependencyAnalysisTests/precisionTests/function-call - assert -gen_add: 1.0 -gen_add_positive: 1.0 -sum: 1.0 -sumUnverified: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -call1: 1.0 -call2: 1.0 -call3: 1.0 -call4: 1.0 - -dependencyAnalysisTests/precisionTests/function-call - assignment_field -gen_add: 1.0 -gen_add_positive: 1.0 -sum: 1.0 -sumUnverified: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -call1: 1.0 -call2: 1.0 -call3: 1.0 -call4: 1.0 - -dependencyAnalysisTests/precisionTests/function-call - assignment_pure -gen_add: 1.0 -gen_add_positive: 1.0 -sum: 1.0 -sumUnverified: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -call1: 1.0 -call2: 1.0 -call3: 1.0 -call4: 1.0 - -dependencyAnalysisTests/precisionTests/function-call - baseline -gen_add: 1.0 -gen_add_positive: 1.0 -sum: 1.0 -sumUnverified: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -call1: 1.0 -call2: 1.0 -call3: 1.0 -call4: 1.0 - -dependencyAnalysisTests/precisionTests/function-call - branch -gen_add: 1.0 -gen_add_positive: 1.0 -sum: 1.0 -sumUnverified: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -call1: 1.0 -call2: 1.0 -call3: 1.0 -call4: 1.0 - -dependencyAnalysisTests/precisionTests/function-call - branch_div0 -gen_add: 1.0 -gen_add_positive: 1.0 -sum: 1.0 -sumUnverified: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -call1: 0.6666666666666667 -call2: 0.8 -call3: 0.8571428571428572 -call4: 0.8 - -dependencyAnalysisTests/precisionTests/function-call - branch_function -gen_add: 1.0 -gen_add_positive: 1.0 -sum: 1.0 -sumUnverified: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -call1: 0.5 -call2: 0.6666666666666667 -call3: 0.75 -call4: 0.5714285714285714 - -dependencyAnalysisTests/precisionTests/function-call - disjoint_ref -gen_add: 1.0 -gen_add_positive: 1.0 -sum: 1.0 -sumUnverified: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -call1: 1.0 -call2: 1.0 -call3: 1.0 -call4: 1.0 - -dependencyAnalysisTests/precisionTests/function-call - disjoint_ref_disjoint_field -gen_add: 1.0 -gen_add_positive: 1.0 -sum: 1.0 -sumUnverified: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -call1: 1.0 -call2: 1.0 -call3: 1.0 -call4: 1.0 - -dependencyAnalysisTests/precisionTests/function-call - disjoint_ref_disjoint_field_quantified -gen_add: 1.0 -gen_add_positive: 1.0 -sum: 1.0 -sumUnverified: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -call1: 1.0 -call2: 1.0 -call3: 1.0 -call4: 1.0 - -dependencyAnalysisTests/precisionTests/function-call - disjoint_ref_quantified -gen_add: 1.0 -gen_add_positive: 1.0 -sum: 1.0 -sumUnverified: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -call1: 1.0 -call2: 1.0 -call3: 1.0 -call4: 1.0 - -dependencyAnalysisTests/precisionTests/function-call - fold_unfold_with_implication -gen_add: 1.0 -gen_add_positive: 1.0 -sum: 1.0 -sumUnverified: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -call1: 0.4 -call2: 0.6666666666666667 -call3: 0.75 -call4: 0.5714285714285714 - -dependencyAnalysisTests/precisionTests/function-call - function_add -gen_add: 1.0 -gen_add_positive: 1.0 -sum: 1.0 -sumUnverified: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -call1: 1.0 -call2: 1.0 -call3: 1.0 -call4: 1.0 - -dependencyAnalysisTests/precisionTests/function-call - goto_pure -gen_add: 1.0 -gen_add_positive: 1.0 -sum: 1.0 -sumUnverified: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -call1: 1.0 -call2: 1.0 -call3: 1.0 -call4: 1.0 - -dependencyAnalysisTests/precisionTests/function-call - infeasible_branch -gen_add: 1.0 -gen_add_positive: 1.0 -sum: 1.0 -sumUnverified: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -call1: 0.6666666666666667 -call2: 0.8 -call3: 0.8571428571428572 -call4: 0.8 - -dependencyAnalysisTests/precisionTests/function-call - inhale_invariant -gen_add: 1.0 -gen_add_positive: 1.0 -sum: 1.0 -sumUnverified: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -call1: 1.0 -call2: 1.0 -call3: 1.0 -call4: 1.0 - -dependencyAnalysisTests/precisionTests/function-call - magic_wand -gen_add: 1.0 -gen_add_positive: 1.0 -sum: 1.0 -sumUnverified: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -call1: 1.0 -call2: 1.0 -call3: 1.0 -call4: 1.0 - -dependencyAnalysisTests/precisionTests/function-call - method_call_impure -gen_add: 1.0 -gen_add_positive: 1.0 -sum: 1.0 -sumUnverified: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -call1: 1.0 -call2: 1.0 -call3: 1.0 -call4: 1.0 - -dependencyAnalysisTests/precisionTests/function-call - method_call_pure -gen_add: 1.0 -gen_add_positive: 1.0 -sum: 1.0 -sumUnverified: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -call1: 1.0 -call2: 1.0 -call3: 1.0 -call4: 1.0 - -dependencyAnalysisTests/precisionTests/function-call - nested_branch -gen_add: 1.0 -gen_add_positive: 1.0 -sum: 1.0 -sumUnverified: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -call1: 0.6666666666666667 -call2: 0.8 -call3: 0.8571428571428572 -call4: 0.8 - -dependencyAnalysisTests/precisionTests/function-call - quasihavoc -gen_add: 1.0 -gen_add_positive: 1.0 -sum: 1.0 -sumUnverified: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -call1: 1.0 -call2: 1.0 -call3: 1.0 -call4: 1.0 - -dependencyAnalysisTests/precisionTests/function-call - quasihavoc_disjoint_ref -gen_add: 1.0 -gen_add_positive: 1.0 -sum: 1.0 -sumUnverified: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -call1: 1.0 -call2: 1.0 -call3: 1.0 -call4: 1.0 - -dependencyAnalysisTests/precisionTests/function-call - quasihavoc_disjoint_ref_disjoint_field -gen_add: 1.0 -gen_add_positive: 1.0 -sum: 1.0 -sumUnverified: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -call1: 1.0 -call2: 1.0 -call3: 1.0 -call4: 1.0 - -dependencyAnalysisTests/precisionTests/function-call - while_perm -gen_add: 1.0 -gen_add_positive: 1.0 -sum: 1.0 -sumUnverified: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -call1: 1.0 -call2: 1.0 -call3: 1.0 -call4: 1.0 - -dependencyAnalysisTests/precisionTests/function-call - while_pure -gen_add: 1.0 -gen_add_positive: 1.0 -sum: 1.0 -sumUnverified: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -call1: 1.0 -call2: 1.0 -call3: 1.0 -call4: 1.0 - -dependencyAnalysisTests/precisionTests/inhaleExhale - assert -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -exhaleInhale: 0.33333333333333337 -exhaleImprecision: 0.6666666666666667 -inhaleImprecision: 0.5 - -dependencyAnalysisTests/precisionTests/inhaleExhale - assignment_field -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -exhaleInhale: 0.33333333333333337 -exhaleImprecision: 0.6666666666666667 -inhaleImprecision: 0.6666666666666667 - -dependencyAnalysisTests/precisionTests/inhaleExhale - assignment_pure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -exhaleInhale: 0.33333333333333337 -exhaleImprecision: 0.6666666666666667 -inhaleImprecision: 0.6666666666666667 - -dependencyAnalysisTests/precisionTests/inhaleExhale - baseline -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -exhaleInhale: 0.33333333333333337 -exhaleImprecision: 0.6666666666666667 -inhaleImprecision: 0.6666666666666667 - -dependencyAnalysisTests/precisionTests/inhaleExhale - branch -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -exhaleInhale: 0.33333333333333337 -exhaleImprecision: 0.6666666666666667 -inhaleImprecision: 0.5 - -dependencyAnalysisTests/precisionTests/inhaleExhale - branch_div0 -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -exhaleInhale: 0.33333333333333337 -exhaleImprecision: 0.5 -inhaleImprecision: 0.5 - -dependencyAnalysisTests/precisionTests/inhaleExhale - branch_function -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -exhaleInhale: 0.33333333333333337 -exhaleImprecision: 0.5 -inhaleImprecision: 0.4 - -dependencyAnalysisTests/precisionTests/inhaleExhale - disjoint_ref -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -exhaleInhale: 0.25 -exhaleImprecision: 0.6666666666666667 -inhaleImprecision: 0.6666666666666667 - -dependencyAnalysisTests/precisionTests/inhaleExhale - disjoint_ref_disjoint_field -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -exhaleInhale: 0.33333333333333337 -exhaleImprecision: 0.6666666666666667 -inhaleImprecision: 0.6666666666666667 - -dependencyAnalysisTests/precisionTests/inhaleExhale - disjoint_ref_disjoint_field_quantified -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -exhaleInhale: 0.33333333333333337 -exhaleImprecision: 0.6666666666666667 -inhaleImprecision: 0.6666666666666667 - -dependencyAnalysisTests/precisionTests/inhaleExhale - disjoint_ref_quantified -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -exhaleInhale: 0.25 -exhaleImprecision: 0.4 -inhaleImprecision: 0.4 - -dependencyAnalysisTests/precisionTests/inhaleExhale - fold_unfold_with_implication -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -exhaleInhale: 0.16666666666666663 -exhaleImprecision: 0.33333333333333337 -inhaleImprecision: 0.33333333333333337 - -dependencyAnalysisTests/precisionTests/inhaleExhale - function_add -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -exhaleInhale: 0.33333333333333337 -exhaleImprecision: 0.6666666666666667 -inhaleImprecision: 0.6666666666666667 - -dependencyAnalysisTests/precisionTests/inhaleExhale - goto_pure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -exhaleInhale: 0.33333333333333337 -exhaleImprecision: 0.6666666666666667 -inhaleImprecision: 0.6666666666666667 - -dependencyAnalysisTests/precisionTests/inhaleExhale - infeasible_branch -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -exhaleInhale: 0.25 -exhaleImprecision: 0.5 -inhaleImprecision: 0.5 - -dependencyAnalysisTests/precisionTests/inhaleExhale - inhale_invariant -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -exhaleInhale: 0.33333333333333337 -exhaleImprecision: 0.6666666666666667 -inhaleImprecision: 0.33333333333333337 - -dependencyAnalysisTests/precisionTests/inhaleExhale - magic_wand -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -exhaleInhale: 0.1428571428571429 -exhaleImprecision: 0.6666666666666667 -inhaleImprecision: 0.6666666666666667 - -dependencyAnalysisTests/precisionTests/inhaleExhale - method_call_impure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -exhaleInhale: 0.25 -exhaleImprecision: 0.6666666666666667 -inhaleImprecision: 0.6666666666666667 - -dependencyAnalysisTests/precisionTests/inhaleExhale - method_call_pure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -exhaleInhale: 0.5 -exhaleImprecision: 0.6666666666666667 -inhaleImprecision: 0.6666666666666667 - -dependencyAnalysisTests/precisionTests/inhaleExhale - nested_branch -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -exhaleInhale: 0.33333333333333337 -exhaleImprecision: 0.5 -inhaleImprecision: 0.4 - -dependencyAnalysisTests/precisionTests/inhaleExhale - quasihavoc -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -exhaleInhale: 0.25 -exhaleImprecision: 0.5 -inhaleImprecision: 0.5 - -dependencyAnalysisTests/precisionTests/inhaleExhale - quasihavoc_disjoint_ref -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -exhaleInhale: 0.19999999999999996 -exhaleImprecision: 0.4 -inhaleImprecision: 0.4 - -dependencyAnalysisTests/precisionTests/inhaleExhale - quasihavoc_disjoint_ref_disjoint_field -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -exhaleInhale: 0.33333333333333337 -exhaleImprecision: 0.6666666666666667 -inhaleImprecision: 0.6666666666666667 - -Program dependencyAnalysisTests/precisionTests/inhaleExhale/while_perm does not verify. Skip -Program dependencyAnalysisTests/precisionTests/inhaleExhale/while_pure does not verify. Skip -dependencyAnalysisTests/precisionTests/loops - assert -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -loop1: 0.7142857142857143 -loop2: 1.0 -loop3: 1.0 -loop4: 1.0 - -dependencyAnalysisTests/precisionTests/loops - assignment_field -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -loop1: 0.7142857142857143 -loop2: 1.0 -loop3: 1.0 -loop4: 1.0 - -dependencyAnalysisTests/precisionTests/loops - assignment_pure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -loop1: 0.7142857142857143 -loop2: 1.0 -loop3: 1.0 -loop4: 1.0 - -dependencyAnalysisTests/precisionTests/loops - baseline -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -loop1: 0.7142857142857143 -loop2: 1.0 -loop3: 1.0 -loop4: 1.0 - -dependencyAnalysisTests/precisionTests/loops - branch -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -loop1: 0.625 -loop2: 1.0 -loop3: 0.8333333333333334 -loop4: 1.0 - -dependencyAnalysisTests/precisionTests/loops - branch_div0 -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -loop1: 0.7142857142857143 -loop2: 0.4285714285714286 -loop3: 0.8333333333333334 -loop4: 1.0 - -dependencyAnalysisTests/precisionTests/loops - branch_function -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -loop1: 0.625 -loop2: 0.2727272727272727 -loop3: 0.7142857142857143 -loop4: 1.0 - -dependencyAnalysisTests/precisionTests/loops - disjoint_ref -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -loop1: 0.7142857142857143 -loop2: 1.0 -loop3: 1.0 -loop4: 1.0 - -dependencyAnalysisTests/precisionTests/loops - disjoint_ref_disjoint_field -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -loop1: 0.7142857142857143 -loop2: 1.0 -loop3: 1.0 -loop4: 1.0 - -dependencyAnalysisTests/precisionTests/loops - disjoint_ref_disjoint_field_quantified -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -loop1: 0.7142857142857143 -loop2: 1.0 -loop3: 1.0 -loop4: 1.0 - -dependencyAnalysisTests/precisionTests/loops - disjoint_ref_quantified -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -loop1: 0.7142857142857143 -loop2: 1.0 -loop3: 1.0 -loop4: 1.0 - -dependencyAnalysisTests/precisionTests/loops - fold_unfold_with_implication -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -loop1: 0.5 -loop2: 0.2727272727272727 -loop3: 0.625 -loop4: 0.5 - -dependencyAnalysisTests/precisionTests/loops - function_add -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -loop1: 0.7142857142857143 -loop2: 1.0 -loop3: 1.0 -loop4: 1.0 - -Program dependencyAnalysisTests/precisionTests/loops/goto_pure does not verify. Skip -dependencyAnalysisTests/precisionTests/loops - infeasible_branch -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -loop1: 0.5555555555555556 -loop2: 0.5 -loop3: 0.8333333333333334 -loop4: 0.6666666666666667 - -dependencyAnalysisTests/precisionTests/loops - inhale_invariant -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -loop1: 0.7142857142857143 -loop2: 1.0 -loop3: 1.0 -loop4: 1.0 - -dependencyAnalysisTests/precisionTests/loops - magic_wand -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -loop1: 0.7142857142857143 -loop2: 1.0 -loop3: 1.0 -loop4: 1.0 - -dependencyAnalysisTests/precisionTests/loops - method_call_impure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -loop1: 0.7142857142857143 -loop2: 1.0 -loop3: 1.0 -loop4: 1.0 - -dependencyAnalysisTests/precisionTests/loops - method_call_pure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -loop1: 0.7142857142857143 -loop2: 1.0 -loop3: 1.0 -loop4: 1.0 - -dependencyAnalysisTests/precisionTests/loops - nested_branch -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -loop1: 0.625 -loop2: 0.375 -loop3: 0.7142857142857143 -loop4: 1.0 - -dependencyAnalysisTests/precisionTests/loops - quasihavoc -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -loop1: 0.7142857142857143 -loop2: 1.0 -loop3: 1.0 -loop4: 1.0 - -dependencyAnalysisTests/precisionTests/loops - quasihavoc_disjoint_ref -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -loop1: 0.7142857142857143 -loop2: 1.0 -loop3: 1.0 -loop4: 1.0 - -dependencyAnalysisTests/precisionTests/loops - quasihavoc_disjoint_ref_disjoint_field -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -loop1: 0.7142857142857143 -loop2: 1.0 -loop3: 1.0 -loop4: 1.0 - -dependencyAnalysisTests/precisionTests/loops - while_perm -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -loop1: 0.7142857142857143 -loop2: 1.0 -loop3: 1.0 -loop4: 1.0 - -Program dependencyAnalysisTests/precisionTests/loops/while_pure does not verify. Skip -dependencyAnalysisTests/precisionTests/magicWands - assert -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -basicApply: 1.0 -basicPackage: 1.0 -advancedPackage: 1.0 -quantifiedWand: 1.0 -packagePrecision: 1.0 -packageExhale: 1.0 -applying1: 1.0 - -dependencyAnalysisTests/precisionTests/magicWands - assignment_field -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -basicApply: 1.0 -basicPackage: 1.0 -advancedPackage: 1.0 -quantifiedWand: 1.0 -packagePrecision: 1.0 -packageExhale: 1.0 -applying1: 1.0 - -dependencyAnalysisTests/precisionTests/magicWands - assignment_pure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -basicApply: 1.0 -basicPackage: 1.0 -advancedPackage: 1.0 -quantifiedWand: 1.0 -packagePrecision: 1.0 -packageExhale: 1.0 -applying1: 1.0 - -dependencyAnalysisTests/precisionTests/magicWands - baseline -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -basicApply: 1.0 -basicPackage: 1.0 -advancedPackage: 1.0 -quantifiedWand: 1.0 -packagePrecision: 1.0 -packageExhale: 1.0 -applying1: 1.0 - -dependencyAnalysisTests/precisionTests/magicWands - branch -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -basicApply: 0.75 -basicPackage: 1.0 -advancedPackage: 1.0 -quantifiedWand: 1.0 -packagePrecision: 1.0 -packageExhale: 1.0 -applying1: 0.75 - -dependencyAnalysisTests/precisionTests/magicWands - branch_div0 -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -basicApply: 0.75 -basicPackage: 1.0 -advancedPackage: 1.0 -quantifiedWand: 1.0 -packagePrecision: 1.0 -packageExhale: 1.0 -applying1: 0.75 - -dependencyAnalysisTests/precisionTests/magicWands - branch_function -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -basicApply: 0.6 -basicPackage: 1.0 -advancedPackage: 1.0 -quantifiedWand: 1.0 -packagePrecision: 1.0 -packageExhale: 1.0 -applying1: 0.6 - -dependencyAnalysisTests/precisionTests/magicWands - disjoint_ref -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -basicApply: 1.0 -basicPackage: 1.0 -advancedPackage: 1.0 -quantifiedWand: 1.0 -packagePrecision: 1.0 -packageExhale: 0.6666666666666667 -applying1: 1.0 - -dependencyAnalysisTests/precisionTests/magicWands - disjoint_ref_disjoint_field -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -basicApply: 1.0 -basicPackage: 1.0 -advancedPackage: 1.0 -quantifiedWand: 1.0 -packagePrecision: 1.0 -packageExhale: 1.0 -applying1: 1.0 - -dependencyAnalysisTests/precisionTests/magicWands - disjoint_ref_disjoint_field_quantified -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -basicApply: 1.0 -basicPackage: 1.0 -advancedPackage: 1.0 -quantifiedWand: 1.0 -packagePrecision: 1.0 -packageExhale: 1.0 -applying1: 1.0 - -dependencyAnalysisTests/precisionTests/magicWands - disjoint_ref_quantified -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -basicApply: 1.0 -basicPackage: 1.0 -advancedPackage: 1.0 -quantifiedWand: 1.0 -packagePrecision: 1.0 -packageExhale: 0.5 -applying1: 1.0 - -dependencyAnalysisTests/precisionTests/magicWands - fold_unfold_with_implication -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -basicApply: 0.5 -basicPackage: 0.6666666666666667 -advancedPackage: 0.7 -quantifiedWand: 0.5714285714285714 -packagePrecision: 0.5 -packageExhale: 0.4 -applying1: 0.5 - -dependencyAnalysisTests/precisionTests/magicWands - function_add -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -basicApply: 1.0 -basicPackage: 1.0 -advancedPackage: 1.0 -quantifiedWand: 1.0 -packagePrecision: 1.0 -packageExhale: 1.0 -applying1: 1.0 - -dependencyAnalysisTests/precisionTests/magicWands - goto_pure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -basicApply: 1.0 -basicPackage: 1.0 -advancedPackage: 1.0 -quantifiedWand: 1.0 -packagePrecision: 1.0 -packageExhale: 1.0 -applying1: 1.0 - -dependencyAnalysisTests/precisionTests/magicWands - infeasible_branch -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -basicApply: 0.75 -basicPackage: 0.8571428571428572 -advancedPackage: 0.875 -quantifiedWand: 0.8 -packagePrecision: 0.75 -packageExhale: 0.6666666666666667 -applying1: 0.75 - -dependencyAnalysisTests/precisionTests/magicWands - inhale_invariant -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -basicApply: 1.0 -basicPackage: 1.0 -advancedPackage: 1.0 -quantifiedWand: 1.0 -packagePrecision: 1.0 -packageExhale: 1.0 -applying1: 1.0 - -dependencyAnalysisTests/precisionTests/magicWands - magic_wand -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -basicApply: 1.0 -basicPackage: 1.0 -advancedPackage: 1.0 -quantifiedWand: 1.0 -packagePrecision: 1.0 -packageExhale: 0.33333333333333337 -applying1: 1.0 - -dependencyAnalysisTests/precisionTests/magicWands - method_call_impure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -basicApply: 1.0 -basicPackage: 1.0 -advancedPackage: 1.0 -quantifiedWand: 1.0 -packagePrecision: 1.0 -packageExhale: 0.6666666666666667 -applying1: 1.0 - -dependencyAnalysisTests/precisionTests/magicWands - method_call_pure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -basicApply: 1.0 -basicPackage: 1.0 -advancedPackage: 1.0 -quantifiedWand: 1.0 -packagePrecision: 1.0 -packageExhale: 1.0 -applying1: 1.0 - -dependencyAnalysisTests/precisionTests/magicWands - nested_branch -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -basicApply: 0.6 -basicPackage: 1.0 -advancedPackage: 1.0 -quantifiedWand: 1.0 -packagePrecision: 1.0 -packageExhale: 1.0 -applying1: 0.6 - -dependencyAnalysisTests/precisionTests/magicWands - quasihavoc -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -basicApply: 1.0 -basicPackage: 0.8571428571428572 -advancedPackage: 1.0 -quantifiedWand: 1.0 -packagePrecision: 1.0 -packageExhale: 0.6 -applying1: 1.0 - -dependencyAnalysisTests/precisionTests/magicWands - quasihavoc_disjoint_ref -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -basicApply: 1.0 -basicPackage: 1.0 -advancedPackage: 1.0 -quantifiedWand: 1.0 -packagePrecision: 1.0 -packageExhale: 0.5 -applying1: 1.0 - -dependencyAnalysisTests/precisionTests/magicWands - quasihavoc_disjoint_ref_disjoint_field -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -basicApply: 1.0 -basicPackage: 1.0 -advancedPackage: 1.0 -quantifiedWand: 1.0 -packagePrecision: 1.0 -packageExhale: 1.0 -applying1: 1.0 - -Failed. Skip -Failed. Skip -dependencyAnalysisTests/precisionTests/method-call - assert -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -sum: 1.0 -sumUnverified: 1.0 -abs: 1.0 -call1: 1.0 -call2: 1.0 -call3: 1.0 -call4: 1.0 -absClient1: 1.0 - -dependencyAnalysisTests/precisionTests/method-call - assignment_field -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -sum: 1.0 -sumUnverified: 1.0 -abs: 1.0 -call1: 1.0 -call2: 1.0 -call3: 1.0 -call4: 1.0 -absClient1: 1.0 - -dependencyAnalysisTests/precisionTests/method-call - assignment_pure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -sum: 1.0 -sumUnverified: 1.0 -abs: 1.0 -call1: 1.0 -call2: 1.0 -call3: 1.0 -call4: 1.0 -absClient1: 1.0 - -dependencyAnalysisTests/precisionTests/method-call - baseline -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -sum: 1.0 -sumUnverified: 1.0 -abs: 1.0 -call1: 1.0 -call2: 1.0 -call3: 1.0 -call4: 1.0 -absClient1: 1.0 - -dependencyAnalysisTests/precisionTests/method-call - branch -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -sum: 1.0 -sumUnverified: 1.0 -abs: 1.0 -call1: 1.0 -call2: 1.0 -call3: 1.0 -call4: 1.0 -absClient1: 1.0 - -dependencyAnalysisTests/precisionTests/method-call - branch_div0 -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -sum: 1.0 -sumUnverified: 1.0 -abs: 1.0 -call1: 0.6666666666666667 -call2: 0.75 -call3: 0.8333333333333334 -call4: 0.75 -absClient1: 1.0 - -dependencyAnalysisTests/precisionTests/method-call - branch_function -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -sum: 1.0 -sumUnverified: 1.0 -abs: 1.0 -call1: 0.5 -call2: 0.6 -call3: 0.7142857142857143 -call4: 0.5 -absClient1: 1.0 - -dependencyAnalysisTests/precisionTests/method-call - disjoint_ref -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -sum: 1.0 -sumUnverified: 1.0 -abs: 1.0 -call1: 1.0 -call2: 1.0 -call3: 1.0 -call4: 1.0 -absClient1: 1.0 - -dependencyAnalysisTests/precisionTests/method-call - disjoint_ref_disjoint_field -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -sum: 1.0 -sumUnverified: 1.0 -abs: 1.0 -call1: 1.0 -call2: 1.0 -call3: 1.0 -call4: 1.0 -absClient1: 1.0 - -dependencyAnalysisTests/precisionTests/method-call - disjoint_ref_disjoint_field_quantified -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -sum: 1.0 -sumUnverified: 1.0 -abs: 1.0 -call1: 1.0 -call2: 1.0 -call3: 1.0 -call4: 1.0 -absClient1: 1.0 - -dependencyAnalysisTests/precisionTests/method-call - disjoint_ref_quantified -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -sum: 1.0 -sumUnverified: 1.0 -abs: 1.0 -call1: 1.0 -call2: 1.0 -call3: 1.0 -call4: 1.0 -absClient1: 1.0 - -dependencyAnalysisTests/precisionTests/method-call - fold_unfold_with_implication -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -sum: 1.0 -sumUnverified: 1.0 -abs: 1.0 -call1: 0.4 -call2: 0.6 -call3: 0.7142857142857143 -call4: 0.5 -absClient1: 1.0 - -dependencyAnalysisTests/precisionTests/method-call - function_add -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -sum: 1.0 -sumUnverified: 1.0 -abs: 1.0 -call1: 1.0 -call2: 1.0 -call3: 1.0 -call4: 1.0 -absClient1: 1.0 - -dependencyAnalysisTests/precisionTests/method-call - goto_pure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -sum: 1.0 -sumUnverified: 1.0 -abs: 1.0 -call1: 1.0 -call2: 1.0 -call3: 1.0 -call4: 1.0 -absClient1: 1.0 - -dependencyAnalysisTests/precisionTests/method-call - infeasible_branch -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -sum: 1.0 -sumUnverified: 1.0 -abs: 1.0 -call1: 0.6666666666666667 -call2: 0.75 -call3: 0.8333333333333334 -call4: 0.75 -absClient1: 1.0 - -dependencyAnalysisTests/precisionTests/method-call - inhale_invariant -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -sum: 1.0 -sumUnverified: 1.0 -abs: 1.0 -call1: 1.0 -call2: 1.0 -call3: 1.0 -call4: 1.0 -absClient1: 1.0 - -dependencyAnalysisTests/precisionTests/method-call - magic_wand -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -sum: 1.0 -sumUnverified: 1.0 -abs: 1.0 -call1: 1.0 -call2: 1.0 -call3: 1.0 -call4: 1.0 -absClient1: 1.0 - -dependencyAnalysisTests/precisionTests/method-call - method_call_impure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -sum: 1.0 -sumUnverified: 1.0 -abs: 1.0 -call1: 1.0 -call2: 1.0 -call3: 1.0 -call4: 1.0 -absClient1: 1.0 - -dependencyAnalysisTests/precisionTests/method-call - method_call_pure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -sum: 1.0 -sumUnverified: 1.0 -abs: 1.0 -call1: 1.0 -call2: 1.0 -call3: 1.0 -call4: 1.0 -absClient1: 1.0 - -dependencyAnalysisTests/precisionTests/method-call - nested_branch -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -sum: 1.0 -sumUnverified: 1.0 -abs: 1.0 -call1: 0.6666666666666667 -call2: 0.75 -call3: 0.8333333333333334 -call4: 0.75 -absClient1: 1.0 - -dependencyAnalysisTests/precisionTests/method-call - quasihavoc -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -sum: 1.0 -sumUnverified: 1.0 -abs: 1.0 -call1: 1.0 -call2: 1.0 -call3: 1.0 -call4: 1.0 -absClient1: 1.0 - -dependencyAnalysisTests/precisionTests/method-call - quasihavoc_disjoint_ref -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -sum: 1.0 -sumUnverified: 1.0 -abs: 1.0 -call1: 1.0 -call2: 1.0 -call3: 1.0 -call4: 1.0 -absClient1: 1.0 - -dependencyAnalysisTests/precisionTests/method-call - quasihavoc_disjoint_ref_disjoint_field -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -sum: 1.0 -sumUnverified: 1.0 -abs: 1.0 -call1: 1.0 -call2: 1.0 -call3: 1.0 -call4: 1.0 -absClient1: 1.0 - -dependencyAnalysisTests/precisionTests/method-call - while_perm -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -sum: 1.0 -sumUnverified: 1.0 -abs: 1.0 -call1: 1.0 -call2: 1.0 -call3: 1.0 -call4: 1.0 -absClient1: 1.0 - -dependencyAnalysisTests/precisionTests/method-call - while_pure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -sum: 1.0 -sumUnverified: 1.0 -abs: 1.0 -call1: 1.0 -call2: 1.0 -call3: 1.0 -call4: 1.0 -absClient1: 1.0 - -dependencyAnalysisTests/precisionTests/misc - assert -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -divBy0: 1.0 -assumeFalse: 1.0 - -dependencyAnalysisTests/precisionTests/misc - assignment_field -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -divBy0: 1.0 -assumeFalse: 1.0 - -dependencyAnalysisTests/precisionTests/misc - assignment_pure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -divBy0: 1.0 -assumeFalse: 1.0 - -dependencyAnalysisTests/precisionTests/misc - baseline -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -divBy0: 1.0 -assumeFalse: 1.0 - -dependencyAnalysisTests/precisionTests/misc - branch -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -divBy0: 1.0 -assumeFalse: 0.5 - -dependencyAnalysisTests/precisionTests/misc - branch_div0 -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -divBy0: 0.6666666666666667 -assumeFalse: 0.5 - -dependencyAnalysisTests/precisionTests/misc - branch_function -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -divBy0: 0.4 -assumeFalse: 0.33333333333333337 - -dependencyAnalysisTests/precisionTests/misc - disjoint_ref -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -divBy0: 1.0 -assumeFalse: 1.0 - -dependencyAnalysisTests/precisionTests/misc - disjoint_ref_disjoint_field -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -divBy0: 1.0 -assumeFalse: 1.0 - -dependencyAnalysisTests/precisionTests/misc - disjoint_ref_disjoint_field_quantified -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -divBy0: 1.0 -assumeFalse: 1.0 - -dependencyAnalysisTests/precisionTests/misc - disjoint_ref_quantified -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -divBy0: 0.33333333333333337 -assumeFalse: 1.0 - -dependencyAnalysisTests/precisionTests/misc - fold_unfold_with_implication -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -divBy0: 0.4 -assumeFalse: 1.0 - -dependencyAnalysisTests/precisionTests/misc - function_add -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -divBy0: 1.0 -assumeFalse: 1.0 - -dependencyAnalysisTests/precisionTests/misc - goto_pure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -divBy0: 1.0 -assumeFalse: 0.5 - -dependencyAnalysisTests/precisionTests/misc - infeasible_branch -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -divBy0: 0.6666666666666667 -assumeFalse: 0.5 - -dependencyAnalysisTests/precisionTests/misc - inhale_invariant -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -divBy0: 1.0 -assumeFalse: 1.0 - -dependencyAnalysisTests/precisionTests/misc - magic_wand -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -divBy0: 1.0 -assumeFalse: 1.0 - -dependencyAnalysisTests/precisionTests/misc - method_call_impure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -divBy0: 1.0 -assumeFalse: 1.0 - -dependencyAnalysisTests/precisionTests/misc - method_call_pure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -divBy0: 1.0 -assumeFalse: 1.0 - -dependencyAnalysisTests/precisionTests/misc - nested_branch -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -divBy0: 0.6666666666666667 -assumeFalse: 0.33333333333333337 - -dependencyAnalysisTests/precisionTests/misc - quasihavoc -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -divBy0: 0.6666666666666667 -assumeFalse: 1.0 - -dependencyAnalysisTests/precisionTests/misc - quasihavoc_disjoint_ref -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -divBy0: 0.5 -assumeFalse: 1.0 - -dependencyAnalysisTests/precisionTests/misc - quasihavoc_disjoint_ref_disjoint_field -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -divBy0: 1.0 -assumeFalse: 1.0 - -dependencyAnalysisTests/precisionTests/misc - while_perm -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -divBy0: 1.0 -assumeFalse: 0.5 - -dependencyAnalysisTests/precisionTests/misc - while_pure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -divBy0: 1.0 -assumeFalse: 0.5 - -dependencyAnalysisTests/precisionTests/permissions - assert -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -currentPerm: 1.0 -perm1: 1.0 -perm2: 1.0 -perm3: 1.0 -perm4: 1.0 -perm5: 1.0 -permAmount1: 1.0 -permAmount2: 0.8 -noAlias: 1.0 - -dependencyAnalysisTests/precisionTests/permissions - assignment_field -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -currentPerm: 1.0 -perm1: 1.0 -perm2: 1.0 -perm3: 1.0 -perm4: 1.0 -perm5: 1.0 -permAmount1: 1.0 -permAmount2: 0.8 -noAlias: 1.0 - -dependencyAnalysisTests/precisionTests/permissions - assignment_pure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -currentPerm: 1.0 -perm1: 1.0 -perm2: 1.0 -perm3: 1.0 -perm4: 1.0 -perm5: 1.0 -permAmount1: 1.0 -permAmount2: 0.8 -noAlias: 1.0 - -dependencyAnalysisTests/precisionTests/permissions - baseline -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -currentPerm: 1.0 -perm1: 1.0 -perm2: 1.0 -perm3: 1.0 -perm4: 1.0 -perm5: 1.0 -permAmount1: 1.0 -permAmount2: 0.8 -noAlias: 1.0 - -dependencyAnalysisTests/precisionTests/permissions - branch -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -currentPerm: 1.0 -perm1: 1.0 -perm2: 1.0 -perm3: 1.0 -perm4: 1.0 -perm5: 1.0 -permAmount1: 1.0 -permAmount2: 0.8 -noAlias: 1.0 - -dependencyAnalysisTests/precisionTests/permissions - branch_div0 -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -currentPerm: 1.0 -perm1: 1.0 -perm2: 1.0 -perm3: 1.0 -perm4: 0.75 -perm5: 1.0 -permAmount1: 1.0 -permAmount2: 0.6666666666666667 -noAlias: 1.0 - -dependencyAnalysisTests/precisionTests/permissions - branch_function -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -currentPerm: 1.0 -perm1: 1.0 -perm2: 1.0 -perm3: 1.0 -perm4: 0.75 -perm5: 1.0 -permAmount1: 1.0 -permAmount2: 0.6666666666666667 -noAlias: 1.0 - -dependencyAnalysisTests/precisionTests/permissions - disjoint_ref -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -currentPerm: 1.0 -perm1: 1.0 -perm2: 1.0 -perm3: 1.0 -perm4: 1.0 -perm5: 1.0 -permAmount1: 1.0 -permAmount2: 0.8 -noAlias: 1.0 - -dependencyAnalysisTests/precisionTests/permissions - disjoint_ref_disjoint_field -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -currentPerm: 1.0 -perm1: 1.0 -perm2: 1.0 -perm3: 1.0 -perm4: 1.0 -perm5: 1.0 -permAmount1: 1.0 -permAmount2: 0.8 -noAlias: 1.0 - -dependencyAnalysisTests/precisionTests/permissions - disjoint_ref_disjoint_field_quantified -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -currentPerm: 1.0 -perm1: 1.0 -perm2: 1.0 -perm3: 1.0 -perm4: 1.0 -perm5: 1.0 -permAmount1: 1.0 -permAmount2: 0.8 -noAlias: 1.0 - -dependencyAnalysisTests/precisionTests/permissions - disjoint_ref_quantified -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -currentPerm: 0.33333333333333337 -perm1: 0.33333333333333337 -perm2: 0.33333333333333337 -perm3: 1.0 -perm4: 0.5 -perm5: 1.0 -permAmount1: 1.0 -permAmount2: 0.5714285714285714 -noAlias: 1.0 - -dependencyAnalysisTests/precisionTests/permissions - fold_unfold_with_implication -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -currentPerm: 0.25 -perm1: 0.25 -perm2: 0.25 -perm3: 0.25 -perm4: 0.5 -perm5: 0.25 -permAmount1: 0.5 -permAmount2: 0.5 -noAlias: 0.4 - -dependencyAnalysisTests/precisionTests/permissions - function_add -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -currentPerm: 1.0 -perm1: 1.0 -perm2: 1.0 -perm3: 1.0 -perm4: 1.0 -perm5: 1.0 -permAmount1: 1.0 -permAmount2: 0.8 -noAlias: 1.0 - -dependencyAnalysisTests/precisionTests/permissions - goto_pure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -currentPerm: 1.0 -perm1: 1.0 -perm2: 1.0 -perm3: 1.0 -perm4: 1.0 -perm5: 1.0 -permAmount1: 1.0 -permAmount2: 0.8 -noAlias: 1.0 - -dependencyAnalysisTests/precisionTests/permissions - infeasible_branch -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -currentPerm: 0.5 -perm1: 0.5 -perm2: 0.5 -perm3: 0.5 -perm4: 0.75 -perm5: 0.5 -permAmount1: 0.75 -permAmount2: 0.6666666666666667 -noAlias: 0.6666666666666667 - -dependencyAnalysisTests/precisionTests/permissions - inhale_invariant -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -currentPerm: 1.0 -perm1: 1.0 -perm2: 1.0 -perm3: 1.0 -perm4: 1.0 -perm5: 1.0 -permAmount1: 1.0 -permAmount2: 0.8 -noAlias: 1.0 - -dependencyAnalysisTests/precisionTests/permissions - magic_wand -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -currentPerm: 0.19999999999999996 -perm1: 1.0 -perm2: 1.0 -perm3: 1.0 -perm4: 1.0 -perm5: 1.0 -permAmount1: 1.0 -permAmount2: 0.8 -noAlias: 1.0 - -dependencyAnalysisTests/precisionTests/permissions - method_call_impure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -currentPerm: 0.5 -perm1: 1.0 -perm2: 1.0 -perm3: 1.0 -perm4: 1.0 -perm5: 1.0 -permAmount1: 1.0 -permAmount2: 0.8 -noAlias: 1.0 - -dependencyAnalysisTests/precisionTests/permissions - method_call_pure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -currentPerm: 1.0 -perm1: 1.0 -perm2: 1.0 -perm3: 1.0 -perm4: 1.0 -perm5: 1.0 -permAmount1: 1.0 -permAmount2: 0.8 -noAlias: 1.0 - -dependencyAnalysisTests/precisionTests/permissions - nested_branch -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -currentPerm: 1.0 -perm1: 1.0 -perm2: 1.0 -perm3: 1.0 -perm4: 0.75 -perm5: 1.0 -permAmount1: 1.0 -permAmount2: 0.6666666666666667 -noAlias: 1.0 - -dependencyAnalysisTests/precisionTests/permissions - quasihavoc -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -currentPerm: 0.5 -perm1: 0.5 -perm2: 0.33333333333333337 -perm3: 0.5 -perm4: 0.6666666666666667 -perm5: 0.5 -permAmount1: 0.6 -permAmount2: 0.625 -noAlias: 0.5 - -dependencyAnalysisTests/precisionTests/permissions - quasihavoc_disjoint_ref -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -currentPerm: 0.33333333333333337 -perm1: 0.33333333333333337 -perm2: 0.33333333333333337 -perm3: 0.33333333333333337 -perm4: 0.6 -perm5: 0.33333333333333337 -permAmount1: 0.6 -permAmount2: 0.5714285714285714 -noAlias: 0.4 - -dependencyAnalysisTests/precisionTests/permissions - quasihavoc_disjoint_ref_disjoint_field -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -currentPerm: 1.0 -perm1: 1.0 -perm2: 1.0 -perm3: 1.0 -perm4: 1.0 -perm5: 1.0 -permAmount1: 1.0 -permAmount2: 0.8 -noAlias: 1.0 - -dependencyAnalysisTests/precisionTests/permissions - while_perm -!!!!!!!!!!! -Failed to verify soundness of precision test dependencyAnalysisTests/precisionTests/permissions/while_perm - - -dependencyAnalysisTests/precisionTests/permissions - while_pure -!!!!!!!!!!! -Failed to verify soundness of precision test dependencyAnalysisTests/precisionTests/permissions/while_pure - - -dependencyAnalysisTests/precisionTests/permWildcard - assert -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -wildcardPerm: 1.0 -wildcardPermDistinctFields: 1.0 -quantifiedPermWildcard: 1.0 - -dependencyAnalysisTests/precisionTests/permWildcard - assignment_field -!!!!!!!!!!! -Failed to verify soundness of precision test dependencyAnalysisTests/precisionTests/permWildcard/assignment_field - - -dependencyAnalysisTests/precisionTests/permWildcard - assignment_pure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -wildcardPerm: 1.0 -wildcardPermDistinctFields: 1.0 -quantifiedPermWildcard: 1.0 - -dependencyAnalysisTests/precisionTests/permWildcard - baseline -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -wildcardPerm: 1.0 -wildcardPermDistinctFields: 1.0 -quantifiedPermWildcard: 1.0 - -dependencyAnalysisTests/precisionTests/permWildcard - branch -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -wildcardPerm: 1.0 -wildcardPermDistinctFields: 1.0 -quantifiedPermWildcard: 0.4 - -dependencyAnalysisTests/precisionTests/permWildcard - branch_div0 -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -wildcardPerm: 1.0 -wildcardPermDistinctFields: 1.0 -quantifiedPermWildcard: 0.6666666666666667 - -dependencyAnalysisTests/precisionTests/permWildcard - branch_function -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -wildcardPerm: 1.0 -wildcardPermDistinctFields: 1.0 -quantifiedPermWildcard: 0.33333333333333337 - -dependencyAnalysisTests/precisionTests/permWildcard - disjoint_ref -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -wildcardPerm: 1.0 -wildcardPermDistinctFields: 1.0 -quantifiedPermWildcard: 1.0 - -dependencyAnalysisTests/precisionTests/permWildcard - disjoint_ref_disjoint_field -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -wildcardPerm: 1.0 -wildcardPermDistinctFields: 1.0 -quantifiedPermWildcard: 1.0 - -dependencyAnalysisTests/precisionTests/permWildcard - disjoint_ref_disjoint_field_quantified -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -wildcardPerm: 1.0 -wildcardPermDistinctFields: 1.0 -quantifiedPermWildcard: 1.0 - -dependencyAnalysisTests/precisionTests/permWildcard - disjoint_ref_quantified -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -wildcardPerm: 0.25 -wildcardPermDistinctFields: 0.33333333333333337 -quantifiedPermWildcard: 0.4 - -dependencyAnalysisTests/precisionTests/permWildcard - fold_unfold_with_implication -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -wildcardPerm: 0.25 -wildcardPermDistinctFields: 0.25 -quantifiedPermWildcard: 0.5 - -dependencyAnalysisTests/precisionTests/permWildcard - function_add -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -wildcardPerm: 1.0 -wildcardPermDistinctFields: 1.0 -quantifiedPermWildcard: 1.0 - -dependencyAnalysisTests/precisionTests/permWildcard - goto_pure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -wildcardPerm: 1.0 -wildcardPermDistinctFields: 1.0 -quantifiedPermWildcard: 1.0 - -dependencyAnalysisTests/precisionTests/permWildcard - infeasible_branch -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -wildcardPerm: 0.5 -wildcardPermDistinctFields: 0.5 -quantifiedPermWildcard: 0.6666666666666667 - -dependencyAnalysisTests/precisionTests/permWildcard - inhale_invariant -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -wildcardPerm: 1.0 -wildcardPermDistinctFields: 1.0 -quantifiedPermWildcard: 1.0 - -dependencyAnalysisTests/precisionTests/permWildcard - magic_wand -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -wildcardPerm: 1.0 -wildcardPermDistinctFields: 1.0 -quantifiedPermWildcard: 1.0 - -dependencyAnalysisTests/precisionTests/permWildcard - method_call_impure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -wildcardPerm: 1.0 -wildcardPermDistinctFields: 1.0 -quantifiedPermWildcard: 0.6666666666666667 - -dependencyAnalysisTests/precisionTests/permWildcard - method_call_pure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -wildcardPerm: 1.0 -wildcardPermDistinctFields: 1.0 -quantifiedPermWildcard: 1.0 - -dependencyAnalysisTests/precisionTests/permWildcard - nested_branch -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -wildcardPerm: 1.0 -wildcardPermDistinctFields: 1.0 -quantifiedPermWildcard: 0.33333333333333337 - -dependencyAnalysisTests/precisionTests/permWildcard - quasihavoc -!!!!!!!!!!! -Failed to verify soundness of precision test dependencyAnalysisTests/precisionTests/permWildcard/quasihavoc - - -dependencyAnalysisTests/precisionTests/permWildcard - quasihavoc_disjoint_ref -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -wildcardPerm: 0.25 -wildcardPermDistinctFields: 0.33333333333333337 -quantifiedPermWildcard: 0.4 - -dependencyAnalysisTests/precisionTests/permWildcard - quasihavoc_disjoint_ref_disjoint_field -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -wildcardPerm: 1.0 -wildcardPermDistinctFields: 1.0 -quantifiedPermWildcard: 1.0 - -dependencyAnalysisTests/precisionTests/permWildcard - while_perm -!!!!!!!!!!! -Failed to verify soundness of precision test dependencyAnalysisTests/precisionTests/permWildcard/while_perm - - -dependencyAnalysisTests/precisionTests/permWildcard - while_pure -!!!!!!!!!!! -Failed to verify soundness of precision test dependencyAnalysisTests/precisionTests/permWildcard/while_pure - - -dependencyAnalysisTests/precisionTests/predicates - assert -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -foldP: 0.6666666666666667 -unfoldP: 0.8 -unfoldFoldP: 1.0 -callWithPredicate: 1.0 -unfoldPrecision: 1.0 -foldPrecision: 0.5 -unfolding1: 0.33333333333333337 -unfoldWithImpl: 1.0 -unfoldingWithImpl: 0.75 - -dependencyAnalysisTests/precisionTests/predicates - assignment_field -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -foldP: 1.0 -unfoldP: 1.0 -unfoldFoldP: 1.0 -callWithPredicate: 1.0 -unfoldPrecision: 1.0 -foldPrecision: 1.0 -unfolding1: 1.0 -unfoldWithImpl: 1.0 -unfoldingWithImpl: 1.0 - -dependencyAnalysisTests/precisionTests/predicates - assignment_pure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -foldP: 1.0 -unfoldP: 1.0 -unfoldFoldP: 1.0 -callWithPredicate: 1.0 -unfoldPrecision: 1.0 -foldPrecision: 1.0 -unfolding1: 1.0 -unfoldWithImpl: 1.0 -unfoldingWithImpl: 1.0 - -dependencyAnalysisTests/precisionTests/predicates - baseline -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -foldP: 1.0 -unfoldP: 1.0 -unfoldFoldP: 1.0 -callWithPredicate: 1.0 -unfoldPrecision: 1.0 -foldPrecision: 1.0 -unfolding1: 1.0 -unfoldWithImpl: 1.0 -unfoldingWithImpl: 1.0 - -dependencyAnalysisTests/precisionTests/predicates - branch -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -foldP: 0.6666666666666667 -unfoldP: 1.0 -unfoldFoldP: 1.0 -callWithPredicate: 1.0 -unfoldPrecision: 0.6666666666666667 -foldPrecision: 1.0 -unfolding1: 0.6666666666666667 -unfoldWithImpl: 0.8 -unfoldingWithImpl: 1.0 - -dependencyAnalysisTests/precisionTests/predicates - branch_div0 -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -foldP: 0.6666666666666667 -unfoldP: 0.8 -unfoldFoldP: 0.75 -callWithPredicate: 1.0 -unfoldPrecision: 0.4 -foldPrecision: 0.33333333333333337 -unfolding1: 0.6666666666666667 -unfoldWithImpl: 0.8 -unfoldingWithImpl: 0.75 - -dependencyAnalysisTests/precisionTests/predicates - branch_function -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -foldP: 0.5 -unfoldP: 0.6666666666666667 -unfoldFoldP: 0.6 -callWithPredicate: 1.0 -unfoldPrecision: 0.33333333333333337 -foldPrecision: 0.25 -unfolding1: 0.5 -unfoldWithImpl: 0.5714285714285714 -unfoldingWithImpl: 0.75 - -dependencyAnalysisTests/precisionTests/predicates - disjoint_ref -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -foldP: 1.0 -unfoldP: 1.0 -unfoldFoldP: 1.0 -callWithPredicate: 1.0 -unfoldPrecision: 1.0 -foldPrecision: 1.0 -unfolding1: 1.0 -unfoldWithImpl: 1.0 -unfoldingWithImpl: 1.0 - -dependencyAnalysisTests/precisionTests/predicates - disjoint_ref_disjoint_field -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -foldP: 1.0 -unfoldP: 1.0 -unfoldFoldP: 1.0 -callWithPredicate: 1.0 -unfoldPrecision: 1.0 -foldPrecision: 1.0 -unfolding1: 1.0 -unfoldWithImpl: 1.0 -unfoldingWithImpl: 1.0 - -dependencyAnalysisTests/precisionTests/predicates - disjoint_ref_disjoint_field_quantified -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -foldP: 1.0 -unfoldP: 1.0 -unfoldFoldP: 1.0 -callWithPredicate: 1.0 -unfoldPrecision: 1.0 -foldPrecision: 1.0 -unfolding1: 1.0 -unfoldWithImpl: 1.0 -unfoldingWithImpl: 1.0 - -dependencyAnalysisTests/precisionTests/predicates - disjoint_ref_quantified -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -foldP: 1.0 -unfoldP: 1.0 -unfoldFoldP: 1.0 -callWithPredicate: 1.0 -unfoldPrecision: 1.0 -foldPrecision: 1.0 -unfolding1: 1.0 -unfoldWithImpl: 1.0 -unfoldingWithImpl: 0.6 - -dependencyAnalysisTests/precisionTests/predicates - fold_unfold_with_implication -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -foldP: 0.4 -unfoldP: 0.5714285714285714 -unfoldFoldP: 0.5 -callWithPredicate: 0.4 -unfoldPrecision: 0.2857142857142857 -foldPrecision: 0.19999999999999996 -unfolding1: 0.4 -unfoldWithImpl: 0.5714285714285714 -unfoldingWithImpl: 0.5 - -dependencyAnalysisTests/precisionTests/predicates - function_add -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -foldP: 1.0 -unfoldP: 1.0 -unfoldFoldP: 1.0 -callWithPredicate: 1.0 -unfoldPrecision: 1.0 -foldPrecision: 1.0 -unfolding1: 1.0 -unfoldWithImpl: 1.0 -unfoldingWithImpl: 1.0 - -dependencyAnalysisTests/precisionTests/predicates - goto_pure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -foldP: 1.0 -unfoldP: 1.0 -unfoldFoldP: 1.0 -callWithPredicate: 1.0 -unfoldPrecision: 1.0 -foldPrecision: 1.0 -unfolding1: 1.0 -unfoldWithImpl: 0.8 -unfoldingWithImpl: 1.0 - -dependencyAnalysisTests/precisionTests/predicates - infeasible_branch -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -foldP: 0.6666666666666667 -unfoldP: 0.8 -unfoldFoldP: 0.75 -callWithPredicate: 0.6666666666666667 -unfoldPrecision: 0.5 -foldPrecision: 0.5 -unfolding1: 0.5 -unfoldWithImpl: 0.8 -unfoldingWithImpl: 0.75 - -dependencyAnalysisTests/precisionTests/predicates - inhale_invariant -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -foldP: 0.5 -unfoldP: 0.6666666666666667 -unfoldFoldP: 1.0 -callWithPredicate: 1.0 -unfoldPrecision: 1.0 -foldPrecision: 0.0 -unfolding1: 0.5 -unfoldWithImpl: 1.0 -unfoldingWithImpl: 0.6666666666666667 - -dependencyAnalysisTests/precisionTests/predicates - magic_wand -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -foldP: 1.0 -unfoldP: 1.0 -unfoldFoldP: 1.0 -callWithPredicate: 1.0 -unfoldPrecision: 1.0 -foldPrecision: 1.0 -unfolding1: 1.0 -unfoldWithImpl: 1.0 -unfoldingWithImpl: 1.0 - -dependencyAnalysisTests/precisionTests/predicates - method_call_impure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -foldP: 1.0 -unfoldP: 1.0 -unfoldFoldP: 1.0 -callWithPredicate: 1.0 -unfoldPrecision: 1.0 -foldPrecision: 1.0 -unfolding1: 1.0 -unfoldWithImpl: 1.0 -unfoldingWithImpl: 1.0 - -dependencyAnalysisTests/precisionTests/predicates - method_call_pure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -foldP: 1.0 -unfoldP: 1.0 -unfoldFoldP: 1.0 -callWithPredicate: 1.0 -unfoldPrecision: 1.0 -foldPrecision: 1.0 -unfolding1: 1.0 -unfoldWithImpl: 1.0 -unfoldingWithImpl: 1.0 - -dependencyAnalysisTests/precisionTests/predicates - nested_branch -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -foldP: 0.5 -unfoldP: 0.8 -unfoldFoldP: 0.75 -callWithPredicate: 1.0 -unfoldPrecision: 0.33333333333333337 -foldPrecision: 0.33333333333333337 -unfolding1: 0.5 -unfoldWithImpl: 0.6666666666666667 -unfoldingWithImpl: 0.75 - -dependencyAnalysisTests/precisionTests/predicates - quasihavoc -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -foldP: 1.0 -unfoldP: 1.0 -unfoldFoldP: 1.0 -callWithPredicate: 1.0 -unfoldPrecision: 1.0 -foldPrecision: 1.0 -unfolding1: 1.0 -unfoldWithImpl: 0.8 -unfoldingWithImpl: 1.0 - -dependencyAnalysisTests/precisionTests/predicates - quasihavoc_disjoint_ref -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -foldP: 1.0 -unfoldP: 1.0 -unfoldFoldP: 1.0 -callWithPredicate: 1.0 -unfoldPrecision: 1.0 -foldPrecision: 1.0 -unfolding1: 1.0 -unfoldWithImpl: 0.6666666666666667 -unfoldingWithImpl: 1.0 - -dependencyAnalysisTests/precisionTests/predicates - quasihavoc_disjoint_ref_disjoint_field -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -foldP: 1.0 -unfoldP: 1.0 -unfoldFoldP: 1.0 -callWithPredicate: 1.0 -unfoldPrecision: 1.0 -foldPrecision: 1.0 -unfolding1: 1.0 -unfoldWithImpl: 1.0 -unfoldingWithImpl: 1.0 - -dependencyAnalysisTests/precisionTests/predicates - while_perm -!!!!!!!!!!! -Failed to verify soundness of precision test dependencyAnalysisTests/precisionTests/predicates/while_perm - - -dependencyAnalysisTests/precisionTests/predicates - while_pure -!!!!!!!!!!! -Failed to verify soundness of precision test dependencyAnalysisTests/precisionTests/predicates/while_pure - - -dependencyAnalysisTests/precisionTests/quantifiedPermissions - assert -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -quantifiedPerm1: 0.6666666666666667 -quantifiedPerm2: 0.6666666666666667 -quantifiedPerm3: 0.75 -quantifiedExhalePartiallyTest: 1.0 -quantifiedExhalePartially: 0.6666666666666667 -quantifiedExhaleFully: 1.0 -quantifiedPermWrite1: 1.0 -quantifiedPermWrite2: 1.0 -quantifiedPermWrite3: 1.0 -quantifiedPermWrite4: 1.0 - -dependencyAnalysisTests/precisionTests/quantifiedPermissions - assignment_field -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -quantifiedPerm1: 1.0 -quantifiedPerm2: 0.6666666666666667 -quantifiedPerm3: 1.0 -quantifiedExhalePartiallyTest: 0.5 -quantifiedExhalePartially: 0.75 -quantifiedExhaleFully: 0.5 -quantifiedPermWrite1: 0.6666666666666667 -quantifiedPermWrite2: 1.0 -quantifiedPermWrite3: 1.0 -quantifiedPermWrite4: 1.0 - -dependencyAnalysisTests/precisionTests/quantifiedPermissions - assignment_pure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -quantifiedPerm1: 1.0 -quantifiedPerm2: 1.0 -quantifiedPerm3: 1.0 -quantifiedExhalePartiallyTest: 1.0 -quantifiedExhalePartially: 0.6666666666666667 -quantifiedExhaleFully: 1.0 -quantifiedPermWrite1: 1.0 -quantifiedPermWrite2: 1.0 -quantifiedPermWrite3: 1.0 -quantifiedPermWrite4: 1.0 - -dependencyAnalysisTests/precisionTests/quantifiedPermissions - baseline -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -quantifiedPerm1: 1.0 -quantifiedPerm2: 1.0 -quantifiedPerm3: 1.0 -quantifiedExhalePartiallyTest: 1.0 -quantifiedExhalePartially: 0.6666666666666667 -quantifiedExhaleFully: 1.0 -quantifiedPermWrite1: 1.0 -quantifiedPermWrite2: 1.0 -quantifiedPermWrite3: 1.0 -quantifiedPermWrite4: 1.0 - -dependencyAnalysisTests/precisionTests/quantifiedPermissions - branch -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -quantifiedPerm1: 1.0 -quantifiedPerm2: 0.6666666666666667 -quantifiedPerm3: 1.0 -quantifiedExhalePartiallyTest: 0.5 -quantifiedExhalePartially: 0.6666666666666667 -quantifiedExhaleFully: 0.5 -quantifiedPermWrite1: 0.6666666666666667 -quantifiedPermWrite2: 0.75 -quantifiedPermWrite3: 0.6 -quantifiedPermWrite4: 1.0 - -dependencyAnalysisTests/precisionTests/quantifiedPermissions - branch_div0 -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -quantifiedPerm1: 1.0 -quantifiedPerm2: 0.6666666666666667 -quantifiedPerm3: 1.0 -quantifiedExhalePartiallyTest: 0.5 -quantifiedExhalePartially: 0.6666666666666667 -quantifiedExhaleFully: 0.5 -quantifiedPermWrite1: 0.5 -quantifiedPermWrite2: 0.75 -quantifiedPermWrite3: 0.6 -quantifiedPermWrite4: 1.0 - -dependencyAnalysisTests/precisionTests/quantifiedPermissions - branch_function -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -quantifiedPerm1: 1.0 -quantifiedPerm2: 0.6666666666666667 -quantifiedPerm3: 0.5714285714285714 -quantifiedExhalePartiallyTest: 0.5 -quantifiedExhalePartially: 0.6666666666666667 -quantifiedExhaleFully: 0.5 -quantifiedPermWrite1: 0.4 -quantifiedPermWrite2: 0.6 -quantifiedPermWrite3: 0.5 -quantifiedPermWrite4: 1.0 - -dependencyAnalysisTests/precisionTests/quantifiedPermissions - disjoint_ref -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -quantifiedPerm1: 1.0 -quantifiedPerm2: 0.6666666666666667 -quantifiedPerm3: 1.0 -quantifiedExhalePartiallyTest: 0.5 -quantifiedExhalePartially: 0.6666666666666667 -quantifiedExhaleFully: 0.5 -quantifiedPermWrite1: 0.6666666666666667 -quantifiedPermWrite2: 1.0 -quantifiedPermWrite3: 1.0 -quantifiedPermWrite4: 1.0 - -dependencyAnalysisTests/precisionTests/quantifiedPermissions - disjoint_ref_disjoint_field -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -quantifiedPerm1: 1.0 -quantifiedPerm2: 0.6666666666666667 -quantifiedPerm3: 1.0 -quantifiedExhalePartiallyTest: 0.5 -quantifiedExhalePartially: 0.6666666666666667 -quantifiedExhaleFully: 0.5 -quantifiedPermWrite1: 0.6666666666666667 -quantifiedPermWrite2: 1.0 -quantifiedPermWrite3: 1.0 -quantifiedPermWrite4: 1.0 - -dependencyAnalysisTests/precisionTests/quantifiedPermissions - disjoint_ref_disjoint_field_quantified -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -quantifiedPerm1: 1.0 -quantifiedPerm2: 0.6666666666666667 -quantifiedPerm3: 1.0 -quantifiedExhalePartiallyTest: 0.5 -quantifiedExhalePartially: 1.0 -quantifiedExhaleFully: 0.5 -quantifiedPermWrite1: 0.6666666666666667 -quantifiedPermWrite2: 1.0 -quantifiedPermWrite3: 1.0 -quantifiedPermWrite4: 1.0 - -dependencyAnalysisTests/precisionTests/quantifiedPermissions - disjoint_ref_quantified -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -quantifiedPerm1: 1.0 -quantifiedPerm2: 0.4 -quantifiedPerm3: 1.0 -quantifiedExhalePartiallyTest: 0.25 -quantifiedExhalePartially: 0.4 -quantifiedExhaleFully: 0.25 -quantifiedPermWrite1: 0.6666666666666667 -quantifiedPermWrite2: 0.5 -quantifiedPermWrite3: 0.5 -quantifiedPermWrite4: 1.0 - -dependencyAnalysisTests/precisionTests/quantifiedPermissions - fold_unfold_with_implication -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -quantifiedPerm1: 0.4 -quantifiedPerm2: 0.4 -quantifiedPerm3: 0.5 -quantifiedExhalePartiallyTest: 0.19999999999999996 -quantifiedExhalePartially: 0.33333333333333337 -quantifiedExhaleFully: 0.19999999999999996 -quantifiedPermWrite1: 0.5 -quantifiedPermWrite2: 0.6 -quantifiedPermWrite3: 0.4285714285714286 -quantifiedPermWrite4: 0.4 - -dependencyAnalysisTests/precisionTests/quantifiedPermissions - function_add -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -quantifiedPerm1: 0.6666666666666667 -quantifiedPerm2: 0.6666666666666667 -quantifiedPerm3: 1.0 -quantifiedExhalePartiallyTest: 0.33333333333333337 -quantifiedExhalePartially: 0.6666666666666667 -quantifiedExhaleFully: 0.33333333333333337 -quantifiedPermWrite1: 0.6666666666666667 -quantifiedPermWrite2: 1.0 -quantifiedPermWrite3: 1.0 -quantifiedPermWrite4: 1.0 - -Program dependencyAnalysisTests/precisionTests/quantifiedPermissions/goto_pure does not verify. Skip -dependencyAnalysisTests/precisionTests/quantifiedPermissions - infeasible_branch -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -quantifiedPerm1: 0.6666666666666667 -quantifiedPerm2: 0.5 -quantifiedPerm3: 0.75 -quantifiedExhalePartiallyTest: 0.33333333333333337 -quantifiedExhalePartially: 0.5 -quantifiedExhaleFully: 0.33333333333333337 -quantifiedPermWrite1: 0.5 -quantifiedPermWrite2: 0.75 -quantifiedPermWrite3: 0.6 -quantifiedPermWrite4: 0.5 - -dependencyAnalysisTests/precisionTests/quantifiedPermissions - inhale_invariant -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -quantifiedPerm1: 0.5 -quantifiedPerm2: 0.5 -quantifiedPerm3: 0.6666666666666667 -quantifiedExhalePartiallyTest: 1.0 -quantifiedExhalePartially: 1.0 -quantifiedExhaleFully: 1.0 -quantifiedPermWrite1: 1.0 -quantifiedPermWrite2: 1.0 -quantifiedPermWrite3: 1.0 -quantifiedPermWrite4: 1.0 - -dependencyAnalysisTests/precisionTests/quantifiedPermissions - magic_wand -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -quantifiedPerm1: 1.0 -quantifiedPerm2: 1.0 -quantifiedPerm3: 0.4285714285714286 -quantifiedExhalePartiallyTest: 1.0 -quantifiedExhalePartially: 0.6666666666666667 -quantifiedExhaleFully: 1.0 -quantifiedPermWrite1: 1.0 -quantifiedPermWrite2: 0.4285714285714286 -quantifiedPermWrite3: 1.0 -quantifiedPermWrite4: 1.0 - -dependencyAnalysisTests/precisionTests/quantifiedPermissions - method_call_impure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -quantifiedPerm1: 1.0 -quantifiedPerm2: 0.6666666666666667 -quantifiedPerm3: 0.6 -quantifiedExhalePartiallyTest: 1.0 -quantifiedExhalePartially: 0.5 -quantifiedExhaleFully: 1.0 -quantifiedPermWrite1: 1.0 -quantifiedPermWrite2: 0.75 -quantifiedPermWrite3: 0.75 -quantifiedPermWrite4: 1.0 - -dependencyAnalysisTests/precisionTests/quantifiedPermissions - method_call_pure -!!!!!!!!!!! -Failed to verify soundness of precision test dependencyAnalysisTests/precisionTests/quantifiedPermissions/method_call_pure - - -dependencyAnalysisTests/precisionTests/quantifiedPermissions - nested_branch -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -quantifiedPerm1: 1.0 -quantifiedPerm2: 0.6666666666666667 -quantifiedPerm3: 1.0 -quantifiedExhalePartiallyTest: 0.5 -quantifiedExhalePartially: 0.6666666666666667 -quantifiedExhaleFully: 0.5 -quantifiedPermWrite1: 0.5 -quantifiedPermWrite2: 0.6 -quantifiedPermWrite3: 0.5 -quantifiedPermWrite4: 1.0 - -dependencyAnalysisTests/precisionTests/quantifiedPermissions - quasihavoc -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -quantifiedPerm1: 0.6666666666666667 -quantifiedPerm2: 0.6666666666666667 -quantifiedPerm3: 0.75 -quantifiedExhalePartiallyTest: 0.33333333333333337 -quantifiedExhalePartially: 0.5 -quantifiedExhaleFully: 0.33333333333333337 -quantifiedPermWrite1: 0.6666666666666667 -quantifiedPermWrite2: 0.6666666666666667 -quantifiedPermWrite3: 0.75 -quantifiedPermWrite4: 0.4 - -dependencyAnalysisTests/precisionTests/quantifiedPermissions - quasihavoc_disjoint_ref -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -quantifiedPerm1: 0.5 -quantifiedPerm2: 0.5 -quantifiedPerm3: 0.6 -quantifiedExhalePartiallyTest: 0.33333333333333337 -quantifiedExhalePartially: 0.4 -quantifiedExhaleFully: 0.33333333333333337 -quantifiedPermWrite1: 0.5 -quantifiedPermWrite2: 0.6 -quantifiedPermWrite3: 0.6 -quantifiedPermWrite4: 0.4 - -dependencyAnalysisTests/precisionTests/quantifiedPermissions - quasihavoc_disjoint_ref_disjoint_field -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -quantifiedPerm1: 1.0 -quantifiedPerm2: 1.0 -quantifiedPerm3: 1.0 -quantifiedExhalePartiallyTest: 1.0 -quantifiedExhalePartially: 1.0 -quantifiedExhaleFully: 1.0 -quantifiedPermWrite1: 1.0 -quantifiedPermWrite2: 1.0 -quantifiedPermWrite3: 1.0 -quantifiedPermWrite4: 1.0 - -dependencyAnalysisTests/precisionTests/quantifiedPermissions - while_perm -!!!!!!!!!!! -Failed to verify soundness of precision test dependencyAnalysisTests/precisionTests/quantifiedPermissions/while_perm - - -Program dependencyAnalysisTests/precisionTests/quantifiedPermissions/while_pure does not verify. Skip -dependencyAnalysisTests/precisionTests/quasihavoc - assert -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -quasihavoc1: 0.75 -quasihavoc2: 0.5 -quasihavoc3: 0.6666666666666667 - -dependencyAnalysisTests/precisionTests/quasihavoc - assignment_field -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -quasihavoc1: 0.75 -quasihavoc2: 0.5 -quasihavoc3: 0.6666666666666667 - -dependencyAnalysisTests/precisionTests/quasihavoc - assignment_pure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -quasihavoc1: 0.75 -quasihavoc2: 0.5 -quasihavoc3: 0.6666666666666667 - -dependencyAnalysisTests/precisionTests/quasihavoc - baseline -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -quasihavoc1: 0.75 -quasihavoc2: 0.5 -quasihavoc3: 0.6666666666666667 - -dependencyAnalysisTests/precisionTests/quasihavoc - branch -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -quasihavoc1: 0.75 -quasihavoc2: 0.5 -quasihavoc3: 0.6666666666666667 - -dependencyAnalysisTests/precisionTests/quasihavoc - branch_div0 -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -quasihavoc1: 0.75 -quasihavoc2: 0.5 -quasihavoc3: 0.6666666666666667 - -dependencyAnalysisTests/precisionTests/quasihavoc - branch_function -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -quasihavoc1: 0.75 -quasihavoc2: 0.5 -quasihavoc3: 0.6666666666666667 - -dependencyAnalysisTests/precisionTests/quasihavoc - disjoint_ref -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -quasihavoc1: 0.75 -quasihavoc2: 0.5 -quasihavoc3: 0.6666666666666667 - -dependencyAnalysisTests/precisionTests/quasihavoc - disjoint_ref_disjoint_field -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -quasihavoc1: 0.75 -quasihavoc2: 0.5 -quasihavoc3: 0.6666666666666667 - -dependencyAnalysisTests/precisionTests/quasihavoc - disjoint_ref_disjoint_field_quantified -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -quasihavoc1: 0.75 -quasihavoc2: 0.5 -quasihavoc3: 0.6666666666666667 - -dependencyAnalysisTests/precisionTests/quasihavoc - disjoint_ref_quantified -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -quasihavoc1: 0.75 -quasihavoc2: 0.5 -quasihavoc3: 0.6666666666666667 - -dependencyAnalysisTests/precisionTests/quasihavoc - fold_unfold_with_implication -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -quasihavoc1: 0.75 -quasihavoc2: 0.5 -quasihavoc3: 0.6666666666666667 - -dependencyAnalysisTests/precisionTests/quasihavoc - function_add -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -quasihavoc1: 0.75 -quasihavoc2: 0.5 -quasihavoc3: 0.6666666666666667 - -dependencyAnalysisTests/precisionTests/quasihavoc - goto_pure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -quasihavoc1: 0.75 -quasihavoc2: 0.5 -quasihavoc3: 0.6666666666666667 - -dependencyAnalysisTests/precisionTests/quasihavoc - infeasible_branch -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -quasihavoc1: 0.75 -quasihavoc2: 0.5 -quasihavoc3: 0.6666666666666667 - -dependencyAnalysisTests/precisionTests/quasihavoc - inhale_invariant -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -quasihavoc1: 0.75 -quasihavoc2: 0.5 -quasihavoc3: 0.6666666666666667 - -dependencyAnalysisTests/precisionTests/quasihavoc - magic_wand -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -quasihavoc1: 0.75 -quasihavoc2: 0.5 -quasihavoc3: 0.6666666666666667 - -dependencyAnalysisTests/precisionTests/quasihavoc - method_call_impure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -quasihavoc1: 0.75 -quasihavoc2: 0.5 -quasihavoc3: 0.6666666666666667 - -dependencyAnalysisTests/precisionTests/quasihavoc - method_call_pure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -quasihavoc1: 0.75 -quasihavoc2: 0.5 -quasihavoc3: 0.6666666666666667 - -dependencyAnalysisTests/precisionTests/quasihavoc - nested_branch -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -quasihavoc1: 0.75 -quasihavoc2: 0.5 -quasihavoc3: 0.6666666666666667 - -dependencyAnalysisTests/precisionTests/quasihavoc - quasihavoc -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -quasihavoc1: 0.75 -quasihavoc2: 0.5 -quasihavoc3: 0.6666666666666667 - -dependencyAnalysisTests/precisionTests/quasihavoc - quasihavoc_disjoint_ref -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -quasihavoc1: 0.75 -quasihavoc2: 0.5 -quasihavoc3: 0.6666666666666667 - -dependencyAnalysisTests/precisionTests/quasihavoc - quasihavoc_disjoint_ref_disjoint_field -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -quasihavoc1: 0.75 -quasihavoc2: 0.5 -quasihavoc3: 0.6666666666666667 - -dependencyAnalysisTests/precisionTests/quasihavoc - while_perm -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -quasihavoc1: 0.75 -quasihavoc2: 0.5 -quasihavoc3: 0.6666666666666667 - -dependencyAnalysisTests/precisionTests/quasihavoc - while_pure -gen_add: 1.0 -gen_add_positive: 1.0 -incr: 1.0 -gen_incr_pure: 1.0 -quasihavoc1: 0.75 -quasihavoc2: 0.5 -quasihavoc3: 0.6666666666666667 - diff --git a/src/test/resources/dependencyAnalysisTests/precisionTests/results/result_table.out b/src/test/resources/dependencyAnalysisTests/precisionTests/results/result_table.out index 4149e9be3..1df286803 100644 --- a/src/test/resources/dependencyAnalysisTests/precisionTests/results/result_table.out +++ b/src/test/resources/dependencyAnalysisTests/precisionTests/results/result_table.out @@ -1,13 +1,31 @@ - & 00_baseline & 01_inhale_invariant & 02_2_assert_invariant & 02_assert_invariant & 03_assignment_pure & 04_2_assignment_field_disjoint_field & 04_assignment_field & 05_assignment_disjoint_ref & 06_assignment_disjoint_ref_disjoint_field & 07_assignment_disjoint_ref_quantified & 08_assignment_disjoint_ref_disjoint_field_quantified & 09_1_fold_unfold & 09_fold_unfold_with_implication & 10_function_add_pure & 11_function_add_impure & 12_method_call_pure & 13_method_call_impure & 14_branch & 15_branch_nested & 16_branch_infeasible & 17_branch_function & 18_while_pure & 19_while_perm & 20_magic_wand & 21_2_quasihavoc_disjoint_field & 21_quasihavoc & 22_quasihavoc_disjoint_ref & 23_quasihavoc_disjoint_ref_disjoint_field & 24_goto_pure & 25_unrelated_sync_points -dependencyAnalysisTests/precisionTests/A-inhaleExhale & 1.000 & 0.963 & 1.000 & 0.972 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.891 & 1.000 & 0.952 & 0.889 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.917 & 0.944 & 1.000 & 1.000 & 0.944 & 1.000 & 0.898 & 0.867 & 1.000 & 1.000 & 1.000 \\ -dependencyAnalysisTests/precisionTests/B-permissions & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.884 & 1.000 & 1.000 & 0.756 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.774 & 1.000 & 1.000 & 1.000 & 0.952 & 1.000 & 0.739 & 0.684 & 1.000 & 1.000 & 1.000 \\ -dependencyAnalysisTests/precisionTests/C-permWildcard & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & NaN & 1.000 & 1.000 & 0.907 & 1.000 & 1.000 & 0.833 & 1.000 & NaN & 1.000 & 1.000 & 1.000 & 1.000 & 0.852 & 0.921 & 1.000 & NaN & 1.000 & 1.000 & NaN & 0.776 & 1.000 & 1.000 & 1.000 \\ -dependencyAnalysisTests/precisionTests/D-quantifiedPermissions & 1.000 & 0.917 & 1.000 & 0.943 & 1.000 & 0.984 & NaN & 0.958 & 0.958 & 0.859 & 0.958 & 0.932 & 0.732 & 0.948 & NaN & NaN & 0.979 & 0.958 & 0.877 & 0.798 & 0.860 & NaN & NaN & 0.938 & 0.984 & NaN & 0.685 & 1.000 & NaN & 0.950 \\ -dependencyAnalysisTests/precisionTests/E-function-call & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.894 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.927 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 \\ -dependencyAnalysisTests/precisionTests/F-method-call & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.899 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.929 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 \\ -dependencyAnalysisTests/precisionTests/G-predicates & 1.000 & 0.822 & 0.967 & 0.870 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.987 & 1.000 & 1.000 & 0.759 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.923 & 0.796 & 0.876 & 1.000 & NaN & 1.000 & 1.000 & 0.987 & 0.978 & 1.000 & 1.000 & 1.000 \\ -dependencyAnalysisTests/precisionTests/H-magicWands & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.981 & 1.000 & 1.000 & 0.838 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.881 & 1.000 & 1.000 & NaN & 0.956 & 1.000 & 0.958 & 0.962 & 1.000 & 1.000 & 1.000 \\ -dependencyAnalysisTests/precisionTests/I-branches & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.852 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.932 & 0.871 & 0.762 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 \\ -dependencyAnalysisTests/precisionTests/J-loops & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.863 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.891 & 0.940 & NaN & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & NaN & 1.000 \\ -dependencyAnalysisTests/precisionTests/K-domain & 1.000 & 0.963 & 1.000 & 0.972 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.887 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.886 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.960 & 1.000 & 1.000 & 1.000 & 1.000 \\ -dependencyAnalysisTests/precisionTests/L-misc & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.938 & 1.000 & 1.000 & 0.950 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.958 & 1.000 & 1.000 & 1.000 & 1.000 & 1.000 & 0.958 & 0.938 & 1.000 & 1.000 & 1.000 \\ + & A-inhaleExhale & B-permissions & C-permWildcard & D-quantifiedPermissions & E-function-call & F-method-call & G-predicates & H-magicWands & I-branches & J-loops & K-domain & L-misc +00_baseline & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 +01_inhale_invariant & 0.96 & 1.00 & 1.00 & 0.92 & 1.00 & 1.00 & 0.82 & 1.00 & 1.00 & 1.00 & 0.96 & 1.00 +02_2_assert_invariant & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 0.97 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 +02_assert_invariant & 0.97 & 1.00 & 1.00 & 0.94 & 1.00 & 1.00 & 0.87 & 1.00 & 1.00 & 1.00 & 0.97 & 1.00 +03_assignment_pure & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 +04_2_assignment_field_disjoint_field & 1.00 & 1.00 & 1.00 & 0.98 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 +04_assignment_field & 1.00 & 1.00 & NaN & NaN & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 +05_assignment_disjoint_ref & 1.00 & 1.00 & 1.00 & 0.96 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 +06_assignment_disjoint_ref_disjoint_field & 1.00 & 1.00 & 1.00 & 0.96 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 +07_assignment_disjoint_ref_quantified & 0.89 & 0.88 & 0.91 & 0.86 & 1.00 & 1.00 & 0.99 & 0.98 & 1.00 & 1.00 & 1.00 & 0.94 +08_assignment_disjoint_ref_disjoint_field_quantified & 1.00 & 1.00 & 1.00 & 0.96 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 +09_1_fold_unfold & 0.95 & 1.00 & 1.00 & 0.93 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 +09_fold_unfold_with_implication & 0.89 & 0.76 & 0.83 & 0.73 & 0.89 & 0.90 & 0.76 & 0.84 & 0.85 & 0.86 & 0.89 & 0.95 +10_function_add_pure & 1.00 & 1.00 & 1.00 & 0.95 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 +11_function_add_impure & 1.00 & 1.00 & NaN & NaN & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 +12_method_call_pure & 1.00 & 1.00 & 1.00 & NaN & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 +13_method_call_impure & 1.00 & 1.00 & 1.00 & 0.98 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 +14_branch & 1.00 & 1.00 & 1.00 & 0.96 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 +15_branch_nested & 1.00 & 1.00 & 1.00 & 0.88 & 1.00 & 1.00 & 0.92 & 1.00 & 0.93 & 1.00 & 1.00 & 1.00 +16_branch_infeasible & 0.92 & 0.77 & 0.85 & 0.80 & 0.93 & 0.93 & 0.80 & 0.88 & 0.87 & 0.89 & 0.89 & 0.96 +17_branch_function & 0.94 & 1.00 & 0.92 & 0.86 & 1.00 & 1.00 & 0.88 & 1.00 & 0.76 & 0.94 & 1.00 & 1.00 +18_while_pure & 1.00 & 1.00 & 1.00 & NaN & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & NaN & 1.00 & 1.00 +19_while_perm & 1.00 & 1.00 & NaN & NaN & 1.00 & 1.00 & NaN & NaN & 1.00 & 1.00 & 1.00 & 1.00 +20_magic_wand & 0.94 & 0.95 & 1.00 & 0.94 & 1.00 & 1.00 & 1.00 & 0.96 & 1.00 & 1.00 & 1.00 & 1.00 +21_2_quasihavoc_disjoint_field & 1.00 & 1.00 & 1.00 & 0.98 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 +21_quasihavoc & 0.90 & 0.74 & NaN & NaN & 1.00 & 1.00 & 0.99 & 0.96 & 1.00 & 1.00 & 0.96 & 0.96 +22_quasihavoc_disjoint_ref & 0.87 & 0.68 & 0.78 & 0.69 & 1.00 & 1.00 & 0.98 & 0.96 & 1.00 & 1.00 & 1.00 & 0.94 +23_quasihavoc_disjoint_ref_disjoint_field & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 +24_goto_pure & 1.00 & 1.00 & 1.00 & NaN & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & NaN & 1.00 & 1.00 +25_unrelated_sync_points & 1.00 & 1.00 & 1.00 & 0.95 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 From 334647a73025be0c1345177bb07bc50dc8afccfa Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 26 Sep 2025 10:53:16 +0200 Subject: [PATCH 259/474] update Readme --- src/test/resources/dependencyAnalysisTests/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test/resources/dependencyAnalysisTests/README.md b/src/test/resources/dependencyAnalysisTests/README.md index d9cf4ab04..23b35a928 100644 --- a/src/test/resources/dependencyAnalysisTests/README.md +++ b/src/test/resources/dependencyAnalysisTests/README.md @@ -29,5 +29,7 @@ Executing the benchmark: Real dependencies are computed as `#(reported dependencies) - #(reported dependencies annotated as irrelevant)`. +The manually crafted examples correspond to programs found in folders `all` and `real-world-examples`. + To analyze the cause of imprecision, `src/test/scala/AssumptionAnalysisTests.scala` with `CHECK_PRECISION=true` can be executed on the program of interest. Imprecise results are reported as `Unexpected dependency` and make the test fail. \ No newline at end of file From f9da376bc733817c81382c4dcbe6bbab08b1e172 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 30 Sep 2025 12:07:19 +0200 Subject: [PATCH 260/474] minor fix --- .../AssumptionAnalysisInterpreter.scala | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala index ae50ea006..338382268 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala @@ -23,8 +23,16 @@ class AssumptionAnalysisInterpreter(name: String, graph: ReadOnlyAssumptionAnaly getNodes.filter(n => !AssumptionType.internalTypes.contains(n.assumptionType)).filter(node => node.sourceInfo.getLineNumber.isDefined && node.sourceInfo.getLineNumber.get == line && node.sourceInfo.getPositionString.startsWith(file + ".")) def getDirectDependencies(nodeIdsToAnalyze: Set[Int]): Set[AssumptionAnalysisNode] = { - val directDependencyIds = nodeIdsToAnalyze flatMap (id => graph.getDirectEdges.getOrElse(id, Set.empty)) - getNonInternalAssumptionNodes.filter(node => directDependencyIds.contains(node.id)) + var queue = nodeIdsToAnalyze + var result: Set[Int] = Set.empty + val internalNodeIds = getNodes.diff(getNonInternalAssumptionNodes).map(_.id) + while(queue.nonEmpty){ + val directDependencyIds = queue flatMap (id => graph.getDirectEdges.getOrElse(id, Set.empty)) + queue = internalNodeIds.intersect(directDependencyIds).diff(result) // internal assumptions are hidden -> add their direct dependencies instead + result = result.union(directDependencyIds) + } + + getNonInternalAssumptionNodes.filter(node => result.contains(node.id)) } def getAllNonInternalDependencies(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[AssumptionAnalysisNode] = { From d1547afe650fe15cff75f4bc5fcfa1f771716bb4 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 14 Nov 2025 10:07:23 +0100 Subject: [PATCH 261/474] optimize graph join through prefiltering/grouping --- .../scala/assumptionAnalysis/AnalysisInfo.scala | 4 ++-- .../assumptionAnalysis/AssumptionAnalyzer.scala | 15 +++++++++++---- src/main/scala/rules/Evaluator.scala | 2 +- src/main/scala/rules/Executor.scala | 4 ++-- .../dependencyAnalysisTests/all/divBy0.vpr | 4 ++-- .../dependencyAnalysisTests/all/function-sum.vpr | 4 ++-- .../dependencyAnalysisTests/all/functions.vpr | 10 +++++----- .../dependencyAnalysisTests/all/method-sum.vpr | 8 ++++---- .../unitTests/E-function-call.vpr | 10 +++++----- .../unitTests/F-method-call.vpr | 14 +++++++------- .../scala/AssumptionAnalysisTestFramework.scala | 4 ++-- 11 files changed, 43 insertions(+), 36 deletions(-) diff --git a/src/main/scala/assumptionAnalysis/AnalysisInfo.scala b/src/main/scala/assumptionAnalysis/AnalysisInfo.scala index db6b20a64..cc9571956 100644 --- a/src/main/scala/assumptionAnalysis/AnalysisInfo.scala +++ b/src/main/scala/assumptionAnalysis/AnalysisInfo.scala @@ -2,11 +2,11 @@ package viper.silicon.assumptionAnalysis object AssumptionType extends Enumeration { type AssumptionType = Value - val Explicit, LoopInvariant, PathCondition, Rewrite, Implicit, Internal, Trigger, ExplicitPostcondition, ImplicitPostcondition = Value + val Explicit, LoopInvariant, PathCondition, Rewrite, Implicit, Internal, Trigger, ExplicitPostcondition, ImplicitPostcondition, ExplicitPostcondAssumption, ImplicitPostcondAssumption = Value def fromString(s: String): Option[Value] = values.find(_.toString == s) - def explicitAssumptionTypes: Set[AssumptionType] = Set(Explicit, ExplicitPostcondition) + def explicitAssumptionTypes: Set[AssumptionType] = Set(Explicit, ExplicitPostcondition, ExplicitPostcondAssumption) def postconditionTypes: Set[AssumptionType] = Set(ImplicitPostcondition, ExplicitPostcondition) // used to join graphs via postconditions def explicitAssertionTypes: Set[AssumptionType] = Set(Explicit) ++ postconditionTypes def internalTypes: Set[AssumptionType] = Set(Internal) // will always be hidden from user diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala index e9ff7a802..5cf37a494 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala @@ -112,13 +112,20 @@ object AssumptionAnalyzer { newGraph.addEdges( assertionNodes.map(_.id), nodes.map(_.id)) } - val types = Set(AssumptionType.Implicit, AssumptionType.Explicit) - val relevantAssumptionNodes = newGraph.nodes filter (node => node.isInstanceOf[GeneralAssumptionNode] && types.contains(node.assumptionType)) + // get all nodes that represent the assumption of a postcondition introduced by a method call or function application + // for a fast lookup, these nodes are stored as a map from string to a set of integers. The string represents the assumed postcondition (which is the join condition) and + // the integers correspond to all nodes associated with said postcondition. + val types = Set(AssumptionType.ImplicitPostcondAssumption, AssumptionType.ExplicitPostcondAssumption) + val relevantAssumptionNodes = newGraph.nodes.filter(node => node.isInstanceOf[GeneralAssumptionNode] && types.contains(node.assumptionType)) + .groupBy(_.sourceInfo.getFineGrainedSource.toString) + .view.mapValues(_.map(_.id)) + .toMap + newGraph.nodes filter (node => AssumptionType.postconditionTypes.contains(node.assumptionType)) foreach { node => val nodeSourceInfoString = node.sourceInfo.getTopLevelSource.toString - val assumptionNodesForJoin = relevantAssumptionNodes filter (aNode => aNode.sourceInfo.getFineGrainedSource.toString.equals(nodeSourceInfoString)) - newGraph.addEdges(node.id, assumptionNodesForJoin map (_.id)) + val assumptionNodesForJoin = relevantAssumptionNodes.getOrElse(nodeSourceInfoString, Seq.empty) + newGraph.addEdges(node.id, assumptionNodesForJoin) } new AssumptionAnalysisInterpreter(name.getOrElse("joined"), newGraph) diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 00eb751a5..32cd69896 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -861,7 +861,7 @@ object evaluator extends EvaluationRules { case fapp @ ast.FuncApp(funcName, eArgs) => val func = s.program.findFunction(funcName) - val funcAssumptionType = if(func.body.isDefined || !AssumptionAnalyzer.extractEnableAnalysisFromInfo(func.info).getOrElse(true)) AssumptionType.Implicit else AssumptionType.Explicit + val funcAssumptionType = if(func.body.isDefined || !AssumptionAnalyzer.extractEnableAnalysisFromInfo(func.info).getOrElse(true)) AssumptionType.ImplicitPostcondAssumption else AssumptionType.ExplicitPostcondAssumption val assumptionType = AssumptionAnalyzer.extractAssumptionTypeFromInfo(fapp.info).getOrElse(funcAssumptionType) evals2(s, eArgs, Nil, _ => pve, v)((s1, tArgs, eArgsNew, v1) => { // bookkeeper.functionApplications += 1 diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 6e171122a..cd863ffe4 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -604,8 +604,8 @@ object executor extends ExecutionRules { val pveCall = CallFailed(call) val pveCallTransformed = pveCall.withReasonNodeTransformed(reasonTransformer) - val methodAnnotatedAssumptionType = AssumptionAnalyzer.extractAssumptionTypeFromInfo(meth.info) - val defaultAssumptionType = if(meth.body.isDefined || !AssumptionAnalyzer.extractEnableAnalysisFromInfo(meth.info).getOrElse(true)) AssumptionType.Implicit else AssumptionType.Explicit + val methodAnnotatedAssumptionType = AssumptionAnalyzer.extractAssumptionTypeFromInfo(meth.info) // TODO: make sure the join can still be made? + val defaultAssumptionType = if(meth.body.isDefined || !AssumptionAnalyzer.extractEnableAnalysisFromInfo(meth.info).getOrElse(true)) AssumptionType.ImplicitPostcondAssumption else AssumptionType.ExplicitPostcondAssumption val finalAssumptionType = annotatedAssumptionTypeOpt.getOrElse(methodAnnotatedAssumptionType.getOrElse(defaultAssumptionType)) val mcLog = new MethodCallRecord(call, s, v.decider.pcs) diff --git a/src/test/resources/dependencyAnalysisTests/all/divBy0.vpr b/src/test/resources/dependencyAnalysisTests/all/divBy0.vpr index ce768e38f..b10aee6e7 100644 --- a/src/test/resources/dependencyAnalysisTests/all/divBy0.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/divBy0.vpr @@ -45,13 +45,13 @@ method sumClient(x: Int, y: Int) @dependency("Explicit") assume x < y var n: Int - @dependency("Implicit") + @dependency("ImplicitPostcondAssumption") n := sum(x, y) // the following stmt reports dependency on n := sum(x, y) because (x < y && n == x + y && n > 100 ==> y != 0) // although you could also prove it via (0 <= x && x < y ==> y != 0) var n2: Int - @dependency("Implicit") + @dependency("ImplicitPostcondAssumption") n2 := sum(x/y, y) @dependency("Implicit") n := n + n2 diff --git a/src/test/resources/dependencyAnalysisTests/all/function-sum.vpr b/src/test/resources/dependencyAnalysisTests/all/function-sum.vpr index 5e8e0d89d..265e85618 100644 --- a/src/test/resources/dependencyAnalysisTests/all/function-sum.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/function-sum.vpr @@ -20,7 +20,7 @@ method call2(){ @dependency("Explicit") assume y > 0 - @dependency("Implicit") + @dependency("ImplicitPostcondAssumption") z := sum(x, y) // $PrecisionTest: $READ_ONLY=x,y,z $INVARIANT=x>0 @@ -36,7 +36,7 @@ method call2Unv(){ @dependency("Explicit") assume y > 0 - @dependency("Explicit") + @dependency("ExplicitPostcondAssumption") z := sumUnverified(x, y) // $PrecisionTest: $READ_ONLY=x,y,z $INVARIANT=x>0 diff --git a/src/test/resources/dependencyAnalysisTests/all/functions.vpr b/src/test/resources/dependencyAnalysisTests/all/functions.vpr index 19fa28ced..2d70a2a3f 100644 --- a/src/test/resources/dependencyAnalysisTests/all/functions.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/functions.vpr @@ -37,7 +37,7 @@ method predicateClient(x: Ref) x.f := 10 @dependency("Rewrite") fold greater0(x) - @dependency("Implicit") + @dependency("ImplicitPostcondAssumption") a := foo(x) @testAssertion("Explicit") @@ -52,7 +52,7 @@ method predicateClientPostcond(x: Ref) x.f := 10 @dependency("Rewrite") fold greater0(x) - @dependency("Explicit") + @dependency("ExplicitPostcondAssumption") a := fooPostcond(x) @testAssertion("Explicit") @@ -64,7 +64,7 @@ method callDiv(){ var x: Int @dependency("Explicit") assume x > 10 - @dependency("Implicit") + @dependency("ImplicitPostcondAssumption") x := div100(x) @testAssertion("Explicit") @@ -75,7 +75,7 @@ method callDivPostcond(){ var x: Int @dependency("Explicit") assume x > 10 - @dependency("Explicit") + @dependency("ExplicitPostcondAssumption") x := div100Postcond(x) @testAssertion("Explicit") @@ -90,7 +90,7 @@ function fooInput(a: Int): Int method client(a: Int) { var res: Int - @dependency("Explicit") + @dependency("ExplicitPostcondAssumption") res := fooInput(a) @testAssertion("Explicit") diff --git a/src/test/resources/dependencyAnalysisTests/all/method-sum.vpr b/src/test/resources/dependencyAnalysisTests/all/method-sum.vpr index a6f02efad..8c685f1f0 100644 --- a/src/test/resources/dependencyAnalysisTests/all/method-sum.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/method-sum.vpr @@ -19,13 +19,13 @@ method sumClient(x: Int, y: Int) @dependency("Explicit") assume x < y var n: Int - @dependency("Implicit") + @dependency("ImplicitPostcondAssumption") n := sum(x, y) // the following stmt reports dependency on n := sum(x, y) because (x < y && n == x + y && n > 100 ==> y != 0) // although you could also prove it via (0 <= x && x < y ==> y != 0) var n2: Int - @dependency("Implicit") + @dependency("ImplicitPostcondAssumption") n2 := sum(x/y, y) @dependency("Implicit") n := n + n2 @@ -43,13 +43,13 @@ method sumClient2(x: Int, y: Int) @irrelevant("Explicit") assume x < y var n: Int - @irrelevant("Implicit") + @irrelevant("ImplicitPostcondAssumption") n := sum(x, x) @irrelevant() assert n >= 100 var n2: Int - @dependency("Implicit") + @dependency("ImplicitPostcondAssumption") n2 := sum(y, y) @testAssertion("Explicit") assert n2 == 2*y diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/E-function-call.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/E-function-call.vpr index 672faac24..afe869006 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/E-function-call.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/E-function-call.vpr @@ -32,7 +32,7 @@ method call2(){ @dependency("Explicit") assume y > 0 - @dependency("Implicit") + @dependency("ImplicitPostcondAssumption") z := sum(x, y) // $PrecisionTest: $READ_ONLY=x,y,z $INVARIANT=x>0 @@ -49,12 +49,12 @@ method call3(x: Int, y: Int) assume y > 0 var n: Int, n2: Int - @dependency("Implicit") + @dependency("ImplicitPostcondAssumption") n := sum(x, y) // $PrecisionTest: $READ_WRITE=n2 $READ_ONLY=x,y,n $INVARIANT=x>0 - @dependency("Implicit") + @dependency("ImplicitPostcondAssumption") n2 := sum(x, y) @dependency("Implicit") n := n + n2 @@ -75,12 +75,12 @@ method call4(x: Int, y: Int, z: Int) assume z > 0 var n: Int, n2: Int - @dependency("Explicit") + @dependency("ExplicitPostcondAssumption") n := sumUnverified(x, y) // $PrecisionTest: $READ_WRITE=n2 $READ_ONLY=n,z,x,y $INVARIANT=x>0 - @irrelevant("Explicit") + @irrelevant("ExplicitPostcondAssumption") n2 := sumUnverified(x, z) @testAssertion("Explicit") diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/F-method-call.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/F-method-call.vpr index 8a3192c00..2e0a1d524 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/F-method-call.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/F-method-call.vpr @@ -31,7 +31,7 @@ method call1(){ // $PrecisionTest: $READ_ONLY=x,y $INVARIANT=y>-5 - @testAssertion("Implicit") + @testAssertion("ImplicitPostcondAssumption") x := sum(x, y) } @@ -42,7 +42,7 @@ method call2(){ @dependency("Explicit") assume y > 0 - @dependency("Implicit") + @dependency("ImplicitPostcondAssumption") z := sum(x, y) // $PrecisionTest: $READ_ONLY=x,y,z $INVARIANT=x>0 @@ -59,12 +59,12 @@ method call3(x: Int, y: Int) assume y > 0 var n: Int, n2: Int - @dependency("Explicit") + @dependency("ExplicitPostcondAssumption") n := sumUnverified(x, y) // $PrecisionTest: $READ_WRITE=n2 $READ_ONLY=x,y,n $INVARIANT=x>0 - @dependency("Explicit") + @dependency("ExplicitPostcondAssumption") n2 := sumUnverified(x, y) @dependency("Implicit") n := n + n2 @@ -85,12 +85,12 @@ method call4(x: Int, y: Int, z: Int) assume z > 0 var n: Int, n2: Int - @dependency("Implicit") + @dependency("ImplicitPostcondAssumption") n := sum(x, y) // $PrecisionTest: $READ_WRITE=n2 $READ_ONLY=n,z,x,y $INVARIANT=x>0 - @irrelevant("Implicit") + @irrelevant("ImplicitPostcondAssumption") n2 := sum(x, z) @testAssertion("Explicit") @@ -101,7 +101,7 @@ method absClient1(x: Ref) requires @dependency("Explicit")(acc(x.f)) requires @irrelevant("Explicit")(x.f > 0) { - @dependency("Implicit") + @dependency("ImplicitPostcondAssumption") abs(x) @testAssertion("Explicit") diff --git a/src/test/scala/AssumptionAnalysisTestFramework.scala b/src/test/scala/AssumptionAnalysisTestFramework.scala index 6a1577229..63f73b087 100644 --- a/src/test/scala/AssumptionAnalysisTestFramework.scala +++ b/src/test/scala/AssumptionAnalysisTestFramework.scala @@ -206,10 +206,10 @@ trait AssumptionAnalysisTestFramework { val allTestAssumptionNodes = getTestAssumptionNodes(assumptionAnalysisInterpreter.getNonInternalAssumptionNodes) - val relevantAssumptionNodes = allTestAssumptionNodes.filter(_.sourceInfo.toString.contains("@" + dependencyKeyword + "(\"Explicit\")")) + val relevantAssumptionNodes = allTestAssumptionNodes.filter(_.sourceInfo.toString.contains("@" + dependencyKeyword + "(\"Explicit")) val resRelevant: Seq[String] = checkDependenciesAndGetErrorMsgs(relevantAssumptionNodes, dependencies, isDependencyExpected = true, "Missing explicit dependency") - val irrelevantNodes = allTestAssumptionNodes.filterNot(_.sourceInfo.toString.contains("@" + dependencyKeyword + "(\"Explicit\")")) + val irrelevantNodes = allTestAssumptionNodes.filterNot(_.sourceInfo.toString.contains("@" + dependencyKeyword + "(\"Explicit")) val resIrrelevant = checkDependenciesAndGetErrorMsgs(irrelevantNodes, dependencies, isDependencyExpected = false, "Unexpected explicit dependency") resRelevant ++ resIrrelevant From 71471e3bdeb1c83b8976010343a93cab09a7ab20 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 20 Nov 2025 10:45:25 +0100 Subject: [PATCH 262/474] renaming assumptionAnalysis -> dependencyAnalysis --- src/main/scala/Config.scala | 32 +++---- src/main/scala/decider/Decider.scala | 88 +++++++++--------- src/main/scala/decider/ProverStdIO.scala | 8 +- .../AnalysisInfo.scala | 6 +- .../AnalysisSourceInfo.scala | 6 +- .../DependencyAnalysisNode.scala} | 12 +-- .../DependencyAnalysisReporter.scala | 6 +- .../DependencyAnalysisUserTool.scala} | 28 +++--- .../DependencyAnalyzer.scala} | 92 ++++++++++--------- .../DependencyGraph.scala} | 34 +++---- .../DependencyGraphInterpreter.scala} | 66 ++++++------- .../README.md | 16 ++-- .../SourceInfoStack.scala | 10 +- .../neo4j_importer.py | 0 .../neo4j_query_saved_cypher_2025-9-17.csv | 0 src/main/scala/interfaces/Verification.scala | 4 +- .../scala/interfaces/decider/Prover.scala | 18 ++-- src/main/scala/interfaces/state/Chunks.scala | 2 +- .../NonQuantifiedPropertyInterpreter.scala | 6 +- src/main/scala/rules/Brancher.scala | 6 +- src/main/scala/rules/ChunkSupporter.scala | 4 +- src/main/scala/rules/Consumer.scala | 10 +- src/main/scala/rules/Evaluator.scala | 10 +- src/main/scala/rules/Executor.scala | 28 +++--- src/main/scala/rules/HavocSupporter.scala | 4 +- src/main/scala/rules/Joiner.scala | 8 +- src/main/scala/rules/MagicWandSupporter.scala | 10 +- .../rules/MoreCompleteExhaleSupporter.scala | 6 +- src/main/scala/rules/PredicateSupporter.scala | 4 +- src/main/scala/rules/Producer.scala | 6 +- .../scala/rules/QuantifiedChunkSupport.scala | 20 ++-- src/main/scala/rules/StateConsolidator.scala | 16 ++-- src/main/scala/state/Chunks.scala | 2 +- src/main/scala/state/State.scala | 2 +- src/main/scala/supporters/Domains.scala | 8 +- .../scala/supporters/MethodSupporter.scala | 8 +- .../PredicateVerificationUnit.scala | 4 +- .../scala/supporters/SnapshotSupporter.scala | 4 +- .../supporters/functions/FunctionData.scala | 8 +- .../functions/FunctionVerificationUnit.scala | 24 ++--- .../scala/verifier/DefaultMainVerifier.scala | 34 +++---- .../verifier/VerificationPoolManager.scala | 4 +- .../dependencyAnalysisTests/README.md | 6 +- ...ependencyAnalysisPrecisionBenchmark.scala} | 26 +++--- ... => DependencyAnalysisTestFramework.scala} | 58 ++++++------ ...ts.scala => DependencyAnalysisTests.scala} | 18 ++-- 46 files changed, 387 insertions(+), 385 deletions(-) rename src/main/scala/{assumptionAnalysis => dependencyAnalysis}/AnalysisInfo.scala (82%) rename src/main/scala/{assumptionAnalysis => dependencyAnalysis}/AnalysisSourceInfo.scala (96%) rename src/main/scala/{assumptionAnalysis/AssumptionAnalysisNode.scala => dependencyAnalysis/DependencyAnalysisNode.scala} (90%) rename src/main/scala/{assumptionAnalysis => dependencyAnalysis}/DependencyAnalysisReporter.scala (50%) rename src/main/scala/{assumptionAnalysis/AssumptionAnalysisUserTool.scala => dependencyAnalysis/DependencyAnalysisUserTool.scala} (88%) rename src/main/scala/{assumptionAnalysis/AssumptionAnalyzer.scala => dependencyAnalysis/DependencyAnalyzer.scala} (81%) rename src/main/scala/{assumptionAnalysis/AssumptionAnalysisGraph.scala => dependencyAnalysis/DependencyGraph.scala} (86%) rename src/main/scala/{assumptionAnalysis/AssumptionAnalysisInterpreter.scala => dependencyAnalysis/DependencyGraphInterpreter.scala} (75%) rename src/main/scala/{assumptionAnalysis => dependencyAnalysis}/README.md (86%) rename src/main/scala/{assumptionAnalysis => dependencyAnalysis}/SourceInfoStack.scala (91%) rename src/main/scala/{assumptionAnalysis => dependencyAnalysis}/neo4j_importer.py (100%) rename src/main/scala/{assumptionAnalysis => dependencyAnalysis}/neo4j_query_saved_cypher_2025-9-17.csv (100%) rename src/test/scala/{AssumptionAnalysisPrecisionBenchmark.scala => DependencyAnalysisPrecisionBenchmark.scala} (78%) rename src/test/scala/{AssumptionAnalysisTestFramework.scala => DependencyAnalysisTestFramework.scala} (75%) rename src/test/scala/{AssumptionAnalysisTests.scala => DependencyAnalysisTests.scala} (85%) diff --git a/src/main/scala/Config.scala b/src/main/scala/Config.scala index 725adfc16..0fd581e9e 100644 --- a/src/main/scala/Config.scala +++ b/src/main/scala/Config.scala @@ -830,14 +830,14 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { noshort = true ) - val enableAssumptionAnalysis: ScallopOption[Boolean] = opt[Boolean]("enableAssumptionAnalysis", - descr = "Enable assumption analysis mode", + val enableDependencyAnalysis: ScallopOption[Boolean] = opt[Boolean]("enableDependencyAnalysis", + descr = "Enable dependency analysis mode", default = Some(false), noshort = true ) - val enableAssumptionAnalysisDebugging: ScallopOption[Boolean] = opt[Boolean]("enableAssumptionAnalysisDebugging", - descr = "Enable debugging for assumption analysis mode", + val enableDependencyAnalysisDebugging: ScallopOption[Boolean] = opt[Boolean]("enableDependencyAnalysisDebugging", + descr = "Enable debugging for dependency analysis mode", default = Some(false), noshort = true ) @@ -848,14 +848,14 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { noshort = true ) - val assumptionAnalysisExportPath: ScallopOption[String] = opt[String]("assumptionAnalysisExportPath", - descr = "Path to the directory where the assumption analysis graphs should be exported to", + val dependencyAnalysisExportPath: ScallopOption[String] = opt[String]("dependencyAnalysisExportPath", + descr = "Path to the directory where the dependency analysis graphs should be exported to", default = None, noshort = true ) - val startAssumptionAnalysisTool: ScallopOption[Boolean] = opt[Boolean]("startAssumptionAnalysisTool", - descr = "Starts the assumption analysis command line tool after verification", + val startDependencyAnalysisTool: ScallopOption[Boolean] = opt[Boolean]("startDependencyAnalysisTool", + descr = "Starts the dependency analysis command line tool after verification", default = Some(false), noshort = true ) @@ -907,34 +907,34 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { validateFileOpt(multisetAxiomatizationFile) validateFileOpt(sequenceAxiomatizationFile) - validateOpt(enableAssumptionAnalysis, parallelizeBranches) { + validateOpt(enableDependencyAnalysis, parallelizeBranches) { case (Some(false), _) => Right(()) case (_, Some(false)) => Right(()) case (Some(true), Some(true)) => - Left(s"Option ${enableAssumptionAnalysis.name} is not supported in combination with ${parallelizeBranches.name}") + Left(s"Option ${enableDependencyAnalysis.name} is not supported in combination with ${parallelizeBranches.name}") case other => sys.error(s"Unexpected combination: $other") } - validateOpt(rawProverArgs, enableAssumptionAnalysis) { + validateOpt(rawProverArgs, enableDependencyAnalysis) { case (_, Some(false)) => Right(()) case (Some(args), Some(true)) if args.contains("proof=true") && args.contains("unsat-core=true") => Right(()) case (_, _) => - Left(s"Option ${enableAssumptionAnalysis.name} requires ${rawProverArgs.name} with \"proof=true unsat-core=true\"") + Left(s"Option ${enableDependencyAnalysis.name} requires ${rawProverArgs.name} with \"proof=true unsat-core=true\"") } - validateOpt(assumptionAnalysisExportPath, enableAssumptionAnalysis) { + validateOpt(dependencyAnalysisExportPath, enableDependencyAnalysis) { case (None, _) => Right(()) case (Some(_), Some(true)) => Right(()) case (Some(_), Some(false)) => - Left(s"Option ${assumptionAnalysisExportPath.name} requires option ${enableAssumptionAnalysis.name}") + Left(s"Option ${dependencyAnalysisExportPath.name} requires option ${enableDependencyAnalysis.name}") } - validateOpt(startAssumptionAnalysisTool, enableAssumptionAnalysis) { + validateOpt(startDependencyAnalysisTool, enableDependencyAnalysis) { case (Some(false), _) => Right(()) case (_, Some(true)) => Right(()) case (_, _) => - Left(s"Option ${startAssumptionAnalysisTool.name} requires option ${enableAssumptionAnalysis.name}") + Left(s"Option ${startDependencyAnalysisTool.name} requires option ${enableDependencyAnalysis.name}") } validateOpt(startDebuggerAutomatically, enableDebugging) { diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 042ee3c04..6b85f4b09 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -8,8 +8,8 @@ package viper.silicon.decider import com.typesafe.scalalogging.Logger import viper.silicon._ -import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType -import viper.silicon.assumptionAnalysis._ +import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType +import viper.silicon.dependencyAnalysis._ import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.interfaces._ @@ -63,7 +63,7 @@ trait Decider { def registerChunk[CH <: GeneralChunk](buildChunk: Term => CH, perm: Term, analysisInfo: AnalysisInfo, isExhale: Boolean): CH def registerDerivedChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, analysisInfo: AnalysisInfo, isExhale: Boolean, createLabel: Boolean=true): CH - def wrapWithAssumptionAnalysisLabel(term: Term, sourceChunks: Iterable[Chunk] = Set.empty, sourceTerms: Iterable[Term] = Set.empty): Term + def wrapWithDependencyAnalysisLabel(term: Term, sourceChunks: Iterable[Chunk] = Set.empty, sourceTerms: Iterable[Term] = Set.empty): Term def isPathInfeasible(): Boolean def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp], assumptionType: AssumptionType): Unit @@ -110,10 +110,10 @@ trait Decider { def statistics(): Map[String, String] - var assumptionAnalyzer: AssumptionAnalyzer + var dependencyAnalyzer: DependencyAnalyzer var analysisSourceInfoStack: AnalysisSourceInfoStack - def initAssumptionAnalyzer(member: Member, preambleNodes: Iterable[AssumptionAnalysisNode]): Unit - def removeAssumptionAnalyzer(): Unit + def initDependencyAnalyzer(member: Member, preambleNodes: Iterable[DependencyAnalysisNode]): Unit + def removeDependencyAnalyzer(): Unit def getAnalysisInfo: AnalysisInfo def getAnalysisInfo(assumptionType: AssumptionType): AnalysisInfo } @@ -145,28 +145,28 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => private var _proverResetOptions: Map[String, String] = Map.empty private val _debuggerAssumedTerms: mutable.Set[Term] = mutable.Set.empty - var assumptionAnalyzer: AssumptionAnalyzer = new NoAssumptionAnalyzer() + var dependencyAnalyzer: DependencyAnalyzer = new NoDependencyAnalyzer() var analysisSourceInfoStack: AnalysisSourceInfoStack = AnalysisSourceInfoStack() - override def initAssumptionAnalyzer(member: Member, preambleNodes: Iterable[AssumptionAnalysisNode]): Unit = { - val isAnalysisEnabled = AssumptionAnalyzer.extractEnableAnalysisFromInfo(member.info).getOrElse(Verifier.config.enableAssumptionAnalysis()) + override def initDependencyAnalyzer(member: Member, preambleNodes: Iterable[DependencyAnalysisNode]): Unit = { + val isAnalysisEnabled = DependencyAnalyzer.extractEnableAnalysisFromInfo(member.info).getOrElse(Verifier.config.enableDependencyAnalysis()) if(isAnalysisEnabled) { - assumptionAnalyzer = new DefaultAssumptionAnalyzer(member) - assumptionAnalyzer.addNodes(preambleNodes) + dependencyAnalyzer = new DefaultDependencyAnalyzer(member) + dependencyAnalyzer.addNodes(preambleNodes) }else{ - removeAssumptionAnalyzer() + removeDependencyAnalyzer() } analysisSourceInfoStack = AnalysisSourceInfoStack() } - override def removeAssumptionAnalyzer(): Unit = { - assumptionAnalyzer = new NoAssumptionAnalyzer + override def removeDependencyAnalyzer(): Unit = { + dependencyAnalyzer = new NoDependencyAnalyzer analysisSourceInfoStack = AnalysisSourceInfoStack() } def getAnalysisInfo: AnalysisInfo = getAnalysisInfo(AssumptionType.Implicit) - def getAnalysisInfo(assumptionType: AssumptionType): AnalysisInfo = AnalysisInfo(this, assumptionAnalyzer, analysisSourceInfoStack.getFullSourceInfo, assumptionType) + def getAnalysisInfo(assumptionType: AssumptionType): AnalysisInfo = AnalysisInfo(this, dependencyAnalyzer, analysisSourceInfoStack.getFullSourceInfo, assumptionType) def functionDecls: Set[FunctionDecl] = _declaredFreshFunctions def macroDecls: Vector[MacroDecl] = _declaredFreshMacros @@ -313,34 +313,34 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } def registerDerivedChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, analysisInfo: AnalysisInfo, isExhale: Boolean, createLabel: Boolean=true): CH = { - if(!Verifier.config.enableAssumptionAnalysis()) + if(!Verifier.config.enableDependencyAnalysis()) return buildChunk(perm) if(isExhale) - assumptionAnalyzer.registerExhaleChunk(sourceChunks, buildChunk, perm, analysisInfo) + dependencyAnalyzer.registerExhaleChunk(sourceChunks, buildChunk, perm, analysisInfo) else { val labelNodeOpt = if(createLabel) getOrCreateAnalysisLabelNode() else getOrCreateAnalysisLabelNode(sourceChunks) - assumptionAnalyzer.registerInhaleChunk(sourceChunks, buildChunk, perm, labelNodeOpt, analysisInfo, createLabel) + dependencyAnalyzer.registerInhaleChunk(sourceChunks, buildChunk, perm, labelNodeOpt, analysisInfo, createLabel) } } private def getOrCreateAnalysisLabelNode(sourceChunks: Iterable[Chunk] = Set.empty, sourceTerms: Iterable[Term] = Set.empty): Option[LabelNode] = { - if(!Verifier.config.enableAssumptionAnalysis()) + if(!Verifier.config.enableDependencyAnalysis()) return None if(sourceChunks.size == 1 && sourceTerms.isEmpty){ - val chunkInhaleNode = assumptionAnalyzer.getChunkInhaleNode(sourceChunks.head) + val chunkInhaleNode = dependencyAnalyzer.getChunkInhaleNode(sourceChunks.head) return chunkInhaleNode.map(_.labelNode) } - val (label, _) = fresh(ast.LocalVar(AssumptionAnalyzer.analysisLabelName, ast.Bool)()) - val labelNode = assumptionAnalyzer.createLabelNode(label, sourceChunks, sourceTerms) - val smtLabel = AssumptionAnalyzer.createAssumptionLabel(labelNode.map(_.id)) + val (label, _) = fresh(ast.LocalVar(DependencyAnalyzer.analysisLabelName, ast.Bool)()) + val labelNode = dependencyAnalyzer.createLabelNode(label, sourceChunks, sourceTerms) + val smtLabel = DependencyAnalyzer.createAssumptionLabel(labelNode.map(_.id)) assumeLabel(label, smtLabel) labelNode } - def wrapWithAssumptionAnalysisLabel(term: Term, sourceChunks: Iterable[Chunk] = Set.empty, sourceTerms: Iterable[Term] = Set.empty): Term = { - if(!Verifier.config.enableAssumptionAnalysis() || term.equals(True) || sourceChunks.size + sourceTerms.size == 0) + def wrapWithDependencyAnalysisLabel(term: Term, sourceChunks: Iterable[Chunk] = Set.empty, sourceTerms: Iterable[Term] = Set.empty): Term = { + if(!Verifier.config.enableDependencyAnalysis() || term.equals(True) || sourceChunks.size + sourceTerms.size == 0) return term val labelNode = getOrCreateAnalysisLabelNode(sourceChunks, sourceTerms) @@ -382,8 +382,8 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } val filteredAssumptionsWithLabels = filteredAssumptions map{case (t, _) => - val assumptionId: Option[Int] = assumptionAnalyzer.addAssumption(t, analysisSourceInfo, assumptionType) - (t, AssumptionAnalyzer.createAssumptionLabel(assumptionId)) + val assumptionId: Option[Int] = dependencyAnalyzer.addAssumption(t, analysisSourceInfo, assumptionType) + (t, DependencyAnalyzer.createAssumptionLabel(assumptionId)) } if (filteredAssumptions.nonEmpty) assumeWithoutSmokeChecks(filteredAssumptionsWithLabels, isDefinition=isDefinition) @@ -418,8 +418,8 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => private def addAssumptionLabels(filteredTerms: Iterable[Term], assumptionType: AssumptionType) = { filteredTerms map (t => { - val assumptionIds = assumptionAnalyzer.addAssumption(t, analysisSourceInfoStack.getFullSourceInfo, assumptionType) - (t, AssumptionAnalyzer.createAssumptionLabel(assumptionIds)) + val assumptionIds = dependencyAnalyzer.addAssumption(t, analysisSourceInfoStack.getFullSourceInfo, assumptionType) + (t, DependencyAnalyzer.createAssumptionLabel(assumptionIds)) }) } @@ -476,20 +476,20 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => /* Asserting facts */ def checkSmoke(isAssert: Boolean = false, assumptionType: AssumptionType=AssumptionType.Implicit): Boolean = { - val (label, checkNodeId) = if(Verifier.config.enableAssumptionAnalysis()){ - val nodeId = assumptionAnalyzer.addAssertFalseNode(!isAssert, assumptionType, analysisSourceInfoStack.getFullSourceInfo) // TODO ake: add node only if it can be verified - (AssumptionAnalyzer.createAssertionLabel(nodeId), nodeId) + val (label, checkNodeId) = if(Verifier.config.enableDependencyAnalysis()){ + val nodeId = dependencyAnalyzer.addAssertFalseNode(!isAssert, assumptionType, analysisSourceInfoStack.getFullSourceInfo) // TODO ake: add node only if it can be verified + (DependencyAnalyzer.createAssertionLabel(nodeId), nodeId) }else{ ("", None) } val timeout = if (isAssert) Verifier.config.assertTimeout.toOption else Verifier.config.checkTimeout.toOption val result = isPathInfeasible() || prover.check(timeout, label) == Unsat if(result) { if(pcs.getCurrentInfeasibilityNode.isDefined){ - assumptionAnalyzer.addDependency(pcs.getCurrentInfeasibilityNode, checkNodeId) + dependencyAnalyzer.addDependency(pcs.getCurrentInfeasibilityNode, checkNodeId) }else { - assumptionAnalyzer.processUnsatCoreAndAddDependencies(prover.getLastUnsatCore, label) - val infeasibleNodeId = assumptionAnalyzer.addInfeasibilityNode(!isAssert, analysisSourceInfoStack.getFullSourceInfo) - assumptionAnalyzer.addDependency(checkNodeId, infeasibleNodeId) + dependencyAnalyzer.processUnsatCoreAndAddDependencies(prover.getLastUnsatCore, label) + val infeasibleNodeId = dependencyAnalyzer.addInfeasibilityNode(!isAssert, analysisSourceInfoStack.getFullSourceInfo) + dependencyAnalyzer.addDependency(checkNodeId, infeasibleNodeId) pcs.setCurrentInfeasibilityNode(checkNodeId) } } @@ -507,8 +507,8 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } val (success, checkNode) = deciderAssert(t, assumptionType, Some(timeout), isCheck=true) if(success){ - infeasibilityNodeId = assumptionAnalyzer.addInfeasibilityNode(isCheck = true, analysisSourceInfoStack.getFullSourceInfo) - assumptionAnalyzer.addDependency(checkNode.map(_.id), infeasibilityNodeId) + infeasibilityNodeId = dependencyAnalyzer.addInfeasibilityNode(isCheck = true, analysisSourceInfoStack.getFullSourceInfo) + dependencyAnalyzer.addDependency(checkNode.map(_.id), infeasibilityNodeId) } (success, infeasibilityNodeId) } @@ -532,13 +532,13 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => val assertRecord = new DeciderAssertRecord(t, timeout) val sepIdentifier = symbExLog.openScope(assertRecord) - val asserted = if(Verifier.config.enableAssumptionAnalysis()) t.equals(True) else isKnownToBeTrue(t) + val asserted = if(Verifier.config.enableDependencyAnalysis()) t.equals(True) else isKnownToBeTrue(t) - val assertNode = if(!asserted) assumptionAnalyzer.createAssertOrCheckNode(t, assumptionType, decider.analysisSourceInfoStack.getFullSourceInfo, isCheck) else None + val assertNode = if(!asserted) dependencyAnalyzer.createAssertOrCheckNode(t, assumptionType, decider.analysisSourceInfoStack.getFullSourceInfo, isCheck) else None - val result = asserted || proverAssert(t, timeout, AssumptionAnalyzer.createAssertionLabel(assertNode map (_.id))) + val result = asserted || proverAssert(t, timeout, DependencyAnalyzer.createAssertionLabel(assertNode map (_.id))) - if(result) assertNode foreach assumptionAnalyzer.addNode + if(result) assertNode foreach dependencyAnalyzer.addNode symbExLog.closeScope(sepIdentifier) (result, assertNode) @@ -560,9 +560,9 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => if(result) if(pcs.getCurrentInfeasibilityNode.isDefined) { // TODO ake: should be checked before calling prover.assert - assumptionAnalyzer.addDependency(pcs.getCurrentInfeasibilityNode, Some(AssumptionAnalyzer.getIdFromLabel(label))) + dependencyAnalyzer.addDependency(pcs.getCurrentInfeasibilityNode, Some(DependencyAnalyzer.getIdFromLabel(label))) }else{ - assumptionAnalyzer.processUnsatCoreAndAddDependencies(prover.getLastUnsatCore, label) + dependencyAnalyzer.processUnsatCoreAndAddDependencies(prover.getLastUnsatCore, label) } symbExLog.whenEnabled { diff --git a/src/main/scala/decider/ProverStdIO.scala b/src/main/scala/decider/ProverStdIO.scala index e0d8707a0..14b72324a 100644 --- a/src/main/scala/decider/ProverStdIO.scala +++ b/src/main/scala/decider/ProverStdIO.scala @@ -251,7 +251,7 @@ abstract class ProverStdIO(uniqueId: String, def assume(term: String, label: String): Unit = { // bookkeeper.assumptionCounter += 1 - if(Verifier.config.enableAssumptionAnalysis() && label.nonEmpty){ + if(Verifier.config.enableDependencyAnalysis() && label.nonEmpty){ writeLine("(assert (! " + term + " :named " + label + "))") }else{ writeLine("(assert " + term + ")") @@ -281,7 +281,7 @@ abstract class ProverStdIO(uniqueId: String, push() setTimeout(timeout) - if(Verifier.config.enableAssumptionAnalysis() && label.nonEmpty){ + if(Verifier.config.enableDependencyAnalysis() && label.nonEmpty){ writeLine("(assert (! (not " + goal + ") :named " + label + "))") }else{ writeLine("(assert (not " + goal + "))") @@ -297,7 +297,7 @@ abstract class ProverStdIO(uniqueId: String, if (!result) { retrieveAndSaveModel() retrieveReasonUnknown() - }else if(Verifier.config.enableAssumptionAnalysis()){ + }else if(Verifier.config.enableDependencyAnalysis()){ lastUnsatCore_ = extractUnsatCore() } @@ -391,7 +391,7 @@ abstract class ProverStdIO(uniqueId: String, case "unknown" => Unknown } - if(result == Unsat && Verifier.config.enableAssumptionAnalysis()) + if(result == Unsat && Verifier.config.enableDependencyAnalysis()) lastUnsatCore_ = extractUnsatCore() result diff --git a/src/main/scala/assumptionAnalysis/AnalysisInfo.scala b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala similarity index 82% rename from src/main/scala/assumptionAnalysis/AnalysisInfo.scala rename to src/main/scala/dependencyAnalysis/AnalysisInfo.scala index cc9571956..2e2d8fd31 100644 --- a/src/main/scala/assumptionAnalysis/AnalysisInfo.scala +++ b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala @@ -1,4 +1,4 @@ -package viper.silicon.assumptionAnalysis +package viper.silicon.dependencyAnalysis object AssumptionType extends Enumeration { type AssumptionType = Value @@ -12,11 +12,11 @@ object AssumptionType extends Enumeration { def internalTypes: Set[AssumptionType] = Set(Internal) // will always be hidden from user } -import viper.silicon.assumptionAnalysis.AssumptionType._ +import viper.silicon.dependencyAnalysis.AssumptionType._ import viper.silicon.decider.Decider -case class AnalysisInfo(decider: Decider, assumptionAnalyzer: AssumptionAnalyzer, sourceInfo: AnalysisSourceInfo, +case class AnalysisInfo(decider: Decider, dependencyAnalyzer: DependencyAnalyzer, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType) { def withAssumptionType(newAssumptionType: AssumptionType): AnalysisInfo = { copy(assumptionType=newAssumptionType) diff --git a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala b/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala similarity index 96% rename from src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala rename to src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala index f92654ab4..ee1b01cc6 100644 --- a/src/main/scala/assumptionAnalysis/AnalysisSourceInfo.scala +++ b/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala @@ -1,4 +1,4 @@ -package viper.silicon.assumptionAnalysis +package viper.silicon.dependencyAnalysis import viper.silver.ast import viper.silver.ast._ @@ -64,7 +64,7 @@ case class ExpAnalysisSourceInfo(source: ast.Exp) extends AnalysisSourceInfo { } } - override def isAnalysisEnabled: Boolean = AssumptionAnalyzer.extractEnableAnalysisFromInfo(source.info).getOrElse(true) + override def isAnalysisEnabled: Boolean = DependencyAnalyzer.extractEnableAnalysisFromInfo(source.info).getOrElse(true) } case class StmtAnalysisSourceInfo(source: ast.Stmt) extends AnalysisSourceInfo { @@ -80,7 +80,7 @@ case class StmtAnalysisSourceInfo(source: ast.Stmt) extends AnalysisSourceInfo { } } - override def isAnalysisEnabled: Boolean = AssumptionAnalyzer.extractEnableAnalysisFromInfo(source.info).getOrElse(true) + override def isAnalysisEnabled: Boolean = DependencyAnalyzer.extractEnableAnalysisFromInfo(source.info).getOrElse(true) } case class StringAnalysisSourceInfo(description: String, position: Position) extends AnalysisSourceInfo { diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisNode.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala similarity index 90% rename from src/main/scala/assumptionAnalysis/AssumptionAnalysisNode.scala rename to src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala index c3bdfff04..496fca74f 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisNode.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala @@ -1,11 +1,11 @@ -package viper.silicon.assumptionAnalysis +package viper.silicon.dependencyAnalysis -import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType +import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType import viper.silicon.interfaces.state.Chunk import viper.silicon.state.terms.{False, Term, Var} -trait AssumptionAnalysisNode { - val id: Int = AssumptionAnalysisGraphHelper.nextId() +trait DependencyAnalysisNode { + val id: Int = DependencyGraphHelper.nextId() val sourceInfo: AnalysisSourceInfo val assumptionType: AssumptionType val isClosed: Boolean @@ -21,10 +21,10 @@ trait AssumptionAnalysisNode { toString.hashCode } -trait GeneralAssumptionNode extends AssumptionAnalysisNode { +trait GeneralAssumptionNode extends DependencyAnalysisNode { override def getNodeType: String = "Assumption" } -trait GeneralAssertionNode extends AssumptionAnalysisNode { +trait GeneralAssertionNode extends DependencyAnalysisNode { override def getNodeType: String = "Assertion" } diff --git a/src/main/scala/assumptionAnalysis/DependencyAnalysisReporter.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisReporter.scala similarity index 50% rename from src/main/scala/assumptionAnalysis/DependencyAnalysisReporter.scala rename to src/main/scala/dependencyAnalysis/DependencyAnalysisReporter.scala index c10a7b549..30660f68f 100644 --- a/src/main/scala/assumptionAnalysis/DependencyAnalysisReporter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisReporter.scala @@ -1,10 +1,10 @@ -package viper.silicon.assumptionAnalysis +package viper.silicon.dependencyAnalysis import viper.silver.reporter.{Message, Reporter} case class DependencyAnalysisReporter(name: String = "dependencyAnalysis_reporter", path: String = "report.csv") extends Reporter { - var assumptionAnalysisInterpretersPerMember: List[AssumptionAnalysisInterpreter] = List.empty - var joinedAssumptionAnalysisInterpreter: Option[AssumptionAnalysisInterpreter] = None + var dependencyGraphInterpretersPerMember: List[DependencyGraphInterpreter] = List.empty + var joinedDependencyGraphInterpreter: Option[DependencyGraphInterpreter] = None override def report(msg: Message): Unit = {} } diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala similarity index 88% rename from src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala rename to src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala index 443f51ea3..18cb65320 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisUserTool.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala @@ -1,6 +1,6 @@ -package silicon.viper.assumptionAnalysis +package viper.silicon.dependencyAnalysis -import viper.silicon.assumptionAnalysis.{AssumptionAnalysisInterpreter, AssumptionAnalysisNode, AxiomAssumptionNode} +import viper.silicon.dependencyAnalysis.{DependencyGraphInterpreter, DependencyAnalysisNode, AxiomAssumptionNode} import viper.silver.ast import viper.silver.ast.Method @@ -8,7 +8,7 @@ import java.io.PrintWriter import scala.annotation.tailrec import scala.io.StdIn.readLine -class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpreter, memberInterpreters: Seq[AssumptionAnalysisInterpreter], +class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterpreter, memberInterpreters: Seq[DependencyGraphInterpreter], program: ast.Program) { private val infoString = "Enter " + "\n\t'dep [line numbers]' to print all dependencies of the given line numbers or" + @@ -20,7 +20,7 @@ class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpr "\n\t'q' to quit" def run(): Unit = { - println("Assumption Analysis Tool started.") + println("Dependency Analysis Tool started.") println(infoString) runInternal() } @@ -73,7 +73,7 @@ class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpr } } - private def handleGraphSizeQuery(interpreter: AssumptionAnalysisInterpreter): Unit = { + private def handleGraphSizeQuery(interpreter: DependencyGraphInterpreter): Unit = { val allAssumptions = interpreter.getNonInternalAssumptionNodes.filter(n => !n.isInstanceOf[AxiomAssumptionNode]) val assumptions = allAssumptions.groupBy(_.sourceInfo.getTopLevelSource.toString) val assertions = interpreter.getNonInternalAssertionNodesPerSource @@ -128,7 +128,7 @@ class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpr println("Done.") } - private def getSourceInfoString(nodes: Set[AssumptionAnalysisNode]) = { + private def getSourceInfoString(nodes: Set[DependencyAnalysisNode]) = { nodes.groupBy(node => node.sourceInfo.getTopLevelSource.toString).map{case (_, nodes) => nodes.head.sourceInfo.getTopLevelSource}.toList.sortBy(_.getLineNumber).mkString("\n\t") } @@ -148,10 +148,10 @@ class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpr private def handleDependencyQuery(inputs: Set[String]): Unit = { val queriedNodes = getQueriedNodesFromInput(inputs) - val (directDependencies, timeDirect) = measureTime[Set[AssumptionAnalysisNode]](fullGraphInterpreter.getDirectDependencies(queriedNodes.map(_.id))) - val (allDependencies, timeAll) = measureTime[Set[AssumptionAnalysisNode]](fullGraphInterpreter.getAllNonInternalDependencies(queriedNodes.map(_.id))) - val (allDependenciesWithoutInfeasibility, timeWithoutInfeasibility) = measureTime[Set[AssumptionAnalysisNode]](fullGraphInterpreter.getAllNonInternalDependencies(queriedNodes.map(_.id), includeInfeasibilityNodes=false)) - val (explicitDependencies, timeExplicit) = measureTime[Set[AssumptionAnalysisNode]](fullGraphInterpreter.getAllExplicitDependencies(queriedNodes.map(_.id))) + val (directDependencies, timeDirect) = measureTime[Set[DependencyAnalysisNode]](fullGraphInterpreter.getDirectDependencies(queriedNodes.map(_.id))) + val (allDependencies, timeAll) = measureTime[Set[DependencyAnalysisNode]](fullGraphInterpreter.getAllNonInternalDependencies(queriedNodes.map(_.id))) + val (allDependenciesWithoutInfeasibility, timeWithoutInfeasibility) = measureTime[Set[DependencyAnalysisNode]](fullGraphInterpreter.getAllNonInternalDependencies(queriedNodes.map(_.id), includeInfeasibilityNodes=false)) + val (explicitDependencies, timeExplicit) = measureTime[Set[DependencyAnalysisNode]](fullGraphInterpreter.getAllExplicitDependencies(queriedNodes.map(_.id))) println(s"Queried:\n\t${getSourceInfoString(queriedNodes)}") @@ -166,9 +166,9 @@ class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpr val queriedNodes = getQueriedNodesFromInput(inputs).intersect(fullGraphInterpreter.getNonInternalAssumptionNodes) - val (allDependents, timeAll) = measureTime[Set[AssumptionAnalysisNode]](fullGraphInterpreter.getAllNonInternalDependents(queriedNodes.map(_.id))) - val (dependentsWithoutInfeasibility, timeWithoutInfeasibility) = measureTime[Set[AssumptionAnalysisNode]](fullGraphInterpreter.getAllNonInternalDependents(queriedNodes.map(_.id), includeInfeasibilityNodes=false)) - val (explicitDependents, timeExplicit) = measureTime[Set[AssumptionAnalysisNode]](fullGraphInterpreter.getAllExplicitDependents(queriedNodes.map(_.id))) + val (allDependents, timeAll) = measureTime[Set[DependencyAnalysisNode]](fullGraphInterpreter.getAllNonInternalDependents(queriedNodes.map(_.id))) + val (dependentsWithoutInfeasibility, timeWithoutInfeasibility) = measureTime[Set[DependencyAnalysisNode]](fullGraphInterpreter.getAllNonInternalDependents(queriedNodes.map(_.id), includeInfeasibilityNodes=false)) + val (explicitDependents, timeExplicit) = measureTime[Set[DependencyAnalysisNode]](fullGraphInterpreter.getAllExplicitDependents(queriedNodes.map(_.id))) println(s"Queried:\n\t${getSourceInfoString(queriedNodes)}") @@ -232,7 +232,7 @@ class AssumptionAnalysisUserTool(fullGraphInterpreter: AssumptionAnalysisInterpr var numLowLevelDeps = 0 for (_ <- 0 to N) { - val (allDependencies, time) = measureTime[Set[AssumptionAnalysisNode]](fullGraphInterpreter.getAllNonInternalDependencies(queriedNodes.map(_.id))) + val (allDependencies, time) = measureTime[Set[DependencyAnalysisNode]](fullGraphInterpreter.getAllNonInternalDependencies(queriedNodes.map(_.id))) allTimes = allTimes :+ time numLowLevelDeps = allDependencies.size numDeps = allDependencies.groupBy(node => node.sourceInfo.getTopLevelSource.toString).size diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala similarity index 81% rename from src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala rename to src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index 5cf37a494..09d04d923 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -1,6 +1,6 @@ -package viper.silicon.assumptionAnalysis +package viper.silicon.dependencyAnalysis -import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType +import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType import viper.silicon.interfaces.state.{Chunk, GeneralChunk} import viper.silicon.state.terms._ import viper.silicon.verifier.Verifier @@ -9,8 +9,8 @@ import viper.silver.ast import scala.collection.mutable -trait AssumptionAnalyzer { - protected val assumptionGraph: AssumptionAnalysisGraph = new AssumptionAnalysisGraph() +trait DependencyAnalyzer { + protected val assumptionGraph: DependencyGraph = new DependencyGraph() protected var isClosed_ = false def disableTransitiveEdges(): Unit = { @@ -23,11 +23,11 @@ trait AssumptionAnalyzer { def getMember: Option[ast.Member] - def getNodes: Iterable[AssumptionAnalysisNode] + def getNodes: Iterable[DependencyAnalysisNode] def getChunkInhaleNode(chunk: Chunk): Option[PermissionInhaleNode] - def addNodes(nodes: Iterable[AssumptionAnalysisNode]): Unit - def addNode(node: AssumptionAnalysisNode): Unit + def addNodes(nodes: Iterable[DependencyAnalysisNode]): Unit + def addNode(node: DependencyAnalysisNode): Unit def addAssumption(assumption: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, description: Option[String] = None): Option[Int] def addAxiom(assumption: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, description: Option[String] = None): Option[Int] def registerInhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, labelNode: Option[LabelNode], analysisInfo: AnalysisInfo, isExhale: Boolean): CH = buildChunk(perm) @@ -47,13 +47,13 @@ trait AssumptionAnalyzer { def addInfeasibilityDepToStmt(infeasNodeId: Option[Int], analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Unit = {} - def buildFinalGraph(): Option[AssumptionAnalysisGraph] + def buildFinalGraph(): Option[DependencyGraph] } -object AssumptionAnalyzer { +object DependencyAnalyzer { val analysisLabelName: String = "$$analysisLabel$$" private val assumptionTypeAnnotationKey = "assumptionType" - private val enableAssumptionAnalysisAnnotationKey = "enableAssumptionAnalysis" + private val enableDependencyAnalysisAnnotationKey = "enableDependencyAnalysis" private def extractAnnotationFromInfo(info: ast.Info, annotationKey: String): Option[Seq[String]] = { info.getAllInfos[ast.AnnotationInfo] @@ -67,7 +67,7 @@ object AssumptionAnalyzer { } def extractEnableAnalysisFromInfo(info: ast.Info): Option[Boolean] = { - val annotation = extractAnnotationFromInfo(info, enableAssumptionAnalysisAnnotationKey) + val annotation = extractAnnotationFromInfo(info, enableDependencyAnalysisAnnotationKey) if(annotation.isDefined && annotation.get.nonEmpty) annotation.get.head.toBooleanOption else None } @@ -98,46 +98,48 @@ object AssumptionAnalyzer { def isAxiomLabel(label: String): Boolean = label.startsWith("axiom_") - def joinGraphsAndGetInterpreter(name: Option[String], assumptionAnalysisInterpreters: Set[AssumptionAnalysisInterpreter]): AssumptionAnalysisInterpreter = { - val newGraph = new AssumptionAnalysisGraph + def joinGraphsAndGetInterpreter(name: Option[String], dependencyGraphInterpreters: Set[DependencyGraphInterpreter]): DependencyGraphInterpreter = { + val newGraph = new DependencyGraph - assumptionAnalysisInterpreters foreach (interpreter => newGraph.addNodes(interpreter.getGraph.getNodes)) - assumptionAnalysisInterpreters foreach (interpreter => interpreter.getGraph.getAllEdges foreach {case (t, deps) => newGraph.addEdges(deps, t)}) + dependencyGraphInterpreters foreach (interpreter => newGraph.addNodes(interpreter.getGraph.getNodes)) + dependencyGraphInterpreters foreach (interpreter => interpreter.getGraph.getAllEdges foreach {case (t, deps) => newGraph.addEdges(deps, t)}) // add edges for axioms since they were added to each interpreter - newGraph.getNodes.filter(_.isInstanceOf[AxiomAssumptionNode]).groupBy(n => (n.sourceInfo.toString, n.assumptionType)).foreach{case (_, nodes) => - newGraph.addEdges(nodes.map(_.id), nodes.map(_.id)) // TODO ake: is this necessary; maybe nodes could be merged instead? - // add edges to assertions that were required to prove the axiom, e.g. to verify the function body/postcondition - val assertionNodes = newGraph.getNodes.filter(n => n.isInstanceOf[GeneralAssertionNode] && n.sourceInfo.getTopLevelSource.equals(nodes.head.sourceInfo.getTopLevelSource)) - newGraph.addEdges( assertionNodes.map(_.id), nodes.map(_.id)) + // in particular, add edges to assertions that were required to introduce the axiom, e.g. to verify the function body/postcondition + val allAssertionNodes = newGraph.getNodes.filter(n => n.isInstanceOf[GeneralAssertionNode]) + newGraph.getNodes.filter(_.isInstanceOf[AxiomAssumptionNode]) + .groupBy(n => (n.sourceInfo.toString, n.assumptionType)) + .map{case (_, nodes) => (nodes.map(_.id), allAssertionNodes.filter(_.sourceInfo.getTopLevelSource.equals(nodes.head.sourceInfo.getTopLevelSource)).map(_.id))} + .foreach{case (nodeIds, assertionNodeIds) => + newGraph.addEdges(nodeIds, nodeIds) // TODO ake: is this necessary; maybe nodes could be merged instead? + newGraph.addEdges(assertionNodeIds, nodeIds) } // get all nodes that represent the assumption of a postcondition introduced by a method call or function application // for a fast lookup, these nodes are stored as a map from string to a set of integers. The string represents the assumed postcondition (which is the join condition) and // the integers correspond to all nodes associated with said postcondition. val types = Set(AssumptionType.ImplicitPostcondAssumption, AssumptionType.ExplicitPostcondAssumption) - val relevantAssumptionNodes = newGraph.nodes.filter(node => node.isInstanceOf[GeneralAssumptionNode] && types.contains(node.assumptionType)) + val relevantAssumptionNodes = newGraph.nodes + .filter(node => node.isInstanceOf[GeneralAssumptionNode] && types.contains(node.assumptionType)) .groupBy(_.sourceInfo.getFineGrainedSource.toString) .view.mapValues(_.map(_.id)) .toMap - newGraph.nodes filter (node => AssumptionType.postconditionTypes.contains(node.assumptionType)) foreach { node => - val nodeSourceInfoString = node.sourceInfo.getTopLevelSource.toString - val assumptionNodesForJoin = relevantAssumptionNodes.getOrElse(nodeSourceInfoString, Seq.empty) - newGraph.addEdges(node.id, assumptionNodesForJoin) - } + newGraph.nodes.filter(node => AssumptionType.postconditionTypes.contains(node.assumptionType)) + .map(node => (node.id, relevantAssumptionNodes.getOrElse(node.sourceInfo.getTopLevelSource.toString, Seq.empty))) + .foreach { case (src, targets) => newGraph.addEdges(src, targets)} - new AssumptionAnalysisInterpreter(name.getOrElse("joined"), newGraph) + new DependencyGraphInterpreter(name.getOrElse("joined"), newGraph) } } -class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { +class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { protected var proofCoverage: Double = 0.0 override def getMember: Option[ast.Member] = Some(member) - override def getNodes: Iterable[AssumptionAnalysisNode] = assumptionGraph.nodes + override def getNodes: Iterable[DependencyAnalysisNode] = assumptionGraph.nodes override def getChunkInhaleNode(chunk: Chunk): Option[PermissionInhaleNode] = { val inhaleNode = assumptionGraph.nodes @@ -160,11 +162,11 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { } - override def addNodes(nodes: Iterable[AssumptionAnalysisNode]): Unit = { + override def addNodes(nodes: Iterable[DependencyAnalysisNode]): Unit = { assumptionGraph.addNodes(nodes) } - override def addNode(node: AssumptionAnalysisNode): Unit = { + override def addNode(node: DependencyAnalysisNode): Unit = { assumptionGraph.addNode(node) } @@ -253,12 +255,12 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { override def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit = { val assumptionLabels = dep.replace("(", "").replace(")", "").split(" ") - val assumptionIds = assumptionLabels.filter(AssumptionAnalyzer.isAssumptionLabel).map(AssumptionAnalyzer.getIdFromLabel) - val assertionIdsFromUnsatCore = assumptionLabels.filter(AssumptionAnalyzer.isAssertionLabel).map(AssumptionAnalyzer.getIdFromLabel) - val assertionIdFromLabel = AssumptionAnalyzer.getIdFromLabel(assertionLabel) + val assumptionIds = assumptionLabels.filter(DependencyAnalyzer.isAssumptionLabel).map(DependencyAnalyzer.getIdFromLabel) + val assertionIdsFromUnsatCore = assumptionLabels.filter(DependencyAnalyzer.isAssertionLabel).map(DependencyAnalyzer.getIdFromLabel) + val assertionIdFromLabel = DependencyAnalyzer.getIdFromLabel(assertionLabel) val assertionIds = assertionIdFromLabel +: assertionIdsFromUnsatCore assumptionGraph.addEdges(assumptionIds, assertionIds) - val axiomIds = assumptionLabels.filter(AssumptionAnalyzer.isAxiomLabel).map(AssumptionAnalyzer.getIdFromLabel) + val axiomIds = assumptionLabels.filter(DependencyAnalyzer.isAxiomLabel).map(DependencyAnalyzer.getIdFromLabel) assumptionGraph.addEdges(axiomIds, assertionIds) } @@ -288,9 +290,9 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { assumptionGraph.addEdges(sourceNodeIds, targetNodes) } - override def buildFinalGraph(): Option[AssumptionAnalysisGraph] = { + override def buildFinalGraph(): Option[DependencyGraph] = { assumptionGraph.removeLabelNodes() - val mergedGraph = if(Verifier.config.enableAssumptionAnalysisDebugging()) assumptionGraph else buildAndGetMergedGraph() + val mergedGraph = if(Verifier.config.enableDependencyAnalysisDebugging()) assumptionGraph else buildAndGetMergedGraph() mergedGraph.addTransitiveEdges() Some(mergedGraph) } @@ -304,10 +306,10 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { } } - private def buildAndGetMergedGraph(): AssumptionAnalysisGraph = { - def keepNode(n: AssumptionAnalysisNode): Boolean = n.isClosed || n.isInstanceOf[InfeasibilityNode] || n.isInstanceOf[AxiomAssumptionNode] + private def buildAndGetMergedGraph(): DependencyGraph = { + def keepNode(n: DependencyAnalysisNode): Boolean = n.isClosed || n.isInstanceOf[InfeasibilityNode] || n.isInstanceOf[AxiomAssumptionNode] - val mergedGraph = new AssumptionAnalysisGraph + val mergedGraph = new DependencyGraph val nodeMap = mutable.HashMap[Int, Int]() assumptionGraph.getNodes.filter(keepNode).foreach { n => nodeMap.put(n.id, n.id) @@ -351,15 +353,15 @@ class DefaultAssumptionAnalyzer(member: ast.Member) extends AssumptionAnalyzer { } } -class NoAssumptionAnalyzer extends AssumptionAnalyzer { +class NoDependencyAnalyzer extends DependencyAnalyzer { override def getMember: Option[ast.Member] = None - override def getNodes: Iterable[AssumptionAnalysisNode] = Set.empty + override def getNodes: Iterable[DependencyAnalysisNode] = Set.empty override def getChunkInhaleNode(chunk: Chunk): Option[PermissionInhaleNode] = None - override def addNodes(nodes: Iterable[AssumptionAnalysisNode]): Unit = {} - override def addNode(node: AssumptionAnalysisNode): Unit = {} + override def addNodes(nodes: Iterable[DependencyAnalysisNode]): Unit = {} + override def addNode(node: DependencyAnalysisNode): Unit = {} override def addAssumption(assumption: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, description: Option[String] = None): Option[Int] = None override def addAxiom(assumption: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, description: Option[String]): Option[Int] = None override def createLabelNode(labelTerm: Var, sourceChunks: Iterable[Chunk], sourceTerms: Iterable[Term]): Option[LabelNode] = None @@ -375,5 +377,5 @@ class NoAssumptionAnalyzer extends AssumptionAnalyzer { override def addCustomExpDependency(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp]): Unit = {} override def addFunctionAxiomEdges(): Unit = {} - override def buildFinalGraph(): Option[AssumptionAnalysisGraph] = None + override def buildFinalGraph(): Option[DependencyGraph] = None } \ No newline at end of file diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala b/src/main/scala/dependencyAnalysis/DependencyGraph.scala similarity index 86% rename from src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala rename to src/main/scala/dependencyAnalysis/DependencyGraph.scala index 8387d4522..666cd1ff9 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisGraph.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraph.scala @@ -1,11 +1,11 @@ -package viper.silicon.assumptionAnalysis +package viper.silicon.dependencyAnalysis import java.io.{File, PrintWriter} import java.util.concurrent.atomic.AtomicInteger import scala.collection.mutable -object AssumptionAnalysisGraphHelper { +object DependencyGraphHelper { private val idCounter: AtomicInteger = new AtomicInteger(0) def nextId(): Int = { @@ -13,8 +13,8 @@ object AssumptionAnalysisGraphHelper { } } -trait ReadOnlyAssumptionAnalysisGraph { - def getNodes: Seq[AssumptionAnalysisNode] +trait ReadOnlyDependencyGraph { + def getNodes: Seq[DependencyAnalysisNode] def getDirectEdges: Map[Int, Set[Int]] // target -> direct dependencies def getTransitiveEdges: Map[Int, Set[Int]] // target -> direct dependencies def getAllEdges: Map[Int, Set[Int]] // target -> direct dependencies @@ -26,12 +26,12 @@ trait ReadOnlyAssumptionAnalysisGraph { def exportGraph(dirName: String): Unit } -class AssumptionAnalysisGraph extends ReadOnlyAssumptionAnalysisGraph { - var nodes: mutable.Seq[AssumptionAnalysisNode] = mutable.Seq() +class DependencyGraph extends ReadOnlyDependencyGraph { + var nodes: mutable.Seq[DependencyAnalysisNode] = mutable.Seq() private val edges: mutable.Map[Int, Set[Int]] = mutable.Map.empty private val transitiveEdges: mutable.Map[Int, Set[Int]] = mutable.Map.empty - def getNodes: Seq[AssumptionAnalysisNode] = nodes.toSeq + def getNodes: Seq[DependencyAnalysisNode] = nodes.toSeq def getDirectEdges: Map[Int, Set[Int]] = edges.toMap def getTransitiveEdges: Map[Int, Set[Int]] = transitiveEdges.toMap def getAllEdges: Map[Int, Set[Int]] = { @@ -43,11 +43,11 @@ class AssumptionAnalysisGraph extends ReadOnlyAssumptionAnalysisGraph { res.toMap } - def addNode(node: AssumptionAnalysisNode): Unit = { + def addNode(node: DependencyAnalysisNode): Unit = { nodes = nodes :+ node } - def addNodes(nodes: Iterable[AssumptionAnalysisNode]): Unit = { + def addNodes(nodes: Iterable[DependencyAnalysisNode]): Unit = { nodes foreach addNode } @@ -109,22 +109,22 @@ class AssumptionAnalysisGraph extends ReadOnlyAssumptionAnalysisGraph { visited } - private def addTransitiveEdges(sources: Iterable[AssumptionAnalysisNode], target: AssumptionAnalysisNode): Unit = { + private def addTransitiveEdges(sources: Iterable[DependencyAnalysisNode], target: DependencyAnalysisNode): Unit = { val oldSources = transitiveEdges.getOrElse(target.id, Set.empty) val newSources = sources map(_.id) // filter(_ > target.id) does not work due to loop invariants if(newSources.nonEmpty) transitiveEdges.update(target.id, oldSources ++ newSources) } - private def addTransitiveEdges(sources: Iterable[AssumptionAnalysisNode], targets: Iterable[AssumptionAnalysisNode]): Unit = { + private def addTransitiveEdges(sources: Iterable[DependencyAnalysisNode], targets: Iterable[DependencyAnalysisNode]): Unit = { targets foreach (t => addTransitiveEdges(sources, t)) } - // TODO ake: maybe move to AssumptionAnalyzer? - private def getNodesPerTransitivitySourceInfo: Map[String, Seq[AssumptionAnalysisNode]] = { + // TODO ake: maybe move to DependencyAnalyzer? + private def getNodesPerTransitivitySourceInfo: Map[String, Seq[DependencyAnalysisNode]] = { getNodes.groupBy(_.sourceInfo.getSourceForTransitiveEdges.toString) } - // TODO ake: maybe move to AssumptionAnalyzer? + // TODO ake: maybe move to DependencyAnalyzer? def addTransitiveEdges(): Unit = { val nodesPerSourceInfo = getNodesPerTransitivitySourceInfo nodesPerSourceInfo foreach {nodes => @@ -137,7 +137,7 @@ class AssumptionAnalysisGraph extends ReadOnlyAssumptionAnalysisGraph { } } - private def removeAllEdgesForNode(node: AssumptionAnalysisNode): Unit = { + private def removeAllEdgesForNode(node: DependencyAnalysisNode): Unit = { val id = node.id val predecessors = (edges filter { case (_, t) => t.contains(id) }).keys val successors = edges.getOrElse(id, Set.empty) @@ -146,7 +146,7 @@ class AssumptionAnalysisGraph extends ReadOnlyAssumptionAnalysisGraph { addEdges(successors, predecessors) } - // TODO ake: maybe move to AssumptionAnalyzer? + // TODO ake: maybe move to DependencyAnalyzer? def removeLabelNodes(): Unit = { nodes filter (_.isInstanceOf[LabelNode]) foreach removeAllEdgesForNode nodes = nodes filter (!_.isInstanceOf[LabelNode]) @@ -169,7 +169,7 @@ class AssumptionAnalysisGraph extends ReadOnlyAssumptionAnalysisGraph { private def exportNodes(fileName: String): Unit = { val sep = "#" - def getNodeExportString(node: AssumptionAnalysisNode): String = { + def getNodeExportString(node: DependencyAnalysisNode): String = { val parts = mutable.Seq(node.id.toString, node.getNodeType, node.assumptionType.toString, node.getNodeString, node.sourceInfo.toString, node.sourceInfo.getPositionString, node.sourceInfo.getFineGrainedSource.toString) parts.map(_.replace("#", "@")).mkString(sep) } diff --git a/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala similarity index 75% rename from src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala rename to src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 338382268..b8ed52511 100644 --- a/src/main/scala/assumptionAnalysis/AssumptionAnalysisInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -1,4 +1,4 @@ -package viper.silicon.assumptionAnalysis +package viper.silicon.dependencyAnalysis import viper.silicon.verifier.Verifier import viper.silver.ast @@ -9,25 +9,25 @@ import viper.silver.ast.{If, Stmt} import java.io.{File, PrintWriter} -class AssumptionAnalysisInterpreter(name: String, graph: ReadOnlyAssumptionAnalysisGraph, member: Option[ast.Member]=None) { +class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependencyGraph, member: Option[ast.Member]=None) { - def getGraph: ReadOnlyAssumptionAnalysisGraph = graph + def getGraph: ReadOnlyDependencyGraph = dependencyGraph def getName: String = name def getMember: Option[ast.Member] = member - def getNodes: Set[AssumptionAnalysisNode] = graph.getNodes.toSet + def getNodes: Set[DependencyAnalysisNode] = dependencyGraph.getNodes.toSet - def getNodesByLine(line: Int): Set[AssumptionAnalysisNode] = + def getNodesByLine(line: Int): Set[DependencyAnalysisNode] = getNodes.filter(n => !AssumptionType.internalTypes.contains(n.assumptionType)).filter(node => node.sourceInfo.getLineNumber.isDefined && node.sourceInfo.getLineNumber.get == line) - def getNodesByPosition(file: String, line: Int): Set[AssumptionAnalysisNode] = + def getNodesByPosition(file: String, line: Int): Set[DependencyAnalysisNode] = getNodes.filter(n => !AssumptionType.internalTypes.contains(n.assumptionType)).filter(node => node.sourceInfo.getLineNumber.isDefined && node.sourceInfo.getLineNumber.get == line && node.sourceInfo.getPositionString.startsWith(file + ".")) - def getDirectDependencies(nodeIdsToAnalyze: Set[Int]): Set[AssumptionAnalysisNode] = { + def getDirectDependencies(nodeIdsToAnalyze: Set[Int]): Set[DependencyAnalysisNode] = { var queue = nodeIdsToAnalyze var result: Set[Int] = Set.empty val internalNodeIds = getNodes.diff(getNonInternalAssumptionNodes).map(_.id) while(queue.nonEmpty){ - val directDependencyIds = queue flatMap (id => graph.getDirectEdges.getOrElse(id, Set.empty)) + val directDependencyIds = queue flatMap (id => dependencyGraph.getDirectEdges.getOrElse(id, Set.empty)) queue = internalNodeIds.intersect(directDependencyIds).diff(result) // internal assumptions are hidden -> add their direct dependencies instead result = result.union(directDependencyIds) } @@ -35,66 +35,66 @@ class AssumptionAnalysisInterpreter(name: String, graph: ReadOnlyAssumptionAnaly getNonInternalAssumptionNodes.filter(node => result.contains(node.id)) } - def getAllNonInternalDependencies(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[AssumptionAnalysisNode] = { - val allDependencies = graph.getAllDependencies(nodeIdsToAnalyze, includeInfeasibilityNodes).diff(nodeIdsToAnalyze) + def getAllNonInternalDependencies(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[DependencyAnalysisNode] = { + val allDependencies = dependencyGraph.getAllDependencies(nodeIdsToAnalyze, includeInfeasibilityNodes).diff(nodeIdsToAnalyze) getNonInternalAssumptionNodes.filter(node => allDependencies.contains(node.id)) } - def getAllExplicitDependencies(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[AssumptionAnalysisNode] = { - val allDependencies = graph.getAllDependencies(nodeIdsToAnalyze, includeInfeasibilityNodes).diff(nodeIdsToAnalyze) + def getAllExplicitDependencies(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[DependencyAnalysisNode] = { + val allDependencies = dependencyGraph.getAllDependencies(nodeIdsToAnalyze, includeInfeasibilityNodes).diff(nodeIdsToAnalyze) getExplicitAssumptionNodes.filter(node => allDependencies.contains(node.id)) } - def getAllNonInternalDependents(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[AssumptionAnalysisNode] = { - val allDependents = graph.getAllDependents(nodeIdsToAnalyze, includeInfeasibilityNodes).diff(nodeIdsToAnalyze) + def getAllNonInternalDependents(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[DependencyAnalysisNode] = { + val allDependents = dependencyGraph.getAllDependents(nodeIdsToAnalyze, includeInfeasibilityNodes).diff(nodeIdsToAnalyze) getNonInternalAssertionNodes.filter(node => allDependents.contains(node.id)) } - def getAllExplicitDependents(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[AssumptionAnalysisNode] = { - val allDependents = graph.getAllDependents(nodeIdsToAnalyze, includeInfeasibilityNodes).diff(nodeIdsToAnalyze) + def getAllExplicitDependents(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[DependencyAnalysisNode] = { + val allDependents = dependencyGraph.getAllDependents(nodeIdsToAnalyze, includeInfeasibilityNodes).diff(nodeIdsToAnalyze) getExplicitAssertionNodes.filter(node => allDependents.contains(node.id)) } - def filterOutNodesBySourceInfo(nodes: Set[AssumptionAnalysisNode], excludeInfos: Set[AnalysisSourceInfo]): Set[AssumptionAnalysisNode] = + def filterOutNodesBySourceInfo(nodes: Set[DependencyAnalysisNode], excludeInfos: Set[AnalysisSourceInfo]): Set[DependencyAnalysisNode] = nodes filterNot (node => excludeInfos.exists(i => i.getTopLevelSource.toString.equals(node.sourceInfo.getTopLevelSource.toString))) - def getNonInternalAssumptionNodes: Set[AssumptionAnalysisNode] = getNodes filter (node => + def getNonInternalAssumptionNodes: Set[DependencyAnalysisNode] = getNodes filter (node => (node.isInstanceOf[GeneralAssumptionNode] && !AssumptionType.internalTypes.contains(node.assumptionType)) || AssumptionType.postconditionTypes.contains(node.assumptionType) // postconditions act as assumptions for callers ) - def getExplicitAssumptionNodes: Set[AssumptionAnalysisNode] = getNonInternalAssumptionNodes filter (node => + def getExplicitAssumptionNodes: Set[DependencyAnalysisNode] = getNonInternalAssumptionNodes filter (node => AssumptionType.explicitAssumptionTypes.contains(node.assumptionType) ) - def hasAnyDependency(nodesToAnalyze: Set[AssumptionAnalysisNode], includeInfeasibilityNodes: Boolean = true): Boolean = + def hasAnyDependency(nodesToAnalyze: Set[DependencyAnalysisNode], includeInfeasibilityNodes: Boolean = true): Boolean = nodesToAnalyze.intersect(getNonInternalAssumptionNodes) - .exists(node => graph.existsAnyDependency(Set(node.id), nodesToAnalyze map (_.id) filter (_ != node.id), includeInfeasibilityNodes)) + .exists(node => dependencyGraph.existsAnyDependency(Set(node.id), nodesToAnalyze map (_.id) filter (_ != node.id), includeInfeasibilityNodes)) - def getNonInternalAssumptionNodesPerSource: Map[String, Set[AssumptionAnalysisNode]] = + def getNonInternalAssumptionNodesPerSource: Map[String, Set[DependencyAnalysisNode]] = getNonInternalAssumptionNodes.groupBy(_.sourceInfo.getTopLevelSource.toString) - def getNonInternalAssertionNodes: Set[AssumptionAnalysisNode] = getNodes filter (node => + def getNonInternalAssertionNodes: Set[DependencyAnalysisNode] = getNodes filter (node => (node.isInstanceOf[GeneralAssertionNode] && !AssumptionType.internalTypes.contains(node.assumptionType)) || AssumptionType.postconditionTypes.contains(node.assumptionType) ) - def getNonInternalAssertionNodesPerSource: Map[String, Set[AssumptionAnalysisNode]] = + def getNonInternalAssertionNodesPerSource: Map[String, Set[DependencyAnalysisNode]] = getNonInternalAssertionNodes.groupBy(_.sourceInfo.getTopLevelSource.toString) - def getExplicitAssertionNodes: Set[AssumptionAnalysisNode] = + def getExplicitAssertionNodes: Set[DependencyAnalysisNode] = getNonInternalAssertionNodes.filter(node => AssumptionType.explicitAssertionTypes.contains(node.assumptionType)) def exportGraph(): Unit = { - if(Verifier.config.assumptionAnalysisExportPath.isEmpty) return - val directory = new File(Verifier.config.assumptionAnalysisExportPath()) + if(Verifier.config.dependencyAnalysisExportPath.isEmpty) return + val directory = new File(Verifier.config.dependencyAnalysisExportPath()) directory.mkdir() - graph.exportGraph(Verifier.config.assumptionAnalysisExportPath() + "/" + name) + dependencyGraph.exportGraph(Verifier.config.dependencyAnalysisExportPath() + "/" + name) } - private def getNodesWithIdenticalSource(nodes: Set[AssumptionAnalysisNode]): Set[AssumptionAnalysisNode] = { + private def getNodesWithIdenticalSource(nodes: Set[DependencyAnalysisNode]): Set[DependencyAnalysisNode] = { val sourceInfos = nodes map (_.sourceInfo.getTopLevelSource.toString) getNodes filter (node => sourceInfos.contains(node.sourceInfo.getTopLevelSource.toString)) } @@ -104,9 +104,9 @@ class AssumptionAnalysisInterpreter(name: String, graph: ReadOnlyAssumptionAnaly computeProofCoverage(explicitAssertionNodes) } - def computeProofCoverage(assertionNodes: Set[AssumptionAnalysisNode]): (Double, Set[String]) = { + def computeProofCoverage(assertionNodes: Set[DependencyAnalysisNode]): (Double, Set[String]) = { val assertionNodeIds = assertionNodes map (_.id) - val dependencies = graph.getAllDependencies(assertionNodeIds, includeInfeasibilityNodes=true) + val dependencies = dependencyGraph.getAllDependencies(assertionNodeIds, includeInfeasibilityNodes=true) val coveredNodes = dependencies ++ assertionNodeIds val nodesPerSourceInfo = getNonInternalAssumptionNodes.filterNot(_.isInstanceOf[AxiomAssumptionNode]).groupBy(_.sourceInfo.getTopLevelSource.toString) if(nodesPerSourceInfo.isEmpty) return (Double.NaN, Set()) @@ -118,7 +118,7 @@ class AssumptionAnalysisInterpreter(name: String, graph: ReadOnlyAssumptionAnaly (proofCoverage, uncoveredSources) } - def getPrunedProgram(crucialNodes: Set[AssumptionAnalysisNode], program: ast.Program): (ast.Program, Double) = { + def getPrunedProgram(crucialNodes: Set[DependencyAnalysisNode], program: ast.Program): (ast.Program, Double) = { def isCrucialExp(exp: ast.Exp, crucialNodesWithExpInfo: Set[AnalysisSourceInfo]): Boolean = { crucialNodesWithExpInfo exists (n => n.getPositionString.equals(AnalysisSourceInfo.extractPositionString(exp.pos))) // TODO ake: currently we compare only lines not columns! @@ -201,7 +201,7 @@ class AssumptionAnalysisInterpreter(name: String, graph: ReadOnlyAssumptionAnaly (newProgram, removed.toDouble / total.toDouble) } - def pruneProgramAndExport(crucialNodes: Set[AssumptionAnalysisNode], program: ast.Program, exportFileName: String): Unit = { + def pruneProgramAndExport(crucialNodes: Set[DependencyAnalysisNode], program: ast.Program, exportFileName: String): Unit = { val writer = new PrintWriter(exportFileName) val (newProgram, pruningFactor) = getPrunedProgram(crucialNodes, program) writer.println("// pruning factor: " + pruningFactor) diff --git a/src/main/scala/assumptionAnalysis/README.md b/src/main/scala/dependencyAnalysis/README.md similarity index 86% rename from src/main/scala/assumptionAnalysis/README.md rename to src/main/scala/dependencyAnalysis/README.md index c84b0e19d..b7a59c5c3 100644 --- a/src/main/scala/assumptionAnalysis/README.md +++ b/src/main/scala/dependencyAnalysis/README.md @@ -4,22 +4,22 @@ # Running Silicon with Dependency Analysis Dependency Analysis is enabled thorough the following configuration options: -`--enableAssumptionAnalysis --disableInfeasibilityChecks --proverArgs "proof=true unsat-core=true"` +`--enableDependencyAnalysis --disableInfeasibilityChecks --proverArgs "proof=true unsat-core=true"` Additionally, to retrieve the results and query the dependency graph, use: -- `--startAssumptionAnalysisTool` +- `--startDependencyAnalysisTool` - Automatically starts the command-line tool once verification terminates. -- `--assumptionAnalysisExportPath [PATH TO EXPORT FOLDER]` - - e.g., `--assumptionAnalysisExportPath "graphExports"` +- `--dependencyAnalysisExportPath [PATH TO EXPORT FOLDER]` + - e.g., `--dependencyAnalysisExportPath "graphExports"` - Exports the graph to a folder named after the verified program under the given path (e.g. `graphExports/src_test_resources_andrea_quickTest` for input program `src/test/resources/andrea/quickTest.vpr`) -For debugging dependency analysis results, the option `--enableAssumptionAnalysisDebugging` can be used which disables the merging of nodes. +For debugging dependency analysis results, the option `--enableDependencyAnalysisDebugging` can be used which disables the merging of nodes. As a result, the graph used for query computation and the exported graph contain all low-level details. # Command-Line Tool -Requires `--startAssumptionAnalysisTool`. +Requires `--startDependencyAnalysisTool`. Example queries for program `src/test/resources/dependencyAnalysisTests/unitTests/B-permissions.vpr`: - `dep 99` @@ -46,7 +46,7 @@ Example queries for program `src/test/resources/dependencyAnalysisTests/unitTest # Neo4j Scripts and Usage -Graphs exported when using `--assumptionAnalysisExportPath [PATH TO EXPORT FOLDER]` can be imported to a [Neo4j database]({https://neo4j.com/) using the `neo4j_importer.py` script. +Graphs exported when using `--dependencyAnalysisExportPath [PATH TO EXPORT FOLDER]` can be imported to a [Neo4j database]({https://neo4j.com/) using the `neo4j_importer.py` script. Importing dependency graphs to Neo4j: @@ -58,7 +58,7 @@ Importing dependency graphs to Neo4j: 1. Make sure the instance is up and running. -1. Execute `python src/main/scala/assumptionAnalysis/neo4j_importer.py` and when queried provide the following inputs: +1. Execute `python src/main/scala/dependencyAnalysis/neo4j_importer.py` and when queried provide the following inputs: 1. foldername: relative path to the export folder of the dependency graph (e.g. `graphExports/src_test_resources_andrea_quickTest`) 1. node_label: label to be given to the nodes created in Neo4j 1. Note that existing nodes with the same label are deleted! diff --git a/src/main/scala/assumptionAnalysis/SourceInfoStack.scala b/src/main/scala/dependencyAnalysis/SourceInfoStack.scala similarity index 91% rename from src/main/scala/assumptionAnalysis/SourceInfoStack.scala rename to src/main/scala/dependencyAnalysis/SourceInfoStack.scala index a5d79e796..118a72d87 100644 --- a/src/main/scala/assumptionAnalysis/SourceInfoStack.scala +++ b/src/main/scala/dependencyAnalysis/SourceInfoStack.scala @@ -1,4 +1,4 @@ -package viper.silicon.assumptionAnalysis +package viper.silicon.dependencyAnalysis import viper.silicon.verifier.Verifier import viper.silver.ast.NoPosition @@ -45,7 +45,7 @@ case class AnalysisSourceInfoStack() extends SourceInfoStack { override def getAnalysisSourceInfos: List[AnalysisSourceInfo] = sourceInfos override def getFullSourceInfo: AnalysisSourceInfo = { - if(!Verifier.config.enableAssumptionAnalysis()) return NoAnalysisSourceInfo() + if(!Verifier.config.enableDependencyAnalysis()) return NoAnalysisSourceInfo() if(forcedMainSource.isDefined) return forcedMainSource.get if(sourceInfos.size <= 1){ @@ -56,17 +56,17 @@ case class AnalysisSourceInfoStack() extends SourceInfoStack { } override def addAnalysisSourceInfo(analysisSourceInfo: AnalysisSourceInfo): Unit = { - if(!Verifier.config.enableAssumptionAnalysis()) return + if(!Verifier.config.enableDependencyAnalysis()) return sourceInfos = analysisSourceInfo +: sourceInfos } override def setAnalysisSourceInfo(analysisSourceInfo: List[AnalysisSourceInfo]): Unit = { - if(!Verifier.config.enableAssumptionAnalysis()) return + if(!Verifier.config.enableDependencyAnalysis()) return sourceInfos = analysisSourceInfo } override def popAnalysisSourceInfo(analysisSourceInfo: AnalysisSourceInfo): Unit = { - if(!Verifier.config.enableAssumptionAnalysis()) return + if(!Verifier.config.enableDependencyAnalysis()) return var currSourceInfo = sourceInfos // popping just one source info might not be enough since infeasible branches might return without popping the source info diff --git a/src/main/scala/assumptionAnalysis/neo4j_importer.py b/src/main/scala/dependencyAnalysis/neo4j_importer.py similarity index 100% rename from src/main/scala/assumptionAnalysis/neo4j_importer.py rename to src/main/scala/dependencyAnalysis/neo4j_importer.py diff --git a/src/main/scala/assumptionAnalysis/neo4j_query_saved_cypher_2025-9-17.csv b/src/main/scala/dependencyAnalysis/neo4j_query_saved_cypher_2025-9-17.csv similarity index 100% rename from src/main/scala/assumptionAnalysis/neo4j_query_saved_cypher_2025-9-17.csv rename to src/main/scala/dependencyAnalysis/neo4j_query_saved_cypher_2025-9-17.csv diff --git a/src/main/scala/interfaces/Verification.scala b/src/main/scala/interfaces/Verification.scala index d694f6b78..bcbdd2bad 100644 --- a/src/main/scala/interfaces/Verification.scala +++ b/src/main/scala/interfaces/Verification.scala @@ -6,7 +6,7 @@ package viper.silicon.interfaces -import viper.silicon.assumptionAnalysis.AssumptionAnalysisInterpreter +import viper.silicon.dependencyAnalysis.DependencyGraphInterpreter import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.{DebugAxiom, DebugExp} import viper.silicon.interfaces.state.Chunk @@ -31,7 +31,7 @@ sealed abstract class VerificationResult { var previous: Vector[VerificationResult] = Vector() //Sets had problems with equality val continueVerification: Boolean = true var isReported: Boolean = false - var assumptionAnalysisInterpreter: Option[AssumptionAnalysisInterpreter] = None + var dependencyGraphInterpreter: Option[DependencyGraphInterpreter] = None def isFatal: Boolean def &&(other: => VerificationResult): VerificationResult diff --git a/src/main/scala/interfaces/decider/Prover.scala b/src/main/scala/interfaces/decider/Prover.scala index 74226dcf6..87da9e24c 100644 --- a/src/main/scala/interfaces/decider/Prover.scala +++ b/src/main/scala/interfaces/decider/Prover.scala @@ -6,8 +6,8 @@ package viper.silicon.interfaces.decider -import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType -import viper.silicon.assumptionAnalysis._ +import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType +import viper.silicon.dependencyAnalysis._ import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.common.config.Version import viper.silicon.debugger.DebugAxiom @@ -27,9 +27,9 @@ object Unknown extends Result trait ProverLike { protected val debugMode = Verifier.config.enableDebugging() var preambleAssumptions: Seq[DebugAxiom] = Seq() - protected var preambleAssumptionAnalyzer: AssumptionAnalyzer = - if(Verifier.config.enableAssumptionAnalysis()) new DefaultAssumptionAnalyzer(ast.Method("none", Seq(), Seq(), Seq(), Seq(), None)()) - else new NoAssumptionAnalyzer() + protected var preambleDependencyAnalyzer: DependencyAnalyzer = + if(Verifier.config.enableDependencyAnalysis()) new DefaultDependencyAnalyzer(ast.Method("none", Seq(), Seq(), Seq(), Seq(), None)()) + else new NoDependencyAnalyzer() def emit(content: String): Unit def emit(contents: Iterable[String]): Unit = { contents foreach emit } def emitSettings(contents: Iterable[String]): Unit @@ -43,18 +43,18 @@ trait ProverLike { if (debugMode) preambleAssumptions :+= new DebugAxiom(description, axioms.map(_._1)) - if(Verifier.config.enableAssumptionAnalysis()){ + if(Verifier.config.enableDependencyAnalysis()){ axioms.foreach(axiom => { val axiomInfo = axiom._2 - val id = if(axiomInfo.isDefined) preambleAssumptionAnalyzer.addAxiom(axiom._1, axiomInfo.get._1, axiomInfo.get._2) else None - assume(axiom._1, AssumptionAnalyzer.createAxiomLabel(id)) + val id = if(axiomInfo.isDefined) preambleDependencyAnalyzer.addAxiom(axiom._1, axiomInfo.get._1, axiomInfo.get._2) else None + assume(axiom._1, DependencyAnalyzer.createAxiomLabel(id)) }) } else{ axioms.foreach(t => assume(t._1)) } } - def getPreambleAnalysisNodes: Iterable[AssumptionAnalysisNode] = preambleAssumptionAnalyzer.getNodes + def getPreambleAnalysisNodes: Iterable[DependencyAnalysisNode] = preambleDependencyAnalyzer.getNodes def setOption(name: String, value: String): String def assume(term: Term): Unit def assume(term: Term, label: String): Unit diff --git a/src/main/scala/interfaces/state/Chunks.scala b/src/main/scala/interfaces/state/Chunks.scala index 623b05f78..ddc455d77 100644 --- a/src/main/scala/interfaces/state/Chunks.scala +++ b/src/main/scala/interfaces/state/Chunks.scala @@ -6,7 +6,7 @@ package viper.silicon.interfaces.state -import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType} +import viper.silicon.dependencyAnalysis.{AnalysisInfo, AssumptionType} import viper.silicon.resources.ResourceID import viper.silicon.state.terms.{Term, Var} import viper.silver.ast diff --git a/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala b/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala index 1e2b760d2..db638f8e3 100644 --- a/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala +++ b/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala @@ -122,7 +122,7 @@ class NonQuantifiedPropertyInterpreter(heap: Iterable[Chunk], verifier: Verifier val conditionTerm = buildPathCondition(condition, info)._1 if (verifier.decider.check(conditionTerm, Verifier.config.checkTimeout())) { val (t, e) = buildPathCondition(thenDo, info) - (verifier.decider.wrapWithAssumptionAnalysisLabel(t, Set.empty, Set(conditionTerm)), e) // TODO ake: causes imprecision! + (verifier.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(conditionTerm)), e) // TODO ake: causes imprecision! } else { buildPathCondition(otherwise, info) } @@ -153,14 +153,14 @@ class NonQuantifiedPropertyInterpreter(heap: Iterable[Chunk], verifier: Verifier case c +: Seq() => buildPathCondition(body, info.addMapping(c, chunk)) case c +: tail => buildForEach(chunks, tail, body, info.addMapping(c, chunk)) } - (verifier.decider.wrapWithAssumptionAnalysisLabel(res._1, Set(chunk)), res._2) + (verifier.decider.wrapWithDependencyAnalysisLabel(res._1, Set(chunk)), res._2) } val conds = chunks.flatMap { chunk => // check that only distinct tuples are handled // TODO: Is it possible to get this behavior without having to check every tuple? if (!info.pm.values.exists(chunk eq _)) { val res = builder(chunk) - Some((verifier.decider.wrapWithAssumptionAnalysisLabel(res._1, Set(chunk)), res._2)) + Some((verifier.decider.wrapWithDependencyAnalysisLabel(res._1, Set(chunk)), res._2)) } else { None } diff --git a/src/main/scala/rules/Brancher.scala b/src/main/scala/rules/Brancher.scala index d2c3430f0..187283f9b 100644 --- a/src/main/scala/rules/Brancher.scala +++ b/src/main/scala/rules/Brancher.scala @@ -6,7 +6,7 @@ package viper.silicon.rules -import viper.silicon.assumptionAnalysis.{ExpAnalysisSourceInfo} +import viper.silicon.dependencyAnalysis.{ExpAnalysisSourceInfo} import java.util.concurrent._ import viper.silicon.common.concurrency._ import viper.silicon.decider.PathConditionStack @@ -64,7 +64,7 @@ object brancher extends BranchingRules { skipPathFeasibilityCheck || !v.decider.check(negatedCondition, Verifier.config.checkTimeout())) - val thenInfeasibilityNode: Option[Int] = if(Verifier.config.enableAssumptionAnalysis() && !executeThenBranch) { + val thenInfeasibilityNode: Option[Int] = if(Verifier.config.enableDependencyAnalysis() && !executeThenBranch) { val (_, node) = v.decider.checkAndGetInfeasibilityNode(negatedCondition, Verifier.config.checkTimeout()) node } else None @@ -75,7 +75,7 @@ object brancher extends BranchingRules { || skipPathFeasibilityCheck || !v.decider.check(condition, Verifier.config.checkTimeout())) - val elseInfeasibilityNode: Option[Int] = if(Verifier.config.enableAssumptionAnalysis() && !executeElseBranch) { + val elseInfeasibilityNode: Option[Int] = if(Verifier.config.enableDependencyAnalysis() && !executeElseBranch) { val (_, node) = v.decider.checkAndGetInfeasibilityNode(condition, Verifier.config.checkTimeout()) node } else None diff --git a/src/main/scala/rules/ChunkSupporter.scala b/src/main/scala/rules/ChunkSupporter.scala index dcc8154bf..c75423feb 100644 --- a/src/main/scala/rules/ChunkSupporter.scala +++ b/src/main/scala/rules/ChunkSupporter.scala @@ -6,8 +6,8 @@ package viper.silicon.rules -import viper.silicon.assumptionAnalysis.AssumptionType -import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType +import viper.silicon.dependencyAnalysis.AssumptionType +import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.state._ import viper.silicon.interfaces.{Success, VerificationResult} diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index c6054870d..a01e4d8b5 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -7,8 +7,8 @@ package viper.silicon.rules import viper.silicon.Config.JoinMode -import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType -import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType, ExpAnalysisSourceInfo, StringAnalysisSourceInfo} +import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType +import viper.silicon.dependencyAnalysis.{AnalysisInfo, AssumptionType, ExpAnalysisSourceInfo, StringAnalysisSourceInfo} import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.VerificationResult import viper.silicon.interfaces.state.Chunk @@ -34,7 +34,7 @@ trait ConsumptionRules extends SymbolicExecutionRules { * @param returnSnap Whether a snapshot should be returned or not. * @param pve The error to report in case the consumption fails. * @param v The verifier to use. - * @param assumptionType The assumption type used for assumption analysis and proof coverage + * @param assumptionType The assumption type used for dependency analysis and proof coverage * @param Q The continuation to invoke if the consumption succeeded, with the following * arguments: state (1st argument) and verifier (3rd argument) resulting from the * consumption, and a heap snapshot (2bd argument )representing the values of the @@ -205,7 +205,7 @@ object consumer extends ConsumptionRules { */ if(v.decider.isPathInfeasible()){ - v.decider.assumptionAnalyzer.addInfeasibilityDepToStmt(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, assumptionType) + v.decider.dependencyAnalyzer.addInfeasibilityDepToStmt(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, assumptionType) return Q(s, h, Option.when(returnSnap)(Unit), v) } @@ -593,7 +593,7 @@ object consumer extends ConsumptionRules { State.mergeHeap( entry1.data._1, And(entry1.pathConditions.branchConditions), Option.when(withExp)(BigAnd(entry1.pathConditions.branchConditionExps.map(_._2.get))), entry2.data._1, And(entry2.pathConditions.branchConditions), Option.when(withExp)(BigAnd(entry2.pathConditions.branchConditionExps.map(_._2.get))), - AnalysisInfo(v.decider, v.decider.assumptionAnalyzer, StringAnalysisSourceInfo("conditional join", e0.pos), AssumptionType.Implicit) + AnalysisInfo(v.decider, v.decider.dependencyAnalyzer, StringAnalysisSourceInfo("conditional join", e0.pos), AssumptionType.Implicit) ), // Assume that entry1.pcs is inverse of entry2.pcs (entry1.data._2, entry2.data._2) match { diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 32cd69896..22c6e7859 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -7,7 +7,7 @@ package viper.silicon.rules import viper.silicon.Config.JoinMode -import viper.silicon.assumptionAnalysis.{AssumptionAnalyzer, AssumptionType, ExpAnalysisSourceInfo} +import viper.silicon.dependencyAnalysis.{DependencyAnalyzer, AssumptionType, ExpAnalysisSourceInfo} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.interfaces._ @@ -826,12 +826,12 @@ object evaluator extends EvaluationRules { val auxNonGlobalsExp = auxExps.map(_._2) val commentGlobal = "Nested auxiliary terms: globals (aux)" v1.decider.prover.comment(commentGlobal) - v1.decider.assumptionAnalyzer.disableTransitiveEdges() + v1.decider.dependencyAnalyzer.disableTransitiveEdges() v1.decider.assume(tAuxGlobal, Option.when(withExp)(DebugExp.createInstance(description=commentGlobal, children=auxGlobalsExp.get)), enforceAssumption = false, assumptionType=AssumptionType.Internal) val commentNonGlobals = "Nested auxiliary terms: non-globals (aux)" v1.decider.prover.comment(commentNonGlobals) v1.decider.assume(tAuxHeapIndep/*tAux*/, Option.when(withExp)(DebugExp.createInstance(description=commentNonGlobals, children=auxNonGlobalsExp.get)), enforceAssumption = false, assumptionType=AssumptionType.Internal) - v1.decider.assumptionAnalyzer.enableTransitiveEdges() + v1.decider.dependencyAnalyzer.enableTransitiveEdges() if (qantOp == Exists) { // For universal quantification, the non-global auxiliary assumptions will contain the information that // forall vars :: all function preconditions are fulfilled. @@ -861,8 +861,8 @@ object evaluator extends EvaluationRules { case fapp @ ast.FuncApp(funcName, eArgs) => val func = s.program.findFunction(funcName) - val funcAssumptionType = if(func.body.isDefined || !AssumptionAnalyzer.extractEnableAnalysisFromInfo(func.info).getOrElse(true)) AssumptionType.ImplicitPostcondAssumption else AssumptionType.ExplicitPostcondAssumption - val assumptionType = AssumptionAnalyzer.extractAssumptionTypeFromInfo(fapp.info).getOrElse(funcAssumptionType) + val funcAssumptionType = if(func.body.isDefined || !DependencyAnalyzer.extractEnableAnalysisFromInfo(func.info).getOrElse(true)) AssumptionType.ImplicitPostcondAssumption else AssumptionType.ExplicitPostcondAssumption + val assumptionType = DependencyAnalyzer.extractAssumptionTypeFromInfo(fapp.info).getOrElse(funcAssumptionType) evals2(s, eArgs, Nil, _ => pve, v)((s1, tArgs, eArgsNew, v1) => { // bookkeeper.functionApplications += 1 val joinFunctionArgs = tArgs //++ c2a.quantifiedVariables.filterNot(tArgs.contains) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index cd863ffe4..42080e5e7 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -7,8 +7,8 @@ package viper.silicon.rules import viper.silicon.Config.JoinMode -import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType -import viper.silicon.assumptionAnalysis.{AssumptionAnalyzer, AssumptionType, ExpAnalysisSourceInfo, StmtAnalysisSourceInfo, TransitivityAnalysisSourceInfo} +import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType +import viper.silicon.dependencyAnalysis.{DependencyAnalyzer, AssumptionType, ExpAnalysisSourceInfo, StmtAnalysisSourceInfo, TransitivityAnalysisSourceInfo} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.decider.RecordedPathConditions @@ -279,7 +279,7 @@ object executor extends ExecutionRules { val s2 = s1.copy(invariantContexts = sLeftover.h +: s1.invariantContexts) intermediateResult combine executionFlowController.locally(s2, v1)((s3, v2) => { v2.decider.declareAndRecordAsFreshFunctions(ff1 -- v2.decider.freshFunctions) /* [BRANCH-PARALLELISATION] */ - v2.decider.assume(pcs.assumptions map (t => v.decider.wrapWithAssumptionAnalysisLabel(t, Set.empty, Set(t))), Some(pcs.assumptionExps), "Loop invariant", enforceAssumption=false, assumptionType=AssumptionType.LoopInvariant) + v2.decider.assume(pcs.assumptions map (t => v.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t))), Some(pcs.assumptionExps), "Loop invariant", enforceAssumption=false, assumptionType=AssumptionType.LoopInvariant) v2.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) if (v2.decider.checkSmoke() && !Verifier.config.disableInfeasibilityChecks()) Success() @@ -333,9 +333,9 @@ object executor extends ExecutionRules { val sepIdentifier = v.symbExLog.openScope(new ExecuteRecord(stmt, s, v.decider.pcs)) val sourceInfo = StmtAnalysisSourceInfo(stmt) v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo) - if(Verifier.config.disableInfeasibilityChecks() && Verifier.config.enableAssumptionAnalysis() && + if(Verifier.config.disableInfeasibilityChecks() && Verifier.config.enableDependencyAnalysis() && v.decider.pcs.getCurrentInfeasibilityNode.isDefined && !alwaysExecute(stmt)){ - v.decider.assumptionAnalyzer.addInfeasibilityDepToStmt(v.decider.pcs.getCurrentInfeasibilityNode, + v.decider.dependencyAnalyzer.addInfeasibilityDepToStmt(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, AssumptionType.Implicit) v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) Q(s, v) @@ -367,7 +367,7 @@ object executor extends ExecutionRules { v.decider.prover.comment("[exec]") v.decider.prover.comment(stmt.toString()) } - val annotatedAssumptionTypeOpt = AssumptionAnalyzer.extractAssumptionTypeFromInfo(stmt.info) + val annotatedAssumptionTypeOpt = DependencyAnalyzer.extractAssumptionTypeFromInfo(stmt.info) val executed = stmt match { case ast.Seqn(stmts, _) => @@ -448,7 +448,7 @@ object executor extends ExecutionRules { v1.decider.assume(FieldTrigger(field.name, sm, tRcvr), debugExp2, AssumptionType.Trigger) } v1.decider.analysisSourceInfoStack.removeForcedSource() - v1.decider.assumptionAnalyzer.addCustomTransitiveDependency(lhsSourceInfo, v1.decider.analysisSourceInfoStack.getFullSourceInfo) + v1.decider.dependencyAnalyzer.addCustomTransitiveDependency(lhsSourceInfo, v1.decider.analysisSourceInfoStack.getFullSourceInfo) val s4 = s3.copy(h = h3 + ch) val (debugHeapName, _) = v.getDebugOldLabel(s4, fa.pos) val s5 = if (withExp) s4.copy(oldHeaps = s4.oldHeaps + (debugHeapName -> magicWandSupporter.getEvalHeap(s4))) else s4 @@ -478,7 +478,7 @@ object executor extends ExecutionRules { val s5 = s4.copy(h = h4) val (debugHeapName, _) = v4.getDebugOldLabel(s5, fa.pos) val s6 = if (withExp) s5.copy(oldHeaps = s5.oldHeaps + (debugHeapName -> magicWandSupporter.getEvalHeap(s5))) else s5 - v4.decider.assumptionAnalyzer.addCustomTransitiveDependency(lhsSourceInfo, v4.decider.analysisSourceInfoStack.getFullSourceInfo) + v4.decider.dependencyAnalyzer.addCustomTransitiveDependency(lhsSourceInfo, v4.decider.analysisSourceInfoStack.getFullSourceInfo) Q(s6, v4) }) }) @@ -521,7 +521,7 @@ object executor extends ExecutionRules { case _ => produce(s, freshSnap, a, InhaleFailed(inhale), v, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit))((s1, v1) => { v1.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterInhale) - if(Verifier.config.enableAssumptionAnalysis() && a.isInstanceOf[ast.FalseLit]) { + if(Verifier.config.enableDependencyAnalysis() && a.isInstanceOf[ast.FalseLit]) { val (_, node) = v1.decider.checkAndGetInfeasibilityNode(False, Verifier.config.checkTimeout(), AssumptionType.Explicit) v1.decider.pcs.setCurrentInfeasibilityNode(node) } @@ -604,8 +604,8 @@ object executor extends ExecutionRules { val pveCall = CallFailed(call) val pveCallTransformed = pveCall.withReasonNodeTransformed(reasonTransformer) - val methodAnnotatedAssumptionType = AssumptionAnalyzer.extractAssumptionTypeFromInfo(meth.info) // TODO: make sure the join can still be made? - val defaultAssumptionType = if(meth.body.isDefined || !AssumptionAnalyzer.extractEnableAnalysisFromInfo(meth.info).getOrElse(true)) AssumptionType.ImplicitPostcondAssumption else AssumptionType.ExplicitPostcondAssumption + val methodAnnotatedAssumptionType = DependencyAnalyzer.extractAssumptionTypeFromInfo(meth.info) // TODO: make sure the join can still be made? + val defaultAssumptionType = if(meth.body.isDefined || !DependencyAnalyzer.extractEnableAnalysisFromInfo(meth.info).getOrElse(true)) AssumptionType.ImplicitPostcondAssumption else AssumptionType.ExplicitPostcondAssumption val finalAssumptionType = annotatedAssumptionTypeOpt.getOrElse(methodAnnotatedAssumptionType.getOrElse(defaultAssumptionType)) val mcLog = new MethodCallRecord(call, s, v.decider.pcs) @@ -650,7 +650,7 @@ object executor extends ExecutionRules { v.decider.startDebugSubExp() val ePerm = pap.perm val predicate = s.program.findPredicate(predicateName) - val predicateAnnotatedAssumptionType = AssumptionAnalyzer.extractAssumptionTypeFromInfo(predicate.info) + val predicateAnnotatedAssumptionType = DependencyAnalyzer.extractAssumptionTypeFromInfo(predicate.info) val finalAssumptionType = annotatedAssumptionTypeOpt.getOrElse(predicateAnnotatedAssumptionType.getOrElse(AssumptionType.Rewrite)) val pve = FoldFailed(fold) evals(s, eArgs, _ => pve, v)((s1, tArgs, eArgsNew, v1) => @@ -668,7 +668,7 @@ object executor extends ExecutionRules { v.decider.startDebugSubExp() val ePerm = pap.perm val predicate = s.program.findPredicate(predicateName) - val predicateAnnotatedAssumptionType = AssumptionAnalyzer.extractAssumptionTypeFromInfo(predicate.info) + val predicateAnnotatedAssumptionType = DependencyAnalyzer.extractAssumptionTypeFromInfo(predicate.info) val finalAssumptionType = annotatedAssumptionTypeOpt.getOrElse(predicateAnnotatedAssumptionType.getOrElse(AssumptionType.Rewrite)) val pve = UnfoldFailed(unfold) evals(s, eArgs, _ => pve, v)((s1, tArgs, eArgsNew, v1) => @@ -777,7 +777,7 @@ object executor extends ExecutionRules { private def ssaifyRhs(rhs: Term, rhsExp: ast.Exp, rhsExpNew: Option[ast.Exp], name: String, typ: ast.Type, v: Verifier, s : State, assumptionType: AssumptionType): (Term, Option[ast.Exp]) = { rhs match { - case _: Var | _: Literal if !Verifier.config.enableAssumptionAnalysis() => + case _: Var | _: Literal if !Verifier.config.enableDependencyAnalysis() => (rhs, rhsExpNew) case _ => diff --git a/src/main/scala/rules/HavocSupporter.scala b/src/main/scala/rules/HavocSupporter.scala index b5bff573a..f0745627d 100644 --- a/src/main/scala/rules/HavocSupporter.scala +++ b/src/main/scala/rules/HavocSupporter.scala @@ -8,8 +8,8 @@ package viper.silicon.rules import viper.silicon.debugger.DebugExp import viper.silicon.Map -import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType -import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType, StmtAnalysisSourceInfo} +import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType +import viper.silicon.dependencyAnalysis.{AnalysisInfo, AssumptionType, StmtAnalysisSourceInfo} import viper.silicon.interfaces.VerificationResult import viper.silicon.interfaces.state.{Chunk, NonQuantifiedChunk, QuantifiedChunk} import viper.silicon.rules.evaluator.{eval, evalQuantified, evals} diff --git a/src/main/scala/rules/Joiner.scala b/src/main/scala/rules/Joiner.scala index 31f4b41c8..5492f349e 100644 --- a/src/main/scala/rules/Joiner.scala +++ b/src/main/scala/rules/Joiner.scala @@ -6,8 +6,8 @@ package viper.silicon.rules -import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType -import viper.silicon.assumptionAnalysis.{AnalysisInfo, AssumptionType, StringAnalysisSourceInfo} +import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType +import viper.silicon.dependencyAnalysis.{AnalysisInfo, AssumptionType, StringAnalysisSourceInfo} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.decider.RecordedPathConditions @@ -25,12 +25,12 @@ case class JoinDataEntry[D](s: State, data: D, pathConditions: RecordedPathCondi // we can directly merge JoinDataEntries to obtain new States, // and the join data entries themselves provide information about the path conditions to State.merge. def pathConditionAwareMerge(other: JoinDataEntry[D], v: Verifier): State = { - val res = State.merge(this.s, this.pathConditions, other.s, other.pathConditions, AnalysisInfo(v.decider, v.decider.assumptionAnalyzer, StringAnalysisSourceInfo("merge", NoPosition), AssumptionType.Implicit)) + val res = State.merge(this.s, this.pathConditions, other.s, other.pathConditions, AnalysisInfo(v.decider, v.decider.dependencyAnalyzer, StringAnalysisSourceInfo("merge", NoPosition), AssumptionType.Implicit)) v.stateConsolidator(s).consolidate(res, v) } def pathConditionAwareMergeWithoutConsolidation(other: JoinDataEntry[D], v: Verifier): State = { - State.merge(this.s, this.pathConditions, other.s, other.pathConditions, AnalysisInfo(v.decider, v.decider.assumptionAnalyzer, StringAnalysisSourceInfo("merge", NoPosition), AssumptionType.Implicit)) + State.merge(this.s, this.pathConditions, other.s, other.pathConditions, AnalysisInfo(v.decider, v.decider.dependencyAnalyzer, StringAnalysisSourceInfo("merge", NoPosition), AssumptionType.Implicit)) } } diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 94021df47..46a33b49a 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -7,8 +7,8 @@ package viper.silicon.rules import viper.silicon._ -import viper.silicon.assumptionAnalysis.AssumptionType -import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType +import viper.silicon.dependencyAnalysis.AssumptionType +import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.decider.RecordedPathConditions @@ -427,7 +427,7 @@ object magicWandSupporter extends SymbolicExecutionRules { } // some of the analysis labels, introduced while verifying the package statement, might be needed later on -> reassume them - analysisLabels foreach (l => v.decider.assume(v.decider.wrapWithAssumptionAnalysisLabel(l, Set.empty, Set(l)), None, AssumptionType.Internal)) + analysisLabels foreach (l => v.decider.assume(v.decider.wrapWithDependencyAnalysisLabel(l, Set.empty, Set(l)), None, AssumptionType.Internal)) val currentAnalysisSourceInfos = v.decider.analysisSourceInfoStack.getAnalysisSourceInfos recordedBranches.foldLeft(tempResult)((prevRes, recordedState) => { @@ -444,10 +444,10 @@ object magicWandSupporter extends SymbolicExecutionRules { val exp = viper.silicon.utils.ast.BigAnd(branchConditionsExp.map(_._1)) val expNew = Option.when(withExp)(viper.silicon.utils.ast.BigAnd(branchConditionsExp.map(_._2.get))) // Set the branch conditions - v1.decider.setCurrentBranchCondition(And(branchConditions map (t => v1.decider.wrapWithAssumptionAnalysisLabel(t, Set.empty, Set(t)))), (exp, expNew)) + v1.decider.setCurrentBranchCondition(And(branchConditions map (t => v1.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t)))), (exp, expNew)) // Recreate all path conditions in the Z3 proof script that we recorded for that branch - v1.decider.assume(conservedPcs._1 map (t => v1.decider.wrapWithAssumptionAnalysisLabel(t, Set.empty, Set(t))), conservedPcs._2, AssumptionType.Internal) + v1.decider.assume(conservedPcs._1 map (t => v1.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t))), conservedPcs._2, AssumptionType.Internal) // Execute the continuation Q Q(s2, magicWandChunk, v1) diff --git a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala index 39487301a..49198b428 100644 --- a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala +++ b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala @@ -6,8 +6,8 @@ package viper.silicon.rules -import viper.silicon.assumptionAnalysis.AssumptionType -import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType +import viper.silicon.dependencyAnalysis.AssumptionType +import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.state._ import viper.silicon.interfaces.{Success, VerificationResult} @@ -480,7 +480,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { newFr = newFr.recordPathSymbol(permTaken.applicable.asInstanceOf[Function]).recordConstraint(constraint) - @unused // required in order to ensure a sound assumption analysis + @unused // required in order to ensure a sound dependency analysis val _ = GeneralChunk.withPerm(ch, permTaken, None, v.decider.getAnalysisInfo(assumptionType), isExhale=true) NonQuantifiedChunk.withPerm(ch, PermMinus(ch.perm, permTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, permTakenExp.get)(pe.pos, pe.info, pe.errT)), v.decider.getAnalysisInfo(AssumptionType.Implicit)) }) diff --git a/src/main/scala/rules/PredicateSupporter.scala b/src/main/scala/rules/PredicateSupporter.scala index b6f6423b7..ab60ed535 100644 --- a/src/main/scala/rules/PredicateSupporter.scala +++ b/src/main/scala/rules/PredicateSupporter.scala @@ -6,8 +6,8 @@ package viper.silicon.rules -import viper.silicon.assumptionAnalysis.AssumptionType -import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType +import viper.silicon.dependencyAnalysis.AssumptionType +import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.VerificationResult diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index 2b845b620..03d075148 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -7,8 +7,8 @@ package viper.silicon.rules import viper.silicon.Config.JoinMode -import viper.silicon.assumptionAnalysis.{AssumptionAnalyzer, AssumptionType, ExpAnalysisSourceInfo} -import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType +import viper.silicon.dependencyAnalysis.{DependencyAnalyzer, AssumptionType, ExpAnalysisSourceInfo} +import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.{Unreachable, VerificationResult} import viper.silicon.logger.records.data.{CondExpRecord, ImpliesRecord, ProduceRecord} @@ -228,7 +228,7 @@ object producer extends ProductionRules { _assumptionType: AssumptionType) (continuation: (State, Verifier) => VerificationResult) : VerificationResult = { - val assumptionType = AssumptionAnalyzer.extractAssumptionTypeFromInfo(a.info).getOrElse(_assumptionType) + val assumptionType = DependencyAnalyzer.extractAssumptionTypeFromInfo(a.info).getOrElse(_assumptionType) v.logger.debug(s"\nPRODUCE ${viper.silicon.utils.ast.sourceLineColumn(a)}: $a") v.logger.debug(v.stateFormatter.format(s, v.decider.pcs)) diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index c2e2915cf..0b02bfc87 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -7,8 +7,8 @@ package viper.silicon.rules import viper.silicon.Map -import viper.silicon.assumptionAnalysis.AssumptionType -import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType +import viper.silicon.dependencyAnalysis.AssumptionType +import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.VerificationResult @@ -733,7 +733,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v: Verifier) : (PermMapDefinition, PmCache) = { v.decider.analysisSourceInfoStack.setForcedSource("summarizing heap") - v.decider.assumptionAnalyzer.disableTransitiveEdges() + v.decider.dependencyAnalyzer.disableTransitiveEdges() val res = Verifier.config.mapCache(s.pmCache.get(resource, relevantChunks)) match { case Some(pmDef) => v.decider.assume(pmDef.valueDefinitions, Option.when(withExp)(DebugExp.createInstance("value definitions", isInternal_ = true)), enforceAssumption = false, assumptionType=AssumptionType.Internal) @@ -746,7 +746,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { (pmDef, s.pmCache + ((resource, relevantChunks) -> pmDef)) } v.decider.analysisSourceInfoStack.removeForcedSource() - v.decider.assumptionAnalyzer.enableTransitiveEdges() + v.decider.dependencyAnalyzer.enableTransitiveEdges() res } @@ -775,7 +775,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optQVarsInstantiations: Option[Seq[Term]] = None) : (SnapshotMapDefinition, SnapshotMapCache) = { v.decider.analysisSourceInfoStack.setForcedSource("summarizing heap") - v.decider.assumptionAnalyzer.disableTransitiveEdges() + v.decider.dependencyAnalyzer.disableTransitiveEdges() def emitSnapshotMapDefinition(s: State, smDef: SnapshotMapDefinition, @@ -847,7 +847,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { emitSnapshotMapDefinition(s, smDef, v, optQVarsInstantiations) v.decider.analysisSourceInfoStack.removeForcedSource() - v.decider.assumptionAnalyzer.enableTransitiveEdges() + v.decider.dependencyAnalyzer.enableTransitiveEdges() (smDef, smCache) } @@ -860,7 +860,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optQVarsInstantiations: Option[Seq[Term]] = None) : (State, SnapshotMapDefinition, PermMapDefinition) = { v.decider.analysisSourceInfoStack.setForcedSource("summarizing heap") - v.decider.assumptionAnalyzer.disableTransitiveEdges() + v.decider.dependencyAnalyzer.disableTransitiveEdges() val (smDef, smCache) = summarisingSnapshotMap( s, resource, codomainQVars, relevantChunks, v, optSmDomainDefinitionCondition, optQVarsInstantiations) @@ -873,7 +873,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val s2 = s1.copy(pmCache = pmCache) v.decider.analysisSourceInfoStack.removeForcedSource() - v.decider.assumptionAnalyzer.enableTransitiveEdges() + v.decider.dependencyAnalyzer.enableTransitiveEdges() (s2, smDef, pmDef) } @@ -887,7 +887,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v: Verifier) : (State, PermMapDefinition) = { v.decider.analysisSourceInfoStack.setForcedSource("summarizing heap") - v.decider.assumptionAnalyzer.disableTransitiveEdges() + v.decider.dependencyAnalyzer.disableTransitiveEdges() val s1 = s val (pmDef, pmCache) = quantifiedChunkSupporter.summarisingPermissionMap( @@ -895,7 +895,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val s2 = s1.copy(pmCache = pmCache) v.decider.analysisSourceInfoStack.removeForcedSource() - v.decider.assumptionAnalyzer.enableTransitiveEdges() + v.decider.dependencyAnalyzer.enableTransitiveEdges() (s2, pmDef) } diff --git a/src/main/scala/rules/StateConsolidator.scala b/src/main/scala/rules/StateConsolidator.scala index 859c62820..0c9ea1d07 100644 --- a/src/main/scala/rules/StateConsolidator.scala +++ b/src/main/scala/rules/StateConsolidator.scala @@ -7,7 +7,7 @@ package viper.silicon.rules import viper.silicon.Config -import viper.silicon.assumptionAnalysis.AssumptionType +import viper.silicon.dependencyAnalysis.AssumptionType import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.state._ @@ -101,16 +101,16 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol mergedChunks.filter(_.isInstanceOf[BasicChunk]) foreach { case ch: BasicChunk => val resource = Resources.resourceDescriptions(ch.resourceID) val pathCond = interpreter.buildPathConditionsForChunk(ch, resource.instanceProperties(s.mayAssumeUpperBounds)) - v.decider.assumptionAnalyzer.disableTransitiveEdges() - pathCond.foreach(p => v.decider.assume(v.decider.wrapWithAssumptionAnalysisLabel(p._1, Set(ch)), Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), AssumptionType.Internal)) - v.decider.assumptionAnalyzer.enableTransitiveEdges() + v.decider.dependencyAnalyzer.disableTransitiveEdges() + pathCond.foreach(p => v.decider.assume(v.decider.wrapWithDependencyAnalysisLabel(p._1, Set(ch)), Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), AssumptionType.Internal)) + v.decider.dependencyAnalyzer.enableTransitiveEdges() } Resources.resourceDescriptions foreach { case (id, desc) => val pathCond = interpreter.buildPathConditionsForResource(id, desc.delayedProperties(s.mayAssumeUpperBounds)) - v.decider.assumptionAnalyzer.disableTransitiveEdges() + v.decider.dependencyAnalyzer.disableTransitiveEdges() pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), AssumptionType.Internal)) - v.decider.assumptionAnalyzer.enableTransitiveEdges() + v.decider.dependencyAnalyzer.enableTransitiveEdges() } v.symbExLog.closeScope(sepIdentifier) @@ -211,8 +211,8 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol private def mergeChunks(fr1: FunctionRecorder, chunk1: Chunk, chunk2: Chunk, qvars: Seq[Var], v: Verifier): Option[(FunctionRecorder, Chunk, Term)] = { val result = mergeChunks1(fr1, chunk1, chunk2, qvars, v) result.map({case (fRec, ch, snapEq) => - v.decider.assumptionAnalyzer.addPermissionDependencies(Set(chunk1, chunk2), Set(), ch) - (fRec, ch, v.decider.wrapWithAssumptionAnalysisLabel(snapEq, Set(chunk1, chunk2)))}) + v.decider.dependencyAnalyzer.addPermissionDependencies(Set(chunk1, chunk2), Set(), ch) + (fRec, ch, v.decider.wrapWithDependencyAnalysisLabel(snapEq, Set(chunk1, chunk2)))}) } private def mergeChunks1(fr1: FunctionRecorder, chunk1: Chunk, chunk2: Chunk, qvars: Seq[Var], v: Verifier): Option[(FunctionRecorder, Chunk, Term)] = (chunk1, chunk2) match { diff --git a/src/main/scala/state/Chunks.scala b/src/main/scala/state/Chunks.scala index bdc344229..ce3a4c8b2 100644 --- a/src/main/scala/state/Chunks.scala +++ b/src/main/scala/state/Chunks.scala @@ -6,7 +6,7 @@ package viper.silicon.state -import viper.silicon.assumptionAnalysis.AnalysisInfo +import viper.silicon.dependencyAnalysis.AnalysisInfo import viper.silicon.interfaces.state._ import viper.silicon.resources._ import viper.silicon.rules.InverseFunctions diff --git a/src/main/scala/state/State.scala b/src/main/scala/state/State.scala index a0a32284d..b0a7198e7 100644 --- a/src/main/scala/state/State.scala +++ b/src/main/scala/state/State.scala @@ -8,7 +8,7 @@ package viper.silicon.state import viper.silicon.Config.JoinMode import viper.silicon.Config.JoinMode.JoinMode -import viper.silicon.assumptionAnalysis.AnalysisInfo +import viper.silicon.dependencyAnalysis.AnalysisInfo import viper.silver.ast import viper.silver.cfg.silver.SilverCfg import viper.silicon.common.Mergeable diff --git a/src/main/scala/supporters/Domains.scala b/src/main/scala/supporters/Domains.scala index 38320937a..82184c211 100644 --- a/src/main/scala/supporters/Domains.scala +++ b/src/main/scala/supporters/Domains.scala @@ -6,8 +6,8 @@ package viper.silicon.supporters -import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType -import viper.silicon.assumptionAnalysis.{AnalysisSourceInfo, AssumptionAnalyzer, AssumptionType, ExpAnalysisSourceInfo} +import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType +import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, DependencyAnalyzer, AssumptionType, ExpAnalysisSourceInfo} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.common.collections.immutable.MultiMap._ import viper.silicon.interfaces.PreambleContributor @@ -100,12 +100,12 @@ class DefaultDomainsContributor(symbolConverter: SymbolConverter, } }) - val isAnalysisForDomainEnabled = AssumptionAnalyzer.extractEnableAnalysisFromInfo(domain.info).getOrElse(true) + val isAnalysisForDomainEnabled = DependencyAnalyzer.extractEnableAnalysisFromInfo(domain.info).getOrElse(true) domain.axioms foreach (axiom => { val tAx = domainTranslator.translateAxiom(axiom, symbolConverter.toSort) val tAxPres = FunctionPreconditionTransformer.transform(tAx, program) - val enableAnalysis = AssumptionAnalyzer.extractEnableAnalysisFromInfo(axiom.info).getOrElse(isAnalysisForDomainEnabled) + val enableAnalysis = DependencyAnalyzer.extractEnableAnalysisFromInfo(axiom.info).getOrElse(isAnalysisForDomainEnabled) collectedAxioms = collectedAxioms.incl((terms.And(tAxPres, tAx), Option.when(enableAnalysis)((ExpAnalysisSourceInfo(axiom.exp), AssumptionType.Explicit)))) }) }) diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index eee09089a..902383ee9 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -7,7 +7,7 @@ package viper.silicon.supporters import com.typesafe.scalalogging.Logger -import viper.silicon.assumptionAnalysis.{AssumptionAnalysisInterpreter, AssumptionAnalyzer, AssumptionType} +import viper.silicon.dependencyAnalysis.{DependencyGraphInterpreter, DependencyAnalyzer, AssumptionType} import viper.silicon.decider.Decider import viper.silicon.interfaces._ import viper.silicon.logger.records.data.WellformednessCheckRecord @@ -93,7 +93,7 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif new java.io.File(s"${Verifier.config.tempDirectory()}/${method.name}.dot")) } - val annotatedAssumptionTypeOpt = AssumptionAnalyzer.extractAssumptionTypeFromInfo(method.info) + val annotatedAssumptionTypeOpt = DependencyAnalyzer.extractAssumptionTypeFromInfo(method.info) val postConditionType = annotatedAssumptionTypeOpt.getOrElse(if(method.body.isDefined) AssumptionType.ImplicitPostcondition else AssumptionType.ExplicitPostcondition) errorsReportedSoFar.set(0) @@ -119,10 +119,10 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif Success()))}) } )})}) if(method.body.isEmpty){ - v.decider.assumptionAnalyzer.addCustomExpDependency(method.pres.flatMap(_.topLevelConjuncts), method.posts.flatMap(_.topLevelConjuncts)) + v.decider.dependencyAnalyzer.addCustomExpDependency(method.pres.flatMap(_.topLevelConjuncts), method.posts.flatMap(_.topLevelConjuncts)) } - result.assumptionAnalysisInterpreter = v.decider.assumptionAnalyzer.buildFinalGraph().map(new AssumptionAnalysisInterpreter(method.name, _, Some(method))) + result.dependencyGraphInterpreter = v.decider.dependencyAnalyzer.buildFinalGraph().map(new DependencyGraphInterpreter(method.name, _, Some(method))) v.decider.resetProverOptions() diff --git a/src/main/scala/supporters/PredicateVerificationUnit.scala b/src/main/scala/supporters/PredicateVerificationUnit.scala index 3b1aa2a72..3a48542ab 100644 --- a/src/main/scala/supporters/PredicateVerificationUnit.scala +++ b/src/main/scala/supporters/PredicateVerificationUnit.scala @@ -7,7 +7,7 @@ package viper.silicon.supporters import com.typesafe.scalalogging.Logger -import viper.silicon.assumptionAnalysis.{AssumptionAnalyzer, AssumptionType} +import viper.silicon.dependencyAnalysis.{DependencyAnalyzer, AssumptionType} import viper.silver.ast import viper.silver.ast.Program import viper.silver.components.StatefulComponent @@ -93,7 +93,7 @@ trait DefaultPredicateVerificationUnitProvider extends VerifierComponent { v: Ve oldHeaps = OldHeaps()) val err = PredicateNotWellformed(predicate) - val assumptionType = AssumptionAnalyzer.extractAssumptionTypeFromInfo(predicate.info).getOrElse(AssumptionType.Explicit) + val assumptionType = DependencyAnalyzer.extractAssumptionTypeFromInfo(predicate.info).getOrElse(AssumptionType.Explicit) val result = predicate.body match { case None => diff --git a/src/main/scala/supporters/SnapshotSupporter.scala b/src/main/scala/supporters/SnapshotSupporter.scala index 4e3b71297..8b5b0babe 100644 --- a/src/main/scala/supporters/SnapshotSupporter.scala +++ b/src/main/scala/supporters/SnapshotSupporter.scala @@ -6,8 +6,8 @@ package viper.silicon.supporters -import viper.silicon.assumptionAnalysis.AssumptionType -import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType +import viper.silicon.dependencyAnalysis.AssumptionType +import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType import viper.silicon.debugger.DebugExp import viper.silicon.state.terms.{Combine, First, Second, Sort, Term, Unit, sorts} import viper.silicon.state.{MagicWandIdentifier, State, SymbolConverter} diff --git a/src/main/scala/supporters/functions/FunctionData.scala b/src/main/scala/supporters/functions/FunctionData.scala index ac32b9fae..41469bcda 100644 --- a/src/main/scala/supporters/functions/FunctionData.scala +++ b/src/main/scala/supporters/functions/FunctionData.scala @@ -7,8 +7,8 @@ package viper.silicon.supporters.functions import com.typesafe.scalalogging.LazyLogging -import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType -import viper.silicon.assumptionAnalysis.{AnalysisSourceInfo, AssumptionAnalyzer, AssumptionType, ExpAnalysisSourceInfo, StringAnalysisSourceInfo} +import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType +import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, DependencyAnalyzer, AssumptionType, ExpAnalysisSourceInfo, StringAnalysisSourceInfo} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.interfaces.FatalResult import viper.silicon.rules.{InverseFunctions, SnapshotMapDefinition, functionSupporter} @@ -79,7 +79,7 @@ class FunctionData(val programFunction: ast.Function, else Seq.fill(1 + formalArgs.size)(None) - val isAnalysisEnabled: Boolean = Verifier.config.enableAssumptionAnalysis() && AssumptionAnalyzer.extractEnableAnalysisFromInfo(programFunction.info).getOrElse(true) + val isAnalysisEnabled: Boolean = Verifier.config.enableDependencyAnalysis() && DependencyAnalyzer.extractEnableAnalysisFromInfo(programFunction.info).getOrElse(true) val functionApplication = App(function, `?s` +: formalArgs.values.toSeq) val limitedFunctionApplication = App(limitedFunction, `?s` +: formalArgs.values.toSeq) @@ -216,7 +216,7 @@ class FunctionData(val programFunction: ast.Function, def wrapBody(body: Term): Term = Let(toMap(bodyBindings), body) - if(Verifier.config.enableAssumptionAnalysis()){ + if(Verifier.config.enableDependencyAnalysis()){ val assumptionType = if(programFunction.body.isDefined) AssumptionType.ImplicitPostcondition else AssumptionType.ExplicitPostcondition (Forall(arguments, wrapBody(And(generateNestedDefinitionalAxioms)), Trigger(limitedFunctionApplication)), Option.empty[(AnalysisSourceInfo, AssumptionType)]) +: programFunction.posts.flatMap(_.topLevelConjuncts).map({p => diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index cd48cc1e9..4cc97ec5b 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -7,8 +7,8 @@ package viper.silicon.supporters.functions import com.typesafe.scalalogging.Logger -import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType -import viper.silicon.assumptionAnalysis._ +import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType +import viper.silicon.dependencyAnalysis._ import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.decider.Decider @@ -161,9 +161,9 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver val res = handleFunction(sInit, function) symbExLog.closeMemberScope() - v.decider.assumptionAnalyzer.addFunctionAxiomEdges() + v.decider.dependencyAnalyzer.addFunctionAxiomEdges() - res.assumptionAnalysisInterpreter = v.decider.assumptionAnalyzer.buildFinalGraph().map(new AssumptionAnalysisInterpreter(function.name, _, Some(function))) + res.dependencyGraphInterpreter = v.decider.dependencyAnalyzer.buildFinalGraph().map(new DependencyGraphInterpreter(function.name, _, Some(function))) Seq(res) } @@ -189,8 +189,8 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver this.postConditionAxioms = this.postConditionAxioms ++ data.postAxiom if (function.body.isEmpty) { - decider.assumptionAnalyzer.addNodes(v.decider.prover.getPreambleAnalysisNodes) - decider.assumptionAnalyzer.addCustomExpDependency(function.pres.flatMap(_.topLevelConjuncts), function.posts.flatMap(_.topLevelConjuncts)) + decider.dependencyAnalyzer.addNodes(v.decider.prover.getPreambleAnalysisNodes) + decider.dependencyAnalyzer.addCustomExpDependency(function.pres.flatMap(_.topLevelConjuncts), function.posts.flatMap(_.topLevelConjuncts)) result1 } else { /* Phase 2: Verify the function's postcondition */ @@ -260,17 +260,17 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver var recorders: Seq[FunctionRecorder] = Vector.empty val wExp = evaluator.withExp - val annotatedAssumptionTypeOpt = AssumptionAnalyzer.extractAssumptionTypeFromInfo(function.info) + val annotatedAssumptionTypeOpt = DependencyAnalyzer.extractAssumptionTypeFromInfo(function.info) val postConditionType = annotatedAssumptionTypeOpt.getOrElse(if(function.body.isDefined) AssumptionType.ImplicitPostcondition else AssumptionType.ExplicitPostcondition) - decider.assumptionAnalyzer.addNodes(v.decider.prover.getPreambleAnalysisNodes) + decider.dependencyAnalyzer.addNodes(v.decider.prover.getPreambleAnalysisNodes) val result = phase1data.foldLeft(Success(): VerificationResult) { case (fatalResult: FatalResult, _) => fatalResult case (intermediateResult, Phase1Data(sPre, bcsPre, bcsPreExp, pcsPre, pcsPreExp)) => intermediateResult && executionFlowController.locally(sPre, v)((s1, _) => { - val labelledBcsPre = And(bcsPre map (t => v.decider.wrapWithAssumptionAnalysisLabel(t, Set.empty, Set(t)))) + val labelledBcsPre = And(bcsPre map (t => v.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t)))) decider.setCurrentBranchCondition(labelledBcsPre, (BigAnd(bcsPreExp.map(_._1)), Option.when(wExp)(BigAnd(bcsPreExp.map(_._2.get))))) - val labelledPcsPre = pcsPre map (t => v.decider.wrapWithAssumptionAnalysisLabel(t, Set.empty, Set(t))) + val labelledPcsPre = pcsPre map (t => v.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t))) decider.assume(labelledPcsPre, pcsPreExp, s"precondition of ${function.name}", enforceAssumption=false, assumptionType=annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit)) v.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) eval(s1, body, FunctionNotWellformed(function), v)((s2, tBody, bodyNew, _) => { @@ -293,9 +293,9 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver private def emitAndRecordFunctionAxioms(axiom: (Term, Option[(AnalysisSourceInfo, AssumptionType)])*): Unit = { val cleanAxiom = - if(!Verifier.config.enableAssumptionAnalysis()) axiom + if(!Verifier.config.enableDependencyAnalysis()) axiom else axiom.map(a => (a._1.transform{ - case Var(name, _, _) if name.name.startsWith(AssumptionAnalyzer.analysisLabelName) => True + case Var(name, _, _) if name.name.startsWith(DependencyAnalyzer.analysisLabelName) => True }(), a._2)) decider.prover.assumeAxiomsWithAnalysisInfo(InsertionOrderedSet(cleanAxiom), "Function axioms") diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index 68e13ddc3..8b04294e6 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -6,10 +6,10 @@ package viper.silicon.verifier -import silicon.viper.assumptionAnalysis.AssumptionAnalysisUserTool +import viper.silicon.dependencyAnalysis.DependencyAnalysisUserTool import viper.silicon.Config.{ExhaleMode, JoinMode} import viper.silicon._ -import viper.silicon.assumptionAnalysis.{AssumptionAnalyzer, DependencyAnalysisReporter} +import viper.silicon.dependencyAnalysis.{DependencyAnalyzer, DependencyAnalysisReporter} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.SiliconDebugger import viper.silicon.decider.SMTLib2PreambleReader @@ -230,10 +230,10 @@ class DefaultMainVerifier(config: Config, */ val functionVerificationResults = functionsSupporter.units.toList flatMap (function => { val startTime = System.currentTimeMillis() - decider.initAssumptionAnalyzer(function, allProvers.getPreambleAnalysisNodes ++ decider.prover.getPreambleAnalysisNodes) + decider.initDependencyAnalyzer(function, allProvers.getPreambleAnalysisNodes ++ decider.prover.getPreambleAnalysisNodes) val results = functionsSupporter.verify(createInitialState(function, program, functionData, predicateData), function) .flatMap(extractAllVerificationResults) - decider.removeAssumptionAnalyzer() + decider.removeDependencyAnalyzer() val elapsed = System.currentTimeMillis() - startTime reporter report VerificationResultMessage(s"silicon", function, elapsed, condenseToViperResult(results)) logger debug s"Silicon finished verification of function `${function.name}` in ${viper.silver.reporter.format.formatMillisReadably(elapsed)} seconds with the following result: ${condenseToViperResult(results).toString}" @@ -242,10 +242,10 @@ class DefaultMainVerifier(config: Config, val predicateVerificationResults = predicateSupporter.units.toList flatMap (predicate => { val startTime = System.currentTimeMillis() - decider.initAssumptionAnalyzer(predicate, allProvers.getPreambleAnalysisNodes ++ decider.prover.getPreambleAnalysisNodes) + decider.initDependencyAnalyzer(predicate, allProvers.getPreambleAnalysisNodes ++ decider.prover.getPreambleAnalysisNodes) val results = predicateSupporter.verify(createInitialState(predicate, program, functionData, predicateData), predicate) .flatMap(extractAllVerificationResults) - decider.removeAssumptionAnalyzer() + decider.removeDependencyAnalyzer() val elapsed = System.currentTimeMillis() - startTime reporter report VerificationResultMessage(s"silicon", predicate, elapsed, condenseToViperResult(results)) logger debug s"Silicon finished verification of predicate `${predicate.name}` in ${viper.silver.reporter.format.formatMillisReadably(elapsed)} seconds with the following result: ${condenseToViperResult(results).toString}" @@ -273,10 +273,10 @@ class DefaultMainVerifier(config: Config, _verificationPoolManager.queueVerificationTask(v => { val startTime = System.currentTimeMillis() - v.decider.initAssumptionAnalyzer(method, allProvers.getPreambleAnalysisNodes ++ v.decider.prover.getPreambleAnalysisNodes) + v.decider.initDependencyAnalyzer(method, allProvers.getPreambleAnalysisNodes ++ v.decider.prover.getPreambleAnalysisNodes) val results = v.methodSupporter.verify(s, method) .flatMap(extractAllVerificationResults) - v.decider.removeAssumptionAnalyzer() + v.decider.removeDependencyAnalyzer() val elapsed = System.currentTimeMillis() - startTime reporter report VerificationResultMessage(s"silicon", method, elapsed, condenseToViperResult(results)) @@ -314,24 +314,24 @@ class DefaultMainVerifier(config: Config, ++ predicateVerificationResults ++ methodVerificationResults) - if(Verifier.config.enableAssumptionAnalysis()){ - val assumptionAnalysisInterpreters = verificationResults.filter(_.assumptionAnalysisInterpreter.isDefined).map(_.assumptionAnalysisInterpreter.get) + if(Verifier.config.enableDependencyAnalysis()){ + val dependencyGraphInterpreters = verificationResults.filter(_.dependencyGraphInterpreter.isDefined).map(_.dependencyGraphInterpreter.get) - assumptionAnalysisInterpreters foreach (_.exportGraph()) + dependencyGraphInterpreters foreach (_.exportGraph()) - val joinedGraphInterpreter = AssumptionAnalyzer.joinGraphsAndGetInterpreter(inputFile.map(_.replaceAll("\\\\", "_").replaceAll(".vpr", "")), assumptionAnalysisInterpreters.toSet) - if(Verifier.config.assumptionAnalysisExportPath.isDefined) + val joinedGraphInterpreter = DependencyAnalyzer.joinGraphsAndGetInterpreter(inputFile.map(_.replaceAll("\\\\", "_").replaceAll(".vpr", "")), dependencyGraphInterpreters.toSet) + if(Verifier.config.dependencyAnalysisExportPath.isDefined) joinedGraphInterpreter.exportGraph() - if(Verifier.config.startAssumptionAnalysisTool()){ - val commandLineTool = new AssumptionAnalysisUserTool(joinedGraphInterpreter, assumptionAnalysisInterpreters, originalProgram) + if(Verifier.config.startDependencyAnalysisTool()){ + val commandLineTool = new DependencyAnalysisUserTool(joinedGraphInterpreter, dependencyGraphInterpreters, originalProgram) commandLineTool.run() } reporter match { case analysisReporter: DependencyAnalysisReporter => - analysisReporter.assumptionAnalysisInterpretersPerMember = assumptionAnalysisInterpreters - analysisReporter.joinedAssumptionAnalysisInterpreter = Some(joinedGraphInterpreter) + analysisReporter.dependencyGraphInterpretersPerMember = dependencyGraphInterpreters + analysisReporter.joinedDependencyGraphInterpreter = Some(joinedGraphInterpreter) case _ => } diff --git a/src/main/scala/verifier/VerificationPoolManager.scala b/src/main/scala/verifier/VerificationPoolManager.scala index d25d12ae9..e863be51d 100644 --- a/src/main/scala/verifier/VerificationPoolManager.scala +++ b/src/main/scala/verifier/VerificationPoolManager.scala @@ -9,8 +9,8 @@ package viper.silicon.verifier import org.apache.commons.pool2.impl.{DefaultPooledObject, GenericObjectPool, GenericObjectPoolConfig} import org.apache.commons.pool2.{BasePooledObjectFactory, ObjectPool, PoolUtils, PooledObject} import viper.silicon.Config -import viper.silicon.assumptionAnalysis.{AnalysisSourceInfo, AssumptionType} -import viper.silicon.assumptionAnalysis.AssumptionType.AssumptionType +import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType} +import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.interfaces.VerificationResult import viper.silicon.interfaces.decider.ProverLike diff --git a/src/test/resources/dependencyAnalysisTests/README.md b/src/test/resources/dependencyAnalysisTests/README.md index 23b35a928..c2e46b9d9 100644 --- a/src/test/resources/dependencyAnalysisTests/README.md +++ b/src/test/resources/dependencyAnalysisTests/README.md @@ -6,7 +6,7 @@ The programs for annotated tests can be found in the folders `all`, `unitTests`, Pruning tests are executed against all annotated tests and additionally the programs in folder `real-world-examples`. -Both are executed using `src/test/scala/AssumptionAnalysisTests.scala`. +Both are executed using `src/test/scala/DependencyAnalysisTests.scala`. By default, only expected dependencies are checked (`@dependency` annotations). Setting `CHECK_PRECISION=true` additionally checks precision through `@irrelevant` annotations and reports false positives as an error. @@ -20,7 +20,7 @@ Executing the benchmark: 1. Generate test programs: `python precisionTests\scripts\precision_test_generator.py` 1. Generates a number of subfolders in `precisionTests`. -1. Execute precision benchmark using `src/test/scala/AssumptionAnalysisPrecisionBenchmark.scala`. +1. Execute precision benchmark using `src/test/scala/DependencyAnalysisPrecisionBenchmark.scala`. 1. Results are written to `precisionTests\results\results_{timestamp}.out` 1. Plot the result: `python precisionTests\scripts\precision_benchmark_plotter.py` @@ -31,5 +31,5 @@ Executing the benchmark: The manually crafted examples correspond to programs found in folders `all` and `real-world-examples`. -To analyze the cause of imprecision, `src/test/scala/AssumptionAnalysisTests.scala` with `CHECK_PRECISION=true` can be executed on the program of interest. +To analyze the cause of imprecision, `src/test/scala/DependencyAnalysisTests.scala` with `CHECK_PRECISION=true` can be executed on the program of interest. Imprecise results are reported as `Unexpected dependency` and make the test fail. \ No newline at end of file diff --git a/src/test/scala/AssumptionAnalysisPrecisionBenchmark.scala b/src/test/scala/DependencyAnalysisPrecisionBenchmark.scala similarity index 78% rename from src/test/scala/AssumptionAnalysisPrecisionBenchmark.scala rename to src/test/scala/DependencyAnalysisPrecisionBenchmark.scala index 48994df42..f04787e78 100644 --- a/src/test/scala/AssumptionAnalysisPrecisionBenchmark.scala +++ b/src/test/scala/DependencyAnalysisPrecisionBenchmark.scala @@ -1,6 +1,6 @@ package viper.silicon.tests -import viper.silicon.assumptionAnalysis.{AssumptionAnalysisInterpreter, DependencyAnalysisReporter} +import viper.silicon.dependencyAnalysis.{DependencyGraphInterpreter, DependencyAnalysisReporter} import viper.silver.ast.Program import viper.silver.verifier import viper.silver.verifier.VerificationResult @@ -10,7 +10,7 @@ import java.time.LocalDateTime import java.time.format.DateTimeFormatter -object AssumptionAnalysisPrecisionBenchmark extends AssumptionAnalysisTestFramework { +object DependencyAnalysisPrecisionBenchmark extends DependencyAnalysisTestFramework { val ignores: Seq[String] = Seq.empty def main(args: Array[String]): Unit = { @@ -36,13 +36,13 @@ object AssumptionAnalysisPrecisionBenchmark extends AssumptionAnalysisTestFramew println(f"Program $filePrefix/$fileName does not verify. Skip.\n$result") return } - val assumptionAnalysisInterpreters = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].assumptionAnalysisInterpretersPerMember - val fullGraphInterpreter = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].joinedAssumptionAnalysisInterpreter + val dependencyGraphInterpreters = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].dependencyGraphInterpretersPerMember + val fullGraphInterpreter = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].joinedDependencyGraphInterpreter println(s"Precision Benchmark for $filePrefix - $fileName started...") writer.println(s"$filePrefix - $fileName") - new AnnotatedPrecisionBenchmark(filePrefix + "/" + fileName, program, assumptionAnalysisInterpreters, fullGraphInterpreter.get, writer).execute() + new AnnotatedPrecisionBenchmark(filePrefix + "/" + fileName, program, dependencyGraphInterpreters, fullGraphInterpreter.get, writer).execute() writer.println() writer.flush() println(s"Precision Benchmark for $filePrefix - $fileName done.") @@ -54,9 +54,9 @@ object AssumptionAnalysisPrecisionBenchmark extends AssumptionAnalysisTestFramew } class AnnotatedPrecisionBenchmark(fileName: String, program: Program, - assumptionAnalysisInterpreters: List[AssumptionAnalysisInterpreter], - fullGraphInterpreter: AssumptionAnalysisInterpreter, - writer: PrintWriter) extends AnnotatedTest(program, assumptionAnalysisInterpreters, true) { + dependencyGraphInterpreters: List[DependencyGraphInterpreter], + fullGraphInterpreter: DependencyGraphInterpreter, + writer: PrintWriter) extends AnnotatedTest(program, dependencyGraphInterpreters, true) { override def execute(): Unit = { if(!verifyTestSoundness()){ writer.println(s"!!!!!!!!!!!\nFailed to verify soundness of precision test $fileName\n") @@ -64,18 +64,18 @@ object AssumptionAnalysisPrecisionBenchmark extends AssumptionAnalysisTestFramew return } - assumptionAnalysisInterpreters foreach {a => + dependencyGraphInterpreters foreach { a => val prec = computePrecision(a) writer.println(s"${a.getMember.map(_.name).getOrElse("unknown")}: $prec") } } - protected def computePrecision(assumptionAnalysisInterpreter: AssumptionAnalysisInterpreter): Double = { - val assumptionNodes = getTestIrrelevantAssumptionNodes(assumptionAnalysisInterpreter.getNonInternalAssumptionNodes) + protected def computePrecision(dependencyGraphInterpreter: DependencyGraphInterpreter): Double = { + val assumptionNodes = getTestIrrelevantAssumptionNodes(dependencyGraphInterpreter.getNonInternalAssumptionNodes) val assumptionsPerSource = assumptionNodes groupBy (n => extractSourceLine(n.sourceInfo.getPosition)) - val assertionNodes = getTestAssertionNodes(assumptionAnalysisInterpreter.getNonInternalAssertionNodes) + val assertionNodes = getTestAssertionNodes(dependencyGraphInterpreter.getNonInternalAssertionNodes) - val dependencies = assumptionAnalysisInterpreter.getAllNonInternalDependencies(assertionNodes.map(_.id)) + val dependencies = dependencyGraphInterpreter.getAllNonInternalDependencies(assertionNodes.map(_.id)) val dependenciesPerSource = dependencies groupBy (n => extractSourceLine(n.sourceInfo.getPosition)) val dependencyIds = dependencies.map(_.id) diff --git a/src/test/scala/AssumptionAnalysisTestFramework.scala b/src/test/scala/DependencyAnalysisTestFramework.scala similarity index 75% rename from src/test/scala/AssumptionAnalysisTestFramework.scala rename to src/test/scala/DependencyAnalysisTestFramework.scala index 63f73b087..c814c1c34 100644 --- a/src/test/scala/AssumptionAnalysisTestFramework.scala +++ b/src/test/scala/DependencyAnalysisTestFramework.scala @@ -1,7 +1,7 @@ package viper.silicon.tests import viper.silicon.SiliconFrontend -import viper.silicon.assumptionAnalysis.{AssumptionAnalysisInterpreter, AssumptionAnalysisNode, AssumptionType, DependencyAnalysisReporter} +import viper.silicon.dependencyAnalysis.{DependencyGraphInterpreter, DependencyAnalysisNode, AssumptionType, DependencyAnalysisReporter} import viper.silver.ast.{Infoed, Program} import viper.silver.ast.utility.ViperStrategy import viper.silver.{ast, verifier} @@ -12,7 +12,7 @@ import java.io.PrintWriter import java.nio.file.{Files, Path, Paths} import scala.annotation.unused -trait AssumptionAnalysisTestFramework { +trait DependencyAnalysisTestFramework { val irrelevantKeyword = "irrelevant" val dependencyKeyword = "dependency" val testAssertionKeyword = "testAssertion" @@ -20,7 +20,7 @@ trait AssumptionAnalysisTestFramework { val ignores: Seq[String] var baseCommandLineArguments: Seq[String] = Seq("--timeout", "300" /* seconds */) var analysisCommandLineArguments: Seq[String] = - baseCommandLineArguments ++ Seq("--enableAssumptionAnalysis", "--disableInfeasibilityChecks", "--proverArgs", "proof=true unsat-core=true") + baseCommandLineArguments ++ Seq("--enableDependencyAnalysis", "--disableInfeasibilityChecks", "--proverArgs", "proof=true unsat-core=true") def visitFiles(dirName: String, function: (String, String) => Unit): Unit = { val path = Paths.get(getClass.getClassLoader.getResource(dirName).toURI) @@ -72,13 +72,13 @@ trait AssumptionAnalysisTestFramework { } /** - * (Almost) Fully automated test, which takes a program and its assumption analysis results and, + * (Almost) Fully automated test, which takes a program and its dependency analysis results and, * for each explicit assertion, builds a new program that only contains said assertion and * all its dependencies. The test passes if all new programs verify successfully. * * Statements that are only required as a trigger need to be manually annotated with @trigger() by the user. */ - case class PruningTest(fileName: String, program: Program, fullGraphInterpreter: AssumptionAnalysisInterpreter) { + case class PruningTest(fileName: String, program: Program, fullGraphInterpreter: DependencyGraphInterpreter) { def execute(): Unit = { val triggerNodeLines = fullGraphInterpreter.getNodes.filter(node => node.sourceInfo.getTopLevelSource.toString.contains("@trigger()")).flatMap(_.sourceInfo.getLineNumber) @@ -112,7 +112,7 @@ trait AssumptionAnalysisTestFramework { } /** - * Takes a Viper program and its assumption analysis results and checks whether the analysis found the + * Takes a Viper program and its dependency analysis results and checks whether the analysis found the * assumptions, assertions and dependencies between them, as annotated by the user. * * Annotations: @@ -127,15 +127,15 @@ trait AssumptionAnalysisTestFramework { * but multiple dependency/irrelevant annotations are allowed * */ - case class AnnotatedTest(program: Program, assumptionAnalysisInterpreters: List[AssumptionAnalysisInterpreter], checkPrecision: Boolean) { + case class AnnotatedTest(program: Program, dependencyGraphInterpreters: List[DependencyGraphInterpreter], checkPrecision: Boolean) { def execute(): Unit = { val stmtsWithAssumptionAnnotation: Set[Infoed] = extractAnnotatedStmts({ annotationInfo => annotationInfo.values.contains(irrelevantKeyword + "(\"") || annotationInfo.values.contains(dependencyKeyword) }) - val allAssumptionNodes = assumptionAnalysisInterpreters.flatMap(_.getNonInternalAssumptionNodes) + val allAssumptionNodes = dependencyGraphInterpreters.flatMap(_.getNonInternalAssumptionNodes) var errorMsgs = stmtsWithAssumptionAnnotation.map(checkAssumptionNodeExists(allAssumptionNodes, _)).filter(_.isDefined).map(_.get).toSeq - errorMsgs ++= assumptionAnalysisInterpreters flatMap checkTestAssertionNodeExists - errorMsgs ++= assumptionAnalysisInterpreters flatMap checkAllDependencies - errorMsgs ++= assumptionAnalysisInterpreters flatMap checkExplicitDependencies + errorMsgs ++= dependencyGraphInterpreters flatMap checkTestAssertionNodeExists + errorMsgs ++= dependencyGraphInterpreters flatMap checkAllDependencies + errorMsgs ++= dependencyGraphInterpreters flatMap checkExplicitDependencies val check = errorMsgs.isEmpty assert(check, "\n" + errorMsgs.mkString("\n")) @@ -156,7 +156,7 @@ trait AssumptionAnalysisTestFramework { nodesWithAnnotation } - protected def checkAssumptionNodeExists(analysisNodes: List[AssumptionAnalysisNode], node: ast.Infoed): Option[String] = { + protected def checkAssumptionNodeExists(analysisNodes: List[DependencyAnalysisNode], node: ast.Infoed): Option[String] = { val pos = extractSourceLine(node.asInstanceOf[ast.Positioned].pos) val annotationInfo = node.info.getUniqueInfo[ast.AnnotationInfo] .map(ai => ai.values.getOrElse(irrelevantKeyword, ai.values.getOrElse(dependencyKeyword, List.empty))).getOrElse(List.empty) @@ -175,36 +175,36 @@ trait AssumptionAnalysisTestFramework { } } - protected def checkTestAssertionNodeExists(assumptionAnalysisInterpreter: AssumptionAnalysisInterpreter): Seq[String] = { - val assumptionNodes = getTestAssumptionNodes(assumptionAnalysisInterpreter.getNonInternalAssumptionNodes) ++ getTestIrrelevantAssumptionNodes(assumptionAnalysisInterpreter.getNonInternalAssumptionNodes) - val assertionNodes = getTestAssertionNodes(assumptionAnalysisInterpreter.getNonInternalAssertionNodes) + protected def checkTestAssertionNodeExists(dependencyGraphInterpreter: DependencyGraphInterpreter): Seq[String] = { + val assumptionNodes = getTestAssumptionNodes(dependencyGraphInterpreter.getNonInternalAssumptionNodes) ++ getTestIrrelevantAssumptionNodes(dependencyGraphInterpreter.getNonInternalAssumptionNodes) + val assertionNodes = getTestAssertionNodes(dependencyGraphInterpreter.getNonInternalAssertionNodes) if (assumptionNodes.nonEmpty && assertionNodes.isEmpty) - Seq(s"Missing testAssertion for member: ${assumptionAnalysisInterpreter.getName}") + Seq(s"Missing testAssertion for member: ${dependencyGraphInterpreter.getName}") else Seq.empty } - protected def checkAllDependencies(assumptionAnalysisInterpreter: AssumptionAnalysisInterpreter): Seq[String] = { - val assertionNodes = getTestAssertionNodes(assumptionAnalysisInterpreter.getNonInternalAssertionNodes) - val dependencies = assumptionAnalysisInterpreter.getAllNonInternalDependencies(assertionNodes.map(_.id))map(_.id) + protected def checkAllDependencies(dependencyGraphInterpreter: DependencyGraphInterpreter): Seq[String] = { + val assertionNodes = getTestAssertionNodes(dependencyGraphInterpreter.getNonInternalAssertionNodes) + val dependencies = dependencyGraphInterpreter.getAllNonInternalDependencies(assertionNodes.map(_.id))map(_.id) - val relevantAssumptionNodes = getTestAssumptionNodes(assumptionAnalysisInterpreter.getNonInternalAssumptionNodes) + val relevantAssumptionNodes = getTestAssumptionNodes(dependencyGraphInterpreter.getNonInternalAssumptionNodes) val resRelevant: Seq[String] = checkDependenciesAndGetErrorMsgs(relevantAssumptionNodes, dependencies, isDependencyExpected = true, "Missing dependency") val resIrrelevant = if(checkPrecision){ - val irrelevantNodes = getTestIrrelevantAssumptionNodes(assumptionAnalysisInterpreter.getNonInternalAssumptionNodes) + val irrelevantNodes = getTestIrrelevantAssumptionNodes(dependencyGraphInterpreter.getNonInternalAssumptionNodes) checkDependenciesAndGetErrorMsgs(irrelevantNodes, dependencies, isDependencyExpected = false, "Unexpected dependency") } else Seq.empty resRelevant ++ resIrrelevant } - protected def checkExplicitDependencies(assumptionAnalysisInterpreter: AssumptionAnalysisInterpreter): Seq[String] = { - val assertionNodes = getTestAssertionNodes(assumptionAnalysisInterpreter.getNonInternalAssertionNodes) - val dependencies = assumptionAnalysisInterpreter.getAllExplicitDependencies(assertionNodes.map(_.id)).map(_.id) + protected def checkExplicitDependencies(dependencyGraphInterpreter: DependencyGraphInterpreter): Seq[String] = { + val assertionNodes = getTestAssertionNodes(dependencyGraphInterpreter.getNonInternalAssertionNodes) + val dependencies = dependencyGraphInterpreter.getAllExplicitDependencies(assertionNodes.map(_.id)).map(_.id) - val allTestAssumptionNodes = getTestAssumptionNodes(assumptionAnalysisInterpreter.getNonInternalAssumptionNodes) + val allTestAssumptionNodes = getTestAssumptionNodes(dependencyGraphInterpreter.getNonInternalAssumptionNodes) val relevantAssumptionNodes = allTestAssumptionNodes.filter(_.sourceInfo.toString.contains("@" + dependencyKeyword + "(\"Explicit")) val resRelevant: Seq[String] = checkDependenciesAndGetErrorMsgs(relevantAssumptionNodes, dependencies, isDependencyExpected = true, "Missing explicit dependency") @@ -215,7 +215,7 @@ trait AssumptionAnalysisTestFramework { resRelevant ++ resIrrelevant } - protected def checkDependenciesAndGetErrorMsgs(relevantAssumptionNodes: Set[AssumptionAnalysisNode], dependencies: Set[Int], isDependencyExpected: Boolean, errorMsg: String): Seq[String] = { + protected def checkDependenciesAndGetErrorMsgs(relevantAssumptionNodes: Set[DependencyAnalysisNode], dependencies: Set[Int], isDependencyExpected: Boolean, errorMsg: String): Seq[String] = { val relevantAssumptionsPerSource = relevantAssumptionNodes groupBy (n => extractSourceLine(n.sourceInfo.getPosition)) val resRelevant = relevantAssumptionsPerSource.map({ case (_, assumptions) => val hasDependency = dependencies.intersect(assumptions.map(_.id)).nonEmpty @@ -224,15 +224,15 @@ trait AssumptionAnalysisTestFramework { resRelevant } - protected def getTestAssertionNodes(nodes: Set[AssumptionAnalysisNode]): Set[AssumptionAnalysisNode] = + protected def getTestAssertionNodes(nodes: Set[DependencyAnalysisNode]): Set[DependencyAnalysisNode] = nodes.filter(node => node.sourceInfo.toString.contains("@" + testAssertionKeyword + "(")) - protected def getTestAssumptionNodes(nodes: Set[AssumptionAnalysisNode]): Set[AssumptionAnalysisNode] = + protected def getTestAssumptionNodes(nodes: Set[DependencyAnalysisNode]): Set[DependencyAnalysisNode] = nodes.filter(_.sourceInfo.toString.contains("@" + dependencyKeyword + "(")) - protected def getTestIrrelevantAssumptionNodes(nodes: Set[AssumptionAnalysisNode]): Set[AssumptionAnalysisNode] = + protected def getTestIrrelevantAssumptionNodes(nodes: Set[DependencyAnalysisNode]): Set[DependencyAnalysisNode] = nodes.filter(_.sourceInfo.toString.contains("@" + irrelevantKeyword + "(")) } diff --git a/src/test/scala/AssumptionAnalysisTests.scala b/src/test/scala/DependencyAnalysisTests.scala similarity index 85% rename from src/test/scala/AssumptionAnalysisTests.scala rename to src/test/scala/DependencyAnalysisTests.scala index 07b2a7786..d425ecd6a 100644 --- a/src/test/scala/AssumptionAnalysisTests.scala +++ b/src/test/scala/DependencyAnalysisTests.scala @@ -1,7 +1,7 @@ package viper.silicon.tests import org.scalatest.funsuite.AnyFunSuite -import viper.silicon.assumptionAnalysis._ +import viper.silicon.dependencyAnalysis._ import viper.silver.ast._ import viper.silver.frontend.SilFrontend import viper.silver.verifier @@ -12,7 +12,7 @@ import java.time.format.DateTimeFormatter import scala.annotation.unused -class AssumptionAnalysisTests extends AnyFunSuite with AssumptionAnalysisTestFramework { +class DependencyAnalysisTests extends AnyFunSuite with DependencyAnalysisTestFramework { val CHECK_PRECISION = false val EXECUTE_TEST = true @@ -58,7 +58,7 @@ class AssumptionAnalysisTests extends AnyFunSuite with AssumptionAnalysisTestFra resetFrontend() executeTest(dirName + "/", fileName, frontend) }catch{ - case t: Throwable => fail(t.getMessage) + case t: Throwable => fail(t.toString) } } } @@ -75,11 +75,11 @@ class AssumptionAnalysisTests extends AnyFunSuite with AssumptionAnalysisTestFra return } - val assumptionAnalysisInterpreters = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].assumptionAnalysisInterpretersPerMember - val joinedAssumptionAnalysisInterpreter = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].joinedAssumptionAnalysisInterpreter + val dependencyGraphInterpreters = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].dependencyGraphInterpretersPerMember + val joinedDependencyGraphInterpreter = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].joinedDependencyGraphInterpreter - AnnotatedTest(program, assumptionAnalysisInterpreters, CHECK_PRECISION).execute() - PruningTest(filePrefix + "/" + fileName, program, joinedAssumptionAnalysisInterpreter.get).execute() + AnnotatedTest(program, dependencyGraphInterpreters, CHECK_PRECISION).execute() + PruningTest(filePrefix + "/" + fileName, program, joinedDependencyGraphInterpreter.get).execute() } def executePerformanceBenchmark(filePrefix: String, @@ -102,10 +102,10 @@ class AssumptionAnalysisTests extends AnyFunSuite with AssumptionAnalysisTestFra return } - val assumptionAnalysisInterpreters = frontend_.reporter.asInstanceOf[DependencyAnalysisReporter].assumptionAnalysisInterpretersPerMember + val dependencyGraphInterpreters = frontend_.reporter.asInstanceOf[DependencyAnalysisReporter].dependencyGraphInterpretersPerMember proofCoverageWriter.println(filePrefix + "/" + fileName) - assumptionAnalysisInterpreters foreach (memberInterpreter => { + dependencyGraphInterpreters foreach (memberInterpreter => { memberInterpreter.getExplicitAssertionNodes.groupBy(_.sourceInfo.getTopLevelSource) foreach {case (source, nodes) => proofCoverageWriter.println(memberInterpreter.getName + " " + source.toString.replace("\n", " ") + " ---> " + memberInterpreter.computeProofCoverage(nodes)._1)} proofCoverageWriter.println("overall " + memberInterpreter.getName + " ---> + " + memberInterpreter.computeProofCoverage()._1) From c730c9bd17158deea3f8297ea5baebfb29188433 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 20 Nov 2025 11:53:57 +0100 Subject: [PATCH 263/474] optimize join for axioms --- .../dependencyAnalysis/AnalysisInfo.scala | 4 +-- .../DependencyAnalyzer.scala | 34 ++++++++++--------- .../dependencyAnalysis/DependencyGraph.scala | 2 +- src/main/scala/rules/Evaluator.scala | 2 +- src/main/scala/rules/Executor.scala | 2 +- .../functions/FunctionVerificationUnit.scala | 2 +- 6 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala index 2e2d8fd31..0a0d7f45d 100644 --- a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala +++ b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala @@ -2,11 +2,11 @@ package viper.silicon.dependencyAnalysis object AssumptionType extends Enumeration { type AssumptionType = Value - val Explicit, LoopInvariant, PathCondition, Rewrite, Implicit, Internal, Trigger, ExplicitPostcondition, ImplicitPostcondition, ExplicitPostcondAssumption, ImplicitPostcondAssumption = Value + val Explicit, LoopInvariant, PathCondition, Rewrite, Implicit, Internal, Trigger, ExplicitPostcondition, ImplicitPostcondition, FunctionBody = Value def fromString(s: String): Option[Value] = values.find(_.toString == s) - def explicitAssumptionTypes: Set[AssumptionType] = Set(Explicit, ExplicitPostcondition, ExplicitPostcondAssumption) + def explicitAssumptionTypes: Set[AssumptionType] = Set(Explicit, ExplicitPostcondition) def postconditionTypes: Set[AssumptionType] = Set(ImplicitPostcondition, ExplicitPostcondition) // used to join graphs via postconditions def explicitAssertionTypes: Set[AssumptionType] = Set(Explicit) ++ postconditionTypes def internalTypes: Set[AssumptionType] = Set(Internal) // will always be hidden from user diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index 09d04d923..c571ebe33 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -98,35 +98,37 @@ object DependencyAnalyzer { def isAxiomLabel(label: String): Boolean = label.startsWith("axiom_") + // TODO ake: implement a lazy join in DependencyGraphInterpreter def joinGraphsAndGetInterpreter(name: Option[String], dependencyGraphInterpreters: Set[DependencyGraphInterpreter]): DependencyGraphInterpreter = { val newGraph = new DependencyGraph dependencyGraphInterpreters foreach (interpreter => newGraph.addNodes(interpreter.getGraph.getNodes)) dependencyGraphInterpreters foreach (interpreter => interpreter.getGraph.getAllEdges foreach {case (t, deps) => newGraph.addEdges(deps, t)}) - // add edges for axioms since they were added to each interpreter - // in particular, add edges to assertions that were required to introduce the axiom, e.g. to verify the function body/postcondition - val allAssertionNodes = newGraph.getNodes.filter(n => n.isInstanceOf[GeneralAssertionNode]) + + // axioms assumed by every method / function should depend on the assertions that justify them + // hence, we add edges from function postconditions & bodies to the corresponding axioms + val axiomAssertionNodes = newGraph.getNodes + .filter(n => (n.isInstanceOf[GeneralAssertionNode] && AssumptionType.postconditionTypes.contains(n.assumptionType)) + || AssumptionType.FunctionBody.equals(n.assumptionType)) + .groupBy(_.sourceInfo.getTopLevelSource.toString) + .view.mapValues(_.map(_.id)) + .toMap newGraph.getNodes.filter(_.isInstanceOf[AxiomAssumptionNode]) - .groupBy(n => (n.sourceInfo.toString, n.assumptionType)) - .map{case (_, nodes) => (nodes.map(_.id), allAssertionNodes.filter(_.sourceInfo.getTopLevelSource.equals(nodes.head.sourceInfo.getTopLevelSource)).map(_.id))} - .foreach{case (nodeIds, assertionNodeIds) => - newGraph.addEdges(nodeIds, nodeIds) // TODO ake: is this necessary; maybe nodes could be merged instead? - newGraph.addEdges(assertionNodeIds, nodeIds) + .groupBy(n => n.sourceInfo.toString) + .map{case (sourceInfo, axiomNodes) => (axiomNodes.map(_.id), axiomAssertionNodes.getOrElse(sourceInfo, Seq.empty))} + .foreach{case (axiomNodeIds, assertionNodeIds) => + newGraph.addEdges(assertionNodeIds, axiomNodeIds) // TODO ake: maybe we could merge the axiom nodes here since they represent the same axiom? } - // get all nodes that represent the assumption of a postcondition introduced by a method call or function application - // for a fast lookup, these nodes are stored as a map from string to a set of integers. The string represents the assumed postcondition (which is the join condition) and - // the integers correspond to all nodes associated with said postcondition. - val types = Set(AssumptionType.ImplicitPostcondAssumption, AssumptionType.ExplicitPostcondAssumption) + // postconditions of methods assumed by every method call should depend on the assertions that justify them + // hence, we add edges from assertions of method postconditions to assumptions of the same postcondition (at method calls) val relevantAssumptionNodes = newGraph.nodes - .filter(node => node.isInstanceOf[GeneralAssumptionNode] && types.contains(node.assumptionType)) + .filter(node => node.isInstanceOf[GeneralAssumptionNode] && AssumptionType.postconditionTypes.contains(node.assumptionType)) .groupBy(_.sourceInfo.getFineGrainedSource.toString) .view.mapValues(_.map(_.id)) .toMap - - - newGraph.nodes.filter(node => AssumptionType.postconditionTypes.contains(node.assumptionType)) + newGraph.nodes.filter(node => node.isInstanceOf[GeneralAssertionNode] && AssumptionType.postconditionTypes.contains(node.assumptionType)) .map(node => (node.id, relevantAssumptionNodes.getOrElse(node.sourceInfo.getTopLevelSource.toString, Seq.empty))) .foreach { case (src, targets) => newGraph.addEdges(src, targets)} diff --git a/src/main/scala/dependencyAnalysis/DependencyGraph.scala b/src/main/scala/dependencyAnalysis/DependencyGraph.scala index 666cd1ff9..0c83017b8 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraph.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraph.scala @@ -29,7 +29,7 @@ trait ReadOnlyDependencyGraph { class DependencyGraph extends ReadOnlyDependencyGraph { var nodes: mutable.Seq[DependencyAnalysisNode] = mutable.Seq() private val edges: mutable.Map[Int, Set[Int]] = mutable.Map.empty - private val transitiveEdges: mutable.Map[Int, Set[Int]] = mutable.Map.empty + private val transitiveEdges: mutable.Map[Int, Set[Int]] = mutable.Map.empty // TODO ake: can be merged into edges? def getNodes: Seq[DependencyAnalysisNode] = nodes.toSeq def getDirectEdges: Map[Int, Set[Int]] = edges.toMap diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 22c6e7859..080ba7617 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -861,7 +861,7 @@ object evaluator extends EvaluationRules { case fapp @ ast.FuncApp(funcName, eArgs) => val func = s.program.findFunction(funcName) - val funcAssumptionType = if(func.body.isDefined || !DependencyAnalyzer.extractEnableAnalysisFromInfo(func.info).getOrElse(true)) AssumptionType.ImplicitPostcondAssumption else AssumptionType.ExplicitPostcondAssumption + val funcAssumptionType = if(func.body.isDefined || !DependencyAnalyzer.extractEnableAnalysisFromInfo(func.info).getOrElse(true)) AssumptionType.ImplicitPostcondition else AssumptionType.ExplicitPostcondition val assumptionType = DependencyAnalyzer.extractAssumptionTypeFromInfo(fapp.info).getOrElse(funcAssumptionType) evals2(s, eArgs, Nil, _ => pve, v)((s1, tArgs, eArgsNew, v1) => { // bookkeeper.functionApplications += 1 diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 42080e5e7..6eac67662 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -605,7 +605,7 @@ object executor extends ExecutionRules { val pveCallTransformed = pveCall.withReasonNodeTransformed(reasonTransformer) val methodAnnotatedAssumptionType = DependencyAnalyzer.extractAssumptionTypeFromInfo(meth.info) // TODO: make sure the join can still be made? - val defaultAssumptionType = if(meth.body.isDefined || !DependencyAnalyzer.extractEnableAnalysisFromInfo(meth.info).getOrElse(true)) AssumptionType.ImplicitPostcondAssumption else AssumptionType.ExplicitPostcondAssumption + val defaultAssumptionType = if(meth.body.isDefined || !DependencyAnalyzer.extractEnableAnalysisFromInfo(meth.info).getOrElse(true)) AssumptionType.ImplicitPostcondition else AssumptionType.ExplicitPostcondition val finalAssumptionType = annotatedAssumptionTypeOpt.getOrElse(methodAnnotatedAssumptionType.getOrElse(defaultAssumptionType)) val mcLog = new MethodCallRecord(call, s, v.decider.pcs) diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index 4cc97ec5b..377740dcb 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -280,7 +280,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver Some(DebugExp.createInstance(e, eNew)) } else { None } decider.analysisSourceInfoStack.setForcedSource(ExpAnalysisSourceInfo(body)) - decider.assume(BuiltinEquals(data.formalResult, tBody), debugExp, AssumptionType.Implicit) + decider.assume(BuiltinEquals(data.formalResult, tBody), debugExp, AssumptionType.FunctionBody) decider.analysisSourceInfoStack.removeForcedSource() consumes(s2, posts, false, postconditionViolated, v, postConditionType)((s3, _, _) => { recorders :+= s3.functionRecorder From 03aa5843577bc5b77cbec4365c1fb9ed6c62e094 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 6 Nov 2025 10:43:44 +0100 Subject: [PATCH 264/474] profiling --- src/main/scala/Config.scala | 12 +++++++ .../DependencyAnalyzer.scala | 35 ++++++++++++++++++- .../scala/verifier/DefaultMainVerifier.scala | 7 ++++ 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/main/scala/Config.scala b/src/main/scala/Config.scala index 0fd581e9e..4b9ee650b 100644 --- a/src/main/scala/Config.scala +++ b/src/main/scala/Config.scala @@ -860,6 +860,18 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { noshort = true ) + val dependencyAnalysisPostProcessingMode: ScallopOption[Int] = opt[Int]("dependencyAnalysisPostProcessingMode", + descr = "Postprocessing mode: 0=default, 1=disable memory footprint optimizations, 2=disable joining of graphs and all of 1 (does not compute dependencies between methods), 3=disable transitive edges and all of 2 (UNSOUND)", + default = Some(0), + noshort = false + ) + + val enableDependencyAnalysisProfiling: ScallopOption[Boolean] = opt[Boolean]("enableDependencyAnalysisProfiling", + descr = "Measures runtime of different parts of the dependency analysis", + default = Some(false), + noshort = true + ) + /* Option validation (trailing file argument is validated by parent class) */ validateOpt(prover) { diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index c571ebe33..ae06162d2 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -6,6 +6,7 @@ import viper.silicon.state.terms._ import viper.silicon.verifier.Verifier import viper.silver.ast +import java.util.concurrent.atomic.AtomicLong import scala.collection.mutable @@ -54,6 +55,33 @@ object DependencyAnalyzer { val analysisLabelName: String = "$$analysisLabel$$" private val assumptionTypeAnnotationKey = "assumptionType" private val enableDependencyAnalysisAnnotationKey = "enableDependencyAnalysis" + private val timeToJoinGraphs: AtomicLong = new AtomicLong(0) + val timeForMemoryOptimizations: AtomicLong = new AtomicLong(0) + val timeToAddTransitiveEdges: AtomicLong = new AtomicLong(0) + val timeToVerifyAndCollectDependencies: AtomicLong = new AtomicLong(0) + val timeToVerifyAndBuildFinalGraph: AtomicLong = new AtomicLong(0) + + def startTimeMeasurement(): Long = { + if(!Verifier.config.enableDependencyAnalysisProfiling()) return 0 + System.nanoTime() + } + + def stopTimeMeasurementAndAddToTotal(startTime: Long, total: AtomicLong): Unit = { + if(!Verifier.config.enableDependencyAnalysisProfiling()) return + + val endTime = System.nanoTime() + total.addAndGet(endTime - startTime) + } + + def printProfilingResults(): Unit = { + if(!Verifier.config.enableDependencyAnalysisProfiling()) return + println(s"Overall runtime = time spent on verification and building the final graph: ${timeToVerifyAndBuildFinalGraph.get() / 1e6}ms") + println(s"This runtime can be categorized into the following, fine-grained measurements.") + println(s" Time spent on verification and collecting low-level dependencies: ${timeToVerifyAndCollectDependencies.get() / 1e6}ms") + println(s" Time spent on memory optimizations: ${timeForMemoryOptimizations.get() / 1e6}ms") + println(s" Time spent on adding transitive edges (post-processing only): ${timeToAddTransitiveEdges.get() / 1e6}ms") + println(s" Time spent on joining graphs: ${timeToJoinGraphs.get() / 1e6}ms") + } private def extractAnnotationFromInfo(info: ast.Info, annotationKey: String): Option[Seq[String]] = { info.getAllInfos[ast.AnnotationInfo] @@ -100,6 +128,8 @@ object DependencyAnalyzer { // TODO ake: implement a lazy join in DependencyGraphInterpreter def joinGraphsAndGetInterpreter(name: Option[String], dependencyGraphInterpreters: Set[DependencyGraphInterpreter]): DependencyGraphInterpreter = { + val startTime = startTimeMeasurement() + val newGraph = new DependencyGraph dependencyGraphInterpreters foreach (interpreter => newGraph.addNodes(interpreter.getGraph.getNodes)) @@ -132,7 +162,9 @@ object DependencyAnalyzer { .map(node => (node.id, relevantAssumptionNodes.getOrElse(node.sourceInfo.getTopLevelSource.toString, Seq.empty))) .foreach { case (src, targets) => newGraph.addEdges(src, targets)} - new DependencyGraphInterpreter(name.getOrElse("joined"), newGraph) + val newInterpreter = new DependencyGraphInterpreter(name.getOrElse("joined"), newGraph) + stopTimeMeasurementAndAddToTotal(startTime, timeToJoinGraphs) + newInterpreter } } @@ -296,6 +328,7 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { assumptionGraph.removeLabelNodes() val mergedGraph = if(Verifier.config.enableDependencyAnalysisDebugging()) assumptionGraph else buildAndGetMergedGraph() mergedGraph.addTransitiveEdges() + AssumptionAnalyzer.stopTimeMeasurementAndAddToTotal(transitiveEdgesStartTime, AssumptionAnalyzer.timeToAddTransitiveEdges) Some(mergedGraph) } diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index 8b04294e6..b1ddeb0ed 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -208,6 +208,7 @@ class DefaultMainVerifier(config: Config, predSnapGenerator.setup(program) // TODO: Why did Nadja put this here? + val verificationStartTime = AssumptionAnalyzer.startTimeMeasurement() allProvers.comment("Started: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(System.currentTimeMillis()) /*bookkeeper.formattedStartTime*/) allProvers.comment("Silicon.version: " + Silicon.version) allProvers.comment(s"Input file: ${inputFile.getOrElse("")}") @@ -314,6 +315,8 @@ class DefaultMainVerifier(config: Config, ++ predicateVerificationResults ++ methodVerificationResults) + AssumptionAnalyzer.stopTimeMeasurementAndAddToTotal(verificationStartTime, AssumptionAnalyzer.timeToVerifyAndCollectDependencies) + if(Verifier.config.enableDependencyAnalysis()){ val dependencyGraphInterpreters = verificationResults.filter(_.dependencyGraphInterpreter.isDefined).map(_.dependencyGraphInterpreter.get) @@ -335,6 +338,10 @@ class DefaultMainVerifier(config: Config, case _ => } + AssumptionAnalyzer.stopTimeMeasurementAndAddToTotal(verificationStartTime, AssumptionAnalyzer.timeToVerifyAndBuildFinalGraph) + + AssumptionAnalyzer.printProfilingResults() + } if (Verifier.config.startDebuggerAutomatically()){ From cca0766c2c8e6391fe826dde5a5a261f25d972aa Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 20 Nov 2025 12:01:04 +0100 Subject: [PATCH 265/474] fix --- .../scala/dependencyAnalysis/DependencyAnalyzer.scala | 2 +- src/main/scala/verifier/DefaultMainVerifier.scala | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index ae06162d2..035120fb4 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -328,7 +328,7 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { assumptionGraph.removeLabelNodes() val mergedGraph = if(Verifier.config.enableDependencyAnalysisDebugging()) assumptionGraph else buildAndGetMergedGraph() mergedGraph.addTransitiveEdges() - AssumptionAnalyzer.stopTimeMeasurementAndAddToTotal(transitiveEdgesStartTime, AssumptionAnalyzer.timeToAddTransitiveEdges) + DependencyAnalyzer.stopTimeMeasurementAndAddToTotal(transitiveEdgesStartTime, DependencyAnalyzer.timeToAddTransitiveEdges) Some(mergedGraph) } diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index b1ddeb0ed..354c38b95 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -208,7 +208,7 @@ class DefaultMainVerifier(config: Config, predSnapGenerator.setup(program) // TODO: Why did Nadja put this here? - val verificationStartTime = AssumptionAnalyzer.startTimeMeasurement() + val verificationStartTime = DependencyAnalyzer.startTimeMeasurement() allProvers.comment("Started: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(System.currentTimeMillis()) /*bookkeeper.formattedStartTime*/) allProvers.comment("Silicon.version: " + Silicon.version) allProvers.comment(s"Input file: ${inputFile.getOrElse("")}") @@ -315,7 +315,7 @@ class DefaultMainVerifier(config: Config, ++ predicateVerificationResults ++ methodVerificationResults) - AssumptionAnalyzer.stopTimeMeasurementAndAddToTotal(verificationStartTime, AssumptionAnalyzer.timeToVerifyAndCollectDependencies) + DependencyAnalyzer.stopTimeMeasurementAndAddToTotal(verificationStartTime, DependencyAnalyzer.timeToVerifyAndCollectDependencies) if(Verifier.config.enableDependencyAnalysis()){ val dependencyGraphInterpreters = verificationResults.filter(_.dependencyGraphInterpreter.isDefined).map(_.dependencyGraphInterpreter.get) @@ -338,9 +338,9 @@ class DefaultMainVerifier(config: Config, case _ => } - AssumptionAnalyzer.stopTimeMeasurementAndAddToTotal(verificationStartTime, AssumptionAnalyzer.timeToVerifyAndBuildFinalGraph) + DependencyAnalyzer.stopTimeMeasurementAndAddToTotal(verificationStartTime, DependencyAnalyzer.timeToVerifyAndBuildFinalGraph) - AssumptionAnalyzer.printProfilingResults() + DependencyAnalyzer.printProfilingResults() } From 21cd2d39fc857a6401a4eeccfcdb794400677555 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 20 Nov 2025 13:53:49 +0100 Subject: [PATCH 266/474] optimize graph join --- .../dependencyAnalysis/AnalysisInfo.scala | 1 + .../DependencyAnalyzer.scala | 33 ++++++++++--------- .../dependencyAnalysis/DependencyGraph.scala | 8 +++-- .../DependencyGraphInterpreter.scala | 7 ++++ .../scala/supporters/MethodSupporter.scala | 1 + .../functions/FunctionVerificationUnit.scala | 1 + 6 files changed, 34 insertions(+), 17 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala index 0a0d7f45d..5fe996e99 100644 --- a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala +++ b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala @@ -10,6 +10,7 @@ object AssumptionType extends Enumeration { def postconditionTypes: Set[AssumptionType] = Set(ImplicitPostcondition, ExplicitPostcondition) // used to join graphs via postconditions def explicitAssertionTypes: Set[AssumptionType] = Set(Explicit) ++ postconditionTypes def internalTypes: Set[AssumptionType] = Set(Internal) // will always be hidden from user + def joinConditionTypes: Set[AssumptionType] = postconditionTypes ++ Set(FunctionBody) } import viper.silicon.dependencyAnalysis.AssumptionType._ diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index 035120fb4..1631378e3 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -55,11 +55,11 @@ object DependencyAnalyzer { val analysisLabelName: String = "$$analysisLabel$$" private val assumptionTypeAnnotationKey = "assumptionType" private val enableDependencyAnalysisAnnotationKey = "enableDependencyAnalysis" - private val timeToJoinGraphs: AtomicLong = new AtomicLong(0) - val timeForMemoryOptimizations: AtomicLong = new AtomicLong(0) - val timeToAddTransitiveEdges: AtomicLong = new AtomicLong(0) val timeToVerifyAndCollectDependencies: AtomicLong = new AtomicLong(0) val timeToVerifyAndBuildFinalGraph: AtomicLong = new AtomicLong(0) + val timeToAddNodes: AtomicLong = new AtomicLong(0) + val timeToAddEdges: AtomicLong = new AtomicLong(0) + val timeToExtractCandidateNodes: AtomicLong = new AtomicLong(0) def startTimeMeasurement(): Long = { if(!Verifier.config.enableDependencyAnalysisProfiling()) return 0 @@ -78,9 +78,9 @@ object DependencyAnalyzer { println(s"Overall runtime = time spent on verification and building the final graph: ${timeToVerifyAndBuildFinalGraph.get() / 1e6}ms") println(s"This runtime can be categorized into the following, fine-grained measurements.") println(s" Time spent on verification and collecting low-level dependencies: ${timeToVerifyAndCollectDependencies.get() / 1e6}ms") - println(s" Time spent on memory optimizations: ${timeForMemoryOptimizations.get() / 1e6}ms") - println(s" Time spent on adding transitive edges (post-processing only): ${timeToAddTransitiveEdges.get() / 1e6}ms") - println(s" Time spent on joining graphs: ${timeToJoinGraphs.get() / 1e6}ms") + println(s" Time spent on adding nodes: ${timeToAddNodes.get() / 1e6}ms") + println(s" Time spent on adding edges: ${timeToAddEdges.get() / 1e6}ms") + println(s" Time spent on extracting candidate nodes: ${timeToExtractCandidateNodes.get() / 1e6}ms") } private def extractAnnotationFromInfo(info: ast.Info, annotationKey: String): Option[Seq[String]] = { @@ -128,23 +128,28 @@ object DependencyAnalyzer { // TODO ake: implement a lazy join in DependencyGraphInterpreter def joinGraphsAndGetInterpreter(name: Option[String], dependencyGraphInterpreters: Set[DependencyGraphInterpreter]): DependencyGraphInterpreter = { - val startTime = startTimeMeasurement() + var startTime = startTimeMeasurement() val newGraph = new DependencyGraph - dependencyGraphInterpreters foreach (interpreter => newGraph.addNodes(interpreter.getGraph.getNodes)) + newGraph.addNodes(dependencyGraphInterpreters.flatMap (_.getGraph.getNodes)) + stopTimeMeasurementAndAddToTotal(startTime, timeToAddNodes) + startTime = startTimeMeasurement() dependencyGraphInterpreters foreach (interpreter => interpreter.getGraph.getAllEdges foreach {case (t, deps) => newGraph.addEdges(deps, t)}) - + stopTimeMeasurementAndAddToTotal(startTime, timeToAddEdges) + startTime = startTimeMeasurement() + val joinCandidateNodes = dependencyGraphInterpreters flatMap(_.getJoinCandidateNodes) + stopTimeMeasurementAndAddToTotal(startTime, timeToExtractCandidateNodes) // axioms assumed by every method / function should depend on the assertions that justify them // hence, we add edges from function postconditions & bodies to the corresponding axioms - val axiomAssertionNodes = newGraph.getNodes + val axiomAssertionNodes = joinCandidateNodes .filter(n => (n.isInstanceOf[GeneralAssertionNode] && AssumptionType.postconditionTypes.contains(n.assumptionType)) || AssumptionType.FunctionBody.equals(n.assumptionType)) .groupBy(_.sourceInfo.getTopLevelSource.toString) .view.mapValues(_.map(_.id)) .toMap - newGraph.getNodes.filter(_.isInstanceOf[AxiomAssumptionNode]) + joinCandidateNodes.filter(_.isInstanceOf[AxiomAssumptionNode]) .groupBy(n => n.sourceInfo.toString) .map{case (sourceInfo, axiomNodes) => (axiomNodes.map(_.id), axiomAssertionNodes.getOrElse(sourceInfo, Seq.empty))} .foreach{case (axiomNodeIds, assertionNodeIds) => @@ -153,17 +158,16 @@ object DependencyAnalyzer { // postconditions of methods assumed by every method call should depend on the assertions that justify them // hence, we add edges from assertions of method postconditions to assumptions of the same postcondition (at method calls) - val relevantAssumptionNodes = newGraph.nodes + val relevantAssumptionNodes = joinCandidateNodes .filter(node => node.isInstanceOf[GeneralAssumptionNode] && AssumptionType.postconditionTypes.contains(node.assumptionType)) .groupBy(_.sourceInfo.getFineGrainedSource.toString) .view.mapValues(_.map(_.id)) .toMap - newGraph.nodes.filter(node => node.isInstanceOf[GeneralAssertionNode] && AssumptionType.postconditionTypes.contains(node.assumptionType)) + joinCandidateNodes.filter(node => node.isInstanceOf[GeneralAssertionNode] && AssumptionType.postconditionTypes.contains(node.assumptionType)) .map(node => (node.id, relevantAssumptionNodes.getOrElse(node.sourceInfo.getTopLevelSource.toString, Seq.empty))) .foreach { case (src, targets) => newGraph.addEdges(src, targets)} val newInterpreter = new DependencyGraphInterpreter(name.getOrElse("joined"), newGraph) - stopTimeMeasurementAndAddToTotal(startTime, timeToJoinGraphs) newInterpreter } } @@ -328,7 +332,6 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { assumptionGraph.removeLabelNodes() val mergedGraph = if(Verifier.config.enableDependencyAnalysisDebugging()) assumptionGraph else buildAndGetMergedGraph() mergedGraph.addTransitiveEdges() - DependencyAnalyzer.stopTimeMeasurementAndAddToTotal(transitiveEdgesStartTime, DependencyAnalyzer.timeToAddTransitiveEdges) Some(mergedGraph) } diff --git a/src/main/scala/dependencyAnalysis/DependencyGraph.scala b/src/main/scala/dependencyAnalysis/DependencyGraph.scala index 0c83017b8..42f5e0fa0 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraph.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraph.scala @@ -47,8 +47,8 @@ class DependencyGraph extends ReadOnlyDependencyGraph { nodes = nodes :+ node } - def addNodes(nodes: Iterable[DependencyAnalysisNode]): Unit = { - nodes foreach addNode + def addNodes(newNodes: Iterable[DependencyAnalysisNode]): Unit = { + nodes = nodes ++ newNodes } def addEdges(source: Int, targets: Iterable[Int]): Unit = { @@ -66,6 +66,10 @@ class DependencyGraph extends ReadOnlyDependencyGraph { targets foreach (addEdges(sources, _)) } + def addEdgesMap(newEdges: Map[Int, Set[Int]]): Unit = { + edges.addAll(newEdges) + } + def existsAnyDependency(sources: Set[Int], targets: Set[Int], includeInfeasibilityNodes: Boolean): Boolean = { val infeasibilityNodeIds: Set[Int] = if(includeInfeasibilityNodes) Set.empty else (getNodes filter (_.isInstanceOf[InfeasibilityNode]) map (_.id)).toSet var visited: Set[Int] = Set.empty diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index b8ed52511..bc2189336 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -10,12 +10,19 @@ import java.io.{File, PrintWriter} class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependencyGraph, member: Option[ast.Member]=None) { + protected var joinCandidateNodes: Seq[DependencyAnalysisNode] = Seq.empty def getGraph: ReadOnlyDependencyGraph = dependencyGraph def getName: String = name def getMember: Option[ast.Member] = member def getNodes: Set[DependencyAnalysisNode] = dependencyGraph.getNodes.toSet + def getJoinCandidateNodes: Iterable[DependencyAnalysisNode] = joinCandidateNodes + + def initJoinCandidateNodes(): Unit = { + joinCandidateNodes = dependencyGraph.getNodes.filter(node => node.isInstanceOf[AxiomAssumptionNode] || AssumptionType.joinConditionTypes.contains(node.assumptionType)) + } + def getNodesByLine(line: Int): Set[DependencyAnalysisNode] = getNodes.filter(n => !AssumptionType.internalTypes.contains(n.assumptionType)).filter(node => node.sourceInfo.getLineNumber.isDefined && node.sourceInfo.getLineNumber.get == line) diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index 902383ee9..d0a3de50f 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -123,6 +123,7 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif } result.dependencyGraphInterpreter = v.decider.dependencyAnalyzer.buildFinalGraph().map(new DependencyGraphInterpreter(method.name, _, Some(method))) + result.dependencyGraphInterpreter.foreach(_.initJoinCandidateNodes()) v.decider.resetProverOptions() diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index 377740dcb..579e386bc 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -164,6 +164,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver v.decider.dependencyAnalyzer.addFunctionAxiomEdges() res.dependencyGraphInterpreter = v.decider.dependencyAnalyzer.buildFinalGraph().map(new DependencyGraphInterpreter(function.name, _, Some(function))) + res.dependencyGraphInterpreter.foreach(_.initJoinCandidateNodes()) Seq(res) } From 1b939c205cbc8d1c1412a5f3052af8fff9b896e3 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 24 Nov 2025 11:00:57 +0100 Subject: [PATCH 267/474] add more profiling instrumentation --- src/main/scala/decider/ProverStdIO.scala | 3 ++ .../DependencyAnalyzer.scala | 34 ++++++++++++++++--- .../dependencyAnalysis/DependencyGraph.scala | 4 --- .../scala/verifier/DefaultMainVerifier.scala | 8 +++-- 4 files changed, 38 insertions(+), 11 deletions(-) diff --git a/src/main/scala/decider/ProverStdIO.scala b/src/main/scala/decider/ProverStdIO.scala index 14b72324a..90b46fbce 100644 --- a/src/main/scala/decider/ProverStdIO.scala +++ b/src/main/scala/decider/ProverStdIO.scala @@ -8,6 +8,7 @@ package viper.silicon.decider import com.typesafe.scalalogging.LazyLogging import viper.silicon.common.config.Version +import viper.silicon.dependencyAnalysis.DependencyAnalyzer import viper.silicon.interfaces.decider._ import viper.silicon.reporting.{ExternalToolError, ProverInteractionFailed} import viper.silicon.state.IdentifierFactory @@ -307,9 +308,11 @@ abstract class ProverStdIO(uniqueId: String, } def extractUnsatCore(): String = { + val startTime = DependencyAnalyzer.startTimeMeasurement() writeLine("(get-unsat-core)") val unsatCore = input.readLine() comment("unsat core: " + unsatCore) + DependencyAnalyzer.stopTimeMeasurementAndAddToTotal(startTime, DependencyAnalyzer.timeToExtractUnsatCore) unsatCore } diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index 1631378e3..68a26e473 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -1,6 +1,7 @@ package viper.silicon.dependencyAnalysis import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType +import viper.silicon.dependencyAnalysis.DependencyAnalyzer.{runtimeOverheadPermissionNodes, startTimeMeasurement, stopTimeMeasurementAndAddToTotal, timeForFunctionJoin, timeToProcessUnsatCore} import viper.silicon.interfaces.state.{Chunk, GeneralChunk} import viper.silicon.state.terms._ import viper.silicon.verifier.Verifier @@ -57,9 +58,15 @@ object DependencyAnalyzer { private val enableDependencyAnalysisAnnotationKey = "enableDependencyAnalysis" val timeToVerifyAndCollectDependencies: AtomicLong = new AtomicLong(0) val timeToVerifyAndBuildFinalGraph: AtomicLong = new AtomicLong(0) + val timeOfPostprocessing: AtomicLong = new AtomicLong(0) val timeToAddNodes: AtomicLong = new AtomicLong(0) val timeToAddEdges: AtomicLong = new AtomicLong(0) val timeToExtractCandidateNodes: AtomicLong = new AtomicLong(0) + val timeForFunctionJoin: AtomicLong = new AtomicLong(0) + val timeForMethodJoin: AtomicLong = new AtomicLong(0) + val runtimeOverheadPermissionNodes: AtomicLong = new AtomicLong(0) + val timeToExtractUnsatCore: AtomicLong = new AtomicLong(0) + val timeToProcessUnsatCore: AtomicLong = new AtomicLong(0) def startTimeMeasurement(): Long = { if(!Verifier.config.enableDependencyAnalysisProfiling()) return 0 @@ -78,9 +85,15 @@ object DependencyAnalyzer { println(s"Overall runtime = time spent on verification and building the final graph: ${timeToVerifyAndBuildFinalGraph.get() / 1e6}ms") println(s"This runtime can be categorized into the following, fine-grained measurements.") println(s" Time spent on verification and collecting low-level dependencies: ${timeToVerifyAndCollectDependencies.get() / 1e6}ms") - println(s" Time spent on adding nodes: ${timeToAddNodes.get() / 1e6}ms") - println(s" Time spent on adding edges: ${timeToAddEdges.get() / 1e6}ms") - println(s" Time spent on extracting candidate nodes: ${timeToExtractCandidateNodes.get() / 1e6}ms") + println(s" Time spent on adding explicit permission nodes: ${runtimeOverheadPermissionNodes.get() / 1e6}ms") + println(s" Time spent on extracting the unsat core: ${timeToExtractUnsatCore.get() / 1e6}ms") + println(s" Time spent on processing the unsat core: ${timeToProcessUnsatCore.get() / 1e6}ms") + println(s" Postprocessing: ${timeOfPostprocessing.get() / 1e6}ms") + println(s" Time spent on adding nodes: ${timeToAddNodes.get() / 1e6}ms") + println(s" Time spent on adding edges: ${timeToAddEdges.get() / 1e6}ms") + println(s" Time spent on extracting candidate nodes: ${timeToExtractCandidateNodes.get() / 1e6}ms") + println(s" Time spent for joins over function calls: ${timeForFunctionJoin.get() / 1e6}ms") + println(s" Time spent for joins over method calls: ${timeForMethodJoin.get() / 1e6}ms") } private def extractAnnotationFromInfo(info: ast.Info, annotationKey: String): Option[Seq[String]] = { @@ -128,7 +141,6 @@ object DependencyAnalyzer { // TODO ake: implement a lazy join in DependencyGraphInterpreter def joinGraphsAndGetInterpreter(name: Option[String], dependencyGraphInterpreters: Set[DependencyGraphInterpreter]): DependencyGraphInterpreter = { - var startTime = startTimeMeasurement() val newGraph = new DependencyGraph @@ -141,6 +153,7 @@ object DependencyAnalyzer { val joinCandidateNodes = dependencyGraphInterpreters flatMap(_.getJoinCandidateNodes) stopTimeMeasurementAndAddToTotal(startTime, timeToExtractCandidateNodes) + startTime = startTimeMeasurement() // axioms assumed by every method / function should depend on the assertions that justify them // hence, we add edges from function postconditions & bodies to the corresponding axioms val axiomAssertionNodes = joinCandidateNodes @@ -156,6 +169,9 @@ object DependencyAnalyzer { newGraph.addEdges(assertionNodeIds, axiomNodeIds) // TODO ake: maybe we could merge the axiom nodes here since they represent the same axiom? } + stopTimeMeasurementAndAddToTotal(startTime, timeForFunctionJoin) + startTime = startTimeMeasurement() + // postconditions of methods assumed by every method call should depend on the assertions that justify them // hence, we add edges from assertions of method postconditions to assumptions of the same postcondition (at method calls) val relevantAssumptionNodes = joinCandidateNodes @@ -167,6 +183,8 @@ object DependencyAnalyzer { .map(node => (node.id, relevantAssumptionNodes.getOrElse(node.sourceInfo.getTopLevelSource.toString, Seq.empty))) .foreach { case (src, targets) => newGraph.addEdges(src, targets)} + stopTimeMeasurementAndAddToTotal(startTime, timeForMethodJoin) + val newInterpreter = new DependencyGraphInterpreter(name.getOrElse("joined"), newGraph) newInterpreter } @@ -222,19 +240,23 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { } override def registerExhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, analysisInfo: AnalysisInfo): CH = { + val startTime = startTimeMeasurement() val chunk = buildChunk(perm) val chunkNode = addPermissionExhaleNode(chunk, chunk.perm, analysisInfo.sourceInfo, analysisInfo.assumptionType) addPermissionDependencies(sourceChunks, Set(), chunkNode) + stopTimeMeasurementAndAddToTotal(startTime, runtimeOverheadPermissionNodes) chunk } override def registerInhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, labelNodeOpt: Option[LabelNode], analysisInfo: AnalysisInfo, isExhale: Boolean): CH = { + val startTime = startTimeMeasurement() val labelNode = labelNodeOpt.get val chunk = buildChunk(Ite(labelNode.term, perm, NoPerm)) val chunkNode = addPermissionInhaleNode(chunk, chunk.perm, analysisInfo.sourceInfo, analysisInfo.assumptionType, labelNode) if(chunkNode.isDefined) addDependency(chunkNode, Some(labelNode.id)) addPermissionDependencies(sourceChunks, Set(), chunkNode) + stopTimeMeasurementAndAddToTotal(startTime, runtimeOverheadPermissionNodes) chunk } @@ -292,6 +314,7 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { } override def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit = { + val startTime = startTimeMeasurement() val assumptionLabels = dep.replace("(", "").replace(")", "").split(" ") val assumptionIds = assumptionLabels.filter(DependencyAnalyzer.isAssumptionLabel).map(DependencyAnalyzer.getIdFromLabel) val assertionIdsFromUnsatCore = assumptionLabels.filter(DependencyAnalyzer.isAssertionLabel).map(DependencyAnalyzer.getIdFromLabel) @@ -300,6 +323,7 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { assumptionGraph.addEdges(assumptionIds, assertionIds) val axiomIds = assumptionLabels.filter(DependencyAnalyzer.isAxiomLabel).map(DependencyAnalyzer.getIdFromLabel) assumptionGraph.addEdges(axiomIds, assertionIds) + stopTimeMeasurementAndAddToTotal(startTime, timeToProcessUnsatCore) } private def addPermissionDependencies(sourceChunks: Set[Chunk], sourceTerms: Set[Term], newChunkNodeId: Option[Int]): Unit = { @@ -310,10 +334,12 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { } override def addPermissionDependencies(sourceChunks: Set[Chunk], sourceTerms: Set[Term], newChunk: Chunk): Unit = { + val startTime = startTimeMeasurement() val newChunkId = assumptionGraph.nodes .filter(c => c.isInstanceOf[PermissionInhaleNode] && c.isInstanceOf[ChunkAnalysisInfo] && newChunk.equals(c.asInstanceOf[ChunkAnalysisInfo].getChunk)) .map(_.id).toSet addPermissionDependencies(sourceChunks, sourceTerms, newChunkId.headOption) + stopTimeMeasurementAndAddToTotal(startTime, runtimeOverheadPermissionNodes) } override def addCustomTransitiveDependency(sourceSourceInfo: AnalysisSourceInfo, targetSourceInfo: AnalysisSourceInfo): Unit = { diff --git a/src/main/scala/dependencyAnalysis/DependencyGraph.scala b/src/main/scala/dependencyAnalysis/DependencyGraph.scala index 42f5e0fa0..6fae3060f 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraph.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraph.scala @@ -66,10 +66,6 @@ class DependencyGraph extends ReadOnlyDependencyGraph { targets foreach (addEdges(sources, _)) } - def addEdgesMap(newEdges: Map[Int, Set[Int]]): Unit = { - edges.addAll(newEdges) - } - def existsAnyDependency(sources: Set[Int], targets: Set[Int], includeInfeasibilityNodes: Boolean): Boolean = { val infeasibilityNodeIds: Set[Int] = if(includeInfeasibilityNodes) Set.empty else (getNodes filter (_.isInstanceOf[InfeasibilityNode]) map (_.id)).toSet var visited: Set[Int] = Set.empty diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index 354c38b95..8b0ee3f66 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -316,6 +316,7 @@ class DefaultMainVerifier(config: Config, ++ methodVerificationResults) DependencyAnalyzer.stopTimeMeasurementAndAddToTotal(verificationStartTime, DependencyAnalyzer.timeToVerifyAndCollectDependencies) + val postProcessingStartTime = DependencyAnalyzer.startTimeMeasurement() if(Verifier.config.enableDependencyAnalysis()){ val dependencyGraphInterpreters = verificationResults.filter(_.dependencyGraphInterpreter.isDefined).map(_.dependencyGraphInterpreter.get) @@ -338,11 +339,12 @@ class DefaultMainVerifier(config: Config, case _ => } - DependencyAnalyzer.stopTimeMeasurementAndAddToTotal(verificationStartTime, DependencyAnalyzer.timeToVerifyAndBuildFinalGraph) + } - DependencyAnalyzer.printProfilingResults() + DependencyAnalyzer.stopTimeMeasurementAndAddToTotal(postProcessingStartTime, DependencyAnalyzer.timeOfPostprocessing) + DependencyAnalyzer.stopTimeMeasurementAndAddToTotal(verificationStartTime, DependencyAnalyzer.timeToVerifyAndBuildFinalGraph) - } + DependencyAnalyzer.printProfilingResults() if (Verifier.config.startDebuggerAutomatically()){ val debugger = new SiliconDebugger(verificationResults, identifierFactory, reporter, FrontendStateCache.resolver, FrontendStateCache.pprogram, FrontendStateCache.translator, this) From 190a62db2b2640f91659c6e9cfe2df9329e87936 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 27 Nov 2025 13:40:51 +0100 Subject: [PATCH 268/474] fix test annotations --- .../dependencyAnalysisTests/all/divBy0.vpr | 4 ++-- .../dependencyAnalysisTests/all/function-sum.vpr | 4 ++-- .../dependencyAnalysisTests/all/functions.vpr | 10 +++++----- .../dependencyAnalysisTests/all/method-sum.vpr | 8 ++++---- .../unitTests/E-function-call.vpr | 10 +++++----- .../unitTests/F-method-call.vpr | 14 +++++++------- 6 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/test/resources/dependencyAnalysisTests/all/divBy0.vpr b/src/test/resources/dependencyAnalysisTests/all/divBy0.vpr index b10aee6e7..3e907a770 100644 --- a/src/test/resources/dependencyAnalysisTests/all/divBy0.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/divBy0.vpr @@ -45,13 +45,13 @@ method sumClient(x: Int, y: Int) @dependency("Explicit") assume x < y var n: Int - @dependency("ImplicitPostcondAssumption") + @dependency("ImplicitPostcondition") n := sum(x, y) // the following stmt reports dependency on n := sum(x, y) because (x < y && n == x + y && n > 100 ==> y != 0) // although you could also prove it via (0 <= x && x < y ==> y != 0) var n2: Int - @dependency("ImplicitPostcondAssumption") + @dependency("ImplicitPostcondition") n2 := sum(x/y, y) @dependency("Implicit") n := n + n2 diff --git a/src/test/resources/dependencyAnalysisTests/all/function-sum.vpr b/src/test/resources/dependencyAnalysisTests/all/function-sum.vpr index 265e85618..435cf38bf 100644 --- a/src/test/resources/dependencyAnalysisTests/all/function-sum.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/function-sum.vpr @@ -20,7 +20,7 @@ method call2(){ @dependency("Explicit") assume y > 0 - @dependency("ImplicitPostcondAssumption") + @dependency("ImplicitPostcondition") z := sum(x, y) // $PrecisionTest: $READ_ONLY=x,y,z $INVARIANT=x>0 @@ -36,7 +36,7 @@ method call2Unv(){ @dependency("Explicit") assume y > 0 - @dependency("ExplicitPostcondAssumption") + @dependency("ExplicitPostcondition") z := sumUnverified(x, y) // $PrecisionTest: $READ_ONLY=x,y,z $INVARIANT=x>0 diff --git a/src/test/resources/dependencyAnalysisTests/all/functions.vpr b/src/test/resources/dependencyAnalysisTests/all/functions.vpr index 2d70a2a3f..2ad23ee0b 100644 --- a/src/test/resources/dependencyAnalysisTests/all/functions.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/functions.vpr @@ -37,7 +37,7 @@ method predicateClient(x: Ref) x.f := 10 @dependency("Rewrite") fold greater0(x) - @dependency("ImplicitPostcondAssumption") + @dependency("ImplicitPostcondition") a := foo(x) @testAssertion("Explicit") @@ -52,7 +52,7 @@ method predicateClientPostcond(x: Ref) x.f := 10 @dependency("Rewrite") fold greater0(x) - @dependency("ExplicitPostcondAssumption") + @dependency("ExplicitPostcondition") a := fooPostcond(x) @testAssertion("Explicit") @@ -64,7 +64,7 @@ method callDiv(){ var x: Int @dependency("Explicit") assume x > 10 - @dependency("ImplicitPostcondAssumption") + @dependency("ImplicitPostcondition") x := div100(x) @testAssertion("Explicit") @@ -75,7 +75,7 @@ method callDivPostcond(){ var x: Int @dependency("Explicit") assume x > 10 - @dependency("ExplicitPostcondAssumption") + @dependency("ExplicitPostcondition") x := div100Postcond(x) @testAssertion("Explicit") @@ -90,7 +90,7 @@ function fooInput(a: Int): Int method client(a: Int) { var res: Int - @dependency("ExplicitPostcondAssumption") + @dependency("ExplicitPostcondition") res := fooInput(a) @testAssertion("Explicit") diff --git a/src/test/resources/dependencyAnalysisTests/all/method-sum.vpr b/src/test/resources/dependencyAnalysisTests/all/method-sum.vpr index 8c685f1f0..78acbaa73 100644 --- a/src/test/resources/dependencyAnalysisTests/all/method-sum.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/method-sum.vpr @@ -19,13 +19,13 @@ method sumClient(x: Int, y: Int) @dependency("Explicit") assume x < y var n: Int - @dependency("ImplicitPostcondAssumption") + @dependency("ImplicitPostcondition") n := sum(x, y) // the following stmt reports dependency on n := sum(x, y) because (x < y && n == x + y && n > 100 ==> y != 0) // although you could also prove it via (0 <= x && x < y ==> y != 0) var n2: Int - @dependency("ImplicitPostcondAssumption") + @dependency("ImplicitPostcondition") n2 := sum(x/y, y) @dependency("Implicit") n := n + n2 @@ -43,13 +43,13 @@ method sumClient2(x: Int, y: Int) @irrelevant("Explicit") assume x < y var n: Int - @irrelevant("ImplicitPostcondAssumption") + @irrelevant("ImplicitPostcondition") n := sum(x, x) @irrelevant() assert n >= 100 var n2: Int - @dependency("ImplicitPostcondAssumption") + @dependency("ImplicitPostcondition") n2 := sum(y, y) @testAssertion("Explicit") assert n2 == 2*y diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/E-function-call.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/E-function-call.vpr index afe869006..d4ce91b18 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/E-function-call.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/E-function-call.vpr @@ -32,7 +32,7 @@ method call2(){ @dependency("Explicit") assume y > 0 - @dependency("ImplicitPostcondAssumption") + @dependency("ImplicitPostcondition") z := sum(x, y) // $PrecisionTest: $READ_ONLY=x,y,z $INVARIANT=x>0 @@ -49,12 +49,12 @@ method call3(x: Int, y: Int) assume y > 0 var n: Int, n2: Int - @dependency("ImplicitPostcondAssumption") + @dependency("ImplicitPostcondition") n := sum(x, y) // $PrecisionTest: $READ_WRITE=n2 $READ_ONLY=x,y,n $INVARIANT=x>0 - @dependency("ImplicitPostcondAssumption") + @dependency("Implicit") n2 := sum(x, y) @dependency("Implicit") n := n + n2 @@ -75,12 +75,12 @@ method call4(x: Int, y: Int, z: Int) assume z > 0 var n: Int, n2: Int - @dependency("ExplicitPostcondAssumption") + @dependency("ExplicitPostcondition") n := sumUnverified(x, y) // $PrecisionTest: $READ_WRITE=n2 $READ_ONLY=n,z,x,y $INVARIANT=x>0 - @irrelevant("ExplicitPostcondAssumption") + @irrelevant("ExplicitPostcondition") n2 := sumUnverified(x, z) @testAssertion("Explicit") diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/F-method-call.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/F-method-call.vpr index 2e0a1d524..c7092ce0d 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/F-method-call.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/F-method-call.vpr @@ -31,7 +31,7 @@ method call1(){ // $PrecisionTest: $READ_ONLY=x,y $INVARIANT=y>-5 - @testAssertion("ImplicitPostcondAssumption") + @testAssertion("ImplicitPostcondition") x := sum(x, y) } @@ -42,7 +42,7 @@ method call2(){ @dependency("Explicit") assume y > 0 - @dependency("ImplicitPostcondAssumption") + @dependency("ImplicitPostcondition") z := sum(x, y) // $PrecisionTest: $READ_ONLY=x,y,z $INVARIANT=x>0 @@ -59,12 +59,12 @@ method call3(x: Int, y: Int) assume y > 0 var n: Int, n2: Int - @dependency("ExplicitPostcondAssumption") + @dependency("ExplicitPostcondition") n := sumUnverified(x, y) // $PrecisionTest: $READ_WRITE=n2 $READ_ONLY=x,y,n $INVARIANT=x>0 - @dependency("ExplicitPostcondAssumption") + @dependency("ExplicitPostcondition") n2 := sumUnverified(x, y) @dependency("Implicit") n := n + n2 @@ -85,12 +85,12 @@ method call4(x: Int, y: Int, z: Int) assume z > 0 var n: Int, n2: Int - @dependency("ImplicitPostcondAssumption") + @dependency("ImplicitPostcondition") n := sum(x, y) // $PrecisionTest: $READ_WRITE=n2 $READ_ONLY=n,z,x,y $INVARIANT=x>0 - @irrelevant("ImplicitPostcondAssumption") + @irrelevant("ImplicitPostcondition") n2 := sum(x, z) @testAssertion("Explicit") @@ -101,7 +101,7 @@ method absClient1(x: Ref) requires @dependency("Explicit")(acc(x.f)) requires @irrelevant("Explicit")(x.f > 0) { - @dependency("ImplicitPostcondAssumption") + @dependency("ImplicitPostcondition") abs(x) @testAssertion("Explicit") From 80fed5795cc600e57d4b127036565d5d8ead1466 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 28 Nov 2025 08:21:31 +0100 Subject: [PATCH 269/474] verification errors fixed but DA unsound!! --- src/main/scala/decider/Decider.scala | 2 +- src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 6b85f4b09..b4f767f6d 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -344,7 +344,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => return term val labelNode = getOrCreateAnalysisLabelNode(sourceChunks, sourceTerms) - labelNode.map(n => Implies(n.term, term)).getOrElse(term) + term // XXX ake: labelNode.map(n => Implies(n.term, term)).getOrElse(term) } def isPathInfeasible(): Boolean = Verifier.config.disableInfeasibilityChecks() && pcs.getCurrentInfeasibilityNode.isDefined diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index 68a26e473..c5a7ed82d 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -201,7 +201,7 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { val inhaleNode = assumptionGraph.nodes .filter(c => c.isInstanceOf[PermissionInhaleNode] && chunk.equals(c.asInstanceOf[ChunkAnalysisInfo].getChunk)) .map(_.asInstanceOf[PermissionInhaleNode]) - assert(inhaleNode.size == 1) +// assert(inhaleNode.size == 1) inhaleNode.headOption } @@ -251,7 +251,7 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { override def registerInhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, labelNodeOpt: Option[LabelNode], analysisInfo: AnalysisInfo, isExhale: Boolean): CH = { val startTime = startTimeMeasurement() val labelNode = labelNodeOpt.get - val chunk = buildChunk(Ite(labelNode.term, perm, NoPerm)) + val chunk = buildChunk(perm) // XXX ake: buildChunk(Ite(labelNode.term, perm, NoPerm)) val chunkNode = addPermissionInhaleNode(chunk, chunk.perm, analysisInfo.sourceInfo, analysisInfo.assumptionType, labelNode) if(chunkNode.isDefined) addDependency(chunkNode, Some(labelNode.id)) From a14cc6cc56fc605925283f5e39a3c76929cc6ad9 Mon Sep 17 00:00:00 2001 From: Marco Eilers Date: Wed, 26 Nov 2025 14:01:46 +0100 Subject: [PATCH 270/474] Making resource bounds the default instead of timeouts (#949) (cherry picked from commit a8e5d1d60bb6db8fe428f598e2fc7723d4d7c2ec) --- src/main/scala/Config.scala | 188 ++---------------- src/main/scala/decider/CVC5ProverStdIO.scala | 18 +- src/main/scala/decider/Z3ProverAPI.scala | 8 +- src/main/scala/decider/Z3ProverStdIO.scala | 4 +- .../scala/verifier/DefaultMainVerifier.scala | 8 +- 5 files changed, 45 insertions(+), 181 deletions(-) diff --git a/src/main/scala/Config.scala b/src/main/scala/Config.scala index 4b9ee650b..535eac819 100644 --- a/src/main/scala/Config.scala +++ b/src/main/scala/Config.scala @@ -237,7 +237,7 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { noshort = true ) - private val rawProverSaturationTimeout = opt[Int]("proverSaturationTimeout", + private val proverSaturationTimeout = opt[Int]("proverSaturationTimeout", descr = ( "Timeout (in ms) used for the prover's state saturation calls (default: 100). " + "A timeout of 0 disables all saturation checks." + s"Note that for the ${Cvc5ProverStdIO.name} prover, state saturation calls can " @@ -254,22 +254,6 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { noshort = true ) - // DEPRECATED and replaced by proverSaturationTimeout - // but continues to work for now for backwards compatibility. - private val rawZ3SaturationTimeout = opt[Int]("z3SaturationTimeout", - descr = ("Warning: This option is deprecated due to standardization in option naming." - + " Please use 'proverSaturationTimeout' instead... " - + "Timeout (in ms) used for Z3 state saturation calls (default: 100). A timeout of " - + "0 disables all saturation checks."), - default = Some(100), - noshort = true - ) - - private lazy val rawCombinedSaturationTimeout: Int = { - if (rawZ3SaturationTimeout.isSupplied) rawZ3SaturationTimeout() - else rawProverSaturationTimeout() - } - /* Attention: Update companion object if number of weights changes! */ case class ProverSaturationTimeoutWeights(afterPreamble: Float = 1, afterContract: Float = 0.5f, @@ -310,30 +294,8 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { noshort = true )(saturationTimeoutWeightsConverter) - // DEPRECATED and replaced by proverSaturationTimeoutWeights - // but continues to work for now for backwards compatibility. - private val rawZ3SaturationTimeoutWeights = opt[ProverSaturationTimeoutWeights]("z3SaturationTimeoutWeights", - descr = ("Warning: This option is deprecated due to standardization in option naming." - + " Please use 'proverSaturationTimeoutWeights' instead... " - + "Weights used to compute the effective timeout for Z3 state saturation calls, " - + "which are made at various points during a symbolic execution. The effective " - + "timeouts for a particular saturation call is computed by multiplying the " - + "corresponding weight with the base timeout for saturation calls. " - + "Defaults to the following weights:\n" - + s" after program preamble: ${defaultProverSaturationTimeoutWeights.afterPreamble}\n" - + s" after inhaling contracts: ${defaultProverSaturationTimeoutWeights.afterContract}\n" - + s" after unfold: ${defaultProverSaturationTimeoutWeights.afterUnfold}\n" - + s" after inhale: ${defaultProverSaturationTimeoutWeights.afterInhale}\n" - + s" before repeated Z3 queries: ${defaultProverSaturationTimeoutWeights.beforeRepetition}\n" - + "Weights must be non-negative, a weight of 0 disables the corresponding saturation " - + "call and a minimal timeout of 10ms is enforced."), - default = Some(defaultProverSaturationTimeoutWeights), - noshort = true - )(saturationTimeoutWeightsConverter) - private lazy val rawCombinedSaturationTimeoutWeights: ProverSaturationTimeoutWeights = { - if (rawZ3SaturationTimeoutWeights.isSupplied) rawZ3SaturationTimeoutWeights() - else rawProverSaturationTimeoutWeights() + rawProverSaturationTimeoutWeights() } /* ATTENTION: Don't access the effective weights before the configuration objects has been @@ -341,10 +303,10 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { */ object proverSaturationTimeouts { private def scale(weight: Float, comment: String): Option[ProverStateSaturationTimeout] = { - if (weight == 0 || rawCombinedSaturationTimeout == 0) { + if (weight == 0 || proverSaturationTimeout() == 0) { None } else { - Some(ProverStateSaturationTimeout(Math.max(10.0, weight * rawCombinedSaturationTimeout).toInt, + Some(ProverStateSaturationTimeout(Math.max(10.0, weight * proverSaturationTimeout()).toInt, comment)) } } @@ -365,67 +327,30 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { scale(rawCombinedSaturationTimeoutWeights.beforeRepetition, "before repetition") } - private val rawProverEnableResourceBounds: ScallopOption[Boolean] = opt[Boolean]("proverEnableResourceBounds", - descr = "Use prover's resource bounds instead of timeouts", - default = Some(false), - noshort = true - ) - - // DEPRECATED and replaced by proverEnableResourceBounds - // but continues to work for now for backwards compatibility. - private val rawZ3EnableResourceBounds: ScallopOption[Boolean] = opt[Boolean]("z3EnableResourceBounds", - descr = ("Warning: This option is deprecated due to standardization in option naming." - + " Please use 'proverEnableResourceBounds' instead... " - + "Use Z3's resource bounds instead of timeouts"), + val proverEnableTimeBounds: ScallopOption[Boolean] = opt[Boolean]("proverEnableTimeBounds", + descr = "Use timeouts instead of prover's resource bounds", default = Some(false), noshort = true ) - lazy val proverEnableResourceBounds: Boolean = { - rawProverEnableResourceBounds() || rawZ3EnableResourceBounds() - } - - private val rawProverResourcesPerMillisecond: ScallopOption[Int] = opt[Int]("proverResourcesPerMillisecond", - descr = "Prover resources per milliseconds. Is used to convert timeouts to resource bounds.", - default = Some(60000), + val z3ResourcesPerMillisecond: ScallopOption[Int] = opt[Int]("z3ResourcesPerMillisecond", + descr = "Z3 resources per milliseconds. Is used to convert timeouts to resource bounds.", + default = Some(10000), noshort = true, ) - // DEPRECATED and replaced by proverResourcesPerMillisecond - // but continues to work for now for backwards compatibility. - private val rawZ3ResourcesPerMillisecond: ScallopOption[Int] = opt[Int]("z3ResourcesPerMillisecond", - descr = ("Warning: This option is deprecated due to standardization in option naming." - + " Please use 'proverResourcesPerMillisecond' instead... " - + "Z3 resources per milliseconds. Is used to convert timeouts to resource bounds."), - default = Some(60000), // Moritz Knüsel empirically determined 1600 in his BSc thesis, but when Malte - noshort = true, // used this value, over 20 tests failed. + val cvc5ResourcesPerMillisecond: ScallopOption[Int] = opt[Int]("cvc5ResourcesPerMillisecond", + descr = "CVC5 resources per milliseconds. Is used to convert timeouts to resource bounds.", + default = Some(50), + noshort = true, ) - lazy val proverResourcesPerMillisecond: Int = { - if (rawZ3ResourcesPerMillisecond.isSupplied) rawZ3ResourcesPerMillisecond() - else rawProverResourcesPerMillisecond() - } - - val rawProverRandomizeSeeds: ScallopOption[Boolean] = opt[Boolean]("proverRandomizeSeeds", + val proverRandomizeSeeds: ScallopOption[Boolean] = opt[Boolean]("proverRandomizeSeeds", descr = "Set various random seeds of the prover to random values", default = Some(false), noshort = true ) - // DEPRECATED and replaced by proverRandomizedSeeds - // but continues to work for now for backwards compatibility. - private val rawZ3RandomizeSeeds: ScallopOption[Boolean] = opt[Boolean]("z3RandomizeSeeds", - descr = ("Warning: This option is deprecated due to standardization in option naming." - + " Please use 'proverRandomizeSeeds' instead... " - + "Set various Z3 random seeds to random values"), - default = Some(false), - noshort = true - ) - - lazy val proverRandomizeSeeds: Boolean = { - rawZ3RandomizeSeeds() || rawProverRandomizeSeeds() - } - val tempDirectory: ScallopOption[String] = opt[String]("tempDirectory", descr = "Path to which all temporary data will be written (default: ./tmp)", default = Some("./tmp"), @@ -439,7 +364,7 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { ) lazy val outputProverLog: Boolean = { - enableTempDirectory.isSupplied || rawProverLogFile.isSupplied || rawZ3LogFile.isSupplied + enableTempDirectory.isSupplied || rawProverLogFile.isSupplied } private val rawZ3Exe = opt[String]("z3Exe", @@ -489,18 +414,6 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { noshort = true )(singleArgConverter[ConfigValue[String]](s => UserValue(s))) - // DEPRECATED and replaced by proverLogFile - // but continues to work for now for backwards compatibility. - private val rawZ3LogFile = opt[ConfigValue[String]]("z3LogFile", - descr = ( "Warning: This option is deprecated due to standardization in option naming." - + " Please use 'proverLogFile' instead... " - + "Log file containing the interaction with the prover, " - + s"extension $proverLogFileExtension will be appended. " - + s"(default: /$defaultRawProverLogFile.$proverLogFileExtension)."), - default = Some(DefaultValue(defaultRawProverLogFile)), - noshort = true - )(singleArgConverter[ConfigValue[String]](s => UserValue(s))) - def getProverLogfile(suffix: String = "", rawLogFile: ConfigValue[String]): Path = { rawLogFile match { case UserValue(logfile) => @@ -521,8 +434,7 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { } def proverLogFile(suffix: String = ""): Path = { - if (rawZ3LogFile.isSupplied) getProverLogfile(suffix, rawZ3LogFile()) - else getProverLogfile(suffix, rawProverLogFile()) + getProverLogfile(suffix, rawProverLogFile()) } private val rawProverArgs: ScallopOption[String] = opt[String]("proverArgs", @@ -532,23 +444,11 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { noshort = true ) - // DEPRECATED and replaced by proverArgs - // but continues to work for now for backwards compatibility. - private val rawZ3Args: ScallopOption[String] = opt[String]("z3Args", - descr = ( "Warning: This option is deprecated due to standardization in option naming." - + " Please use 'proverArgs' instead... " - + "Command-line arguments which should be forwarded to Z3. " - + "The expected format is \" ... \", excluding the quotation marks."), - default = None, - noshort = true - ) - lazy val proverArgs: Option[String] = { - if (rawZ3Args.isSupplied) rawZ3Args.toOption - else rawProverArgs.toOption + rawProverArgs.toOption } - private val rawProverConfigArgs: ScallopOption[Map[String, String]] = opt[Map[String, String]]("proverConfigArgs", + val proverConfigArgs: ScallopOption[Map[String, String]] = opt[Map[String, String]]("proverConfigArgs", descr = ( "Configuration options which should be forwarded to the prover. " + "The expected format is \"= = ... =\", " + "excluding the quotation marks. " @@ -557,27 +457,9 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { noshort = true )(smtlibOptionsConverter) - // DEPRECATED and replaced by proverConfigArgs - // but continues to work for now for backwards compatibility. - private val rawZ3ConfigArgs: ScallopOption[Map[String, String]] = opt[Map[String, String]]("z3ConfigArgs", - descr = ( "Warning: This option is deprecated due to standardization in option naming." - + " Please use 'proverConfigArgs' instead... " - + "Configuration options which should be forwarded to Z3. " - + "The expected format is \"= = ... =\", " - + "excluding the quotation marks. " - + "The configuration options given here will override those from Silicon's Z3 preamble."), - default = Some(Map()), - noshort = true - )(smtlibOptionsConverter) - - lazy val proverConfigArgs: Map[String, String] = { - if (rawZ3ConfigArgs.isSupplied) rawZ3ConfigArgs() - else rawProverConfigArgs() - } - lazy val proverTimeout: Int = None.orElse( - proverConfigArgs.collectFirst { + proverConfigArgs().collectFirst { case (k, v) if k.toLowerCase == "timeout" && v.forall(Character.isDigit) => v.toInt }) .orElse{ @@ -587,19 +469,6 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { lazy val useFlyweight: Boolean = prover() == "Z3-API" - val maxHeuristicsDepth: ScallopOption[Int] = opt[Int]("maxHeuristicsDepth", - descr = "Maximal number of nested heuristics applications (default: 3)", - default = Some(3), - noshort = true - ) - - val handlePureConjunctsIndividually: ScallopOption[Boolean] = opt[Boolean]("handlePureConjunctsIndividually", - descr = ( "Handle pure conjunction individually." - + "Increases precision of error reporting, but may slow down verification."), - default = Some(false), - noshort = true - ) - val assertionMode: ScallopOption[AssertionMode] = opt[AssertionMode]("assertionMode", descr = ( "Determines how assertion checks are encoded in SMTLIB. Options are " + "'pp' (push-pop) and 'sc' (soft constraints) (default: pp)."), @@ -795,7 +664,7 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { ) val conditionalizePermissions: ScallopOption[Boolean] = opt[Boolean]("conditionalizePermissions", - descr = "Potentially reduces the number of symbolic execution paths, by conditionalising " + + descr = "Potentially reduces the number of symbolic execution paths, by conditionalising " + "permission expressions. E.g. rewrite \"b ==> acc(x.f, p)\" to \"acc(x.f, b ? p : none)\"." + "This is an experimental feature; report problems if you observe any.", default = Some(false), @@ -1025,22 +894,5 @@ object Config { |""".stripMargin } -// private val converter: ValueConverter[StateConsolidationMode] = new ValueConverter[StateConsolidationMode] { -// Try { -// -// } -// -// val pushPopRegex: Regex = """(?i)(pp)""".r -// val softConstraintsRegex: Regex = """(?i)(sc)""".r -// -// def parse(s: List[(String, List[String])]): Either[String, Option[AssertionMode]] = s match { -// case (_, pushPopRegex(_) :: Nil) :: Nil => Right(Some(AssertionMode.PushPop)) -// case (_, softConstraintsRegex(_) :: Nil) :: Nil => Right(Some(AssertionMode.SoftConstraints)) -// case Nil => Right(None) -// case _ => Left(s"unexpected arguments") -// } -// -// val argType: ArgType.V = org.rogach.scallop.ArgType.SINGLE -// } } } diff --git a/src/main/scala/decider/CVC5ProverStdIO.scala b/src/main/scala/decider/CVC5ProverStdIO.scala index 26a302a9d..740e899f2 100644 --- a/src/main/scala/decider/CVC5ProverStdIO.scala +++ b/src/main/scala/decider/CVC5ProverStdIO.scala @@ -30,7 +30,7 @@ class Cvc5ProverStdIO(uniqueId: String, identifierFactory: IdentifierFactory, reporter: Reporter) extends ProverStdIO(uniqueId, termConverter, identifierFactory, reporter) { - + val name: String = Cvc5ProverStdIO.name val minVersion: Version = Cvc5ProverStdIO.minVersion val maxVersion: Option[Version] = Cvc5ProverStdIO.maxVersion @@ -40,8 +40,20 @@ class Cvc5ProverStdIO(uniqueId: String, val startUpArgs: Seq[String] = Cvc5ProverStdIO.startUpArgs val randomizeSeedsOptions: Seq[String] = Cvc5ProverStdIO.randomizeSeedsOptions - // cvc5 does not support per-check timeouts after instantiation. - protected def setTimeout(timeout: Option[Int]): Unit = {} + protected def setTimeout(timeout: Option[Int]): Unit = { + val effectiveTimeout = timeout.getOrElse(Verifier.config.proverTimeout) + + if (lastTimeout != effectiveTimeout) { + lastTimeout = effectiveTimeout + + if (!Verifier.config.proverEnableTimeBounds()) { + writeLine(s"(set-option :reproducible-resource-limit ${effectiveTimeout * Verifier.config.cvc5ResourcesPerMillisecond()})") + } else { + writeLine(s"(set-option :tlimit-per $effectiveTimeout)") + } + readSuccess() + } + } protected def getProverPath: Path = { Paths.get(Verifier.config.cvc5Exe) diff --git a/src/main/scala/decider/Z3ProverAPI.scala b/src/main/scala/decider/Z3ProverAPI.scala index ce2e0edaa..dce2df33c 100644 --- a/src/main/scala/decider/Z3ProverAPI.scala +++ b/src/main/scala/decider/Z3ProverAPI.scala @@ -151,7 +151,7 @@ class Z3ProverAPI(uniqueId: String, if (Verifier.config.disableNL.getOrElse(false)) { params.add("arith.nl", false) } - val userProvidedArgs = Verifier.config.proverConfigArgs + val userProvidedArgs = Verifier.config.proverConfigArgs() prover = ctx.mkSolver() val descrs = prover.getParameterDescriptions for ((origKey, vl) <- userProvidedArgs) { @@ -415,7 +415,7 @@ class Z3ProverAPI(uniqueId: String, val standardOptionPrefix = Seq("(set-option :auto_config false)", "(set-option :type_check true)") ++ Z3ProverAPI.allParams.map(bp => s"(set-option :${bp._1} ${bp._2})") - val customOptionPrefix = Verifier.config.proverConfigArgs.map(a => s"(set-option :${a._1} ${a._2})") + val customOptionPrefix = Verifier.config.proverConfigArgs().map(a => s"(set-option :${a._1} ${a._2})") val merged = (standardOptionPrefix ++ customOptionPrefix ++ emittedPreambleString).mkString("\n") val parsed = ctx.parseSMTLIB2String(merged, emittedSortSymbols.toArray, emittedSorts.toArray, emittedFuncSymbols.toArray, emittedFuncs.toArray) @@ -544,8 +544,8 @@ class Z3ProverAPI(uniqueId: String, if (lastTimeout != effectiveTimeout) { lastTimeout = effectiveTimeout - if (Verifier.config.proverEnableResourceBounds) { - ctx.updateParamValue("rlimit", (effectiveTimeout * Verifier.config.proverResourcesPerMillisecond).toString) + if (!Verifier.config.proverEnableTimeBounds()) { + ctx.updateParamValue("rlimit", (effectiveTimeout * Verifier.config.z3ResourcesPerMillisecond()).toString) } else { ctx.updateParamValue("timeout", effectiveTimeout.toString) } diff --git a/src/main/scala/decider/Z3ProverStdIO.scala b/src/main/scala/decider/Z3ProverStdIO.scala index cfd2f9122..dffa32d56 100644 --- a/src/main/scala/decider/Z3ProverStdIO.scala +++ b/src/main/scala/decider/Z3ProverStdIO.scala @@ -51,8 +51,8 @@ class Z3ProverStdIO(uniqueId: String, if (lastTimeout != effectiveTimeout) { lastTimeout = effectiveTimeout - if (Verifier.config.proverEnableResourceBounds) { - writeLine(s"(set-option :rlimit ${effectiveTimeout * Verifier.config.proverResourcesPerMillisecond})") + if (!Verifier.config.proverEnableTimeBounds()) { + writeLine(s"(set-option :rlimit ${effectiveTimeout * Verifier.config.z3ResourcesPerMillisecond()})") } else { writeLine(s"(set-option :timeout $effectiveTimeout)") } diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index 8b0ee3f66..30125c229 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -484,8 +484,8 @@ class DefaultMainVerifier(config: Config, sink.comment(s"\n; ${decider.prover.staticPreamble}") preambleReader.emitPreamble(decider.prover.staticPreamble, sink, true) - if (config.proverRandomizeSeeds) { - sink.comment(s"\n; Randomise seeds [--${config.rawProverRandomizeSeeds.name}]") + if (config.proverRandomizeSeeds()) { + sink.comment(s"\n; Randomise seeds [--${config.proverRandomizeSeeds.name}]") val options = decider.prover.randomizeSeedsOptions .map (key => s"(set-option :$key ${Random.nextInt(10000)})") @@ -493,11 +493,11 @@ class DefaultMainVerifier(config: Config, } val smt2ConfigOptions = - config.proverConfigArgs.map { case (k, v) => s"(set-option :$k $v)" } + config.proverConfigArgs().map { case (k, v) => s"(set-option :$k $v)" } if (smt2ConfigOptions.nonEmpty) { // One can pass options to the prover. This allows to check whether they have been received. - val msg = s"Additional prover configuration options are '${config.proverConfigArgs}'" + val msg = s"Additional prover configuration options are '${config.proverConfigArgs().mkString(", ")}'" reporter report ConfigurationConfirmation(msg) logger info msg preambleReader.emitPreamble(smt2ConfigOptions, sink, true) From 01f7b5270e8368a212e48645e65c30b4c338b2a1 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 2 Dec 2025 08:09:17 +0100 Subject: [PATCH 271/474] verification errors fixed but DA unsound!! --- src/main/scala/decider/Decider.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index b4f767f6d..6b85f4b09 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -344,7 +344,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => return term val labelNode = getOrCreateAnalysisLabelNode(sourceChunks, sourceTerms) - term // XXX ake: labelNode.map(n => Implies(n.term, term)).getOrElse(term) + labelNode.map(n => Implies(n.term, term)).getOrElse(term) } def isPathInfeasible(): Boolean = Verifier.config.disableInfeasibilityChecks() && pcs.getCurrentInfeasibilityNode.isDefined From ff3325bca74fcb5786f4be3a6101b640a9afb708 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 8 Dec 2025 10:07:46 +0100 Subject: [PATCH 272/474] fix: sound again --- src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index c5a7ed82d..68a26e473 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -201,7 +201,7 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { val inhaleNode = assumptionGraph.nodes .filter(c => c.isInstanceOf[PermissionInhaleNode] && chunk.equals(c.asInstanceOf[ChunkAnalysisInfo].getChunk)) .map(_.asInstanceOf[PermissionInhaleNode]) -// assert(inhaleNode.size == 1) + assert(inhaleNode.size == 1) inhaleNode.headOption } @@ -251,7 +251,7 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { override def registerInhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, labelNodeOpt: Option[LabelNode], analysisInfo: AnalysisInfo, isExhale: Boolean): CH = { val startTime = startTimeMeasurement() val labelNode = labelNodeOpt.get - val chunk = buildChunk(perm) // XXX ake: buildChunk(Ite(labelNode.term, perm, NoPerm)) + val chunk = buildChunk(Ite(labelNode.term, perm, NoPerm)) val chunkNode = addPermissionInhaleNode(chunk, chunk.perm, analysisInfo.sourceInfo, analysisInfo.assumptionType, labelNode) if(chunkNode.isDefined) addDependency(chunkNode, Some(labelNode.id)) From 7d4bb116e528ea9c2324b493e98743bfa04b9108 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 8 Dec 2025 20:35:08 +0100 Subject: [PATCH 273/474] cleanup analysis source info --- .../AnalysisSourceInfo.scala | 11 ++++++++++- .../DependencyAnalysisUserTool.scala | 8 ++++---- .../DependencyAnalyzer.scala | 10 +++++----- .../dependencyAnalysis/DependencyGraph.scala | 4 ++-- .../DependencyGraphInterpreter.scala | 18 +++++++++--------- 5 files changed, 30 insertions(+), 21 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala b/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala index ee1b01cc6..13b51e048 100644 --- a/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala +++ b/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala @@ -8,7 +8,7 @@ object AnalysisSourceInfo { p match { case NoPosition => "???" case filePos: AbstractSourcePosition => filePos.file.getFileName.toString + " @ line " + filePos.line - case column: HasLineColumn => "line " + column.line.toString + case lineColumn: HasLineColumn => "line " + lineColumn.line.toString case VirtualPosition(identifier) => "label " + identifier } } @@ -48,6 +48,8 @@ abstract class AnalysisSourceInfo { case class NoAnalysisSourceInfo() extends AnalysisSourceInfo { override def getPosition: Position = NoPosition + + override def equals(obj: Any): Boolean = false } case class ExpAnalysisSourceInfo(source: ast.Exp) extends AnalysisSourceInfo { @@ -86,6 +88,13 @@ case class StmtAnalysisSourceInfo(source: ast.Stmt) extends AnalysisSourceInfo { case class StringAnalysisSourceInfo(description: String, position: Position) extends AnalysisSourceInfo { override def toString: String = description.replaceAll("\n", "\t") + " (" + super.toString + ")" override def getPosition: Position = position + + override def equals(obj: Any): Boolean = + obj match { + case info: StringAnalysisSourceInfo => + info.description.equals(this.description) && info.getPosition.equals(this.getPosition) + case _ => false + } } case class TransitivityAnalysisSourceInfo(actualSource: AnalysisSourceInfo, transitivitySource: AnalysisSourceInfo) extends AnalysisSourceInfo { diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala index 18cb65320..5ab4e789a 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala @@ -75,9 +75,9 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete private def handleGraphSizeQuery(interpreter: DependencyGraphInterpreter): Unit = { val allAssumptions = interpreter.getNonInternalAssumptionNodes.filter(n => !n.isInstanceOf[AxiomAssumptionNode]) - val assumptions = allAssumptions.groupBy(_.sourceInfo.getTopLevelSource.toString) + val assumptions = allAssumptions.groupBy(_.sourceInfo.getTopLevelSource) val assertions = interpreter.getNonInternalAssertionNodesPerSource - val nodes = interpreter.getNonInternalAssertionNodes.union(allAssumptions).groupBy(_.sourceInfo.getTopLevelSource.toString) + val nodes = interpreter.getNonInternalAssertionNodes.union(allAssumptions).groupBy(_.sourceInfo.getTopLevelSource) println(s"#Assumptions = ${assumptions.size}") println(s"#Assertions = ${assertions.size}") println(s"#Nodes = ${nodes.size}") @@ -129,7 +129,7 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete } private def getSourceInfoString(nodes: Set[DependencyAnalysisNode]) = { - nodes.groupBy(node => node.sourceInfo.getTopLevelSource.toString).map{case (_, nodes) => nodes.head.sourceInfo.getTopLevelSource}.toList.sortBy(_.getLineNumber).mkString("\n\t") + nodes.groupBy(node => node.sourceInfo.getTopLevelSource).map{case (_, nodes) => nodes.head.sourceInfo.getTopLevelSource}.toList.sortBy(_.getLineNumber).mkString("\n\t") } private def getQueriedNodesFromInput(inputs: Set[String])= { @@ -235,7 +235,7 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete val (allDependencies, time) = measureTime[Set[DependencyAnalysisNode]](fullGraphInterpreter.getAllNonInternalDependencies(queriedNodes.map(_.id))) allTimes = allTimes :+ time numLowLevelDeps = allDependencies.size - numDeps = allDependencies.groupBy(node => node.sourceInfo.getTopLevelSource.toString).size + numDeps = allDependencies.groupBy(node => node.sourceInfo.getTopLevelSource).size } writer.println(s"$userInput,$numLowLevelDeps,$numDeps,${allTimes.mkString(",")}") diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index 68a26e473..908f59693 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -159,11 +159,11 @@ object DependencyAnalyzer { val axiomAssertionNodes = joinCandidateNodes .filter(n => (n.isInstanceOf[GeneralAssertionNode] && AssumptionType.postconditionTypes.contains(n.assumptionType)) || AssumptionType.FunctionBody.equals(n.assumptionType)) - .groupBy(_.sourceInfo.getTopLevelSource.toString) + .groupBy(_.sourceInfo.getTopLevelSource) .view.mapValues(_.map(_.id)) .toMap joinCandidateNodes.filter(_.isInstanceOf[AxiomAssumptionNode]) - .groupBy(n => n.sourceInfo.toString) + .groupBy(n => n.sourceInfo) .map{case (sourceInfo, axiomNodes) => (axiomNodes.map(_.id), axiomAssertionNodes.getOrElse(sourceInfo, Seq.empty))} .foreach{case (axiomNodeIds, assertionNodeIds) => newGraph.addEdges(assertionNodeIds, axiomNodeIds) // TODO ake: maybe we could merge the axiom nodes here since they represent the same axiom? @@ -176,11 +176,11 @@ object DependencyAnalyzer { // hence, we add edges from assertions of method postconditions to assumptions of the same postcondition (at method calls) val relevantAssumptionNodes = joinCandidateNodes .filter(node => node.isInstanceOf[GeneralAssumptionNode] && AssumptionType.postconditionTypes.contains(node.assumptionType)) - .groupBy(_.sourceInfo.getFineGrainedSource.toString) + .groupBy(_.sourceInfo.getFineGrainedSource) .view.mapValues(_.map(_.id)) .toMap joinCandidateNodes.filter(node => node.isInstanceOf[GeneralAssertionNode] && AssumptionType.postconditionTypes.contains(node.assumptionType)) - .map(node => (node.id, relevantAssumptionNodes.getOrElse(node.sourceInfo.getTopLevelSource.toString, Seq.empty))) + .map(node => (node.id, relevantAssumptionNodes.getOrElse(node.sourceInfo.getTopLevelSource, Seq.empty))) .foreach { case (src, targets) => newGraph.addEdges(src, targets)} stopTimeMeasurementAndAddToTotal(startTime, timeForMethodJoin) @@ -381,7 +381,7 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { } val nodesBySource = assumptionGraph.getNodes.filter(!keepNode(_)) - .groupBy(n => (n.sourceInfo.getSourceForTransitiveEdges.toString, n.sourceInfo.getTopLevelSource.toString, n.sourceInfo.getFineGrainedSource, n.assumptionType)) + .groupBy(n => (n.sourceInfo.getSourceForTransitiveEdges, n.sourceInfo.getTopLevelSource, n.sourceInfo.getFineGrainedSource, n.assumptionType)) nodesBySource foreach { case ((_, _, _, assumptionType), nodes) => val assumptionNodes = nodes.filter(_.isInstanceOf[GeneralAssumptionNode]) diff --git a/src/main/scala/dependencyAnalysis/DependencyGraph.scala b/src/main/scala/dependencyAnalysis/DependencyGraph.scala index 6fae3060f..c9569ccc3 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraph.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraph.scala @@ -120,8 +120,8 @@ class DependencyGraph extends ReadOnlyDependencyGraph { } // TODO ake: maybe move to DependencyAnalyzer? - private def getNodesPerTransitivitySourceInfo: Map[String, Seq[DependencyAnalysisNode]] = { - getNodes.groupBy(_.sourceInfo.getSourceForTransitiveEdges.toString) + private def getNodesPerTransitivitySourceInfo: Map[AnalysisSourceInfo, Seq[DependencyAnalysisNode]] = { + getNodes.groupBy(_.sourceInfo.getSourceForTransitiveEdges) } // TODO ake: maybe move to DependencyAnalyzer? diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index bc2189336..b198adddc 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -63,7 +63,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen } def filterOutNodesBySourceInfo(nodes: Set[DependencyAnalysisNode], excludeInfos: Set[AnalysisSourceInfo]): Set[DependencyAnalysisNode] = - nodes filterNot (node => excludeInfos.exists(i => i.getTopLevelSource.toString.equals(node.sourceInfo.getTopLevelSource.toString))) + nodes filterNot (node => excludeInfos.exists(i => i.getTopLevelSource.equals(node.sourceInfo.getTopLevelSource))) def getNonInternalAssumptionNodes: Set[DependencyAnalysisNode] = getNodes filter (node => (node.isInstanceOf[GeneralAssumptionNode] && !AssumptionType.internalTypes.contains(node.assumptionType)) @@ -78,8 +78,8 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen nodesToAnalyze.intersect(getNonInternalAssumptionNodes) .exists(node => dependencyGraph.existsAnyDependency(Set(node.id), nodesToAnalyze map (_.id) filter (_ != node.id), includeInfeasibilityNodes)) - def getNonInternalAssumptionNodesPerSource: Map[String, Set[DependencyAnalysisNode]] = - getNonInternalAssumptionNodes.groupBy(_.sourceInfo.getTopLevelSource.toString) + def getNonInternalAssumptionNodesPerSource: Map[AnalysisSourceInfo, Set[DependencyAnalysisNode]] = + getNonInternalAssumptionNodes.groupBy(_.sourceInfo.getTopLevelSource) def getNonInternalAssertionNodes: Set[DependencyAnalysisNode] = getNodes filter (node => @@ -87,8 +87,8 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen || AssumptionType.postconditionTypes.contains(node.assumptionType) ) - def getNonInternalAssertionNodesPerSource: Map[String, Set[DependencyAnalysisNode]] = - getNonInternalAssertionNodes.groupBy(_.sourceInfo.getTopLevelSource.toString) + def getNonInternalAssertionNodesPerSource: Map[AnalysisSourceInfo, Set[DependencyAnalysisNode]] = + getNonInternalAssertionNodes.groupBy(_.sourceInfo.getTopLevelSource) def getExplicitAssertionNodes: Set[DependencyAnalysisNode] = getNonInternalAssertionNodes.filter(node => AssumptionType.explicitAssertionTypes.contains(node.assumptionType)) @@ -102,8 +102,8 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen } private def getNodesWithIdenticalSource(nodes: Set[DependencyAnalysisNode]): Set[DependencyAnalysisNode] = { - val sourceInfos = nodes map (_.sourceInfo.getTopLevelSource.toString) - getNodes filter (node => sourceInfos.contains(node.sourceInfo.getTopLevelSource.toString)) + val sourceInfos = nodes map (_.sourceInfo.getTopLevelSource) + getNodes filter (node => sourceInfos.contains(node.sourceInfo.getTopLevelSource)) } def computeProofCoverage(): (Double, Set[String]) = { @@ -115,14 +115,14 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen val assertionNodeIds = assertionNodes map (_.id) val dependencies = dependencyGraph.getAllDependencies(assertionNodeIds, includeInfeasibilityNodes=true) val coveredNodes = dependencies ++ assertionNodeIds - val nodesPerSourceInfo = getNonInternalAssumptionNodes.filterNot(_.isInstanceOf[AxiomAssumptionNode]).groupBy(_.sourceInfo.getTopLevelSource.toString) + val nodesPerSourceInfo = getNonInternalAssumptionNodes.filterNot(_.isInstanceOf[AxiomAssumptionNode]).groupBy(_.sourceInfo.getTopLevelSource) if(nodesPerSourceInfo.isEmpty) return (Double.NaN, Set()) val uncoveredSources = (nodesPerSourceInfo filter { case (_, nodes) => coveredNodes.intersect(nodes map (_.id)).isEmpty }).keys.toSet val proofCoverage = 1.0 - (uncoveredSources.size.toDouble / nodesPerSourceInfo.size.toDouble) - (proofCoverage, uncoveredSources) + (proofCoverage, uncoveredSources.map(_.toString)) } def getPrunedProgram(crucialNodes: Set[DependencyAnalysisNode], program: ast.Program): (ast.Program, Double) = { From fa85f41cfeffd0c6cb00b967eb3f4ff10021a79b Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 11 Dec 2025 09:40:03 +0100 Subject: [PATCH 274/474] minor fixes --- .../dependencyAnalysis/DependencyAnalysisUserTool.scala | 2 +- .../dependencyAnalysis/DependencyGraphInterpreter.scala | 2 +- src/main/scala/rules/Consumer.scala | 2 +- src/main/scala/rules/Executor.scala | 7 ++++++- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala index 5ab4e789a..2a763e27b 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala @@ -146,7 +146,7 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete } private def handleDependencyQuery(inputs: Set[String]): Unit = { - val queriedNodes = getQueriedNodesFromInput(inputs) + val queriedNodes = getQueriedNodesFromInput(inputs).filter(node => node.isInstanceOf[GeneralAssertionNode]) val (directDependencies, timeDirect) = measureTime[Set[DependencyAnalysisNode]](fullGraphInterpreter.getDirectDependencies(queriedNodes.map(_.id))) val (allDependencies, timeAll) = measureTime[Set[DependencyAnalysisNode]](fullGraphInterpreter.getAllNonInternalDependencies(queriedNodes.map(_.id))) diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index b198adddc..ff17c15d2 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -39,7 +39,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen result = result.union(directDependencyIds) } - getNonInternalAssumptionNodes.filter(node => result.contains(node.id)) + getNonInternalAssumptionNodes.filter(node => result.contains(node.id) && !nodeIdsToAnalyze.contains(node.id)) } def getAllNonInternalDependencies(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[DependencyAnalysisNode] = { diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index a01e4d8b5..23de50054 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -648,7 +648,7 @@ object consumer extends ConsumptionRules { case false => val failure = createFailure(pve dueTo AssertionFalse(e), v2, s3, termToAssert, eNew) if (s3.retryLevel == 0 && v2.reportFurtherErrors()){ - v2.decider.assume(t, Option.when(withExp)(e), eNew, AssumptionType.Internal) + v2.decider.assume(t, Option.when(withExp)(e), eNew, AssumptionType.Explicit) failure combine QS(s3, v2) } else failure}}) })((s4, v4) => { diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 6eac67662..68852e9dc 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -540,7 +540,12 @@ object executor extends ExecutionRules { QS(s1.copy(h = s.h), v1) else createFailure(AssertFailed(assert) dueTo AssertionFalse(a), v1, s1, False, true, Option.when(withExp)(a)) - })((_, _) => Success()) + })((s2, v2) => + if (Verifier.config.disableInfeasibilityChecks()) + Q(s2, v2) + else + Success() + ) case assert @ ast.Assert(a) if Verifier.config.disableSubsumption() => val r = From a61ab6d610892017c446ba37887f0a426d92c0f4 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 11 Dec 2025 09:40:19 +0100 Subject: [PATCH 275/474] introduce precondition assumption type --- .../dependencyAnalysis/AnalysisInfo.scala | 2 +- .../scala/supporters/MethodSupporter.scala | 2 +- .../functions/FunctionVerificationUnit.scala | 4 +-- .../dependencyAnalysisTests/all/aliasing.vpr | 28 +++++++++---------- .../all/assume_assert.vpr | 4 +-- .../all/function_vs_method.vpr | 4 +-- .../all/implicitUpperBounds.vpr | 16 +++++------ .../all/imprecision-fixed.vpr | 18 ++++++------ .../all/imprecision.vpr | 24 ++++++++-------- .../all/infeasible.vpr | 10 +++---- .../all/quantified-perm.vpr | 12 ++++---- .../all/quasihavoc.vpr | 16 +++++------ .../dependencyAnalysisTests/all/sequences.vpr | 12 ++++---- .../all/state_consolidation.vpr | 24 ++++++++-------- .../dependencyAnalysisTests/all/sum-loops.vpr | 10 +++---- .../dependencyAnalysisTests/all/tuples.vpr | 4 +-- .../mce/imprecisions.vpr | 2 +- .../minimalExamples-precision/debug.vpr | 2 +- .../real-world-examples/gaussian.vpr | 6 ++-- .../real-world-examples/listAppend.vpr | 12 ++++---- .../unitTests/A-inhaleExhale.vpr | 2 +- .../unitTests/B-permissions.vpr | 8 +++--- .../unitTests/C-permWildcard.vpr | 12 ++++---- .../unitTests/D-quantifiedPermissions.vpr | 2 +- .../unitTests/F-method-call.vpr | 4 +-- .../unitTests/G-predicates.vpr | 22 +++++++-------- .../unitTests/H-magicWands.vpr | 10 +++---- .../unitTests/L-misc.vpr | 6 ++-- 28 files changed, 139 insertions(+), 139 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala index 5fe996e99..2a75661fa 100644 --- a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala +++ b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala @@ -2,7 +2,7 @@ package viper.silicon.dependencyAnalysis object AssumptionType extends Enumeration { type AssumptionType = Value - val Explicit, LoopInvariant, PathCondition, Rewrite, Implicit, Internal, Trigger, ExplicitPostcondition, ImplicitPostcondition, FunctionBody = Value + val Explicit, LoopInvariant, PathCondition, Rewrite, Implicit, Internal, Trigger, ExplicitPostcondition, ImplicitPostcondition, FunctionBody, Precondition = Value def fromString(s: String): Option[Value] = values.find(_.toString == s) diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index d0a3de50f..0705a84f5 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -102,7 +102,7 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif * rules in Smans' paper. */ executionFlowController.locally(s, v)((s1, v1) => { - produces(s1, freshSnap, pres, ContractNotWellformed, v1, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit))((s2, v2) => { + produces(s1, freshSnap, pres, ContractNotWellformed, v1, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Precondition))((s2, v2) => { v2.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) val s2a = s2.copy(oldHeaps = s2.oldHeaps + (Verifier.PRE_STATE_LABEL -> s2.h)) ( executionFlowController.locally(s2a, v2)((s3, v3) => { diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index 579e386bc..9bc671f87 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -231,7 +231,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver val result = executionFlowController.locally(s, v)((s0, _) => { val preMark = decider.setPathConditionMark() - produces(s0, toSf(`?s`), pres, ContractNotWellformed, v, AssumptionType.Explicit)((s1, _) => { + produces(s0, toSf(`?s`), pres, ContractNotWellformed, v, AssumptionType.Precondition)((s1, _) => { val relevantPathConditionStack = decider.pcs.after(preMark) phase1Data :+= Phase1Data(s1, relevantPathConditionStack.branchConditions, relevantPathConditionStack.branchConditionExps, relevantPathConditionStack.assumptions, Option.when(evaluator.withExp)(relevantPathConditionStack.assumptionExps)) @@ -272,7 +272,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver val labelledBcsPre = And(bcsPre map (t => v.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t)))) decider.setCurrentBranchCondition(labelledBcsPre, (BigAnd(bcsPreExp.map(_._1)), Option.when(wExp)(BigAnd(bcsPreExp.map(_._2.get))))) val labelledPcsPre = pcsPre map (t => v.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t))) - decider.assume(labelledPcsPre, pcsPreExp, s"precondition of ${function.name}", enforceAssumption=false, assumptionType=annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit)) + decider.assume(labelledPcsPre, pcsPreExp, s"precondition of ${function.name}", enforceAssumption=false, assumptionType=annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Precondition)) v.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) eval(s1, body, FunctionNotWellformed(function), v)((s2, tBody, bodyNew, _) => { val debugExp = if (wExp) { diff --git a/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr b/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr index 375e8028b..397a41030 100644 --- a/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr @@ -2,12 +2,12 @@ field f: Int method maybeAlias(a: Ref, b: Ref, c: Bool, n: Int) - requires @dependency("Explicit")(acc(a.f, 1/2)) - requires @dependency("Explicit")(acc(b.f, 1/2)) - requires @dependency("Explicit")(c ==> a == b) - requires @irrelevant("Explicit")(a.f > 0 && n > 0 && b.f >= 0) - requires @irrelevant("Explicit")(a.f < 100) - requires @irrelevant("Explicit")(!c ==> a.f < b.f) + requires @dependency("Precondition")(acc(a.f, 1/2)) + requires @dependency("Precondition")(acc(b.f, 1/2)) + requires @dependency("Precondition")(c ==> a == b) + requires @irrelevant("Precondition")(a.f > 0 && n > 0 && b.f >= 0) + requires @irrelevant("Precondition")(a.f < 100) + requires @irrelevant("Precondition")(!c ==> a.f < b.f) { if(@dependency("PathCondition")(c)){ @testAssertion("Implicit") @@ -16,9 +16,9 @@ method maybeAlias(a: Ref, b: Ref, c: Bool, n: Int) } method aliasing1(x: Ref, n: Int) - requires @dependency("Explicit")(acc(x.f)) - requires @irrelevant("Explicit")(n > 0) - requires @irrelevant("Explicit")(x.f > n) + requires @dependency("Precondition")(acc(x.f)) + requires @irrelevant("Precondition")(n > 0) + requires @irrelevant("Precondition")(x.f > n) { @dependency("Implicit") x.f := n + 1 @@ -33,10 +33,10 @@ method aliasing1(x: Ref, n: Int) method aliasing2(x: Ref, y: Ref, n: Int) - requires @dependency("Explicit")(acc(x.f, 1/2)) - requires @dependency("Explicit")(acc(y.f, 1/2)) - requires @irrelevant("Explicit")(n > 0) - requires @irrelevant("Explicit")(x.f > n) + requires @dependency("Precondition")(acc(x.f, 1/2)) + requires @dependency("Precondition")(acc(y.f, 1/2)) + requires @irrelevant("Precondition")(n > 0) + requires @irrelevant("Precondition")(x.f > n) { if(@dependency("PathCondition")(x == y)){ @testAssertion("Implicit") @@ -45,7 +45,7 @@ method aliasing2(x: Ref, y: Ref, n: Int) } method alias1(a: Ref, b: Ref, c: Ref) - requires @dependency("Explicit")(acc(a.f)) + requires @dependency("Precondition")(acc(a.f)) requires @irrelevant("Explicit")(acc(b.f)) { var x: Int diff --git a/src/test/resources/dependencyAnalysisTests/all/assume_assert.vpr b/src/test/resources/dependencyAnalysisTests/all/assume_assert.vpr index 8985a50ac..58e3b18a1 100644 --- a/src/test/resources/dependencyAnalysisTests/all/assume_assert.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/assume_assert.vpr @@ -1,7 +1,7 @@ field f: Int method assert1(a: Int) - requires @dependency("Explicit")(a > 0) + requires @dependency("Precondition")(a > 0) { @irrelevant() assert a >= 0 @@ -10,7 +10,7 @@ method assert1(a: Int) } method assume1(a: Int) - requires @dependency("Explicit")(a > 0) + requires @dependency("Precondition")(a > 0) { @irrelevant("Explicit") assume a >= 0 diff --git a/src/test/resources/dependencyAnalysisTests/all/function_vs_method.vpr b/src/test/resources/dependencyAnalysisTests/all/function_vs_method.vpr index 34aa3099e..54599534d 100644 --- a/src/test/resources/dependencyAnalysisTests/all/function_vs_method.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/function_vs_method.vpr @@ -16,7 +16,7 @@ function add(x: Ref, a: Int): Int } method client_func(x: Ref, a: Int) - requires @dependency("Explicit")(acc(x.f)) + requires @dependency("Precondition")(acc(x.f)) { var b: Int @irrelevant("Implicit") @@ -30,7 +30,7 @@ method client_func(x: Ref, a: Int) } method client_meth(x: Ref, a: Int) - requires @dependency("Explicit")(acc(x.f)) + requires @dependency("Precondition")(acc(x.f)) { var b: Int @dependency("Implicit") diff --git a/src/test/resources/dependencyAnalysisTests/all/implicitUpperBounds.vpr b/src/test/resources/dependencyAnalysisTests/all/implicitUpperBounds.vpr index eaae7afce..743e16374 100644 --- a/src/test/resources/dependencyAnalysisTests/all/implicitUpperBounds.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/implicitUpperBounds.vpr @@ -1,8 +1,8 @@ field f: Int method implicitUpperBounds(x: Ref, y: Ref) - requires @dependency("Explicit")(acc(x.f)) - requires @dependency("Explicit")(acc(y.f, wildcard)) + requires @dependency("Precondition")(acc(x.f)) + requires @dependency("Precondition")(acc(y.f, wildcard)) { var z: Ref @irrelevant("Explicit") @@ -16,9 +16,9 @@ method implicitUpperBounds(x: Ref, y: Ref) } method implicitUpperBounds_quantified(gen_xs: Seq[Ref], y: Ref) - requires @dependency("Explicit")(|gen_xs|> 2) - requires @dependency("Explicit")(forall gen_xi: Ref :: gen_xi in gen_xs ==> acc(gen_xi.f)) - requires @dependency("Explicit")(acc(y.f, wildcard)) + requires @dependency("Precondition")(|gen_xs|> 2) + requires @dependency("Precondition")(forall gen_xi: Ref :: gen_xi in gen_xs ==> acc(gen_xi.f)) + requires @dependency("Precondition")(acc(y.f, wildcard)) { var z: Ref @irrelevant("Explicit") @@ -32,9 +32,9 @@ method implicitUpperBounds_quantified(gen_xs: Seq[Ref], y: Ref) } method implicitUpperBounds_quantified_2(gen_xs: Seq[Ref], y: Ref) - requires @dependency("Explicit")(|gen_xs|> 2) - requires @dependency("Explicit")(forall gen_xi: Ref :: gen_xi in gen_xs ==> acc(gen_xi.f)) - requires @dependency("Explicit")(acc(y.f, 1/2)) + requires @dependency("Precondition")(|gen_xs|> 2) + requires @dependency("Precondition")(forall gen_xi: Ref :: gen_xi in gen_xs ==> acc(gen_xi.f)) + requires @dependency("Precondition")(acc(y.f, 1/2)) { var z: Ref @irrelevant("Explicit") diff --git a/src/test/resources/dependencyAnalysisTests/all/imprecision-fixed.vpr b/src/test/resources/dependencyAnalysisTests/all/imprecision-fixed.vpr index 4c206a8c5..acf4a0de2 100644 --- a/src/test/resources/dependencyAnalysisTests/all/imprecision-fixed.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/imprecision-fixed.vpr @@ -1,8 +1,8 @@ field f: Int method permTest(a: Ref, b: Ref, n: Int) - requires @dependency("Explicit")(acc(a.f)) - requires @irrelevant("Explicit")(acc(b.f)) && @irrelevant("Explicit")(b.f > 0) + requires @dependency("Precondition")(acc(a.f)) + requires @irrelevant("Precondition")(acc(b.f)) && @irrelevant("Explicit")(b.f > 0) { @dependency("Explicit") assume n > 0 @@ -16,8 +16,8 @@ method permTest(a: Ref, b: Ref, n: Int) method quantifiedPerm4(xs: Seq[Ref], ys: Seq[Ref]) - requires @dependency("Explicit")(|xs| > 5) - requires @irrelevant("Explicit")(|ys| > 3) + requires @dependency("Precondition")(|xs| > 5) + requires @irrelevant("Precondition")(|ys| > 3) { @dependency("Explicit") inhale (forall x: Ref :: x in xs ==> acc(x.f) && x.f > 0) @@ -38,8 +38,8 @@ method quantifiedPerm4(xs: Seq[Ref], ys: Seq[Ref]) // - exhale is implicit // - lhs and rhs are split such that transitive edges only go from lhs to rhs method quantifiedPerm5(xs: Seq[Ref], ys: Seq[Ref]) - requires @dependency("Explicit")(|xs| > 5) - requires @irrelevant("Explicit")(|ys| > 3) + requires @dependency("Precondition")(|xs| > 5) + requires @irrelevant("Precondition")(|ys| > 3) { @dependency("Explicit") inhale forall x: Ref :: x in xs ==> acc(x.f) @@ -60,9 +60,9 @@ method quantifiedPerm5(xs: Seq[Ref], ys: Seq[Ref]) // solution: wrap with analysis labels (see viper.silicon.resources.NonQuantifiedPropertyInterpreter.buildForEach) // and disable transitive edges (see viper.silicon.rules.DefaultStateConsolidator.consolidate) method noAlias(a: Ref, b: Ref, c: Ref) - requires @dependency("Explicit")(acc(a.f)) - requires @dependency("Explicit")(acc(b.f, 1/2)) - requires @irrelevant("Explicit")(acc(c.f, 1/2)) // fixed imprecision (state consolidation) + requires @dependency("Precondition")(acc(a.f)) + requires @dependency("Precondition")(acc(b.f, 1/2)) + requires @irrelevant("Precondition")(acc(c.f, 1/2)) // fixed imprecision (state consolidation) { @testAssertion("Explicit") assert a != b diff --git a/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr b/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr index d203bbbf9..c0ecfc25a 100644 --- a/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr @@ -5,7 +5,7 @@ field g: Int // If the branch is not executed, the 2nd inhale is reported as a dependency // In reality, the 2nd inhale is all we need for the assertion. method nonUniqueUnsatCore(x: Ref) - requires @irrelevant("Explicit")(x != null ==> acc(x.f)) + requires @irrelevant("Precondition")(x != null ==> acc(x.f)) { var a: Int if(@irrelevant("PathCondition")(x == null)){ @@ -41,7 +41,7 @@ method unprovableInfeasibility(){ // If x == null is assumed, only the assignment is reported as a dependency // If x != null, the branch is infeasible, but we executed it anyways. The implication and branch condition are reported as dependencies. method infeasible2(x: Ref) - requires @irrelevant("Explicit")(x != null ==> acc(x.f)) // unexpected dependency + requires @irrelevant("Precondition")(x != null ==> acc(x.f)) // unexpected dependency { if(@irrelevant("PathCondition")(x == null)){ // unexpected dependency var a: Int @@ -75,8 +75,8 @@ method exhaleImprecision(){ // this is precise as opposed to later examples with wildcards method quantifiedPerm2(xs: Seq[Ref], ys: Seq[Ref]) - requires @dependency("Explicit")(|xs| > 5) - requires @irrelevant("Explicit")(|ys| > 3) + requires @dependency("Precondition")(|xs| > 5) + requires @irrelevant("Precondition")(|ys| > 3) { @dependency("Explicit") inhale (forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0)) @@ -90,8 +90,8 @@ method quantifiedPerm2(xs: Seq[Ref], ys: Seq[Ref]) // issue #01 - imprecision due to wildcards // when accessing a qp resource, we check Z < (x in xs? k1:Z) + (y in ys? k2:Z) the unsat core contains dependencies to all qp perm amounts (e.g. wildcard) method quantifiedPerm3(xs: Seq[Ref], ys: Seq[Ref]) - requires @dependency("Explicit")(|xs| > 5) - requires @irrelevant("Explicit")(|ys| > 3) + requires @dependency("Precondition")(|xs| > 5) + requires @irrelevant("Precondition")(|ys| > 3) { @dependency("Explicit") inhale (forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0)) @@ -105,8 +105,8 @@ method quantifiedPerm3(xs: Seq[Ref], ys: Seq[Ref]) // issue #01 - imprecision due to perm amounts // when accessing a qp resource, we check Z < (x in xs? k1:Z) + (y in ys? k2:Z) the unsat core contains dependencies to all qp perm amounts method quantifiedPerm4(xs: Seq[Ref], ys: Seq[Ref]) - requires @dependency("Explicit")(|xs| > 5) - requires @irrelevant("Explicit")(|ys| > 3) + requires @dependency("Precondition")(|xs| > 5) + requires @irrelevant("Precondition")(|ys| > 3) { var p1: Perm @dependency("Explicit") @@ -129,9 +129,9 @@ method quantifiedPerm4(xs: Seq[Ref], ys: Seq[Ref]) // havocs all resources of the given fields conditionally on whether it can prove // non-aliasing or not (e.g. x.f --> x == y? new snap : old snap) method quasihavoc1(x: Ref, y: Ref) - requires @dependency("Explicit")(acc(x.f)) - requires @dependency("Explicit")(x.f == 3) - requires @dependency("Explicit")(x != y) + requires @dependency("Precondition")(acc(x.f)) + requires @dependency("Precondition")(x.f == 3) + requires @dependency("Precondition")(x != y) { @irrelevant() // unexpected dependency quasihavoc y.f // does nothing. we have no permission @@ -156,7 +156,7 @@ method inhaleImprecision(){ } method packageExhale2(x: Ref) - requires @dependency("Explicit")(acc(x.f)) + requires @dependency("Precondition")(acc(x.f)) { @irrelevant("Rewrite") diff --git a/src/test/resources/dependencyAnalysisTests/all/infeasible.vpr b/src/test/resources/dependencyAnalysisTests/all/infeasible.vpr index 11e66680d..9a8714f31 100644 --- a/src/test/resources/dependencyAnalysisTests/all/infeasible.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/infeasible.vpr @@ -1,7 +1,7 @@ field f: Int method infeasibleBranch1(a: Int, b: Int) - requires @dependency("Explicit")(a > 0) + requires @dependency("Precondition")(a > 0) { if(@dependency("PathCondition")(a < 0)){ @testAssertion("Explicit") @@ -10,7 +10,7 @@ method infeasibleBranch1(a: Int, b: Int) } method infeasibleBranch2(a: Int, b: Int) - requires @dependency("Explicit")(a > 0) + requires @dependency("Precondition")(a > 0) { var res: Int if(@dependency("PathCondition")(a < 0)){ @@ -26,8 +26,8 @@ method infeasibleBranch2(a: Int, b: Int) method infeasibleBranchPerm(a: Ref, b: Ref) - requires @dependency("Explicit")(acc(a.f, 1/2)) - requires @dependency("Explicit")(a.f > 0) + requires @dependency("Precondition")(acc(a.f, 1/2)) + requires @dependency("Precondition")(a.f > 0) { if(@dependency("PathCondition")(a.f < 0)){ @testAssertion("Implicit") @@ -37,7 +37,7 @@ method infeasibleBranchPerm(a: Ref, b: Ref) method infeasibleBranchNoPerm(a: Int, b: Ref) - requires @dependency("Explicit")(a > 0) + requires @dependency("Precondition")(a > 0) { var res: Int if(@dependency("PathCondition")(a < 0)){ diff --git a/src/test/resources/dependencyAnalysisTests/all/quantified-perm.vpr b/src/test/resources/dependencyAnalysisTests/all/quantified-perm.vpr index 4a7289d5b..f33242e99 100644 --- a/src/test/resources/dependencyAnalysisTests/all/quantified-perm.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/quantified-perm.vpr @@ -5,13 +5,13 @@ field second : Ref method quantifiedWritePerm(nodes: Set[Ref], x: Ref) - requires @irrelevant("Explicit")(forall n:Ref :: { n.first } n in nodes ==> + requires @irrelevant("Precondition")(forall n:Ref :: { n.first } n in nodes ==> acc(n.first) && (n.first != null ==> n.first in nodes)) - requires @dependency("Explicit")(forall n:Ref :: { n.second } n in nodes ==> + requires @dependency("Precondition")(forall n:Ref :: { n.second } n in nodes ==> acc(n.second) && (n.second != null ==> n.second in nodes)) - requires @dependency("Explicit")(x in nodes) + requires @dependency("Precondition")(x in nodes) { var y : Ref if(@dependency("PathCondition")(x.second != null)) { @@ -25,12 +25,12 @@ method quantifiedWritePerm(nodes: Set[Ref], x: Ref) } method quantifiedSum(nodes: Set[Ref], x: Ref) - requires @dependency("Explicit")(forall n:Ref :: { n.first } n in nodes ==> + requires @dependency("Precondition")(forall n:Ref :: { n.first } n in nodes ==> acc(n.first) && (n.first != null ==> n.first in nodes)) - requires @dependency("Explicit")(forall n:Ref :: { n.f } n in nodes ==> + requires @dependency("Precondition")(forall n:Ref :: { n.f } n in nodes ==> acc(n.f) && 0 <= n.f && n.f <= 100) - requires @dependency("Explicit")(x in nodes) + requires @dependency("Precondition")(x in nodes) { var a: Int @dependency("Implicit") diff --git a/src/test/resources/dependencyAnalysisTests/all/quasihavoc.vpr b/src/test/resources/dependencyAnalysisTests/all/quasihavoc.vpr index 76d71491e..56ac9c3eb 100644 --- a/src/test/resources/dependencyAnalysisTests/all/quasihavoc.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/quasihavoc.vpr @@ -1,9 +1,9 @@ field f: Int method quasihavoc1(x: Ref, y: Ref) - requires @dependency("Explicit")(acc(x.f)) - requires @dependency("Explicit")(x.f == 3) - requires @dependency("Explicit")(x != y) + requires @dependency("Precondition")(acc(x.f)) + requires @dependency("Precondition")(x.f == 3) + requires @dependency("Precondition")(x != y) { @irrelevant() quasihavoc y.f // does nothing. we have no permission @@ -12,9 +12,9 @@ method quasihavoc1(x: Ref, y: Ref) } method quasihavoc2(x: Ref, y: Ref) - requires @dependency("Explicit")(acc(x.f)) - requires @irrelevant("Explicit")(acc(y.f)) - requires @dependency("Explicit")(x.f == 42) + requires @dependency("Precondition")(acc(x.f)) + requires @irrelevant("Precondition")(acc(y.f)) + requires @dependency("Precondition")(x.f == 42) { @irrelevant() quasihavoc y.f @@ -23,8 +23,8 @@ method quasihavoc2(x: Ref, y: Ref) } method quasihavoc3(x: Ref) - requires @dependency("Explicit")(acc(x.f)) - requires @irrelevant("Explicit")(x.f == 0) + requires @dependency("Precondition")(acc(x.f)) + requires @irrelevant("Precondition")(x.f == 0) { @irrelevant() quasihavoc x.f diff --git a/src/test/resources/dependencyAnalysisTests/all/sequences.vpr b/src/test/resources/dependencyAnalysisTests/all/sequences.vpr index 275cf9fa0..b133ba99f 100644 --- a/src/test/resources/dependencyAnalysisTests/all/sequences.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/sequences.vpr @@ -1,7 +1,7 @@ method sequenceUpdate(xs: Seq[Int], ys: Seq[Int]) returns (res: Seq[Int]) - requires @dependency("Explicit")(|xs| > 5) - requires @irrelevant("Explicit")(|ys| > 3) + requires @dependency("Precondition")(|xs| > 5) + requires @irrelevant("Precondition")(|ys| > 3) { @irrelevant("Explicit") inhale forall x: Int :: x in xs ==> x > 0 @@ -16,8 +16,8 @@ method sequenceUpdate(xs: Seq[Int], ys: Seq[Int]) returns (res: Seq[Int]) } method sequence1(xs: Seq[Int], ys: Seq[Int]) returns (res: Seq[Int]) - requires @dependency("Explicit")(|xs| > 5) - requires @irrelevant("Explicit")(|ys| > 3) + requires @dependency("Precondition")(|xs| > 5) + requires @irrelevant("Precondition")(|ys| > 3) { @dependency("Explicit") inhale forall x: Int :: x in xs ==> x > 0 @@ -32,8 +32,8 @@ method sequence1(xs: Seq[Int], ys: Seq[Int]) returns (res: Seq[Int]) } method sequence2(xs: Seq[Int], ys: Seq[Int]) returns (res: Seq[Int]) - requires @irrelevant("Explicit")(|xs| > 5) - requires @irrelevant("Explicit")(|ys| > 3) + requires @irrelevant("Precondition")(|xs| > 5) + requires @irrelevant("Precondition")(|ys| > 3) { @irrelevant("Explicit") inhale forall x: Int :: x in xs ==> x > 0 diff --git a/src/test/resources/dependencyAnalysisTests/all/state_consolidation.vpr b/src/test/resources/dependencyAnalysisTests/all/state_consolidation.vpr index 828e50f28..ec2ef06ac 100644 --- a/src/test/resources/dependencyAnalysisTests/all/state_consolidation.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/state_consolidation.vpr @@ -1,9 +1,9 @@ field f: Int method stateConsolidation(x: Ref, y: Ref, z: Ref) - requires @dependency("Explicit")(acc(x.f, 1/2)) - requires @dependency("Explicit")(acc(y.f, 1/2)) - requires @irrelevant("Explicit")(acc(z.f, 1/2)) + requires @dependency("Precondition")(acc(x.f, 1/2)) + requires @dependency("Precondition")(acc(y.f, 1/2)) + requires @irrelevant("Precondition")(acc(z.f, 1/2)) { var a: Ref @dependency("Explicit") @@ -17,9 +17,9 @@ method stateConsolidation(x: Ref, y: Ref, z: Ref) } method stateConsolidation2(x: Ref, y: Ref, z: Ref) - requires @dependency("Explicit")(acc(x.f, 1/2)) - requires @dependency("Explicit")(acc(y.f, 1/2)) - requires @irrelevant("Explicit")(acc(z.f, 1/2)) + requires @dependency("Precondition")(acc(x.f, 1/2)) + requires @dependency("Precondition")(acc(y.f, 1/2)) + requires @irrelevant("Precondition")(acc(z.f, 1/2)) { var a: Ref @dependency("Explicit") @@ -34,9 +34,9 @@ method stateConsolidation2(x: Ref, y: Ref, z: Ref) method stateConsolidation3(x: Ref, y: Ref, z: Ref) - requires @dependency("Explicit")(acc(x.f, 1/2)) - requires @dependency("Explicit")(acc(y.f, 1/2)) - requires @irrelevant("Explicit")(acc(z.f, 1/2)) + requires @dependency("Precondition")(acc(x.f, 1/2)) + requires @dependency("Precondition")(acc(y.f, 1/2)) + requires @irrelevant("Precondition")(acc(z.f, 1/2)) { var a: Ref @dependency("Explicit") @@ -48,9 +48,9 @@ method stateConsolidation3(x: Ref, y: Ref, z: Ref) method noAlias(a: Ref, b: Ref, c: Ref) - requires @dependency("Explicit")(acc(a.f)) - requires @dependency("Explicit")(acc(b.f, 1/2)) - requires @irrelevant("Explicit")(acc(c.f, 1/2)) + requires @dependency("Precondition")(acc(a.f)) + requires @dependency("Precondition")(acc(b.f, 1/2)) + requires @irrelevant("Precondition")(acc(c.f, 1/2)) { @testAssertion("Explicit") diff --git a/src/test/resources/dependencyAnalysisTests/all/sum-loops.vpr b/src/test/resources/dependencyAnalysisTests/all/sum-loops.vpr index 0c39e9653..4f5f6a1a3 100644 --- a/src/test/resources/dependencyAnalysisTests/all/sum-loops.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/sum-loops.vpr @@ -1,7 +1,7 @@ field f: Int method sum(n: Int) returns (res: Int) - requires @dependency("Explicit")(0 <= n) + requires @dependency("Precondition")(0 <= n) ensures res == n * (n + 1) / 2 { @dependency("Implicit") @@ -25,8 +25,8 @@ method sum(n: Int) returns (res: Int) method sumPerm1(n: Int, res: Ref) - requires @dependency("Explicit")(acc(res.f)) - requires @dependency("Explicit")(0 <= n) + requires @dependency("Precondition")(acc(res.f)) + requires @dependency("Precondition")(0 <= n) ensures acc(res.f) ensures res.f == n * (n + 1) / 2 { @@ -51,8 +51,8 @@ method sumPerm1(n: Int, res: Ref) } method sumPerm2(n: Int, res: Ref) - requires @dependency("Explicit")(acc(res.f)) - requires @irrelevant("Explicit")(0 <= n) + requires @dependency("Precondition")(acc(res.f)) + requires @irrelevant("Precondition")(0 <= n) ensures acc(res.f) { @irrelevant("Implicit") diff --git a/src/test/resources/dependencyAnalysisTests/all/tuples.vpr b/src/test/resources/dependencyAnalysisTests/all/tuples.vpr index 3e5aa27e2..115f341e4 100644 --- a/src/test/resources/dependencyAnalysisTests/all/tuples.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/tuples.vpr @@ -6,7 +6,7 @@ predicate tuple(this: Ref) { } method setTuple(this: Ref, l: Int, r: Int) - requires @dependency("Explicit")(tuple(this)) + requires @dependency("Precondition")(tuple(this)) ensures tuple(this) { @dependency("Implicit") @@ -20,7 +20,7 @@ method setTuple(this: Ref, l: Int, r: Int) } method addTuple(this: Ref) returns (sum: Int) - requires @dependency("Explicit")(acc(tuple(this), 1/2)) + requires @dependency("Precondition")(acc(tuple(this), 1/2)) ensures acc(tuple(this), 1/2) { @dependency("Implicit") diff --git a/src/test/resources/dependencyAnalysisTests/mce/imprecisions.vpr b/src/test/resources/dependencyAnalysisTests/mce/imprecisions.vpr index dba7374cb..c0987ca6d 100644 --- a/src/test/resources/dependencyAnalysisTests/mce/imprecisions.vpr +++ b/src/test/resources/dependencyAnalysisTests/mce/imprecisions.vpr @@ -1,7 +1,7 @@ field f: Int method exhaleInhale(a: Ref) // inhaleExhale/assignment_field - requires @dependency("Explicit")(acc(a.f)) + requires @dependency("Precondition")(acc(a.f)) { exhale acc(a.f, 1/2) inhale acc(a.f, 1/2) diff --git a/src/test/resources/dependencyAnalysisTests/minimalExamples-precision/debug.vpr b/src/test/resources/dependencyAnalysisTests/minimalExamples-precision/debug.vpr index f6e77a054..947e7691e 100644 --- a/src/test/resources/dependencyAnalysisTests/minimalExamples-precision/debug.vpr +++ b/src/test/resources/dependencyAnalysisTests/minimalExamples-precision/debug.vpr @@ -13,7 +13,7 @@ predicate implPred(a: Int, x: Ref){ method unfoldingWithImpl(a: Int, x: Ref) - requires @dependency("Explicit")(implPred(a, x)) + requires @dependency("Precondition")(implPred(a, x)) { var res: Int if(@dependency("PathCondition")(a > 5)){ diff --git a/src/test/resources/dependencyAnalysisTests/real-world-examples/gaussian.vpr b/src/test/resources/dependencyAnalysisTests/real-world-examples/gaussian.vpr index b85ae08f9..1860359f1 100644 --- a/src/test/resources/dependencyAnalysisTests/real-world-examples/gaussian.vpr +++ b/src/test/resources/dependencyAnalysisTests/real-world-examples/gaussian.vpr @@ -2,7 +2,7 @@ field f: Int method gaussianSimple(n: Int) returns (res: Int) requires 0 <= n - requires @irrelevant("Explicit")(n <= 5) + requires @irrelevant("Precondition")(n <= 5) { res := 0 var i: Int := 0 @@ -22,8 +22,8 @@ method gaussianSimple(n: Int) returns (res: Int) } method gaussianPerm(a: Ref, p: Perm) returns (res: Int) - requires @dependency("Explicit")(none < p) && p < write - requires @dependency("Explicit")(acc(a.f, p)) + requires @dependency("Precondition")(none < p) && p < write + requires @dependency("Precondition")(acc(a.f, p)) requires 0 <= a.f requires a.f <= 5 ensures acc(a.f, p) diff --git a/src/test/resources/dependencyAnalysisTests/real-world-examples/listAppend.vpr b/src/test/resources/dependencyAnalysisTests/real-world-examples/listAppend.vpr index 46f4014e1..c46f3a566 100644 --- a/src/test/resources/dependencyAnalysisTests/real-world-examples/listAppend.vpr +++ b/src/test/resources/dependencyAnalysisTests/real-world-examples/listAppend.vpr @@ -14,8 +14,8 @@ function listLength(l:Ref) : Int } method appendList1(this: Ref, e: Int) - requires @irrelevant("Explicit")(list(this)) - requires @irrelevant("Explicit")(0 <= e && e < 100) + requires @irrelevant("Precondition")(list(this)) + requires @irrelevant("Precondition")(0 <= e && e < 100) ensures list(this) { @irrelevant("Implicit") @@ -44,8 +44,8 @@ method appendList1(this: Ref, e: Int) } method appendList2(this: Ref, e: Int) - requires @dependency("Explicit")(list(this)) - requires @dependency("Explicit")(0 <= e && e < 100) + requires @dependency("Precondition")(list(this)) + requires @dependency("Precondition")(0 <= e && e < 100) ensures list(this) { @dependency("Implicit") @@ -75,8 +75,8 @@ method appendList2(this: Ref, e: Int) } method appendListFull(this: Ref, e: Int) - requires @dependency("Explicit")(list(this)) - requires @dependency("Explicit")(0 <= e && e < 100) + requires @dependency("Precondition")(list(this)) + requires @dependency("Precondition")(0 <= e && e < 100) ensures list(this) { @dependency("Implicit") diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/A-inhaleExhale.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/A-inhaleExhale.vpr index 6034d82bb..cfdd567f5 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/A-inhaleExhale.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/A-inhaleExhale.vpr @@ -1,7 +1,7 @@ field f: Int method exhaleInhale(a: Ref) - requires @dependency("Explicit")(acc(a.f)) + requires @dependency("Precondition")(acc(a.f)) { exhale acc(a.f, 1/2) inhale acc(a.f, 1/2) diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/B-permissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/B-permissions.vpr index 996cabcf4..6aa629f8d 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/B-permissions.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/B-permissions.vpr @@ -1,7 +1,7 @@ field f: Int method currentPerm(x: Ref) - requires @dependency("Explicit")(acc(x.f, 1/2)) + requires @dependency("Precondition")(acc(x.f, 1/2)) { // $PrecisionTest: $READ_ONLY=x.f $ACC_INVARIANT=acc(x.f,1/4) @@ -73,8 +73,8 @@ method perm5(){ } method permAmount1(x: Ref, p: Perm) - requires @dependency("Explicit")(p > none) - requires @dependency("Explicit")(acc(x.f, p)) + requires @dependency("Precondition")(p > none) + requires @dependency("Precondition")(acc(x.f, p)) { @dependency("Explicit") assume p > 1/2 @@ -87,7 +87,7 @@ method permAmount1(x: Ref, p: Perm) method permAmount2(x: Ref, p: Perm) requires p > none - requires @dependency("Explicit")(acc(x.f, p)) + requires @dependency("Precondition")(acc(x.f, p)) { @dependency("Explicit") inhale x.f > 0 diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/C-permWildcard.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/C-permWildcard.vpr index 63b0ae640..5a3e8ed6c 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/C-permWildcard.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/C-permWildcard.vpr @@ -2,8 +2,8 @@ field f: Int field g: Int method wildcardPerm(x: Ref, y: Ref) - requires @dependency("Explicit")(acc(x.f, wildcard)) - requires @irrelevant("Explicit")(acc(y.f, wildcard)) + requires @dependency("Precondition")(acc(x.f, wildcard)) + requires @irrelevant("Precondition")(acc(y.f, wildcard)) requires x != y { // $PrecisionTest: $READ_ONLY=x.f,y.f $ACC_INVARIANT=acc(x.f,wildcard)&&acc(y.f,wildcard) @@ -14,8 +14,8 @@ method wildcardPerm(x: Ref, y: Ref) method wildcardPermDistinctFields(x: Ref, y: Ref) - requires @dependency("Explicit")(acc(x.f, wildcard)) - requires @irrelevant("Explicit")(acc(y.g, wildcard)) + requires @dependency("Precondition")(acc(x.f, wildcard)) + requires @irrelevant("Precondition")(acc(y.g, wildcard)) { // $PrecisionTest: $READ_ONLY=x.f,y.g $ACC_INVARIANT=acc(x.f,wildcard)&&acc(y.g,wildcard) @@ -24,8 +24,8 @@ method wildcardPermDistinctFields(x: Ref, y: Ref) } method quantifiedPermWildcard(xs: Seq[Ref], ys: Seq[Ref]) - requires @dependency("Explicit")(|xs| > 5) - requires @irrelevant("Explicit")(|ys| > 3) + requires @dependency("Precondition")(|xs| > 5) + requires @irrelevant("Precondition")(|ys| > 3) { @dependency("Explicit") inhale (forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0)) diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/D-quantifiedPermissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/D-quantifiedPermissions.vpr index bdf2b6089..9844d6545 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/D-quantifiedPermissions.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/D-quantifiedPermissions.vpr @@ -131,7 +131,7 @@ method quantifiedPermWrite3(xs: Seq[Ref]) { } method quantifiedPermWrite4(xs: Seq[Ref], ys: Seq[Ref]) - requires @dependency("Explicit")(|xs| > 5) + requires @dependency("Precondition")(|xs| > 5) requires |ys| > 3 { @dependency("Explicit") diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/F-method-call.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/F-method-call.vpr index c7092ce0d..e057154d9 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/F-method-call.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/F-method-call.vpr @@ -98,8 +98,8 @@ method call4(x: Int, y: Int, z: Int) } method absClient1(x: Ref) - requires @dependency("Explicit")(acc(x.f)) - requires @irrelevant("Explicit")(x.f > 0) + requires @dependency("Precondition")(acc(x.f)) + requires @irrelevant("Precondition")(x.f > 0) { @dependency("ImplicitPostcondition") abs(x) diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/G-predicates.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/G-predicates.vpr index 5196e48f3..1e732f1a2 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/G-predicates.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/G-predicates.vpr @@ -9,7 +9,7 @@ predicate greater5(n: Int){ } method foldP(n: Int) - requires @dependency("Explicit")(n > 10) + requires @dependency("Precondition")(n > 10) { var x: Int @dependency("Implicit") @@ -22,7 +22,7 @@ method foldP(n: Int) } method unfoldP(n: Int) - requires @dependency("Explicit")(greater0(n)) + requires @dependency("Precondition")(greater0(n)) { var x: Int @dependency("Implicit") @@ -39,7 +39,7 @@ method unfoldP(n: Int) } method unfoldFoldP(a: Int, b: Int) - requires @dependency("Explicit")(greater0(a)) && @dependency("Explicit")(greater5(b)) + requires @dependency("Precondition")(greater0(a)) && @dependency("Precondition")(greater5(b)) ensures greater5(a + b) { @dependency("Implicit") @@ -54,7 +54,7 @@ method unfoldFoldP(a: Int, b: Int) } method callWithPredicate(a: Int, b: Int) - requires @dependency("Explicit")(greater0(a)) && @dependency("Explicit")(greater5(b)) + requires @dependency("Precondition")(greater0(a)) && @dependency("Precondition")(greater5(b)) ensures greater5(a + b) { @dependency("Implicit") @@ -67,8 +67,8 @@ method callWithPredicate(a: Int, b: Int) } method unfoldPrecision(a: Int, b: Int) - requires @irrelevant("Explicit")(greater0(a)) - requires @dependency("Explicit")(greater5(b)) + requires @irrelevant("Precondition")(greater0(a)) + requires @dependency("Precondition")(greater5(b)) ensures greater0(b) { @@ -84,8 +84,8 @@ method unfoldPrecision(a: Int, b: Int) } method foldPrecision(a: Int, b: Int) - requires @irrelevant("Explicit")(a > 5) - requires @dependency("Explicit")(b > 5) + requires @irrelevant("Precondition")(a > 5) + requires @dependency("Precondition")(b > 5) { // $PrecisionTest: $READ_ONLY=a,b $INVARIANT=b>2 @@ -94,7 +94,7 @@ method foldPrecision(a: Int, b: Int) } method unfolding1(n: Int) - requires @dependency("Explicit")(greater5(n)) + requires @dependency("Precondition")(greater5(n)) { // $PrecisionTest: $READ_ONLY=n $INVARIANT=(unfolding$_$greater5(n)$_$in$_$n>0) $ACC_INVARIANT=greater5(n) @@ -108,7 +108,7 @@ predicate implPred(a: Int, x: Ref){ } method unfoldWithImpl(a: Int, x: Ref) - requires @dependency("Explicit")(implPred(a, x)) + requires @dependency("Precondition")(implPred(a, x)) { @dependency("Rewrite") unfold implPred(a, x) @@ -123,7 +123,7 @@ method unfoldWithImpl(a: Int, x: Ref) } method unfoldingWithImpl(a: Int, x: Ref) - requires @dependency("Explicit")(implPred(a, x)) + requires @dependency("Precondition")(implPred(a, x)) { var res: Int if(@dependency("PathCondition")(a > 5)){ diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/H-magicWands.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/H-magicWands.vpr index 0be3f1298..e9647b9e0 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/H-magicWands.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/H-magicWands.vpr @@ -29,7 +29,7 @@ method basicApply() method basicPackage(l: Ref) - requires @dependency("Explicit")(list(l)) + requires @dependency("Precondition")(list(l)) ensures list(l) { @dependency("Implicit") @@ -88,8 +88,8 @@ method advancedPackage(a: Int, b: Int) } method quantifiedWand(xs: Seq[Int], b: Int) - requires @dependency("Explicit")(|xs| > 2) - requires @dependency("Explicit")(forall x: Int :: x in xs ==> (greater0(x) --* greater0(x + b))) + requires @dependency("Precondition")(|xs| > 2) + requires @dependency("Precondition")(forall x: Int :: x in xs ==> (greater0(x) --* greater0(x + b))) { @dependency("Explicit") inhale greater0(xs[0]) @@ -103,7 +103,7 @@ method quantifiedWand(xs: Seq[Int], b: Int) } method packagePrecision(l: Ref) - requires @dependency("Explicit")(list(l)) + requires @dependency("Precondition")(list(l)) { @dependency("Rewrite") unfold list(l) @@ -125,7 +125,7 @@ method packagePrecision(l: Ref) } method packageExhale(x: Ref) - requires @dependency("Explicit")(acc(x.f)) + requires @dependency("Precondition")(acc(x.f)) { @dependency("Implicit") diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/L-misc.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/L-misc.vpr index b10d85a56..610264d0c 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/L-misc.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/L-misc.vpr @@ -1,9 +1,9 @@ field f: Int method divBy0(a: Ref, n: Int) - requires @dependency("Explicit")(n > 0) - requires @dependency("Explicit")(acc(a.f)) - requires @irrelevant("Explicit")(a.f > 0) + requires @dependency("Precondition")(n > 0) + requires @dependency("Precondition")(acc(a.f)) + requires @irrelevant("Precondition")(a.f > 0) { var res: Int From b24ee22b346afeef7bb40de621fac2048864aa36 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 11 Dec 2025 18:27:58 +0100 Subject: [PATCH 276/474] (WIP) progress metric --- .../DependencyAnalysisUserTool.scala | 13 ++++++++- .../DependencyGraphInterpreter.scala | 28 +++++++++++++++++-- .../scala/supporters/MethodSupporter.scala | 2 +- 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala index 2a763e27b..5b28b05ae 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala @@ -16,6 +16,7 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete "\n\t'hasDep [line numbers]' to print whether there exists any dependency between any pair of the given lines or" + "\n\t'cov [members]' to print proof coverage of given member or" + "\n\t'covL member [line numbers]' to print proof coverage of given lines of given member or" + + "\n\t'progress' to compute the verification progress of the program or" + "\n\t'prune [line numbers]' to prune the program with respect to the given line numbers and export the new program or" + "\n\t'q' to quit" @@ -52,6 +53,8 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete handleProofCoverageQuery(inputParts.tail) }else if (inputParts.head.equalsIgnoreCase("covLines") || inputParts.head.equalsIgnoreCase("covL")) { handleProofCoverageLineQuery(inputParts.tail) + }else if (inputParts.head.equalsIgnoreCase("progress") || inputParts.head.equalsIgnoreCase("prog")) { + handleVerificationProgressQuery() }else if(inputParts.head.equalsIgnoreCase("prune")) { handlePruningRequest(inputParts.tail) }else if(inputParts.head.equalsIgnoreCase("benchmark")) { @@ -128,11 +131,19 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete println("Done.") } + private def handleVerificationProgressQuery(): Unit = { + println("Computing verification progress...") + val ((progress, info), time) = measureTime(fullGraphInterpreter.computeVerificationProgress()) + println(s"Overall verification progress: $progress") + println(s"$info") + println(s"Finished in ${time}ms") + } + private def getSourceInfoString(nodes: Set[DependencyAnalysisNode]) = { nodes.groupBy(node => node.sourceInfo.getTopLevelSource).map{case (_, nodes) => nodes.head.sourceInfo.getTopLevelSource}.toList.sortBy(_.getLineNumber).mkString("\n\t") } - private def getQueriedNodesFromInput(inputs: Set[String])= { + private def getQueriedNodesFromInput(inputs: Set[String]): Set[DependencyAnalysisNode] = { inputs flatMap (input => { val parts = input.split("@") if(parts.size == 2) diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index ff17c15d2..8c85a558d 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -83,8 +83,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen def getNonInternalAssertionNodes: Set[DependencyAnalysisNode] = getNodes filter (node => - (node.isInstanceOf[GeneralAssertionNode] && !AssumptionType.internalTypes.contains(node.assumptionType)) - || AssumptionType.postconditionTypes.contains(node.assumptionType) + node.isInstanceOf[GeneralAssertionNode] && !AssumptionType.internalTypes.contains(node.assumptionType) ) def getNonInternalAssertionNodesPerSource: Map[AnalysisSourceInfo, Set[DependencyAnalysisNode]] = @@ -215,4 +214,29 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen writer.println(newProgram.toString()) writer.close() } + + def computeVerificationProgress(): (Double, String) = { + val assumptionsPerSource = getNonInternalAssumptionNodes.groupBy(_.sourceInfo.getTopLevelSource) + if(assumptionsPerSource.isEmpty) return (Double.NaN, "Error: no assumptions found") + + val relevantDependencies = getExplicitAssertionNodes // TODO ake: only postconditions? + .groupBy(_.sourceInfo.getTopLevelSource) + .map(g => getNodesWithIdenticalSource(g._2)) + .flatMap(nodes => getAllNonInternalDependencies(nodes.map(_.id))) + + val coveredExplicitSources = relevantDependencies.filter(node => AssumptionType.Explicit.equals(node.assumptionType)).groupBy(_.sourceInfo.getTopLevelSource).keys.toSet // TODO ake: other assumption types? + val coveredImplicitSources = relevantDependencies.groupBy(_.sourceInfo.getTopLevelSource).keys.toSet.diff(coveredExplicitSources) + val uncoveredExplicitSources = getExplicitAssumptionNodes.groupBy(_.sourceInfo.getTopLevelSource).keys.toSet.diff(relevantDependencies.groupBy(_.sourceInfo.getTopLevelSource).keys.toSet) + val uncoveredImplicitSources = assumptionsPerSource.keys.toSet.diff(coveredImplicitSources).diff(coveredExplicitSources) + val relevantAssumptions = assumptionsPerSource.keys.toSet + val verificationProgress = (coveredImplicitSources.size.toDouble + 0.5*uncoveredExplicitSources.size.toDouble)/ relevantAssumptions.size.toDouble + val info = + s"Uncovered Explicit Assumptions:\n\t${uncoveredExplicitSources.mkString("\n\t")}" + "\n" + + s"Uncovered Implicit Assumptions:\n\t${uncoveredImplicitSources.mkString("\n\t")}" + "\n" + + s"Covered Explicit Assumptions:\n\t${coveredExplicitSources.mkString("\n\t")}" + "\n" + + s"Covered Implicit Assumptions:\n\t${coveredImplicitSources.mkString("\n\t")}" + "\n" + + s"All Relevant Assumptions:\n\t${relevantAssumptions.mkString("\n\t")}" + "\n" + + s"Verification Progress: ${(coveredImplicitSources.size.toDouble + 0.5*uncoveredExplicitSources.size.toDouble)}/${relevantAssumptions.size.toDouble} = $verificationProgress" + (verificationProgress, info) + } } diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index 0705a84f5..fdc9e0886 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -109,7 +109,7 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif val s4 = s3.copy(h = Heap()) val impLog = new WellformednessCheckRecord(posts, s, v.decider.pcs) val sepIdentifier = symbExLog.openScope(impLog) - produces(s4, freshSnap, posts, ContractNotWellformed, v3, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit))((_, _) => { + produces(s4, freshSnap, posts, ContractNotWellformed, v3, annotatedAssumptionTypeOpt.getOrElse(postConditionType))((_, _) => { symbExLog.closeScope(sepIdentifier) Success()})}) && { From 455598ecce810f82576897ec9746c785ef688b7a Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 12 Dec 2025 13:58:41 +0100 Subject: [PATCH 277/474] (WIP) progress metric --- .../DependencyGraphInterpreter.scala | 20 ++++++++++--------- src/main/scala/rules/Evaluator.scala | 2 +- src/main/scala/rules/Executor.scala | 2 +- .../all/function-sum.vpr | 2 +- .../dependencyAnalysisTests/all/functions.vpr | 6 +++--- .../unitTests/E-function-call.vpr | 4 ++-- .../unitTests/F-method-call.vpr | 4 ++-- 7 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 8c85a558d..50ccbc8b1 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -224,19 +224,21 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen .map(g => getNodesWithIdenticalSource(g._2)) .flatMap(nodes => getAllNonInternalDependencies(nodes.map(_.id))) - val coveredExplicitSources = relevantDependencies.filter(node => AssumptionType.Explicit.equals(node.assumptionType)).groupBy(_.sourceInfo.getTopLevelSource).keys.toSet // TODO ake: other assumption types? + val coveredExplicitSources = relevantDependencies.filter(node => AssumptionType.explicitAssumptionTypes.contains(node.assumptionType)).groupBy(_.sourceInfo.getTopLevelSource).keys.toSet // TODO ake: other assumption types? val coveredImplicitSources = relevantDependencies.groupBy(_.sourceInfo.getTopLevelSource).keys.toSet.diff(coveredExplicitSources) val uncoveredExplicitSources = getExplicitAssumptionNodes.groupBy(_.sourceInfo.getTopLevelSource).keys.toSet.diff(relevantDependencies.groupBy(_.sourceInfo.getTopLevelSource).keys.toSet) - val uncoveredImplicitSources = assumptionsPerSource.keys.toSet.diff(coveredImplicitSources).diff(coveredExplicitSources) + val uncoveredImplicitSources = assumptionsPerSource.keys.toSet.diff(coveredImplicitSources).diff(coveredExplicitSources).diff(uncoveredExplicitSources) val relevantAssumptions = assumptionsPerSource.keys.toSet - val verificationProgress = (coveredImplicitSources.size.toDouble + 0.5*uncoveredExplicitSources.size.toDouble)/ relevantAssumptions.size.toDouble + + val verificationProgress = coveredImplicitSources.size.toDouble / relevantAssumptions.size.toDouble + val info = - s"Uncovered Explicit Assumptions:\n\t${uncoveredExplicitSources.mkString("\n\t")}" + "\n" + - s"Uncovered Implicit Assumptions:\n\t${uncoveredImplicitSources.mkString("\n\t")}" + "\n" + - s"Covered Explicit Assumptions:\n\t${coveredExplicitSources.mkString("\n\t")}" + "\n" + - s"Covered Implicit Assumptions:\n\t${coveredImplicitSources.mkString("\n\t")}" + "\n" + - s"All Relevant Assumptions:\n\t${relevantAssumptions.mkString("\n\t")}" + "\n" + - s"Verification Progress: ${(coveredImplicitSources.size.toDouble + 0.5*uncoveredExplicitSources.size.toDouble)}/${relevantAssumptions.size.toDouble} = $verificationProgress" + s"Uncovered Explicit Assumptions:\n\t${uncoveredExplicitSources.toSeq.sortBy(_.getLineNumber).mkString("\n\t")}" + "\n" + + s"Covered Explicit Assumptions:\n\t${coveredExplicitSources.toSeq.sortBy(_.getLineNumber).mkString("\n\t")}" + "\n" + + s"Uncovered Implicit Assumptions:\n\t${uncoveredImplicitSources.toSeq.sortBy(_.getLineNumber).mkString("\n\t")}" + "\n" + + s"Covered Implicit Assumptions:\n\t${coveredImplicitSources.toSeq.sortBy(_.getLineNumber).mkString("\n\t")}" + "\n" + + s"All Relevant Assumptions:\n\t${relevantAssumptions.toSeq.sortBy(_.getLineNumber).mkString("\n\t")}" + "\n" + + s"Verification Progress: ${coveredImplicitSources.size.toDouble}/${relevantAssumptions.size.toDouble} = $verificationProgress" (verificationProgress, info) } } diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 080ba7617..0d2e9fc13 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -861,7 +861,7 @@ object evaluator extends EvaluationRules { case fapp @ ast.FuncApp(funcName, eArgs) => val func = s.program.findFunction(funcName) - val funcAssumptionType = if(func.body.isDefined || !DependencyAnalyzer.extractEnableAnalysisFromInfo(func.info).getOrElse(true)) AssumptionType.ImplicitPostcondition else AssumptionType.ExplicitPostcondition + val funcAssumptionType = AssumptionType.ImplicitPostcondition val assumptionType = DependencyAnalyzer.extractAssumptionTypeFromInfo(fapp.info).getOrElse(funcAssumptionType) evals2(s, eArgs, Nil, _ => pve, v)((s1, tArgs, eArgsNew, v1) => { // bookkeeper.functionApplications += 1 diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 68852e9dc..0ee2954b5 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -610,7 +610,7 @@ object executor extends ExecutionRules { val pveCallTransformed = pveCall.withReasonNodeTransformed(reasonTransformer) val methodAnnotatedAssumptionType = DependencyAnalyzer.extractAssumptionTypeFromInfo(meth.info) // TODO: make sure the join can still be made? - val defaultAssumptionType = if(meth.body.isDefined || !DependencyAnalyzer.extractEnableAnalysisFromInfo(meth.info).getOrElse(true)) AssumptionType.ImplicitPostcondition else AssumptionType.ExplicitPostcondition + val defaultAssumptionType = AssumptionType.ImplicitPostcondition val finalAssumptionType = annotatedAssumptionTypeOpt.getOrElse(methodAnnotatedAssumptionType.getOrElse(defaultAssumptionType)) val mcLog = new MethodCallRecord(call, s, v.decider.pcs) diff --git a/src/test/resources/dependencyAnalysisTests/all/function-sum.vpr b/src/test/resources/dependencyAnalysisTests/all/function-sum.vpr index 435cf38bf..d84322780 100644 --- a/src/test/resources/dependencyAnalysisTests/all/function-sum.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/function-sum.vpr @@ -36,7 +36,7 @@ method call2Unv(){ @dependency("Explicit") assume y > 0 - @dependency("ExplicitPostcondition") + @dependency("ImplicitPostcondition") z := sumUnverified(x, y) // $PrecisionTest: $READ_ONLY=x,y,z $INVARIANT=x>0 diff --git a/src/test/resources/dependencyAnalysisTests/all/functions.vpr b/src/test/resources/dependencyAnalysisTests/all/functions.vpr index 2ad23ee0b..ac64728dd 100644 --- a/src/test/resources/dependencyAnalysisTests/all/functions.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/functions.vpr @@ -52,7 +52,7 @@ method predicateClientPostcond(x: Ref) x.f := 10 @dependency("Rewrite") fold greater0(x) - @dependency("ExplicitPostcondition") + @dependency("ImplicitPostcondition") a := fooPostcond(x) @testAssertion("Explicit") @@ -75,7 +75,7 @@ method callDivPostcond(){ var x: Int @dependency("Explicit") assume x > 10 - @dependency("ExplicitPostcondition") + @dependency("ImplicitPostcondition") x := div100Postcond(x) @testAssertion("Explicit") @@ -90,7 +90,7 @@ function fooInput(a: Int): Int method client(a: Int) { var res: Int - @dependency("ExplicitPostcondition") + @dependency("ImplicitPostcondition") res := fooInput(a) @testAssertion("Explicit") diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/E-function-call.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/E-function-call.vpr index d4ce91b18..9cddca733 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/E-function-call.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/E-function-call.vpr @@ -75,12 +75,12 @@ method call4(x: Int, y: Int, z: Int) assume z > 0 var n: Int, n2: Int - @dependency("ExplicitPostcondition") + @dependency("ImplicitPostcondition") n := sumUnverified(x, y) // $PrecisionTest: $READ_WRITE=n2 $READ_ONLY=n,z,x,y $INVARIANT=x>0 - @irrelevant("ExplicitPostcondition") + @irrelevant("ImplicitPostcondition") n2 := sumUnverified(x, z) @testAssertion("Explicit") diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/F-method-call.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/F-method-call.vpr index e057154d9..e38fb8538 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/F-method-call.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/F-method-call.vpr @@ -59,12 +59,12 @@ method call3(x: Int, y: Int) assume y > 0 var n: Int, n2: Int - @dependency("ExplicitPostcondition") + @dependency("ImplicitPostcondition") n := sumUnverified(x, y) // $PrecisionTest: $READ_WRITE=n2 $READ_ONLY=x,y,n $INVARIANT=x>0 - @dependency("ExplicitPostcondition") + @dependency("ImplicitPostcondition") n2 := sumUnverified(x, y) @dependency("Implicit") n := n + n2 From 6d4ff4f73005319a0287343bbf38a36daf744131 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 15 Dec 2025 11:07:10 +0100 Subject: [PATCH 278/474] add PostconditionCall assumption type --- .../scala/dependencyAnalysis/AnalysisInfo.scala | 4 ++-- .../dependencyAnalysis/DependencyAnalyzer.scala | 3 +-- src/main/scala/rules/Evaluator.scala | 2 +- src/main/scala/rules/Executor.scala | 2 +- src/main/scala/supporters/MethodSupporter.scala | 7 +++++-- .../dependencyAnalysisTests/all/divBy0.vpr | 4 ++-- .../dependencyAnalysisTests/all/function-sum.vpr | 4 ++-- .../dependencyAnalysisTests/all/functions.vpr | 10 +++++----- .../dependencyAnalysisTests/all/method-sum.vpr | 8 ++++---- .../unitTests/E-function-call.vpr | 8 ++++---- .../unitTests/F-method-call.vpr | 14 +++++++------- 11 files changed, 34 insertions(+), 32 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala index 2a75661fa..6063ce0c6 100644 --- a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala +++ b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala @@ -2,12 +2,12 @@ package viper.silicon.dependencyAnalysis object AssumptionType extends Enumeration { type AssumptionType = Value - val Explicit, LoopInvariant, PathCondition, Rewrite, Implicit, Internal, Trigger, ExplicitPostcondition, ImplicitPostcondition, FunctionBody, Precondition = Value + val Explicit, LoopInvariant, PathCondition, Rewrite, Implicit, Internal, Trigger, ExplicitPostcondition, ImplicitPostcondition, CallPostcondition, FunctionBody, Precondition = Value def fromString(s: String): Option[Value] = values.find(_.toString == s) def explicitAssumptionTypes: Set[AssumptionType] = Set(Explicit, ExplicitPostcondition) - def postconditionTypes: Set[AssumptionType] = Set(ImplicitPostcondition, ExplicitPostcondition) // used to join graphs via postconditions + def postconditionTypes: Set[AssumptionType] = Set(ImplicitPostcondition, ExplicitPostcondition, CallPostcondition) // used to join graphs via postconditions def explicitAssertionTypes: Set[AssumptionType] = Set(Explicit) ++ postconditionTypes def internalTypes: Set[AssumptionType] = Set(Internal) // will always be hidden from user def joinConditionTypes: Set[AssumptionType] = postconditionTypes ++ Set(FunctionBody) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index 908f59693..d094abbd4 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -139,7 +139,6 @@ object DependencyAnalyzer { def isAxiomLabel(label: String): Boolean = label.startsWith("axiom_") - // TODO ake: implement a lazy join in DependencyGraphInterpreter def joinGraphsAndGetInterpreter(name: Option[String], dependencyGraphInterpreters: Set[DependencyGraphInterpreter]): DependencyGraphInterpreter = { var startTime = startTimeMeasurement() val newGraph = new DependencyGraph @@ -363,7 +362,7 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { override def addFunctionAxiomEdges(): Unit = { val axiomNodes = getNodes.filter(_.isInstanceOf[AxiomAssumptionNode]) - val postcondNodes = getNodes.filter(n => n.assumptionType.equals(AssumptionType.ExplicitPostcondition) || n.assumptionType.equals(AssumptionType.ImplicitPostcondition)) + val postcondNodes = getNodes.filter(n => AssumptionType.postconditionTypes.contains(n.assumptionType)) axiomNodes foreach {aNode => val pNodes = postcondNodes filter (_.sourceInfo.toString.equals(aNode.sourceInfo.toString)) map (_.id) assumptionGraph.addEdges(pNodes, aNode.id) diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 0d2e9fc13..ddf0489f1 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -861,7 +861,7 @@ object evaluator extends EvaluationRules { case fapp @ ast.FuncApp(funcName, eArgs) => val func = s.program.findFunction(funcName) - val funcAssumptionType = AssumptionType.ImplicitPostcondition + val funcAssumptionType = AssumptionType.CallPostcondition val assumptionType = DependencyAnalyzer.extractAssumptionTypeFromInfo(fapp.info).getOrElse(funcAssumptionType) evals2(s, eArgs, Nil, _ => pve, v)((s1, tArgs, eArgsNew, v1) => { // bookkeeper.functionApplications += 1 diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 0ee2954b5..e0bc3b6bc 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -610,7 +610,7 @@ object executor extends ExecutionRules { val pveCallTransformed = pveCall.withReasonNodeTransformed(reasonTransformer) val methodAnnotatedAssumptionType = DependencyAnalyzer.extractAssumptionTypeFromInfo(meth.info) // TODO: make sure the join can still be made? - val defaultAssumptionType = AssumptionType.ImplicitPostcondition + val defaultAssumptionType = AssumptionType.CallPostcondition val finalAssumptionType = annotatedAssumptionTypeOpt.getOrElse(methodAnnotatedAssumptionType.getOrElse(defaultAssumptionType)) val mcLog = new MethodCallRecord(call, s, v.decider.pcs) diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index fdc9e0886..fb3ef51ba 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -114,9 +114,12 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif Success()})}) && { executionFlowController.locally(s2a, v2)((s3, v3) => { - exec(s3, body, v3)((s4, v4) => + val da = v3.decider.dependencyAnalyzer + if(method.body.isEmpty) v3.decider.removeDependencyAnalyzer() + exec(s3, body, v3)((s4, v4) => { + if(method.body.isEmpty) v3.decider.dependencyAnalyzer = da consumes(s4, posts, false, postViolated, v4, postConditionType)((_, _, _) => - Success()))}) } )})}) + Success())})}) } )})}) if(method.body.isEmpty){ v.decider.dependencyAnalyzer.addCustomExpDependency(method.pres.flatMap(_.topLevelConjuncts), method.posts.flatMap(_.topLevelConjuncts)) diff --git a/src/test/resources/dependencyAnalysisTests/all/divBy0.vpr b/src/test/resources/dependencyAnalysisTests/all/divBy0.vpr index 3e907a770..155883ea3 100644 --- a/src/test/resources/dependencyAnalysisTests/all/divBy0.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/divBy0.vpr @@ -45,13 +45,13 @@ method sumClient(x: Int, y: Int) @dependency("Explicit") assume x < y var n: Int - @dependency("ImplicitPostcondition") + @dependency("CallPostcondition") n := sum(x, y) // the following stmt reports dependency on n := sum(x, y) because (x < y && n == x + y && n > 100 ==> y != 0) // although you could also prove it via (0 <= x && x < y ==> y != 0) var n2: Int - @dependency("ImplicitPostcondition") + @dependency("CallPostcondition") n2 := sum(x/y, y) @dependency("Implicit") n := n + n2 diff --git a/src/test/resources/dependencyAnalysisTests/all/function-sum.vpr b/src/test/resources/dependencyAnalysisTests/all/function-sum.vpr index d84322780..88e943850 100644 --- a/src/test/resources/dependencyAnalysisTests/all/function-sum.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/function-sum.vpr @@ -20,7 +20,7 @@ method call2(){ @dependency("Explicit") assume y > 0 - @dependency("ImplicitPostcondition") + @dependency("CallPostcondition") z := sum(x, y) // $PrecisionTest: $READ_ONLY=x,y,z $INVARIANT=x>0 @@ -36,7 +36,7 @@ method call2Unv(){ @dependency("Explicit") assume y > 0 - @dependency("ImplicitPostcondition") + @dependency("CallPostcondition") z := sumUnverified(x, y) // $PrecisionTest: $READ_ONLY=x,y,z $INVARIANT=x>0 diff --git a/src/test/resources/dependencyAnalysisTests/all/functions.vpr b/src/test/resources/dependencyAnalysisTests/all/functions.vpr index ac64728dd..16ab8dba8 100644 --- a/src/test/resources/dependencyAnalysisTests/all/functions.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/functions.vpr @@ -37,7 +37,7 @@ method predicateClient(x: Ref) x.f := 10 @dependency("Rewrite") fold greater0(x) - @dependency("ImplicitPostcondition") + @dependency("CallPostcondition") a := foo(x) @testAssertion("Explicit") @@ -52,7 +52,7 @@ method predicateClientPostcond(x: Ref) x.f := 10 @dependency("Rewrite") fold greater0(x) - @dependency("ImplicitPostcondition") + @dependency("CallPostcondition") a := fooPostcond(x) @testAssertion("Explicit") @@ -64,7 +64,7 @@ method callDiv(){ var x: Int @dependency("Explicit") assume x > 10 - @dependency("ImplicitPostcondition") + @dependency("CallPostcondition") x := div100(x) @testAssertion("Explicit") @@ -75,7 +75,7 @@ method callDivPostcond(){ var x: Int @dependency("Explicit") assume x > 10 - @dependency("ImplicitPostcondition") + @dependency("CallPostcondition") x := div100Postcond(x) @testAssertion("Explicit") @@ -90,7 +90,7 @@ function fooInput(a: Int): Int method client(a: Int) { var res: Int - @dependency("ImplicitPostcondition") + @dependency("CallPostcondition") res := fooInput(a) @testAssertion("Explicit") diff --git a/src/test/resources/dependencyAnalysisTests/all/method-sum.vpr b/src/test/resources/dependencyAnalysisTests/all/method-sum.vpr index 78acbaa73..d253f4274 100644 --- a/src/test/resources/dependencyAnalysisTests/all/method-sum.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/method-sum.vpr @@ -19,13 +19,13 @@ method sumClient(x: Int, y: Int) @dependency("Explicit") assume x < y var n: Int - @dependency("ImplicitPostcondition") + @dependency("CallPostcondition") n := sum(x, y) // the following stmt reports dependency on n := sum(x, y) because (x < y && n == x + y && n > 100 ==> y != 0) // although you could also prove it via (0 <= x && x < y ==> y != 0) var n2: Int - @dependency("ImplicitPostcondition") + @dependency("CallPostcondition") n2 := sum(x/y, y) @dependency("Implicit") n := n + n2 @@ -43,13 +43,13 @@ method sumClient2(x: Int, y: Int) @irrelevant("Explicit") assume x < y var n: Int - @irrelevant("ImplicitPostcondition") + @irrelevant("CallPostcondition") n := sum(x, x) @irrelevant() assert n >= 100 var n2: Int - @dependency("ImplicitPostcondition") + @dependency("CallPostcondition") n2 := sum(y, y) @testAssertion("Explicit") assert n2 == 2*y diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/E-function-call.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/E-function-call.vpr index 9cddca733..dbff5e6c3 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/E-function-call.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/E-function-call.vpr @@ -32,7 +32,7 @@ method call2(){ @dependency("Explicit") assume y > 0 - @dependency("ImplicitPostcondition") + @dependency("CallPostcondition") z := sum(x, y) // $PrecisionTest: $READ_ONLY=x,y,z $INVARIANT=x>0 @@ -49,7 +49,7 @@ method call3(x: Int, y: Int) assume y > 0 var n: Int, n2: Int - @dependency("ImplicitPostcondition") + @dependency("CallPostcondition") n := sum(x, y) // $PrecisionTest: $READ_WRITE=n2 $READ_ONLY=x,y,n $INVARIANT=x>0 @@ -75,12 +75,12 @@ method call4(x: Int, y: Int, z: Int) assume z > 0 var n: Int, n2: Int - @dependency("ImplicitPostcondition") + @dependency("CallPostcondition") n := sumUnverified(x, y) // $PrecisionTest: $READ_WRITE=n2 $READ_ONLY=n,z,x,y $INVARIANT=x>0 - @irrelevant("ImplicitPostcondition") + @irrelevant("CallPostcondition") n2 := sumUnverified(x, z) @testAssertion("Explicit") diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/F-method-call.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/F-method-call.vpr index e38fb8538..d31fd81f2 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/F-method-call.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/F-method-call.vpr @@ -31,7 +31,7 @@ method call1(){ // $PrecisionTest: $READ_ONLY=x,y $INVARIANT=y>-5 - @testAssertion("ImplicitPostcondition") + @testAssertion("CallPostcondition") x := sum(x, y) } @@ -42,7 +42,7 @@ method call2(){ @dependency("Explicit") assume y > 0 - @dependency("ImplicitPostcondition") + @dependency("CallPostcondition") z := sum(x, y) // $PrecisionTest: $READ_ONLY=x,y,z $INVARIANT=x>0 @@ -59,12 +59,12 @@ method call3(x: Int, y: Int) assume y > 0 var n: Int, n2: Int - @dependency("ImplicitPostcondition") + @dependency("CallPostcondition") n := sumUnverified(x, y) // $PrecisionTest: $READ_WRITE=n2 $READ_ONLY=x,y,n $INVARIANT=x>0 - @dependency("ImplicitPostcondition") + @dependency("CallPostcondition") n2 := sumUnverified(x, y) @dependency("Implicit") n := n + n2 @@ -85,12 +85,12 @@ method call4(x: Int, y: Int, z: Int) assume z > 0 var n: Int, n2: Int - @dependency("ImplicitPostcondition") + @dependency("CallPostcondition") n := sum(x, y) // $PrecisionTest: $READ_WRITE=n2 $READ_ONLY=n,z,x,y $INVARIANT=x>0 - @irrelevant("ImplicitPostcondition") + @irrelevant("CallPostcondition") n2 := sum(x, z) @testAssertion("Explicit") @@ -101,7 +101,7 @@ method absClient1(x: Ref) requires @dependency("Precondition")(acc(x.f)) requires @irrelevant("Precondition")(x.f > 0) { - @dependency("ImplicitPostcondition") + @dependency("CallPostcondition") abs(x) @testAssertion("Explicit") From 2821bfe88b6eb243ad6c6b71c48ba2e7a3f82a65 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 15 Dec 2025 11:08:37 +0100 Subject: [PATCH 279/474] update verification progress metric --- .../DependencyAnalysisUserTool.scala | 6 +++--- .../DependencyGraphInterpreter.scala | 16 ++++++++++------ .../scala/verifier/DefaultMainVerifier.scala | 3 ++- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala index 5b28b05ae..d0f2b84fb 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala @@ -1,6 +1,6 @@ package viper.silicon.dependencyAnalysis -import viper.silicon.dependencyAnalysis.{DependencyGraphInterpreter, DependencyAnalysisNode, AxiomAssumptionNode} +import viper.silicon.interfaces.Failure import viper.silver.ast import viper.silver.ast.Method @@ -9,7 +9,7 @@ import scala.annotation.tailrec import scala.io.StdIn.readLine class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterpreter, memberInterpreters: Seq[DependencyGraphInterpreter], - program: ast.Program) { + program: ast.Program, verificationErrors: List[Failure]) { private val infoString = "Enter " + "\n\t'dep [line numbers]' to print all dependencies of the given line numbers or" + "\n\t'downDep [line numbers]' to print all dependents of the given line numbers or" + @@ -133,7 +133,7 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete private def handleVerificationProgressQuery(): Unit = { println("Computing verification progress...") - val ((progress, info), time) = measureTime(fullGraphInterpreter.computeVerificationProgress()) + val ((progress, info), time) = measureTime(fullGraphInterpreter.computeVerificationProgress(verificationErrors)) println(s"Overall verification progress: $progress") println(s"$info") println(s"Finished in ${time}ms") diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 50ccbc8b1..3b944509a 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -1,5 +1,6 @@ package viper.silicon.dependencyAnalysis +import viper.silicon.interfaces.Failure import viper.silicon.verifier.Verifier import viper.silver.ast import viper.silver.ast.utility.ViperStrategy @@ -215,7 +216,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen writer.close() } - def computeVerificationProgress(): (Double, String) = { + def computeVerificationProgress(verificationErrors: List[Failure]=List.empty): (Double, String) = { val assumptionsPerSource = getNonInternalAssumptionNodes.groupBy(_.sourceInfo.getTopLevelSource) if(assumptionsPerSource.isEmpty) return (Double.NaN, "Error: no assumptions found") @@ -224,21 +225,24 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen .map(g => getNodesWithIdenticalSource(g._2)) .flatMap(nodes => getAllNonInternalDependencies(nodes.map(_.id))) + val implicitPostConds = getNonInternalAssertionNodes.filter(node => AssumptionType.ImplicitPostcondition.equals(node.assumptionType)).groupBy(_.sourceInfo.getTopLevelSource).keys.toSet val coveredExplicitSources = relevantDependencies.filter(node => AssumptionType.explicitAssumptionTypes.contains(node.assumptionType)).groupBy(_.sourceInfo.getTopLevelSource).keys.toSet // TODO ake: other assumption types? - val coveredImplicitSources = relevantDependencies.groupBy(_.sourceInfo.getTopLevelSource).keys.toSet.diff(coveredExplicitSources) + val coveredImplicitSources = relevantDependencies.groupBy(_.sourceInfo.getTopLevelSource).keys.toSet.diff(coveredExplicitSources).diff(implicitPostConds) val uncoveredExplicitSources = getExplicitAssumptionNodes.groupBy(_.sourceInfo.getTopLevelSource).keys.toSet.diff(relevantDependencies.groupBy(_.sourceInfo.getTopLevelSource).keys.toSet) - val uncoveredImplicitSources = assumptionsPerSource.keys.toSet.diff(coveredImplicitSources).diff(coveredExplicitSources).diff(uncoveredExplicitSources) - val relevantAssumptions = assumptionsPerSource.keys.toSet + val uncoveredImplicitSources = assumptionsPerSource.keys.toSet.diff(coveredImplicitSources).diff(coveredExplicitSources).diff(uncoveredExplicitSources).diff(implicitPostConds) + val relevantAssumptions = assumptionsPerSource.keys.toSet.diff(implicitPostConds) - val verificationProgress = coveredImplicitSources.size.toDouble / relevantAssumptions.size.toDouble + val verificationProgress = coveredImplicitSources.size.toDouble / (relevantAssumptions.size.toDouble + verificationErrors.size) val info = s"Uncovered Explicit Assumptions:\n\t${uncoveredExplicitSources.toSeq.sortBy(_.getLineNumber).mkString("\n\t")}" + "\n" + s"Covered Explicit Assumptions:\n\t${coveredExplicitSources.toSeq.sortBy(_.getLineNumber).mkString("\n\t")}" + "\n" + s"Uncovered Implicit Assumptions:\n\t${uncoveredImplicitSources.toSeq.sortBy(_.getLineNumber).mkString("\n\t")}" + "\n" + s"Covered Implicit Assumptions:\n\t${coveredImplicitSources.toSeq.sortBy(_.getLineNumber).mkString("\n\t")}" + "\n" + + s"Implicit Postconditions:\n\t${implicitPostConds.toSeq.sortBy(_.getLineNumber).mkString("\n\t")}" + "\n" + s"All Relevant Assumptions:\n\t${relevantAssumptions.toSeq.sortBy(_.getLineNumber).mkString("\n\t")}" + "\n" + - s"Verification Progress: ${coveredImplicitSources.size.toDouble}/${relevantAssumptions.size.toDouble} = $verificationProgress" + s"#Verification Errors: ${verificationErrors.size}" + "\n\n" + + s"Verification Progress: ${coveredImplicitSources.size.toDouble}/(${relevantAssumptions.size.toDouble}+${verificationErrors.size}) = $verificationProgress" (verificationProgress, info) } } diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index 30125c229..aaed311aa 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -320,6 +320,7 @@ class DefaultMainVerifier(config: Config, if(Verifier.config.enableDependencyAnalysis()){ val dependencyGraphInterpreters = verificationResults.filter(_.dependencyGraphInterpreter.isDefined).map(_.dependencyGraphInterpreter.get) + val verificationErrors: List[Failure] = (verificationResults filter (_.isInstanceOf[Failure])) map (_.asInstanceOf[Failure]) dependencyGraphInterpreters foreach (_.exportGraph()) @@ -328,7 +329,7 @@ class DefaultMainVerifier(config: Config, joinedGraphInterpreter.exportGraph() if(Verifier.config.startDependencyAnalysisTool()){ - val commandLineTool = new DependencyAnalysisUserTool(joinedGraphInterpreter, dependencyGraphInterpreters, originalProgram) + val commandLineTool = new DependencyAnalysisUserTool(joinedGraphInterpreter, dependencyGraphInterpreters, originalProgram, verificationErrors) commandLineTool.run() } From 759c9d9e085f5c2266721047410b9f8836e71bfe Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 15 Dec 2025 17:09:05 +0100 Subject: [PATCH 280/474] WIP: pass dependency results to Gobra --- .../DependencyAnalysisNode.scala | 3 ++- .../DependencyAnalysisUserTool.scala | 21 +++++++++++-------- .../dependencyAnalysis/DependencyGraph.scala | 9 +++++--- .../DependencyGraphInterpreter.scala | 8 ++++--- .../scala/verifier/DefaultMainVerifier.scala | 13 ++++++------ 5 files changed, 32 insertions(+), 22 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala index 496fca74f..b961a5da7 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala @@ -3,8 +3,9 @@ package viper.silicon.dependencyAnalysis import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType import viper.silicon.interfaces.state.Chunk import viper.silicon.state.terms.{False, Term, Var} +import viper.silver.dependencyAnalysis.AbstractDependencyAnalysisNode -trait DependencyAnalysisNode { +trait DependencyAnalysisNode extends AbstractDependencyAnalysisNode{ val id: Int = DependencyGraphHelper.nextId() val sourceInfo: AnalysisSourceInfo val assumptionType: AssumptionType diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala index d0f2b84fb..0b87005cf 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala @@ -28,16 +28,19 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete @tailrec private def runInternal(): Unit = { - val userInput = readLine() - if(userInput.equalsIgnoreCase("q") || userInput.equalsIgnoreCase("quit")){ - return - } - if(userInput.nonEmpty) { - handleUserInput(userInput) - }else{ - println(infoString) + try { + val userInput = readLine() + if (userInput.equalsIgnoreCase("q") || userInput.equalsIgnoreCase("quit")) { + return + } + if (userInput.nonEmpty) { + handleUserInput(userInput) + } else { + println(infoString) + } + }catch { + case e: Exception => println(e.getMessage) } - runInternal() } diff --git a/src/main/scala/dependencyAnalysis/DependencyGraph.scala b/src/main/scala/dependencyAnalysis/DependencyGraph.scala index c9569ccc3..782a465e8 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraph.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraph.scala @@ -1,6 +1,9 @@ package viper.silicon.dependencyAnalysis -import java.io.{File, PrintWriter} +import viper.silver.dependencyAnalysis.AbstractReadOnlyDependencyGraph + +import java.io.PrintWriter +import java.nio.file.Paths import java.util.concurrent.atomic.AtomicInteger import scala.collection.mutable @@ -13,7 +16,7 @@ object DependencyGraphHelper { } } -trait ReadOnlyDependencyGraph { +trait ReadOnlyDependencyGraph extends AbstractReadOnlyDependencyGraph { def getNodes: Seq[DependencyAnalysisNode] def getDirectEdges: Map[Int, Set[Int]] // target -> direct dependencies def getTransitiveEdges: Map[Int, Set[Int]] // target -> direct dependencies @@ -153,7 +156,7 @@ class DependencyGraph extends ReadOnlyDependencyGraph { } def exportGraph(dirName: String): Unit = { - val directory = new File(dirName) + val directory = Paths.get(dirName).toFile directory.mkdir() exportNodes(dirName + "/nodes.csv") exportEdges(dirName + "/edges.csv") diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 3b944509a..9b7993df6 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -6,11 +6,13 @@ import viper.silver.ast import viper.silver.ast.utility.ViperStrategy import viper.silver.ast.utility.rewriter.Traverse import viper.silver.ast.{If, Stmt} +import viper.silver.dependencyAnalysis.AbstractDependencyGraphInterpreter -import java.io.{File, PrintWriter} +import java.io.PrintWriter +import java.nio.file.Paths -class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependencyGraph, member: Option[ast.Member]=None) { +class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependencyGraph, member: Option[ast.Member]=None) extends AbstractDependencyGraphInterpreter{ protected var joinCandidateNodes: Seq[DependencyAnalysisNode] = Seq.empty def getGraph: ReadOnlyDependencyGraph = dependencyGraph @@ -96,7 +98,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen def exportGraph(): Unit = { if(Verifier.config.dependencyAnalysisExportPath.isEmpty) return - val directory = new File(Verifier.config.dependencyAnalysisExportPath()) + val directory = Paths.get(Verifier.config.dependencyAnalysisExportPath()).toFile directory.mkdir() dependencyGraph.exportGraph(Verifier.config.dependencyAnalysisExportPath() + "/" + name) } diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index aaed311aa..8bdcd04be 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -6,13 +6,12 @@ package viper.silicon.verifier -import viper.silicon.dependencyAnalysis.DependencyAnalysisUserTool import viper.silicon.Config.{ExhaleMode, JoinMode} import viper.silicon._ -import viper.silicon.dependencyAnalysis.{DependencyAnalyzer, DependencyAnalysisReporter} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.SiliconDebugger import viper.silicon.decider.SMTLib2PreambleReader +import viper.silicon.dependencyAnalysis.{DependencyAnalysisReporter, DependencyAnalysisUserTool, DependencyAnalyzer} import viper.silicon.extensions.ConditionalPermissionRewriter import viper.silicon.interfaces._ import viper.silicon.interfaces.decider.ProverLike @@ -31,6 +30,7 @@ import viper.silver.cfg.silver.SilverCfg import viper.silver.components.StatefulComponent import viper.silver.frontend.FrontendStateCache import viper.silver.reporter._ +import viper.silver.verifier.errors.DependencyAnalysisFakeError import viper.silver.verifier.VerifierWarning import java.text.SimpleDateFormat @@ -311,9 +311,9 @@ class DefaultMainVerifier(config: Config, } reporter report VerificationTerminationMessage() - val verificationResults = ( functionVerificationResults - ++ predicateVerificationResults - ++ methodVerificationResults) + var verificationResults = (functionVerificationResults + ++ predicateVerificationResults + ++ methodVerificationResults) DependencyAnalyzer.stopTimeMeasurementAndAddToTotal(verificationStartTime, DependencyAnalyzer.timeToVerifyAndCollectDependencies) val postProcessingStartTime = DependencyAnalyzer.startTimeMeasurement() @@ -324,7 +324,7 @@ class DefaultMainVerifier(config: Config, dependencyGraphInterpreters foreach (_.exportGraph()) - val joinedGraphInterpreter = DependencyAnalyzer.joinGraphsAndGetInterpreter(inputFile.map(_.replaceAll("\\\\", "_").replaceAll(".vpr", "")), dependencyGraphInterpreters.toSet) + val joinedGraphInterpreter = DependencyAnalyzer.joinGraphsAndGetInterpreter(inputFile.map(_.replaceAll("\\\\", "_").replaceAll("/", "_").replaceAll(".vpr", "")), dependencyGraphInterpreters.toSet) if(Verifier.config.dependencyAnalysisExportPath.isDefined) joinedGraphInterpreter.exportGraph() @@ -340,6 +340,7 @@ class DefaultMainVerifier(config: Config, case _ => } + verificationResults = Failure(DependencyAnalysisFakeError(joinedGraphInterpreter)) +: verificationResults } DependencyAnalyzer.stopTimeMeasurementAndAddToTotal(postProcessingStartTime, DependencyAnalyzer.timeOfPostprocessing) From a09bd4104346ae3e3493a400221dfdc60cb5829b Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 16 Dec 2025 16:16:05 +0100 Subject: [PATCH 281/474] WIP: fix unsoundness issues in Gobra DA --- src/main/scala/dependencyAnalysis/DependencyGraph.scala | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/scala/dependencyAnalysis/DependencyGraph.scala b/src/main/scala/dependencyAnalysis/DependencyGraph.scala index 782a465e8..294b4e7c9 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraph.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraph.scala @@ -46,6 +46,11 @@ class DependencyGraph extends ReadOnlyDependencyGraph { res.toMap } + // TODO ake: remove + def setEdges(newEdges: Map[Int, Set[Int]]): Unit = { + newEdges.foreach(e => edges.update(e._1, e._2)) + } + def addNode(node: DependencyAnalysisNode): Unit = { nodes = nodes :+ node } From 6ce376f41a9475ebd963256f8fc2a14ee74520b5 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 17 Dec 2025 10:28:29 +0100 Subject: [PATCH 282/474] minor fixes --- .../scala/dependencyAnalysis/DependencyAnalysisUserTool.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala index 0b87005cf..8f040d11d 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala @@ -143,7 +143,7 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete } private def getSourceInfoString(nodes: Set[DependencyAnalysisNode]) = { - nodes.groupBy(node => node.sourceInfo.getTopLevelSource).map{case (_, nodes) => nodes.head.sourceInfo.getTopLevelSource}.toList.sortBy(_.getLineNumber).mkString("\n\t") + nodes.groupBy(node => node.sourceInfo.getTopLevelSource.toString).map{case (_, nodes) => nodes.head.sourceInfo.getTopLevelSource}.toList.sortBy(_.getLineNumber).mkString("\n\t") } private def getQueriedNodesFromInput(inputs: Set[String]): Set[DependencyAnalysisNode] = { From d9c3feca74bffc63088b477a80e4becce31765e1 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 17 Dec 2025 11:30:32 +0100 Subject: [PATCH 283/474] tmp --- .../DependencyAnalysisUserTool.scala | 6 +-- .../DependencyAnalyzer.scala | 10 ++--- .../DependencyGraphInterpreter.scala | 42 +++++++++---------- .../verificationProgress/permMax-2.vpr | 11 +++++ .../dependencyAnalysisTests/viperTest.vpr | 25 +++++++++++ .../dependencyAnalysisTests/viperTest2.vpr | 24 +++++++++++ src/test/scala/DependencyAnalysisTests.scala | 4 +- 7 files changed, 91 insertions(+), 31 deletions(-) create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgress/permMax-2.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/viperTest.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/viperTest2.vpr diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala index 8f040d11d..a011949de 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala @@ -81,9 +81,9 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete private def handleGraphSizeQuery(interpreter: DependencyGraphInterpreter): Unit = { val allAssumptions = interpreter.getNonInternalAssumptionNodes.filter(n => !n.isInstanceOf[AxiomAssumptionNode]) - val assumptions = allAssumptions.groupBy(_.sourceInfo.getTopLevelSource) + val assumptions = allAssumptions.groupBy(_.sourceInfo.getTopLevelSource.toString) val assertions = interpreter.getNonInternalAssertionNodesPerSource - val nodes = interpreter.getNonInternalAssertionNodes.union(allAssumptions).groupBy(_.sourceInfo.getTopLevelSource) + val nodes = interpreter.getNonInternalAssertionNodes.union(allAssumptions).groupBy(_.sourceInfo.getTopLevelSource.toString) println(s"#Assumptions = ${assumptions.size}") println(s"#Assertions = ${assertions.size}") println(s"#Nodes = ${nodes.size}") @@ -249,7 +249,7 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete val (allDependencies, time) = measureTime[Set[DependencyAnalysisNode]](fullGraphInterpreter.getAllNonInternalDependencies(queriedNodes.map(_.id))) allTimes = allTimes :+ time numLowLevelDeps = allDependencies.size - numDeps = allDependencies.groupBy(node => node.sourceInfo.getTopLevelSource).size + numDeps = allDependencies.groupBy(node => node.sourceInfo.getTopLevelSource.toString).size } writer.println(s"$userInput,$numLowLevelDeps,$numDeps,${allTimes.mkString(",")}") diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index d094abbd4..dba1863e8 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -158,12 +158,12 @@ object DependencyAnalyzer { val axiomAssertionNodes = joinCandidateNodes .filter(n => (n.isInstanceOf[GeneralAssertionNode] && AssumptionType.postconditionTypes.contains(n.assumptionType)) || AssumptionType.FunctionBody.equals(n.assumptionType)) - .groupBy(_.sourceInfo.getTopLevelSource) + .groupBy(_.sourceInfo.getTopLevelSource.toString) .view.mapValues(_.map(_.id)) .toMap joinCandidateNodes.filter(_.isInstanceOf[AxiomAssumptionNode]) .groupBy(n => n.sourceInfo) - .map{case (sourceInfo, axiomNodes) => (axiomNodes.map(_.id), axiomAssertionNodes.getOrElse(sourceInfo, Seq.empty))} + .map{case (sourceInfo, axiomNodes) => (axiomNodes.map(_.id), axiomAssertionNodes.getOrElse(sourceInfo.toString, Seq.empty))} .foreach{case (axiomNodeIds, assertionNodeIds) => newGraph.addEdges(assertionNodeIds, axiomNodeIds) // TODO ake: maybe we could merge the axiom nodes here since they represent the same axiom? } @@ -175,11 +175,11 @@ object DependencyAnalyzer { // hence, we add edges from assertions of method postconditions to assumptions of the same postcondition (at method calls) val relevantAssumptionNodes = joinCandidateNodes .filter(node => node.isInstanceOf[GeneralAssumptionNode] && AssumptionType.postconditionTypes.contains(node.assumptionType)) - .groupBy(_.sourceInfo.getFineGrainedSource) + .groupBy(_.sourceInfo.getFineGrainedSource.toString) .view.mapValues(_.map(_.id)) .toMap joinCandidateNodes.filter(node => node.isInstanceOf[GeneralAssertionNode] && AssumptionType.postconditionTypes.contains(node.assumptionType)) - .map(node => (node.id, relevantAssumptionNodes.getOrElse(node.sourceInfo.getTopLevelSource, Seq.empty))) + .map(node => (node.id, relevantAssumptionNodes.getOrElse(node.sourceInfo.getTopLevelSource.toString, Seq.empty))) .foreach { case (src, targets) => newGraph.addEdges(src, targets)} stopTimeMeasurementAndAddToTotal(startTime, timeForMethodJoin) @@ -380,7 +380,7 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { } val nodesBySource = assumptionGraph.getNodes.filter(!keepNode(_)) - .groupBy(n => (n.sourceInfo.getSourceForTransitiveEdges, n.sourceInfo.getTopLevelSource, n.sourceInfo.getFineGrainedSource, n.assumptionType)) + .groupBy(n => (n.sourceInfo.getSourceForTransitiveEdges, n.sourceInfo.getTopLevelSource.toString, n.sourceInfo.getFineGrainedSource, n.assumptionType)) nodesBySource foreach { case ((_, _, _, assumptionType), nodes) => val assumptionNodes = nodes.filter(_.isInstanceOf[GeneralAssumptionNode]) diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 9b7993df6..9b1474306 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -66,7 +66,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen } def filterOutNodesBySourceInfo(nodes: Set[DependencyAnalysisNode], excludeInfos: Set[AnalysisSourceInfo]): Set[DependencyAnalysisNode] = - nodes filterNot (node => excludeInfos.exists(i => i.getTopLevelSource.equals(node.sourceInfo.getTopLevelSource))) + nodes filterNot (node => excludeInfos.exists(i => i.getTopLevelSource.toString.equals(node.sourceInfo.getTopLevelSource.toString))) def getNonInternalAssumptionNodes: Set[DependencyAnalysisNode] = getNodes filter (node => (node.isInstanceOf[GeneralAssumptionNode] && !AssumptionType.internalTypes.contains(node.assumptionType)) @@ -81,16 +81,16 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen nodesToAnalyze.intersect(getNonInternalAssumptionNodes) .exists(node => dependencyGraph.existsAnyDependency(Set(node.id), nodesToAnalyze map (_.id) filter (_ != node.id), includeInfeasibilityNodes)) - def getNonInternalAssumptionNodesPerSource: Map[AnalysisSourceInfo, Set[DependencyAnalysisNode]] = - getNonInternalAssumptionNodes.groupBy(_.sourceInfo.getTopLevelSource) + def getNonInternalAssumptionNodesPerSource: Map[String, Set[DependencyAnalysisNode]] = + getNonInternalAssumptionNodes.groupBy(_.sourceInfo.getTopLevelSource.toString) def getNonInternalAssertionNodes: Set[DependencyAnalysisNode] = getNodes filter (node => node.isInstanceOf[GeneralAssertionNode] && !AssumptionType.internalTypes.contains(node.assumptionType) ) - def getNonInternalAssertionNodesPerSource: Map[AnalysisSourceInfo, Set[DependencyAnalysisNode]] = - getNonInternalAssertionNodes.groupBy(_.sourceInfo.getTopLevelSource) + def getNonInternalAssertionNodesPerSource: Map[String, Set[DependencyAnalysisNode]] = + getNonInternalAssertionNodes.groupBy(_.sourceInfo.getTopLevelSource.toString) def getExplicitAssertionNodes: Set[DependencyAnalysisNode] = getNonInternalAssertionNodes.filter(node => AssumptionType.explicitAssertionTypes.contains(node.assumptionType)) @@ -104,8 +104,8 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen } private def getNodesWithIdenticalSource(nodes: Set[DependencyAnalysisNode]): Set[DependencyAnalysisNode] = { - val sourceInfos = nodes map (_.sourceInfo.getTopLevelSource) - getNodes filter (node => sourceInfos.contains(node.sourceInfo.getTopLevelSource)) + val sourceInfos = nodes map (_.sourceInfo.getTopLevelSource.toString) + getNodes filter (node => sourceInfos.contains(node.sourceInfo.getTopLevelSource.toString)) } def computeProofCoverage(): (Double, Set[String]) = { @@ -117,14 +117,14 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen val assertionNodeIds = assertionNodes map (_.id) val dependencies = dependencyGraph.getAllDependencies(assertionNodeIds, includeInfeasibilityNodes=true) val coveredNodes = dependencies ++ assertionNodeIds - val nodesPerSourceInfo = getNonInternalAssumptionNodes.filterNot(_.isInstanceOf[AxiomAssumptionNode]).groupBy(_.sourceInfo.getTopLevelSource) + val nodesPerSourceInfo = getNonInternalAssumptionNodes.filterNot(_.isInstanceOf[AxiomAssumptionNode]).groupBy(_.sourceInfo.getTopLevelSource.toString) if(nodesPerSourceInfo.isEmpty) return (Double.NaN, Set()) val uncoveredSources = (nodesPerSourceInfo filter { case (_, nodes) => coveredNodes.intersect(nodes map (_.id)).isEmpty }).keys.toSet val proofCoverage = 1.0 - (uncoveredSources.size.toDouble / nodesPerSourceInfo.size.toDouble) - (proofCoverage, uncoveredSources.map(_.toString)) + (proofCoverage, uncoveredSources) } def getPrunedProgram(crucialNodes: Set[DependencyAnalysisNode], program: ast.Program): (ast.Program, Double) = { @@ -219,30 +219,30 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen } def computeVerificationProgress(verificationErrors: List[Failure]=List.empty): (Double, String) = { - val assumptionsPerSource = getNonInternalAssumptionNodes.groupBy(_.sourceInfo.getTopLevelSource) + val assumptionsPerSource = getNonInternalAssumptionNodes.groupBy(_.sourceInfo.getTopLevelSource.toString) if(assumptionsPerSource.isEmpty) return (Double.NaN, "Error: no assumptions found") val relevantDependencies = getExplicitAssertionNodes // TODO ake: only postconditions? - .groupBy(_.sourceInfo.getTopLevelSource) + .groupBy(_.sourceInfo.getTopLevelSource.toString) .map(g => getNodesWithIdenticalSource(g._2)) .flatMap(nodes => getAllNonInternalDependencies(nodes.map(_.id))) - val implicitPostConds = getNonInternalAssertionNodes.filter(node => AssumptionType.ImplicitPostcondition.equals(node.assumptionType)).groupBy(_.sourceInfo.getTopLevelSource).keys.toSet - val coveredExplicitSources = relevantDependencies.filter(node => AssumptionType.explicitAssumptionTypes.contains(node.assumptionType)).groupBy(_.sourceInfo.getTopLevelSource).keys.toSet // TODO ake: other assumption types? - val coveredImplicitSources = relevantDependencies.groupBy(_.sourceInfo.getTopLevelSource).keys.toSet.diff(coveredExplicitSources).diff(implicitPostConds) - val uncoveredExplicitSources = getExplicitAssumptionNodes.groupBy(_.sourceInfo.getTopLevelSource).keys.toSet.diff(relevantDependencies.groupBy(_.sourceInfo.getTopLevelSource).keys.toSet) + val implicitPostConds = getNonInternalAssertionNodes.filter(node => AssumptionType.ImplicitPostcondition.equals(node.assumptionType)).groupBy(_.sourceInfo.getTopLevelSource.toString).keys.toSet + val coveredExplicitSources = relevantDependencies.filter(node => AssumptionType.explicitAssumptionTypes.contains(node.assumptionType)).groupBy(_.sourceInfo.getTopLevelSource.toString).keys.toSet // TODO ake: other assumption types? + val coveredImplicitSources = relevantDependencies.groupBy(_.sourceInfo.getTopLevelSource.toString).keys.toSet.diff(coveredExplicitSources).diff(implicitPostConds) + val uncoveredExplicitSources = getExplicitAssumptionNodes.groupBy(_.sourceInfo.getTopLevelSource.toString).keys.toSet.diff(relevantDependencies.groupBy(_.sourceInfo.getTopLevelSource.toString).keys.toSet) val uncoveredImplicitSources = assumptionsPerSource.keys.toSet.diff(coveredImplicitSources).diff(coveredExplicitSources).diff(uncoveredExplicitSources).diff(implicitPostConds) val relevantAssumptions = assumptionsPerSource.keys.toSet.diff(implicitPostConds) val verificationProgress = coveredImplicitSources.size.toDouble / (relevantAssumptions.size.toDouble + verificationErrors.size) val info = - s"Uncovered Explicit Assumptions:\n\t${uncoveredExplicitSources.toSeq.sortBy(_.getLineNumber).mkString("\n\t")}" + "\n" + - s"Covered Explicit Assumptions:\n\t${coveredExplicitSources.toSeq.sortBy(_.getLineNumber).mkString("\n\t")}" + "\n" + - s"Uncovered Implicit Assumptions:\n\t${uncoveredImplicitSources.toSeq.sortBy(_.getLineNumber).mkString("\n\t")}" + "\n" + - s"Covered Implicit Assumptions:\n\t${coveredImplicitSources.toSeq.sortBy(_.getLineNumber).mkString("\n\t")}" + "\n" + - s"Implicit Postconditions:\n\t${implicitPostConds.toSeq.sortBy(_.getLineNumber).mkString("\n\t")}" + "\n" + - s"All Relevant Assumptions:\n\t${relevantAssumptions.toSeq.sortBy(_.getLineNumber).mkString("\n\t")}" + "\n" + + s"Uncovered Explicit Assumptions:\n\t${uncoveredExplicitSources.mkString("\n\t")}" + "\n" + + s"Covered Explicit Assumptions:\n\t${coveredExplicitSources.mkString("\n\t")}" + "\n" + + s"Uncovered Implicit Assumptions:\n\t${uncoveredImplicitSources.mkString("\n\t")}" + "\n" + + s"Covered Implicit Assumptions:\n\t${coveredImplicitSources.mkString("\n\t")}" + "\n" + + s"Implicit Postconditions:\n\t${implicitPostConds.mkString("\n\t")}" + "\n" + + s"All Relevant Assumptions:\n\t${relevantAssumptions.mkString("\n\t")}" + "\n" + s"#Verification Errors: ${verificationErrors.size}" + "\n\n" + s"Verification Progress: ${coveredImplicitSources.size.toDouble}/(${relevantAssumptions.size.toDouble}+${verificationErrors.size}) = $verificationProgress" (verificationProgress, info) diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgress/permMax-2.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgress/permMax-2.vpr new file mode 100644 index 000000000..faa955d6d --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgress/permMax-2.vpr @@ -0,0 +1,11 @@ +field f: Int + + +method foo(a: Ref) + requires acc(a.f) + ensures acc(a.f) + ensures a.f >= 0 + if(a.f < 0){ + a.f := -a.f + } +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/viperTest.vpr b/src/test/resources/dependencyAnalysisTests/viperTest.vpr new file mode 100644 index 000000000..4d2b3b073 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/viperTest.vpr @@ -0,0 +1,25 @@ + + +method foo(a: Int) returns (res: Int) + requires a > 0 + ensures res > 0 +{ + res := a + 1 +} + + +method bar(a: Int) returns (res: Int) + requires a > 0 + ensures res > 0 +{ + res := foo(a+1) + assert res >= 0 + + var b: Int + assume b > 0 + assume b < 100 + assert b >= 0 + + b := 0 + b := 1 +} diff --git a/src/test/resources/dependencyAnalysisTests/viperTest2.vpr b/src/test/resources/dependencyAnalysisTests/viperTest2.vpr new file mode 100644 index 000000000..1a0796467 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/viperTest2.vpr @@ -0,0 +1,24 @@ + + +method foo(a: Int) returns (res: Int) + requires a > 0 + ensures res > 0 +{ + assume false +} + + +method bar(a: Int) returns (res: Int) + requires a > 0 +{ + assume a < 100 + res := what(a+1) + assert res >= 0 + assert res < 100 + assert res < 100 +} + + +method what(a: Int) returns (res: Int) + requires a > 0 + ensures res > 0 diff --git a/src/test/scala/DependencyAnalysisTests.scala b/src/test/scala/DependencyAnalysisTests.scala index d425ecd6a..11c15b1b1 100644 --- a/src/test/scala/DependencyAnalysisTests.scala +++ b/src/test/scala/DependencyAnalysisTests.scala @@ -106,8 +106,8 @@ class DependencyAnalysisTests extends AnyFunSuite with DependencyAnalysisTestFra proofCoverageWriter.println(filePrefix + "/" + fileName) dependencyGraphInterpreters foreach (memberInterpreter => { - memberInterpreter.getExplicitAssertionNodes.groupBy(_.sourceInfo.getTopLevelSource) foreach {case (source, nodes) => - proofCoverageWriter.println(memberInterpreter.getName + " " + source.toString.replace("\n", " ") + " ---> " + memberInterpreter.computeProofCoverage(nodes)._1)} + memberInterpreter.getExplicitAssertionNodes.groupBy(_.sourceInfo.getTopLevelSource.toString) foreach {case (source, nodes) => + proofCoverageWriter.println(memberInterpreter.getName + " " + source.replace("\n", " ") + " ---> " + memberInterpreter.computeProofCoverage(nodes)._1)} proofCoverageWriter.println("overall " + memberInterpreter.getName + " ---> + " + memberInterpreter.computeProofCoverage()._1) }) proofCoverageWriter.println() From d3976d1b6033a6bd96aa75e762b2fc78b4e36f25 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 17 Dec 2025 14:45:28 +0100 Subject: [PATCH 284/474] tmp --- .../scala/dependencyAnalysis/DependencyAnalysisUserTool.scala | 2 +- .../dependencyAnalysisTests/verificationProgress/permMax-2.vpr | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala index a011949de..88dfe44f9 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala @@ -142,7 +142,7 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete println(s"Finished in ${time}ms") } - private def getSourceInfoString(nodes: Set[DependencyAnalysisNode]) = { + protected def getSourceInfoString(nodes: Set[DependencyAnalysisNode]): String = { nodes.groupBy(node => node.sourceInfo.getTopLevelSource.toString).map{case (_, nodes) => nodes.head.sourceInfo.getTopLevelSource}.toList.sortBy(_.getLineNumber).mkString("\n\t") } diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgress/permMax-2.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgress/permMax-2.vpr index faa955d6d..081f5c3fb 100644 --- a/src/test/resources/dependencyAnalysisTests/verificationProgress/permMax-2.vpr +++ b/src/test/resources/dependencyAnalysisTests/verificationProgress/permMax-2.vpr @@ -1,3 +1,5 @@ +// Verification Progress: 1.0 + field f: Int @@ -5,6 +7,7 @@ method foo(a: Ref) requires acc(a.f) ensures acc(a.f) ensures a.f >= 0 +{ if(a.f < 0){ a.f := -a.f } From 15fb4a0101206f536b2f57b63d70fff0c4e1b01d Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 18 Dec 2025 11:39:03 +0100 Subject: [PATCH 285/474] modifications for Gobra dependency analysis --- .../dependencyAnalysis/AnalysisSourceInfo.scala | 12 ++++++++---- .../dependencyAnalysis/DependencyAnalyzer.scala | 2 +- .../scala/dependencyAnalysis/DependencyGraph.scala | 12 ++++++------ 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala b/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala index 13b51e048..5de485151 100644 --- a/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala +++ b/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala @@ -53,10 +53,12 @@ case class NoAnalysisSourceInfo() extends AnalysisSourceInfo { } case class ExpAnalysisSourceInfo(source: ast.Exp) extends AnalysisSourceInfo { - override def toString: String = (if(source.info.getSourceString.isEmpty) source.toString else source.info.getSourceString).replaceAll("\n", "\t") + + val dependencyAnalysisInfo: Option[DependencyAnalysisInfo] = source.info.getUniqueInfo[DependencyAnalysisInfo] + + override def toString: String = (if(dependencyAnalysisInfo.isDefined) dependencyAnalysisInfo.get.info else source.toString).replaceAll("\n", "\t") + " (" + super.toString + ")" - override def getPosition: Position = source.pos + override def getPosition: Position = if(dependencyAnalysisInfo.isDefined) dependencyAnalysisInfo.get.pos else source.pos override def equals(obj: Any): Boolean = { obj match { @@ -70,9 +72,11 @@ case class ExpAnalysisSourceInfo(source: ast.Exp) extends AnalysisSourceInfo { } case class StmtAnalysisSourceInfo(source: ast.Stmt) extends AnalysisSourceInfo { - override def toString: String = (if(source.info.getSourceString.isEmpty) source.toString() else source.info.getSourceString).replaceAll("\n", "\t") + + val dependencyAnalysisInfo: Option[DependencyAnalysisInfo] = source.info.getUniqueInfo[DependencyAnalysisInfo] + + override def toString: String = (if(dependencyAnalysisInfo.isDefined) dependencyAnalysisInfo.get.info else source.toString()).replaceAll("\n", "\t") + " (" + super.toString + ")" - override def getPosition: Position = source.pos + override def getPosition: Position = if(dependencyAnalysisInfo.isDefined) dependencyAnalysisInfo.get.pos else source.pos override def equals(obj: Any): Boolean = { obj match { diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index dba1863e8..513d31fd7 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -380,7 +380,7 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { } val nodesBySource = assumptionGraph.getNodes.filter(!keepNode(_)) - .groupBy(n => (n.sourceInfo.getSourceForTransitiveEdges, n.sourceInfo.getTopLevelSource.toString, n.sourceInfo.getFineGrainedSource, n.assumptionType)) + .groupBy(n => (n.sourceInfo.getSourceForTransitiveEdges.toString, n.sourceInfo.getTopLevelSource.toString, n.sourceInfo.getFineGrainedSource.toString, n.assumptionType)) nodesBySource foreach { case ((_, _, _, assumptionType), nodes) => val assumptionNodes = nodes.filter(_.isInstanceOf[GeneralAssumptionNode]) diff --git a/src/main/scala/dependencyAnalysis/DependencyGraph.scala b/src/main/scala/dependencyAnalysis/DependencyGraph.scala index 294b4e7c9..edf8a8113 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraph.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraph.scala @@ -128,19 +128,19 @@ class DependencyGraph extends ReadOnlyDependencyGraph { } // TODO ake: maybe move to DependencyAnalyzer? - private def getNodesPerTransitivitySourceInfo: Map[AnalysisSourceInfo, Seq[DependencyAnalysisNode]] = { - getNodes.groupBy(_.sourceInfo.getSourceForTransitiveEdges) + private def getNodesPerTransitivitySourceInfo: Map[String, Seq[DependencyAnalysisNode]] = { + getNodes.groupBy(_.sourceInfo.getSourceForTransitiveEdges.toString) } // TODO ake: maybe move to DependencyAnalyzer? def addTransitiveEdges(): Unit = { val nodesPerSourceInfo = getNodesPerTransitivitySourceInfo - nodesPerSourceInfo foreach {nodes => - val asserts = nodes._2.filter(_.isInstanceOf[GeneralAssertionNode]) - val assumes = nodes._2.filter(n => !n.isClosed && n.isInstanceOf[GeneralAssumptionNode] && !n.isInstanceOf[LabelNode]) + nodesPerSourceInfo foreach {case (_, nodes) => + val asserts = nodes.filter(_.isInstanceOf[GeneralAssertionNode]) + val assumes = nodes.filter(n => !n.isClosed && n.isInstanceOf[GeneralAssumptionNode] && !n.isInstanceOf[LabelNode]) addTransitiveEdges(asserts, assumes) val checks = asserts.filter(_.isInstanceOf[SimpleCheckNode]) - val notChecks = nodes._2.filter(n => !n.isClosed && !n.isInstanceOf[SimpleCheckNode]) + val notChecks = nodes.filter(n => !n.isClosed && !n.isInstanceOf[SimpleCheckNode]) addTransitiveEdges(checks, notChecks) } } From 762b64ce1790d0fdb1e2d471e307e7f5696b4a90 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 19 Dec 2025 17:31:01 +0100 Subject: [PATCH 286/474] minor fix --- src/main/scala/rules/Executor.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index e0bc3b6bc..d4c8dd8b2 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -279,7 +279,7 @@ object executor extends ExecutionRules { val s2 = s1.copy(invariantContexts = sLeftover.h +: s1.invariantContexts) intermediateResult combine executionFlowController.locally(s2, v1)((s3, v2) => { v2.decider.declareAndRecordAsFreshFunctions(ff1 -- v2.decider.freshFunctions) /* [BRANCH-PARALLELISATION] */ - v2.decider.assume(pcs.assumptions map (t => v.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t))), Some(pcs.assumptionExps), "Loop invariant", enforceAssumption=false, assumptionType=AssumptionType.LoopInvariant) + v2.decider.assume(pcs.assumptions map (t => v.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t))), Some(pcs.assumptionExps), "Loop invariant", enforceAssumption=false, assumptionType=AssumptionType.Internal) v2.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) if (v2.decider.checkSmoke() && !Verifier.config.disableInfeasibilityChecks()) Success() From 628bc7acd9fbcae3832fa35bc40e03e0df5f010e Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 22 Dec 2025 13:13:42 +0100 Subject: [PATCH 287/474] add documentation --- .../dependencyAnalysis/AnalysisInfo.scala | 3 +- .../AnalysisSourceInfo.scala | 3 +- .../DependencyAnalysisNode.scala | 45 +++++++++++- .../DependencyAnalyzer.scala | 68 ++++++++++++++++--- .../scala/supporters/MethodSupporter.scala | 2 +- .../functions/FunctionVerificationUnit.scala | 2 +- 6 files changed, 108 insertions(+), 15 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala index 6063ce0c6..eac1d2b88 100644 --- a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala +++ b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala @@ -8,8 +8,9 @@ object AssumptionType extends Enumeration { def explicitAssumptionTypes: Set[AssumptionType] = Set(Explicit, ExplicitPostcondition) def postconditionTypes: Set[AssumptionType] = Set(ImplicitPostcondition, ExplicitPostcondition, CallPostcondition) // used to join graphs via postconditions - def explicitAssertionTypes: Set[AssumptionType] = Set(Explicit) ++ postconditionTypes + def explicitAssertionTypes: Set[AssumptionType] = Set(Explicit, ImplicitPostcondition, ExplicitPostcondition) def internalTypes: Set[AssumptionType] = Set(Internal) // will always be hidden from user + def implicitTypes: Set[AssumptionType] = AssumptionType.values.diff(explicitAssumptionTypes).diff(internalTypes) def joinConditionTypes: Set[AssumptionType] = postconditionTypes ++ Set(FunctionBody) } diff --git a/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala b/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala index 5de485151..ada974a5a 100644 --- a/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala +++ b/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala @@ -29,7 +29,8 @@ abstract class AnalysisSourceInfo { def getPosition: Position /** - * @return the analysis source info used for merging nodes + * @return the analysis source info used for lifting low-level graphs to higher-level graphs + * and presenting dependency results to the user */ def getTopLevelSource: AnalysisSourceInfo = this diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala index b961a5da7..ecff07987 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala @@ -5,16 +5,46 @@ import viper.silicon.interfaces.state.Chunk import viper.silicon.state.terms.{False, Term, Var} import viper.silver.dependencyAnalysis.AbstractDependencyAnalysisNode -trait DependencyAnalysisNode extends AbstractDependencyAnalysisNode{ + +trait DependencyAnalysisNode extends AbstractDependencyAnalysisNode { + + /** + * The unique node id, which is also given to the SMT solver such that unsat cores can be mapped back to dependency nodes. + */ val id: Int = DependencyGraphHelper.nextId() + + /** + * Stores information about which source code statement / expression created this node. + * This information is crucial to lift lower-level graphs to higher-level graphs and to present user-readable + * dependency results. + */ val sourceInfo: AnalysisSourceInfo + + /** + * The assumption / assertion type of the node which is heavily used in dependency graph queries to filter nodes, + * for example, to only present explicit assumptions. + */ val assumptionType: AssumptionType + + /** + * A flag that, when set to true, indicates that the node should not receive any additional edges, unless explicitly added. + * This is useful to increase precision of the dependency analysis, for example, to ensure that an assumption does not + * depend on more assertions than necessary. + */ val isClosed: Boolean + + /** + * The assumes or asserted Silicon term. Currently, only used for debugging purposes. + */ val term: Term def getTerm: Term = term + /* + Some string representations, mainly used for debugging purposes. + The strings represented to users are obtained via sourceInfo.toString and do not contain any low-level information + about the node (such as the id or term). + */ override def toString: String = id.toString + " | " + getNodeString + " | " + sourceInfo.toString - def getNodeString: String def getNodeType: String @@ -61,6 +91,10 @@ case class PermissionExhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSo override def getNodeString: String = "exhale " + chunk.toString } +/** + * Label nodes are internally used nodes, mostly used to improve precision of the dependency analysis. + * They are completely hidden from users. + */ case class LabelNode(term: Var) extends GeneralAssumptionNode { val sourceInfo: AnalysisSourceInfo = NoAnalysisSourceInfo() val assumptionType: AssumptionType = AssumptionType.Internal @@ -70,6 +104,13 @@ case class LabelNode(term: Var) extends GeneralAssumptionNode { override def getNodeString: String = "assume " + description } +/** + * Represents the fact that a branch has been found to be infeasible. + * Infeasibility nodes should always depend on the proof of false and all subsequent assertions on the infeasible path + * should depend on the infeasibility node. + * Infeasibility nodes allow to distinguish between dependencies coming from the fact that the assertion is not reachable + * on a given path and dependencies used to prove the assertion on feasible paths. + */ case class InfeasibilityNode(sourceInfo: AnalysisSourceInfo) extends GeneralAssumptionNode { val term: Term = False val assumptionType: AssumptionType = AssumptionType.Implicit diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index 513d31fd7..157ae3691 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -44,11 +44,26 @@ trait DependencyAnalyzer { def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit def addPermissionDependencies(sourceChunks: Set[Chunk], sourceTerms: Set[Term], targetChunk: Chunk): Unit def addCustomTransitiveDependency(sourceSourceInfo: AnalysisSourceInfo, targetSourceInfo: AnalysisSourceInfo): Unit - def addCustomExpDependency(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp]): Unit + + /** + * Adds dependencies between all pairs of sourceExps and targetExps, where sourceExps should be preconditions and + * targetExps should be postconditions of an abstract function or method. + */ + def addDependenciesForExplicitPostconditions(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp]): Unit + + /** + * Adds edges connecting nodes representing function postconditions with the corresponding axiom nodes. + */ def addFunctionAxiomEdges(): Unit - def addInfeasibilityDepToStmt(infeasNodeId: Option[Int], analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Unit = {} + /** + * Adds an assertion and assumption node with the given analysis source info and dependencies to the current infeasibility node. + */ + def addInfeasibilityDepToStmt(infeasNodeId: Option[Int], analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Unit = {} + /** + * @return the final dependency graph representing all direct and transitive dependencies + */ def buildFinalGraph(): Option[DependencyGraph] } @@ -80,6 +95,7 @@ object DependencyAnalyzer { total.addAndGet(endTime - startTime) } + // TODO ake: remove all profiling artifacts def printProfilingResults(): Unit = { if(!Verifier.config.enableDependencyAnalysisProfiling()) return println(s"Overall runtime = time spent on verification and building the final graph: ${timeToVerifyAndBuildFinalGraph.get() / 1e6}ms") @@ -139,6 +155,15 @@ object DependencyAnalyzer { def isAxiomLabel(label: String): Boolean = label.startsWith("axiom_") + /** + * + * @param name Optional name for the result graph. + * @param dependencyGraphInterpreters The graphs which should be joined. + * @return A dependency graph interpreter operating on a new dependency graph that represents all input graphs and + * dependencies between them. + * The new graph is built by adding all existing nodes and edges of all input graphs and joining them via postconditions + * of functions and methods. + */ def joinGraphsAndGetInterpreter(name: Option[String], dependencyGraphInterpreters: Set[DependencyGraphInterpreter]): DependencyGraphInterpreter = { var startTime = startTimeMeasurement() val newGraph = new DependencyGraph @@ -342,17 +367,25 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { } override def addCustomTransitiveDependency(sourceSourceInfo: AnalysisSourceInfo, targetSourceInfo: AnalysisSourceInfo): Unit = { - val sourceNodes = assumptionGraph.nodes filter (n => n.isInstanceOf[GeneralAssertionNode] && n.sourceInfo.getSourceForTransitiveEdges.equals(sourceSourceInfo.getSourceForTransitiveEdges)) - val targetNodes = assumptionGraph.nodes filter (n => n.isInstanceOf[GeneralAssumptionNode] && n.sourceInfo.getSourceForTransitiveEdges.equals(targetSourceInfo.getSourceForTransitiveEdges)) - assumptionGraph.addEdges(sourceNodes map (_.id), targetNodes map (_.id)) + // TODO ake: remove this since this is already done in buildFinalGraph()? +// val sourceNodes = assumptionGraph.nodes filter (n => n.isInstanceOf[GeneralAssertionNode] && n.sourceInfo.getSourceForTransitiveEdges.equals(sourceSourceInfo.getSourceForTransitiveEdges)) +// val targetNodes = assumptionGraph.nodes filter (n => n.isInstanceOf[GeneralAssumptionNode] && n.sourceInfo.getSourceForTransitiveEdges.equals(targetSourceInfo.getSourceForTransitiveEdges)) +// assumptionGraph.addEdges(sourceNodes map (_.id), targetNodes map (_.id)) } - def addCustomExpDependency(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp]): Unit = { - val sourceNodeIds = sourceExps.flatMap(e => addAssumption(True, ExpAnalysisSourceInfo(e), AssumptionType.Explicit, None)) + override def addDependenciesForExplicitPostconditions(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp]): Unit = { + val sourceNodeIds = sourceExps.flatMap(e => addAssumption(True, ExpAnalysisSourceInfo(e), AssumptionType.Precondition, None)) val targetNodes = targetExps.flatMap(e => addAssertNode(True, AssumptionType.ExplicitPostcondition, ExpAnalysisSourceInfo(e))) assumptionGraph.addEdges(sourceNodeIds, targetNodes) } + /** + * + * @return the final dependency graph + * This operation ensures sound computation of transitive dependencies by adding edges between nodes originating from the same + * source code statement. + * Further, this operation removes unnecessary details from the graph by, for example, removing label nodes and merging identical nodes. + */ override def buildFinalGraph(): Option[DependencyGraph] = { assumptionGraph.removeLabelNodes() val mergedGraph = if(Verifier.config.enableDependencyAnalysisDebugging()) assumptionGraph else buildAndGetMergedGraph() @@ -369,6 +402,13 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { } } + /** + * Creates a new graph where nodes that only differ in irrelevant information are merged into one node. + * As a result, this operation removes some lower-level details from the graph. + * This step can be skipped for debugging purposes by setting the enableDependencyAnalysisDebugging flag. Doing so + * has no effect on the dependency results but allows to inspect low-level details while debugging and exporting + * the low-level graph containing all details. + */ private def buildAndGetMergedGraph(): DependencyGraph = { def keepNode(n: DependencyAnalysisNode): Boolean = n.isClosed || n.isInstanceOf[InfeasibilityNode] || n.isInstanceOf[AxiomAssumptionNode] @@ -408,14 +448,24 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { mergedGraph } + /** + * Adds an assertion and assumption node with the given analysis source info and dependencies to the current infeasibility node. + * If the infeasibility node is not defined, this operation does nothing. + * The resulting assertion node is required to detect dependencies of the source statement/expression on infeasible paths. + * The resulting assumption node is required to ensure that unreachable statements/expressions are represented in the graph and + * thus taken into account by graph queries, e.g. when determining uncovered statements or computing coverage. + */ override def addInfeasibilityDepToStmt(infeasNodeId: Option[Int], analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Unit = { val newAssertionNodeId = addAssertNode(False, assumptionType, analysisSourceInfo) + addDependency(infeasNodeId, newAssertionNodeId) val newAssumptionNodeId = addAssumption(False, analysisSourceInfo, assumptionType) addDependency(infeasNodeId, newAssumptionNodeId) - addDependency(infeasNodeId, newAssertionNodeId) } } +/** + * This DependencyAnalyzer implementation is used by default and does nothing. + */ class NoDependencyAnalyzer extends DependencyAnalyzer { override def getMember: Option[ast.Member] = None @@ -437,7 +487,7 @@ class NoDependencyAnalyzer extends DependencyAnalyzer { override def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit = {} override def addPermissionDependencies(sourceChunks: Set[Chunk], sourceTerms: Set[Term], targetChunk: Chunk): Unit = {} override def addCustomTransitiveDependency(sourceSourceInfo: AnalysisSourceInfo, targetSourceInfo: AnalysisSourceInfo): Unit = {} - override def addCustomExpDependency(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp]): Unit = {} + override def addDependenciesForExplicitPostconditions(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp]): Unit = {} override def addFunctionAxiomEdges(): Unit = {} override def buildFinalGraph(): Option[DependencyGraph] = None diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index fb3ef51ba..e351ab19a 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -122,7 +122,7 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif Success())})}) } )})}) if(method.body.isEmpty){ - v.decider.dependencyAnalyzer.addCustomExpDependency(method.pres.flatMap(_.topLevelConjuncts), method.posts.flatMap(_.topLevelConjuncts)) + v.decider.dependencyAnalyzer.addDependenciesForExplicitPostconditions(method.pres.flatMap(_.topLevelConjuncts), method.posts.flatMap(_.topLevelConjuncts)) } result.dependencyGraphInterpreter = v.decider.dependencyAnalyzer.buildFinalGraph().map(new DependencyGraphInterpreter(method.name, _, Some(method))) diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index 9bc671f87..942495671 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -191,7 +191,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver if (function.body.isEmpty) { decider.dependencyAnalyzer.addNodes(v.decider.prover.getPreambleAnalysisNodes) - decider.dependencyAnalyzer.addCustomExpDependency(function.pres.flatMap(_.topLevelConjuncts), function.posts.flatMap(_.topLevelConjuncts)) + decider.dependencyAnalyzer.addDependenciesForExplicitPostconditions(function.pres.flatMap(_.topLevelConjuncts), function.posts.flatMap(_.topLevelConjuncts)) result1 } else { /* Phase 2: Verify the function's postcondition */ From 669617d4a58d947f1dab1b0112d6bf5411d3c4cb Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 22 Dec 2025 16:00:00 +0100 Subject: [PATCH 288/474] (WIP) implement alternative progress metric --- .../DependencyAnalysisUserTool.scala | 2 +- .../DependencyAnalyzer.scala | 2 +- .../DependencyGraphInterpreter.scala | 47 +++++++++----- .../scala/supporters/MethodSupporter.scala | 4 +- .../functions/FunctionVerificationUnit.scala | 5 +- .../DependencyAnalysisProgressTests.scala | 61 +++++++++++++++++++ 6 files changed, 102 insertions(+), 19 deletions(-) create mode 100644 src/test/scala/DependencyAnalysisProgressTests.scala diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala index 88dfe44f9..3508209a7 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala @@ -136,7 +136,7 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete private def handleVerificationProgressQuery(): Unit = { println("Computing verification progress...") - val ((progress, info), time) = measureTime(fullGraphInterpreter.computeVerificationProgress(verificationErrors)) + val ((progress, _, info), time) = measureTime(fullGraphInterpreter.computeVerificationProgress()) println(s"Overall verification progress: $progress") println(s"$info") println(s"Finished in ${time}ms") diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index 157ae3691..fc9485d1f 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -209,7 +209,7 @@ object DependencyAnalyzer { stopTimeMeasurementAndAddToTotal(startTime, timeForMethodJoin) - val newInterpreter = new DependencyGraphInterpreter(name.getOrElse("joined"), newGraph) + val newInterpreter = new DependencyGraphInterpreter(name.getOrElse("joined"), newGraph, dependencyGraphInterpreters.toList.flatMap(_.getErrors)) newInterpreter } } diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 9b1474306..b2cfa6e86 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -12,13 +12,14 @@ import java.io.PrintWriter import java.nio.file.Paths -class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependencyGraph, member: Option[ast.Member]=None) extends AbstractDependencyGraphInterpreter{ +class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependencyGraph, errors: List[Failure], member: Option[ast.Member]=None) extends AbstractDependencyGraphInterpreter{ protected var joinCandidateNodes: Seq[DependencyAnalysisNode] = Seq.empty def getGraph: ReadOnlyDependencyGraph = dependencyGraph def getName: String = name def getMember: Option[ast.Member] = member def getNodes: Set[DependencyAnalysisNode] = dependencyGraph.getNodes.toSet + def getErrors: List[Failure] = errors def getJoinCandidateNodes: Iterable[DependencyAnalysisNode] = joinCandidateNodes @@ -218,14 +219,21 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen writer.close() } - def computeVerificationProgress(verificationErrors: List[Failure]=List.empty): (Double, String) = { + def computeVerificationProgress(): (Double, Double, String) = { val assumptionsPerSource = getNonInternalAssumptionNodes.groupBy(_.sourceInfo.getTopLevelSource.toString) - if(assumptionsPerSource.isEmpty) return (Double.NaN, "Error: no assumptions found") + if(assumptionsPerSource.isEmpty) return (Double.NaN, Double.NaN, "Error: no assumptions found") - val relevantDependencies = getExplicitAssertionNodes // TODO ake: only postconditions? + val allExplicitAssertions = getExplicitAssertionNodes // TODO ake: only postconditions? .groupBy(_.sourceInfo.getTopLevelSource.toString) - .map(g => getNodesWithIdenticalSource(g._2)) - .flatMap(nodes => getAllNonInternalDependencies(nodes.map(_.id))) + + val relevantDependenciesPerAssertion = allExplicitAssertions + .view.mapValues(g => getAllNonInternalDependencies(getNodesWithIdenticalSource(g).map(_.id))) + + val assertionsWithoutExplicitAssumptions = relevantDependenciesPerAssertion.filter { case (_, deps) => + !deps.exists(dep => AssumptionType.explicitAssumptionTypes.contains(dep.assumptionType)) + }.keys + + val relevantDependencies = relevantDependenciesPerAssertion.flatMap(_._2) val implicitPostConds = getNonInternalAssertionNodes.filter(node => AssumptionType.ImplicitPostcondition.equals(node.assumptionType)).groupBy(_.sourceInfo.getTopLevelSource.toString).keys.toSet val coveredExplicitSources = relevantDependencies.filter(node => AssumptionType.explicitAssumptionTypes.contains(node.assumptionType)).groupBy(_.sourceInfo.getTopLevelSource.toString).keys.toSet // TODO ake: other assumption types? @@ -234,17 +242,26 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen val uncoveredImplicitSources = assumptionsPerSource.keys.toSet.diff(coveredImplicitSources).diff(coveredExplicitSources).diff(uncoveredExplicitSources).diff(implicitPostConds) val relevantAssumptions = assumptionsPerSource.keys.toSet.diff(implicitPostConds) - val verificationProgress = coveredImplicitSources.size.toDouble / (relevantAssumptions.size.toDouble + verificationErrors.size) + val verificationProgressAndrea = coveredImplicitSources.size.toDouble / (relevantAssumptions.size.toDouble + errors.size) - val info = + val specQuality = coveredImplicitSources.size.toDouble / (coveredImplicitSources.size.toDouble + uncoveredImplicitSources.size.toDouble) + val proofQuality = (assertionsWithoutExplicitAssumptions.size.toDouble - errors.size.toDouble) / allExplicitAssertions.size.toDouble + val verificationProgressPeter = specQuality * proofQuality + + val info = { + s"All Assumptions and Statements:\n\t${relevantAssumptions.mkString("\n\t")}" + "\n" + s"Uncovered Explicit Assumptions:\n\t${uncoveredExplicitSources.mkString("\n\t")}" + "\n" + s"Covered Explicit Assumptions:\n\t${coveredExplicitSources.mkString("\n\t")}" + "\n" + - s"Uncovered Implicit Assumptions:\n\t${uncoveredImplicitSources.mkString("\n\t")}" + "\n" + - s"Covered Implicit Assumptions:\n\t${coveredImplicitSources.mkString("\n\t")}" + "\n" + - s"Implicit Postconditions:\n\t${implicitPostConds.mkString("\n\t")}" + "\n" + - s"All Relevant Assumptions:\n\t${relevantAssumptions.mkString("\n\t")}" + "\n" + - s"#Verification Errors: ${verificationErrors.size}" + "\n\n" + - s"Verification Progress: ${coveredImplicitSources.size.toDouble}/(${relevantAssumptions.size.toDouble}+${verificationErrors.size}) = $verificationProgress" - (verificationProgress, info) + s"Uncovered Statements:\n\t${uncoveredImplicitSources.mkString("\n\t")}" + "\n" + + s"Covered Statements:\n\t${coveredImplicitSources.mkString("\n\t")}" + "\n" + + s"Postconditions of methods/functions with bodies:\n\t${implicitPostConds.mkString("\n\t")}" + "\n" + + s"Explicit Assertions:\n\t${allExplicitAssertions.keys.mkString("\n\t")}" + "\n\n" + + s"Explicit Assertions verified without explicit assumptions:\n\t${assertionsWithoutExplicitAssumptions.mkString("\n\t")}" + "\n\n" + + s"#Verification Errors: ${errors.size}" + "\n\n" + + s"Verification Progress (Andrea):\n\t${coveredImplicitSources.size}/(${relevantAssumptions.size}+${errors.size}) = $verificationProgressAndrea" + "\n\n" + + s"Verification Progress (Peter):\n\t${coveredImplicitSources.size}/(${coveredImplicitSources.size + uncoveredImplicitSources.size}) * " + + s"${assertionsWithoutExplicitAssumptions.size - errors.size.toDouble}/${allExplicitAssertions.size} = $verificationProgressPeter" + "\n" + } + (verificationProgressAndrea, verificationProgressPeter, info) } } diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index e351ab19a..52d618153 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -125,7 +125,9 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif v.decider.dependencyAnalyzer.addDependenciesForExplicitPostconditions(method.pres.flatMap(_.topLevelConjuncts), method.posts.flatMap(_.topLevelConjuncts)) } - result.dependencyGraphInterpreter = v.decider.dependencyAnalyzer.buildFinalGraph().map(new DependencyGraphInterpreter(method.name, _, Some(method))) + val allErrors = (result :: result.previous.toList).filter(_.isInstanceOf[Failure]).map(_.asInstanceOf[Failure]) + + result.dependencyGraphInterpreter = v.decider.dependencyAnalyzer.buildFinalGraph().map(new DependencyGraphInterpreter(method.name, _, allErrors, Some(method))) result.dependencyGraphInterpreter.foreach(_.initJoinCandidateNodes()) v.decider.resetProverOptions() diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index 942495671..2540085e1 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -163,7 +163,10 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver v.decider.dependencyAnalyzer.addFunctionAxiomEdges() - res.dependencyGraphInterpreter = v.decider.dependencyAnalyzer.buildFinalGraph().map(new DependencyGraphInterpreter(function.name, _, Some(function))) + val allErrors = (res :: res.previous.toList).filter(_.isInstanceOf[Failure]).map(_.asInstanceOf[Failure]) + + res.dependencyGraphInterpreter = v.decider.dependencyAnalyzer.buildFinalGraph().map(new DependencyGraphInterpreter(function.name, _, + allErrors, Some(function))) res.dependencyGraphInterpreter.foreach(_.initJoinCandidateNodes()) Seq(res) diff --git a/src/test/scala/DependencyAnalysisProgressTests.scala b/src/test/scala/DependencyAnalysisProgressTests.scala new file mode 100644 index 000000000..ed2e9b127 --- /dev/null +++ b/src/test/scala/DependencyAnalysisProgressTests.scala @@ -0,0 +1,61 @@ +import org.scalatest.funsuite.AnyFunSuite +import viper.silicon.dependencyAnalysis.DependencyAnalysisReporter +import viper.silicon.tests.DependencyAnalysisTestFramework +import viper.silver.ast.Program +import viper.silver.frontend.SilFrontend +import viper.silver.verifier.{Failure, VerificationResult} + +import java.nio.file.{Files, Path, Paths} +import scala.collection.convert.ImplicitConversions.`iterable AsScalaIterable` + +class DependencyAnalysisProgressTests extends AnyFunSuite with DependencyAnalysisTestFramework { + + val ignores: Seq[String] = Seq.empty + val testDirectories: Seq[String] = Seq( + "dependencyAnalysisTests/verificationProgress/incrRand" + ) + + testDirectories foreach (dir => createSingleTest(dir, "")) + + def createSingleTest(dirName: String, fileName: String): Unit = { + test(dirName) { + try{ + val directoryStream = Files.newDirectoryStream(Paths.get(getClass.getClassLoader.getResource(dirName).toURI)) + val dirContent = directoryStream.toList + + for (filePath: Path <- dirContent.sorted + if Files.isReadable(filePath)) { + val rawFileName = filePath.getFileName.toString + if (rawFileName.endsWith(".vpr")) { + val vprFileName = rawFileName.replace(".vpr", "") + if (!ignores.contains(vprFileName)) + resetFrontend() + executeTest(dirName + "/", vprFileName, frontend) + } + } + }catch{ + case t: Throwable => fail(t.toString) + } + } + } + + + def executeTest(filePrefix: String, + fileName: String, + frontend: SilFrontend): Unit = { + + val program: Program = tests.loadProgram(filePrefix, fileName, frontend) + val result: VerificationResult = frontend.verifier.verify(program) + val errors = result match { + case failure: Failure => failure.errors + case _ => List.empty + } + + val joinedDependencyGraphInterpreter = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].joinedDependencyGraphInterpreter + + val (progressA, progressB, info) = joinedDependencyGraphInterpreter.get.computeVerificationProgress() + + println(s"$filePrefix$fileName:\n\tProgress A = $progressA\t\t\tProgress P = $progressB") + println(info) + } +} From 2130d0a6552a3c958c2973b5f0eee331638f65c0 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 5 Jan 2026 11:48:28 +0100 Subject: [PATCH 289/474] fix position mapping for DA --- src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala b/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala index ada974a5a..cf6a4cb78 100644 --- a/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala +++ b/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala @@ -45,6 +45,8 @@ abstract class AnalysisSourceInfo { def getFineGrainedSource: AnalysisSourceInfo = this def isAnalysisEnabled: Boolean = true + + val dependencyAnalysisInfo: Option[DependencyAnalysisInfo] = None } case class NoAnalysisSourceInfo() extends AnalysisSourceInfo { @@ -54,7 +56,7 @@ case class NoAnalysisSourceInfo() extends AnalysisSourceInfo { } case class ExpAnalysisSourceInfo(source: ast.Exp) extends AnalysisSourceInfo { - val dependencyAnalysisInfo: Option[DependencyAnalysisInfo] = source.info.getUniqueInfo[DependencyAnalysisInfo] + override val dependencyAnalysisInfo: Option[DependencyAnalysisInfo] = source.info.getUniqueInfo[DependencyAnalysisInfo] override def toString: String = (if(dependencyAnalysisInfo.isDefined) dependencyAnalysisInfo.get.info else source.toString).replaceAll("\n", "\t") + " (" + super.toString + ")" @@ -73,7 +75,7 @@ case class ExpAnalysisSourceInfo(source: ast.Exp) extends AnalysisSourceInfo { } case class StmtAnalysisSourceInfo(source: ast.Stmt) extends AnalysisSourceInfo { - val dependencyAnalysisInfo: Option[DependencyAnalysisInfo] = source.info.getUniqueInfo[DependencyAnalysisInfo] + override val dependencyAnalysisInfo: Option[DependencyAnalysisInfo] = source.info.getUniqueInfo[DependencyAnalysisInfo] override def toString: String = (if(dependencyAnalysisInfo.isDefined) dependencyAnalysisInfo.get.info else source.toString()).replaceAll("\n", "\t") + " (" + super.toString + ")" From 5e8eba72a82dff52855bc14ccb0a4d1fd6249cfd Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 7 Jan 2026 16:35:58 +0100 Subject: [PATCH 290/474] minor renaming --- .../DependencyAnalysisNode.scala | 4 +++ .../DependencyAnalysisUserTool.scala | 8 +++--- .../DependencyAnalyzer.scala | 6 ++-- .../DependencyGraphInterpreter.scala | 28 +++++++++---------- .../DependencyAnalysisTestFramework.scala | 2 +- src/test/scala/DependencyAnalysisTests.scala | 2 +- 6 files changed, 27 insertions(+), 23 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala index ecff07987..2b861ce6c 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala @@ -3,6 +3,7 @@ package viper.silicon.dependencyAnalysis import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType import viper.silicon.interfaces.state.Chunk import viper.silicon.state.terms.{False, Term, Var} +import viper.silver.ast.Position import viper.silver.dependencyAnalysis.AbstractDependencyAnalysisNode @@ -39,6 +40,9 @@ trait DependencyAnalysisNode extends AbstractDependencyAnalysisNode { val term: Term def getTerm: Term = term + def getUserLevelRepresentation: String = sourceInfo.getTopLevelSource.toString + def getSourceCodePosition: Position = sourceInfo.getTopLevelSource.getPosition + /* Some string representations, mainly used for debugging purposes. The strings represented to users are obtained via sourceInfo.toString and do not contain any low-level information diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala index 3508209a7..46fdde814 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala @@ -81,9 +81,9 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete private def handleGraphSizeQuery(interpreter: DependencyGraphInterpreter): Unit = { val allAssumptions = interpreter.getNonInternalAssumptionNodes.filter(n => !n.isInstanceOf[AxiomAssumptionNode]) - val assumptions = allAssumptions.groupBy(_.sourceInfo.getTopLevelSource.toString) + val assumptions = allAssumptions.groupBy(_.getUserLevelRepresentation) val assertions = interpreter.getNonInternalAssertionNodesPerSource - val nodes = interpreter.getNonInternalAssertionNodes.union(allAssumptions).groupBy(_.sourceInfo.getTopLevelSource.toString) + val nodes = interpreter.getNonInternalAssertionNodes.union(allAssumptions).groupBy(_.getUserLevelRepresentation) println(s"#Assumptions = ${assumptions.size}") println(s"#Assertions = ${assertions.size}") println(s"#Nodes = ${nodes.size}") @@ -143,7 +143,7 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete } protected def getSourceInfoString(nodes: Set[DependencyAnalysisNode]): String = { - nodes.groupBy(node => node.sourceInfo.getTopLevelSource.toString).map{case (_, nodes) => nodes.head.sourceInfo.getTopLevelSource}.toList.sortBy(_.getLineNumber).mkString("\n\t") + nodes.groupBy(node => node.getUserLevelRepresentation).map{case (_, nodes) => nodes.head.sourceInfo.getTopLevelSource}.toList.sortBy(_.getLineNumber).mkString("\n\t") } private def getQueriedNodesFromInput(inputs: Set[String]): Set[DependencyAnalysisNode] = { @@ -249,7 +249,7 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete val (allDependencies, time) = measureTime[Set[DependencyAnalysisNode]](fullGraphInterpreter.getAllNonInternalDependencies(queriedNodes.map(_.id))) allTimes = allTimes :+ time numLowLevelDeps = allDependencies.size - numDeps = allDependencies.groupBy(node => node.sourceInfo.getTopLevelSource.toString).size + numDeps = allDependencies.groupBy(node => node.getUserLevelRepresentation).size } writer.println(s"$userInput,$numLowLevelDeps,$numDeps,${allTimes.mkString(",")}") diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index fc9485d1f..bcb305572 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -183,7 +183,7 @@ object DependencyAnalyzer { val axiomAssertionNodes = joinCandidateNodes .filter(n => (n.isInstanceOf[GeneralAssertionNode] && AssumptionType.postconditionTypes.contains(n.assumptionType)) || AssumptionType.FunctionBody.equals(n.assumptionType)) - .groupBy(_.sourceInfo.getTopLevelSource.toString) + .groupBy(_.getUserLevelRepresentation) .view.mapValues(_.map(_.id)) .toMap joinCandidateNodes.filter(_.isInstanceOf[AxiomAssumptionNode]) @@ -204,7 +204,7 @@ object DependencyAnalyzer { .view.mapValues(_.map(_.id)) .toMap joinCandidateNodes.filter(node => node.isInstanceOf[GeneralAssertionNode] && AssumptionType.postconditionTypes.contains(node.assumptionType)) - .map(node => (node.id, relevantAssumptionNodes.getOrElse(node.sourceInfo.getTopLevelSource.toString, Seq.empty))) + .map(node => (node.id, relevantAssumptionNodes.getOrElse(node.getUserLevelRepresentation, Seq.empty))) .foreach { case (src, targets) => newGraph.addEdges(src, targets)} stopTimeMeasurementAndAddToTotal(startTime, timeForMethodJoin) @@ -420,7 +420,7 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { } val nodesBySource = assumptionGraph.getNodes.filter(!keepNode(_)) - .groupBy(n => (n.sourceInfo.getSourceForTransitiveEdges.toString, n.sourceInfo.getTopLevelSource.toString, n.sourceInfo.getFineGrainedSource.toString, n.assumptionType)) + .groupBy(n => (n.sourceInfo.getSourceForTransitiveEdges.toString, n.getUserLevelRepresentation, n.sourceInfo.getFineGrainedSource.toString, n.assumptionType)) nodesBySource foreach { case ((_, _, _, assumptionType), nodes) => val assumptionNodes = nodes.filter(_.isInstanceOf[GeneralAssumptionNode]) diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index b2cfa6e86..fbfa7bbf5 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -67,7 +67,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen } def filterOutNodesBySourceInfo(nodes: Set[DependencyAnalysisNode], excludeInfos: Set[AnalysisSourceInfo]): Set[DependencyAnalysisNode] = - nodes filterNot (node => excludeInfos.exists(i => i.getTopLevelSource.toString.equals(node.sourceInfo.getTopLevelSource.toString))) + nodes filterNot (node => excludeInfos.exists(i => i.getTopLevelSource.toString.equals(node.getUserLevelRepresentation))) def getNonInternalAssumptionNodes: Set[DependencyAnalysisNode] = getNodes filter (node => (node.isInstanceOf[GeneralAssumptionNode] && !AssumptionType.internalTypes.contains(node.assumptionType)) @@ -83,7 +83,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen .exists(node => dependencyGraph.existsAnyDependency(Set(node.id), nodesToAnalyze map (_.id) filter (_ != node.id), includeInfeasibilityNodes)) def getNonInternalAssumptionNodesPerSource: Map[String, Set[DependencyAnalysisNode]] = - getNonInternalAssumptionNodes.groupBy(_.sourceInfo.getTopLevelSource.toString) + getNonInternalAssumptionNodes.groupBy(_.getUserLevelRepresentation) def getNonInternalAssertionNodes: Set[DependencyAnalysisNode] = getNodes filter (node => @@ -91,7 +91,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen ) def getNonInternalAssertionNodesPerSource: Map[String, Set[DependencyAnalysisNode]] = - getNonInternalAssertionNodes.groupBy(_.sourceInfo.getTopLevelSource.toString) + getNonInternalAssertionNodes.groupBy(_.getUserLevelRepresentation) def getExplicitAssertionNodes: Set[DependencyAnalysisNode] = getNonInternalAssertionNodes.filter(node => AssumptionType.explicitAssertionTypes.contains(node.assumptionType)) @@ -105,8 +105,8 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen } private def getNodesWithIdenticalSource(nodes: Set[DependencyAnalysisNode]): Set[DependencyAnalysisNode] = { - val sourceInfos = nodes map (_.sourceInfo.getTopLevelSource.toString) - getNodes filter (node => sourceInfos.contains(node.sourceInfo.getTopLevelSource.toString)) + val sourceInfos = nodes map (_.getUserLevelRepresentation) + getNodes filter (node => sourceInfos.contains(node.getUserLevelRepresentation)) } def computeProofCoverage(): (Double, Set[String]) = { @@ -118,7 +118,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen val assertionNodeIds = assertionNodes map (_.id) val dependencies = dependencyGraph.getAllDependencies(assertionNodeIds, includeInfeasibilityNodes=true) val coveredNodes = dependencies ++ assertionNodeIds - val nodesPerSourceInfo = getNonInternalAssumptionNodes.filterNot(_.isInstanceOf[AxiomAssumptionNode]).groupBy(_.sourceInfo.getTopLevelSource.toString) + val nodesPerSourceInfo = getNonInternalAssumptionNodes.filterNot(_.isInstanceOf[AxiomAssumptionNode]).groupBy(_.getUserLevelRepresentation) if(nodesPerSourceInfo.isEmpty) return (Double.NaN, Set()) val uncoveredSources = (nodesPerSourceInfo filter { case (_, nodes) => @@ -220,25 +220,25 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen } def computeVerificationProgress(): (Double, Double, String) = { - val assumptionsPerSource = getNonInternalAssumptionNodes.groupBy(_.sourceInfo.getTopLevelSource.toString) + val assumptionsPerSource = getNonInternalAssumptionNodes.groupBy(_.getUserLevelRepresentation) if(assumptionsPerSource.isEmpty) return (Double.NaN, Double.NaN, "Error: no assumptions found") - val allExplicitAssertions = getExplicitAssertionNodes // TODO ake: only postconditions? - .groupBy(_.sourceInfo.getTopLevelSource.toString) + val allExplicitAssertions = getExplicitAssertionNodes.filterNot(_.assumptionType.equals(AssumptionType.ExplicitPostcondition)) // TODO ake: only postconditions? + .groupBy(_.getUserLevelRepresentation) val relevantDependenciesPerAssertion = allExplicitAssertions .view.mapValues(g => getAllNonInternalDependencies(getNodesWithIdenticalSource(g).map(_.id))) - val assertionsWithoutExplicitAssumptions = relevantDependenciesPerAssertion.filter { case (_, deps) => + val assertionsWithoutExplicitAssumptions = relevantDependenciesPerAssertion.filter { case (node, deps) => !deps.exists(dep => AssumptionType.explicitAssumptionTypes.contains(dep.assumptionType)) }.keys val relevantDependencies = relevantDependenciesPerAssertion.flatMap(_._2) - val implicitPostConds = getNonInternalAssertionNodes.filter(node => AssumptionType.ImplicitPostcondition.equals(node.assumptionType)).groupBy(_.sourceInfo.getTopLevelSource.toString).keys.toSet - val coveredExplicitSources = relevantDependencies.filter(node => AssumptionType.explicitAssumptionTypes.contains(node.assumptionType)).groupBy(_.sourceInfo.getTopLevelSource.toString).keys.toSet // TODO ake: other assumption types? - val coveredImplicitSources = relevantDependencies.groupBy(_.sourceInfo.getTopLevelSource.toString).keys.toSet.diff(coveredExplicitSources).diff(implicitPostConds) - val uncoveredExplicitSources = getExplicitAssumptionNodes.groupBy(_.sourceInfo.getTopLevelSource.toString).keys.toSet.diff(relevantDependencies.groupBy(_.sourceInfo.getTopLevelSource.toString).keys.toSet) + val implicitPostConds = getNonInternalAssertionNodes.filter(node => AssumptionType.ImplicitPostcondition.equals(node.assumptionType)).groupBy(_.getUserLevelRepresentation).keys.toSet + val coveredExplicitSources = relevantDependencies.filter(node => AssumptionType.explicitAssumptionTypes.contains(node.assumptionType)).groupBy(_.getUserLevelRepresentation).keys.toSet // TODO ake: other assumption types? + val coveredImplicitSources = relevantDependencies.groupBy(_.getUserLevelRepresentation).keys.toSet.diff(coveredExplicitSources).diff(implicitPostConds) + val uncoveredExplicitSources = getExplicitAssumptionNodes.groupBy(_.getUserLevelRepresentation).keys.toSet.diff(relevantDependencies.groupBy(_.getUserLevelRepresentation).keys.toSet) val uncoveredImplicitSources = assumptionsPerSource.keys.toSet.diff(coveredImplicitSources).diff(coveredExplicitSources).diff(uncoveredExplicitSources).diff(implicitPostConds) val relevantAssumptions = assumptionsPerSource.keys.toSet.diff(implicitPostConds) diff --git a/src/test/scala/DependencyAnalysisTestFramework.scala b/src/test/scala/DependencyAnalysisTestFramework.scala index c814c1c34..fa6457f50 100644 --- a/src/test/scala/DependencyAnalysisTestFramework.scala +++ b/src/test/scala/DependencyAnalysisTestFramework.scala @@ -81,7 +81,7 @@ trait DependencyAnalysisTestFramework { case class PruningTest(fileName: String, program: Program, fullGraphInterpreter: DependencyGraphInterpreter) { def execute(): Unit = { - val triggerNodeLines = fullGraphInterpreter.getNodes.filter(node => node.sourceInfo.getTopLevelSource.toString.contains("@trigger()")).flatMap(_.sourceInfo.getLineNumber) + val triggerNodeLines = fullGraphInterpreter.getNodes.filter(node => node.getUserLevelRepresentation.contains("@trigger()")).flatMap(_.sourceInfo.getLineNumber) var id: Int = 0 // TODO ake: safer would be to work with position string instead of line numbers fullGraphInterpreter.getExplicitAssertionNodes flatMap (_.sourceInfo.getLineNumber) foreach {line => diff --git a/src/test/scala/DependencyAnalysisTests.scala b/src/test/scala/DependencyAnalysisTests.scala index 11c15b1b1..2a4307ca6 100644 --- a/src/test/scala/DependencyAnalysisTests.scala +++ b/src/test/scala/DependencyAnalysisTests.scala @@ -106,7 +106,7 @@ class DependencyAnalysisTests extends AnyFunSuite with DependencyAnalysisTestFra proofCoverageWriter.println(filePrefix + "/" + fileName) dependencyGraphInterpreters foreach (memberInterpreter => { - memberInterpreter.getExplicitAssertionNodes.groupBy(_.sourceInfo.getTopLevelSource.toString) foreach {case (source, nodes) => + memberInterpreter.getExplicitAssertionNodes.groupBy(_.getUserLevelRepresentation) foreach {case (source, nodes) => proofCoverageWriter.println(memberInterpreter.getName + " " + source.replace("\n", " ") + " ---> " + memberInterpreter.computeProofCoverage(nodes)._1)} proofCoverageWriter.println("overall " + memberInterpreter.getName + " ---> + " + memberInterpreter.computeProofCoverage()._1) }) From e4ec91e77c43641835438f33a11c1cab123a0aa0 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 9 Jan 2026 16:27:07 +0100 Subject: [PATCH 291/474] add some examples --- .../showcase/abstractInc.vpr | 27 +++++++ .../showcase/branches.vpr | 77 +++++++++++++++++++ .../showcase/gaussian.vpr | 17 ++++ .../incrRand/incrRand-8a.vpr | 24 ++++++ .../incrRand/incrRand-8b.vpr | 25 ++++++ .../incrRand/incrRand-8c.vpr | 25 ++++++ 6 files changed, 195 insertions(+) create mode 100644 src/test/resources/dependencyAnalysisTests/showcase/abstractInc.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/showcase/branches.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/showcase/gaussian.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-8a.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-8b.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-8c.vpr diff --git a/src/test/resources/dependencyAnalysisTests/showcase/abstractInc.vpr b/src/test/resources/dependencyAnalysisTests/showcase/abstractInc.vpr new file mode 100644 index 000000000..31f76d20d --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/showcase/abstractInc.vpr @@ -0,0 +1,27 @@ +field f: Int + +method inc(x: Ref) returns (res:Int) + requires acc(x.f, 1/2) + ensures acc(x.f, 1/2) + ensures res == x.f + 1 + ensures res > 0 + +method client1() +{ + var res: Int + var x: Ref := new(f) + + res := inc(x) + + assert res > 0 +} + +method client2() +{ + var res: Int + var x: Ref := new(f) + + res := inc(x) + + assert res == x.f + 1 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/showcase/branches.vpr b/src/test/resources/dependencyAnalysisTests/showcase/branches.vpr new file mode 100644 index 000000000..d52bfea9e --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/showcase/branches.vpr @@ -0,0 +1,77 @@ +method branches1(a: Int, b: Int) +{ + var n:Int, c: Bool + + assume a > 0 + assume b > 4 + + assume c ==> a < 100 + + if(c){ + n := a + 1 + }else{ + n := b + 1 + } + + assert n > 1 +} + +method branches2(a: Int, b: Int) +{ + var n:Int, c: Bool + + assume a > 0 + assume a < 100 + assume a < b + assume b < 50 + + var x: Int + if(a >= n){ + x := a + b + }else{ + x := n + 1 + assert x > 1 + } +} + +method branches3(a: Int, b: Int) +{ + var n:Int, c: Bool + + assume b > 0 + + var x: Int + if(a >= n){ + x := a + b + }else{ + x := n + 1 + } + + assert x > n +} + + +method nestedBranches1(a: Int, b: Int) +{ + var n:Int, c: Bool + + assume a > 0 + assume a < 100 + assume b > 0 + assume b < 50 + assume c ==> a > 5 + + if(c){ + if(a > b){ + n := a - b + }else{ + n := a + b + } + n := n - 1 + }else{ + n := a + b + } + + assert n <= a + b + assert c ==> n < a + b +} diff --git a/src/test/resources/dependencyAnalysisTests/showcase/gaussian.vpr b/src/test/resources/dependencyAnalysisTests/showcase/gaussian.vpr new file mode 100644 index 000000000..51eb1941a --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/showcase/gaussian.vpr @@ -0,0 +1,17 @@ + +method gaussianSimple(n: Int) returns (res: Int) + requires 0 <= n + requires n <= 5 + ensures res == n * (n + 1) / 2 +{ + res := 0 + var i: Int := 0 + while(i <= n) + invariant i <= (n + 1) + invariant i <= 6 + invariant res == (i - 1) * i / 2 + { + res := res + i + i := i + 1 + } +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-8a.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-8a.vpr new file mode 100644 index 000000000..1324d817f --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-8a.vpr @@ -0,0 +1,24 @@ +// Verification Progress: 1.0 (A), 1.0 (P) + +method rand() returns (res: Int) + ensures res > 0 +{ + res := 10 +} + + +method incrRand(a: Int) returns (res: Int) + requires a >= 0 + ensures res > a + ensures res > 0 + ensures res >= 0 + ensures res >= a + ensures res >= a-1 +{ + var r: Int := rand() + res := a + r + + var x: Int + x := 0 + assert x >= 0 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-8b.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-8b.vpr new file mode 100644 index 000000000..680dc146f --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-8b.vpr @@ -0,0 +1,25 @@ +// Verification Progress: 0.83 (A), 0.83 (P) + +method rand() returns (res: Int) + ensures res > 0 +{ + res := 10 +} + + +method incrRand(a: Int) returns (res: Int) + requires a >= 0 + ensures res > a + ensures res > 0 + ensures res >= 0 + ensures res >= a + ensures res >= a-1 +{ + var r: Int := rand() + res := a + r + + var x: Int + x := 1 + x := 0 + assert x >= 0 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-8c.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-8c.vpr new file mode 100644 index 000000000..b3f0c1ef5 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-8c.vpr @@ -0,0 +1,25 @@ +// Verification Progress: 0.83 (A), 0.86 (P) + +method rand() returns (res: Int) + ensures res > 0 +{ + res := 10 +} + + +method incrRand(a: Int) returns (res: Int) + requires a >= 0 + ensures res > a + ensures res > 0 + ensures res >= 0 + ensures res >= a + ensures res >= a-1 +{ + var r: Int := rand() + res := a + r + + var x: Int, y: Int + assume y == 0 + x := y + assert x >= 0 +} \ No newline at end of file From ca64da4ef014bb53e81ddee5928cc57b766d0658 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 9 Jan 2026 16:51:47 +0100 Subject: [PATCH 292/474] cleanup of how passing dependency results to Gobra --- src/main/scala/Silicon.scala | 5 +- .../DependencyAnalysisResult.scala | 14 ++++ .../DependencyAnalyzer.scala | 6 +- .../scala/verifier/DefaultMainVerifier.scala | 68 ++++++++++--------- 4 files changed, 58 insertions(+), 35 deletions(-) create mode 100644 src/main/scala/dependencyAnalysis/DependencyAnalysisResult.scala diff --git a/src/main/scala/Silicon.scala b/src/main/scala/Silicon.scala index b9285840a..f91f2158a 100644 --- a/src/main/scala/Silicon.scala +++ b/src/main/scala/Silicon.scala @@ -23,8 +23,9 @@ import viper.silicon.reporting.{MultiRunRecorders, condenseToViperResult} import viper.silicon.verifier.DefaultMainVerifier import viper.silicon.decider.{Cvc5ProverStdIO, Z3ProverStdIO} import viper.silver.cfg.silver.SilverCfg +import viper.silver.dependencyAnalysis.AbstractDependencyAnalysisResult import viper.silver.logger.ViperStdOutLogger -import viper.silver.utility.{FileProgramSubmitter} +import viper.silver.utility.FileProgramSubmitter import scala.util.chaining._ @@ -314,6 +315,8 @@ class Silicon(val reporter: Reporter, private var debugInfo: Seq[(String, Any)] logger.setLevel(Level.toLevel(loggerLevelString)) } } + + override def getDependencyAnalysisResult: Option[AbstractDependencyAnalysisResult] = verifier.dependencyAnalysisResult } class SiliconFrontend(override val reporter: Reporter, diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisResult.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisResult.scala new file mode 100644 index 000000000..d25ec3870 --- /dev/null +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisResult.scala @@ -0,0 +1,14 @@ +package viper.silicon.dependencyAnalysis + +import viper.silver.ast.Program +import viper.silver.dependencyAnalysis.AbstractDependencyAnalysisResult + +case class DependencyAnalysisResult(programName: String, program: Program, dependencyGraphInterpreters: Iterable[DependencyGraphInterpreter]) + extends AbstractDependencyAnalysisResult(programName, program, dependencyGraphInterpreters){ + + protected lazy val fullDependencyGraphInterpreter: DependencyGraphInterpreter = + DependencyAnalyzer.joinGraphsAndGetInterpreter(programName, dependencyGraphInterpreters) + + override def getFullDependencyGraphInterpreter: DependencyGraphInterpreter = fullDependencyGraphInterpreter + +} diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index bcb305572..761709b43 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -1,7 +1,7 @@ package viper.silicon.dependencyAnalysis import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType -import viper.silicon.dependencyAnalysis.DependencyAnalyzer.{runtimeOverheadPermissionNodes, startTimeMeasurement, stopTimeMeasurementAndAddToTotal, timeForFunctionJoin, timeToProcessUnsatCore} +import viper.silicon.dependencyAnalysis.DependencyAnalyzer.{runtimeOverheadPermissionNodes, startTimeMeasurement, stopTimeMeasurementAndAddToTotal, timeToProcessUnsatCore} import viper.silicon.interfaces.state.{Chunk, GeneralChunk} import viper.silicon.state.terms._ import viper.silicon.verifier.Verifier @@ -164,7 +164,7 @@ object DependencyAnalyzer { * The new graph is built by adding all existing nodes and edges of all input graphs and joining them via postconditions * of functions and methods. */ - def joinGraphsAndGetInterpreter(name: Option[String], dependencyGraphInterpreters: Set[DependencyGraphInterpreter]): DependencyGraphInterpreter = { + def joinGraphsAndGetInterpreter(name: String, dependencyGraphInterpreters: Iterable[DependencyGraphInterpreter]): DependencyGraphInterpreter = { var startTime = startTimeMeasurement() val newGraph = new DependencyGraph @@ -209,7 +209,7 @@ object DependencyAnalyzer { stopTimeMeasurementAndAddToTotal(startTime, timeForMethodJoin) - val newInterpreter = new DependencyGraphInterpreter(name.getOrElse("joined"), newGraph, dependencyGraphInterpreters.toList.flatMap(_.getErrors)) + val newInterpreter = new DependencyGraphInterpreter(name, newGraph, dependencyGraphInterpreters.toList.flatMap(_.getErrors)) newInterpreter } } diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index 8bdcd04be..422e7d5c2 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -11,7 +11,7 @@ import viper.silicon._ import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.SiliconDebugger import viper.silicon.decider.SMTLib2PreambleReader -import viper.silicon.dependencyAnalysis.{DependencyAnalysisReporter, DependencyAnalysisUserTool, DependencyAnalyzer} +import viper.silicon.dependencyAnalysis._ import viper.silicon.extensions.ConditionalPermissionRewriter import viper.silicon.interfaces._ import viper.silicon.interfaces.decider.ProverLike @@ -30,7 +30,6 @@ import viper.silver.cfg.silver.SilverCfg import viper.silver.components.StatefulComponent import viper.silver.frontend.FrontendStateCache import viper.silver.reporter._ -import viper.silver.verifier.errors.DependencyAnalysisFakeError import viper.silver.verifier.VerifierWarning import java.text.SimpleDateFormat @@ -95,6 +94,8 @@ class DefaultMainVerifier(config: Config, MultiRunRecorders /* In lieu of a better place, include MultiRunRecorders singleton here */ ) + var dependencyAnalysisResult: Option[DependencyAnalysisResult] = None + /* Lifetime */ override def start(): Unit = { @@ -311,39 +312,12 @@ class DefaultMainVerifier(config: Config, } reporter report VerificationTerminationMessage() - var verificationResults = (functionVerificationResults + val verificationResults = (functionVerificationResults ++ predicateVerificationResults ++ methodVerificationResults) DependencyAnalyzer.stopTimeMeasurementAndAddToTotal(verificationStartTime, DependencyAnalyzer.timeToVerifyAndCollectDependencies) - val postProcessingStartTime = DependencyAnalyzer.startTimeMeasurement() - - if(Verifier.config.enableDependencyAnalysis()){ - val dependencyGraphInterpreters = verificationResults.filter(_.dependencyGraphInterpreter.isDefined).map(_.dependencyGraphInterpreter.get) - val verificationErrors: List[Failure] = (verificationResults filter (_.isInstanceOf[Failure])) map (_.asInstanceOf[Failure]) - - dependencyGraphInterpreters foreach (_.exportGraph()) - - val joinedGraphInterpreter = DependencyAnalyzer.joinGraphsAndGetInterpreter(inputFile.map(_.replaceAll("\\\\", "_").replaceAll("/", "_").replaceAll(".vpr", "")), dependencyGraphInterpreters.toSet) - if(Verifier.config.dependencyAnalysisExportPath.isDefined) - joinedGraphInterpreter.exportGraph() - - if(Verifier.config.startDependencyAnalysisTool()){ - val commandLineTool = new DependencyAnalysisUserTool(joinedGraphInterpreter, dependencyGraphInterpreters, originalProgram, verificationErrors) - commandLineTool.run() - } - - reporter match { - case analysisReporter: DependencyAnalysisReporter => - analysisReporter.dependencyGraphInterpretersPerMember = dependencyGraphInterpreters - analysisReporter.joinedDependencyGraphInterpreter = Some(joinedGraphInterpreter) - case _ => - } - - verificationResults = Failure(DependencyAnalysisFakeError(joinedGraphInterpreter)) +: verificationResults - } - - DependencyAnalyzer.stopTimeMeasurementAndAddToTotal(postProcessingStartTime, DependencyAnalyzer.timeOfPostprocessing) + runDependencyAnalysisWorkflow(verificationResults, program, inputFile) DependencyAnalyzer.stopTimeMeasurementAndAddToTotal(verificationStartTime, DependencyAnalyzer.timeToVerifyAndBuildFinalGraph) DependencyAnalyzer.printProfilingResults() @@ -677,4 +651,36 @@ class DefaultMainVerifier(config: Config, */ private def extractAllVerificationResults(res: VerificationResult): Seq[VerificationResult] = res :: res.previous.toList + + + def runDependencyAnalysisWorkflow(verificationResults: List[VerificationResult], program: ast.Program, inputFile: Option[String]): Unit = { + if(!Verifier.config.enableDependencyAnalysis()) return + + val postProcessingStartTime = DependencyAnalyzer.startTimeMeasurement() + + val dependencyGraphInterpreters = verificationResults.filter(_.dependencyGraphInterpreter.isDefined).map(_.dependencyGraphInterpreter.get) + val verificationErrors: List[Failure] = (verificationResults filter (_.isInstanceOf[Failure])) map (_.asInstanceOf[Failure]) + + val result = DependencyAnalysisResult(inputFile.map(_.replaceAll("\\\\", "_").replaceAll("/", "_").replaceAll(".vpr", "")).getOrElse("joined"), program, dependencyGraphInterpreters) + dependencyAnalysisResult = Some(result) + + if (Verifier.config.dependencyAnalysisExportPath.isDefined) { + result.dependencyGraphInterpreters foreach (_.exportGraph()) + result.getFullDependencyGraphInterpreter.exportGraph() + } + + if (Verifier.config.startDependencyAnalysisTool()) { + val commandLineTool = new DependencyAnalysisUserTool(result.getFullDependencyGraphInterpreter, dependencyGraphInterpreters, program, verificationErrors) + commandLineTool.run() + } + + reporter match { + case analysisReporter: DependencyAnalysisReporter => + analysisReporter.dependencyGraphInterpretersPerMember = dependencyGraphInterpreters + analysisReporter.joinedDependencyGraphInterpreter = Some(result.getFullDependencyGraphInterpreter) + case _ => + } + + DependencyAnalyzer.stopTimeMeasurementAndAddToTotal(postProcessingStartTime, DependencyAnalyzer.timeOfPostprocessing) + } } From f8ea7f8abfd53a11120de83ee8be5cc3cf58eaea Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 9 Jan 2026 18:06:41 +0100 Subject: [PATCH 293/474] implement progress metric --- .../dependencyAnalysis/AnalysisInfo.scala | 4 +- .../DependencyGraphInterpreter.scala | 76 ++++++++++++------- 2 files changed, 53 insertions(+), 27 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala index eac1d2b88..f4c13137d 100644 --- a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala +++ b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala @@ -2,7 +2,7 @@ package viper.silicon.dependencyAnalysis object AssumptionType extends Enumeration { type AssumptionType = Value - val Explicit, LoopInvariant, PathCondition, Rewrite, Implicit, Internal, Trigger, ExplicitPostcondition, ImplicitPostcondition, CallPostcondition, FunctionBody, Precondition = Value + val Explicit, LoopInvariant, PathCondition, Rewrite, Implicit /* TODO ake: rename to Stmt? */, Internal, Trigger, ExplicitPostcondition, ImplicitPostcondition, CallPostcondition, FunctionBody, Precondition = Value def fromString(s: String): Option[Value] = values.find(_.toString == s) @@ -12,6 +12,8 @@ object AssumptionType extends Enumeration { def internalTypes: Set[AssumptionType] = Set(Internal) // will always be hidden from user def implicitTypes: Set[AssumptionType] = AssumptionType.values.diff(explicitAssumptionTypes).diff(internalTypes) def joinConditionTypes: Set[AssumptionType] = postconditionTypes ++ Set(FunctionBody) + def verificationAnnotationTypes: Set[AssumptionType] = Set(LoopInvariant, Rewrite, ExplicitPostcondition, ImplicitPostcondition, Precondition, Explicit) + def sourceCodeStatementTypes: Set[AssumptionType] = Set(PathCondition, Implicit, CallPostcondition, FunctionBody) } import viper.silicon.dependencyAnalysis.AssumptionType._ diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index fbfa7bbf5..9366998f8 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -223,45 +223,69 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen val assumptionsPerSource = getNonInternalAssumptionNodes.groupBy(_.getUserLevelRepresentation) if(assumptionsPerSource.isEmpty) return (Double.NaN, Double.NaN, "Error: no assumptions found") - val allExplicitAssertions = getExplicitAssertionNodes.filterNot(_.assumptionType.equals(AssumptionType.ExplicitPostcondition)) // TODO ake: only postconditions? - .groupBy(_.getUserLevelRepresentation) + val allAssertions = getNonInternalAssertionNodes.groupBy(_.getUserLevelRepresentation) - val relevantDependenciesPerAssertion = allExplicitAssertions + val relevantDependenciesPerAssertion = allAssertions .view.mapValues(g => getAllNonInternalDependencies(getNodesWithIdenticalSource(g).map(_.id))) - val assertionsWithoutExplicitAssumptions = relevantDependenciesPerAssertion.filter { case (node, deps) => + // TODO ake: make sure to handle failing assertions properly! + val nonTrivialAssertionsGroupedPerAssertion = relevantDependenciesPerAssertion.filter{case (_, assumptions) => assumptions.nonEmpty} + + val assertionsWithoutExplicitAssumptions = nonTrivialAssertionsGroupedPerAssertion.filter { case (_, deps) => !deps.exists(dep => AssumptionType.explicitAssumptionTypes.contains(dep.assumptionType)) }.keys + val relevantDependencies = relevantDependenciesPerAssertion.flatMap(_._2) - val implicitPostConds = getNonInternalAssertionNodes.filter(node => AssumptionType.ImplicitPostcondition.equals(node.assumptionType)).groupBy(_.getUserLevelRepresentation).keys.toSet - val coveredExplicitSources = relevantDependencies.filter(node => AssumptionType.explicitAssumptionTypes.contains(node.assumptionType)).groupBy(_.getUserLevelRepresentation).keys.toSet // TODO ake: other assumption types? - val coveredImplicitSources = relevantDependencies.groupBy(_.getUserLevelRepresentation).keys.toSet.diff(coveredExplicitSources).diff(implicitPostConds) - val uncoveredExplicitSources = getExplicitAssumptionNodes.groupBy(_.getUserLevelRepresentation).keys.toSet.diff(relevantDependencies.groupBy(_.getUserLevelRepresentation).keys.toSet) - val uncoveredImplicitSources = assumptionsPerSource.keys.toSet.diff(coveredImplicitSources).diff(coveredExplicitSources).diff(uncoveredExplicitSources).diff(implicitPostConds) - val relevantAssumptions = assumptionsPerSource.keys.toSet.diff(implicitPostConds) + def filterAndGroup(nodes: Iterable[DependencyAnalysisNode], filterCondition: DependencyAnalysisNode => Boolean) = { + nodes.filter(filterCondition).groupBy(_.getUserLevelRepresentation).keys.toSet + } + + val coveredExplicitSources = filterAndGroup(relevantDependencies, node => AssumptionType.explicitAssumptionTypes.contains(node.assumptionType)) + val coveredVerificationAnnotations = filterAndGroup(relevantDependencies, node => AssumptionType.verificationAnnotationTypes.contains(node.assumptionType)).diff(coveredExplicitSources) + val coveredSourceCodeStmts = filterAndGroup(relevantDependencies, _ => true).diff(coveredExplicitSources).diff(coveredVerificationAnnotations) + + def getUncoveredNodesAndGroup(nodes: Iterable[DependencyAnalysisNode]) = { + nodes.groupBy(_.getUserLevelRepresentation).keys.toSet.diff(relevantDependencies.groupBy(_.getUserLevelRepresentation).keys.toSet) + } + + val uncoveredExplicitSources = getUncoveredNodesAndGroup(getExplicitAssumptionNodes) + val uncoveredVerificationAnnotations = getUncoveredNodesAndGroup(getNonInternalAssumptionNodes.filter(node => AssumptionType.verificationAnnotationTypes.contains(node.assumptionType))).diff(uncoveredExplicitSources) + val uncoveredSourceCodeStmts = getUncoveredNodesAndGroup(getNonInternalAssumptionNodes).diff(uncoveredExplicitSources).diff(uncoveredVerificationAnnotations) - val verificationProgressAndrea = coveredImplicitSources.size.toDouble / (relevantAssumptions.size.toDouble + errors.size) + // Peter's metric + val specQuality = coveredSourceCodeStmts.size.toDouble / (coveredSourceCodeStmts.size.toDouble + uncoveredSourceCodeStmts.size.toDouble) + val proofQualityPeter = (assertionsWithoutExplicitAssumptions.size.toDouble - errors.size.toDouble) / nonTrivialAssertionsGroupedPerAssertion.size.toDouble + val verificationProgressPeter = specQuality * proofQualityPeter - val specQuality = coveredImplicitSources.size.toDouble / (coveredImplicitSources.size.toDouble + uncoveredImplicitSources.size.toDouble) - val proofQuality = (assertionsWithoutExplicitAssumptions.size.toDouble - errors.size.toDouble) / allExplicitAssertions.size.toDouble - val verificationProgressPeter = specQuality * proofQuality + // Lea's metric + val proofQualityNumerator = nonTrivialAssertionsGroupedPerAssertion.map{case (_, assumptions) => filterAndGroup(assumptions, node => !AssumptionType.explicitAssumptionTypes.contains(node.assumptionType)).size.toDouble / assumptions.groupBy(_.getUserLevelRepresentation).keys.size.toDouble}.sum + val proofQualityLea = proofQualityNumerator / nonTrivialAssertionsGroupedPerAssertion.keys.size.toDouble + val verificationProgressLea = specQuality * proofQualityLea val info = { - s"All Assumptions and Statements:\n\t${relevantAssumptions.mkString("\n\t")}" + "\n" + - s"Uncovered Explicit Assumptions:\n\t${uncoveredExplicitSources.mkString("\n\t")}" + "\n" + - s"Covered Explicit Assumptions:\n\t${coveredExplicitSources.mkString("\n\t")}" + "\n" + - s"Uncovered Statements:\n\t${uncoveredImplicitSources.mkString("\n\t")}" + "\n" + - s"Covered Statements:\n\t${coveredImplicitSources.mkString("\n\t")}" + "\n" + - s"Postconditions of methods/functions with bodies:\n\t${implicitPostConds.mkString("\n\t")}" + "\n" + - s"Explicit Assertions:\n\t${allExplicitAssertions.keys.mkString("\n\t")}" + "\n\n" + - s"Explicit Assertions verified without explicit assumptions:\n\t${assertionsWithoutExplicitAssumptions.mkString("\n\t")}" + "\n\n" + + s"Covered\n" + + s"\tExplicit Assumptions:\n\t\t${coveredExplicitSources.mkString("\n\t\t")}" + "\n" + + s"\tVerification Annotations:\n\t\t${coveredVerificationAnnotations.mkString("\n\t\t")}" + "\n" + + s"\tSource Code:\n\t\t${coveredSourceCodeStmts.mkString("\n\t\t")}" + "\n" + + "\n" + + s"Uncovered\n" + + s"\tExplicit Assumptions:\n\t\t${uncoveredExplicitSources.mkString("\n\t\t")}" + "\n" + + s"\tVerification Annotations:\n\t\t${uncoveredVerificationAnnotations.mkString("\n\t\t")}" + "\n" + + s"\tSource Code:\n\t\t${uncoveredSourceCodeStmts.mkString("\n\t\t")}" + "\n" + + "\n" + + s"Fully verified assertions:\n\t${assertionsWithoutExplicitAssumptions.mkString("\n\t")}" + "\n\n" + + s"Assertions depending on explicit assumptions:\n\t${nonTrivialAssertionsGroupedPerAssertion.keySet.diff(assertionsWithoutExplicitAssumptions.toSet).mkString("\n\t")}" + "\n\n" + + s"Failing assertions:\n\tTODO" + "\n\n" + // TODO ake + "\n" + s"#Verification Errors: ${errors.size}" + "\n\n" + - s"Verification Progress (Andrea):\n\t${coveredImplicitSources.size}/(${relevantAssumptions.size}+${errors.size}) = $verificationProgressAndrea" + "\n\n" + - s"Verification Progress (Peter):\n\t${coveredImplicitSources.size}/(${coveredImplicitSources.size + uncoveredImplicitSources.size}) * " + - s"${assertionsWithoutExplicitAssumptions.size - errors.size.toDouble}/${allExplicitAssertions.size} = $verificationProgressPeter" + "\n" + "\n" + + s"Verification Progress (Peter):\n\t${coveredSourceCodeStmts.size}/(${coveredSourceCodeStmts.size + uncoveredSourceCodeStmts.size}) * " + + s"${assertionsWithoutExplicitAssumptions.size - errors.size.toDouble}/${nonTrivialAssertionsGroupedPerAssertion.size} = $verificationProgressPeter" + "\n" + + s"Verification Progress (Lea):\n\t${coveredSourceCodeStmts.size}/(${coveredSourceCodeStmts.size + uncoveredSourceCodeStmts.size}) * " + + s"$proofQualityNumerator/${nonTrivialAssertionsGroupedPerAssertion.keys.size} = $verificationProgressLea" + "\n" } - (verificationProgressAndrea, verificationProgressPeter, info) + (verificationProgressPeter, verificationProgressLea, info) } } From 14c87f94ae15fe21aff64092e4897ff880476f25 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 12 Jan 2026 10:56:24 +0100 Subject: [PATCH 294/474] fix lifting from low level to user level graph --- .../AnalysisSourceInfo.scala | 8 ++ .../DependencyAnalysisUserTool.scala | 11 +- .../DependencyAnalyzer.scala | 18 +-- .../DependencyGraphInterpreter.scala | 119 +++++++++--------- .../UserLevelDependencyAnalysisNode.scala | 68 ++++++++++ src/test/scala/DependencyAnalysisTests.scala | 5 +- 6 files changed, 151 insertions(+), 78 deletions(-) create mode 100644 src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala diff --git a/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala b/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala index cf6a4cb78..a13fc28a1 100644 --- a/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala +++ b/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala @@ -47,6 +47,14 @@ abstract class AnalysisSourceInfo { def isAnalysisEnabled: Boolean = true val dependencyAnalysisInfo: Option[DependencyAnalysisInfo] = None + + override def equals(obj: Any): Boolean = obj match { + case other: AnalysisSourceInfo => this.getPosition.equals(other.getPosition) && this.toString.equals(other.toString) + case _ => false + } + + override def hashCode(): Int = + (this.toString + this.getPosition.toString).hashCode } case class NoAnalysisSourceInfo() extends AnalysisSourceInfo { diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala index 46fdde814..ee58843f8 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala @@ -1,5 +1,6 @@ package viper.silicon.dependencyAnalysis +import dependencyAnalysis.UserLevelDependencyAnalysisNode import viper.silicon.interfaces.Failure import viper.silver.ast import viper.silver.ast.Method @@ -81,9 +82,9 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete private def handleGraphSizeQuery(interpreter: DependencyGraphInterpreter): Unit = { val allAssumptions = interpreter.getNonInternalAssumptionNodes.filter(n => !n.isInstanceOf[AxiomAssumptionNode]) - val assumptions = allAssumptions.groupBy(_.getUserLevelRepresentation) - val assertions = interpreter.getNonInternalAssertionNodesPerSource - val nodes = interpreter.getNonInternalAssertionNodes.union(allAssumptions).groupBy(_.getUserLevelRepresentation) + val assumptions = UserLevelDependencyAnalysisNode.from(allAssumptions) + val assertions = UserLevelDependencyAnalysisNode.from(interpreter.getNonInternalAssertionNodes) + val nodes = UserLevelDependencyAnalysisNode.from(interpreter.getNonInternalAssertionNodes.union(allAssumptions)) println(s"#Assumptions = ${assumptions.size}") println(s"#Assertions = ${assertions.size}") println(s"#Nodes = ${nodes.size}") @@ -143,7 +144,7 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete } protected def getSourceInfoString(nodes: Set[DependencyAnalysisNode]): String = { - nodes.groupBy(node => node.getUserLevelRepresentation).map{case (_, nodes) => nodes.head.sourceInfo.getTopLevelSource}.toList.sortBy(_.getLineNumber).mkString("\n\t") + UserLevelDependencyAnalysisNode.mkUserLevelString(nodes, "\n\t") } private def getQueriedNodesFromInput(inputs: Set[String]): Set[DependencyAnalysisNode] = { @@ -249,7 +250,7 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete val (allDependencies, time) = measureTime[Set[DependencyAnalysisNode]](fullGraphInterpreter.getAllNonInternalDependencies(queriedNodes.map(_.id))) allTimes = allTimes :+ time numLowLevelDeps = allDependencies.size - numDeps = allDependencies.groupBy(node => node.getUserLevelRepresentation).size + numDeps = UserLevelDependencyAnalysisNode.from(allDependencies).size } writer.println(s"$userInput,$numLowLevelDeps,$numDeps,${allTimes.mkString(",")}") diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index 761709b43..d4e7784fc 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -200,11 +200,11 @@ object DependencyAnalyzer { // hence, we add edges from assertions of method postconditions to assumptions of the same postcondition (at method calls) val relevantAssumptionNodes = joinCandidateNodes .filter(node => node.isInstanceOf[GeneralAssumptionNode] && AssumptionType.postconditionTypes.contains(node.assumptionType)) - .groupBy(_.sourceInfo.getFineGrainedSource.toString) + .groupBy(_.sourceInfo.getFineGrainedSource) .view.mapValues(_.map(_.id)) .toMap joinCandidateNodes.filter(node => node.isInstanceOf[GeneralAssertionNode] && AssumptionType.postconditionTypes.contains(node.assumptionType)) - .map(node => (node.id, relevantAssumptionNodes.getOrElse(node.getUserLevelRepresentation, Seq.empty))) + .map(node => (node.id, relevantAssumptionNodes.getOrElse(node.sourceInfo.getTopLevelSource, Seq.empty))) .foreach { case (src, targets) => newGraph.addEdges(src, targets)} stopTimeMeasurementAndAddToTotal(startTime, timeForMethodJoin) @@ -420,29 +420,29 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { } val nodesBySource = assumptionGraph.getNodes.filter(!keepNode(_)) - .groupBy(n => (n.sourceInfo.getSourceForTransitiveEdges.toString, n.getUserLevelRepresentation, n.sourceInfo.getFineGrainedSource.toString, n.assumptionType)) + .groupBy(n => (n.sourceInfo.getSourceForTransitiveEdges.toString, n.sourceInfo, n.sourceInfo.getFineGrainedSource.toString, n.assumptionType)) - nodesBySource foreach { case ((_, _, _, assumptionType), nodes) => + nodesBySource foreach { case ((_, sourceInfo, _, assumptionType), nodes) => val assumptionNodes = nodes.filter(_.isInstanceOf[GeneralAssumptionNode]) if (assumptionNodes.nonEmpty) { - val newNode = SimpleAssumptionNode(True, None, assumptionNodes.head.sourceInfo, assumptionType, isClosed = false) + val newNode = SimpleAssumptionNode(True, None, sourceInfo, assumptionType, isClosed = false) assumptionNodes foreach (n => nodeMap.put(n.id, newNode.id)) mergedGraph.addNode(newNode) } } - nodesBySource foreach { case ((_, _, _, assumptionType), nodes) => + nodesBySource foreach { case ((_, sourceInfo, _, assumptionType), nodes) => val assertionNodes = nodes.filter(_.isInstanceOf[GeneralAssertionNode]) if (assertionNodes.nonEmpty) { - val newNode = SimpleAssertionNode(True, assumptionType, assertionNodes.head.sourceInfo, isClosed = false) + val newNode = SimpleAssertionNode(True, assumptionType, sourceInfo, isClosed = false) assertionNodes foreach (n => nodeMap.put(n.id, newNode.id)) mergedGraph.addNode(newNode) } } assumptionGraph.getAllEdges foreach { case (target, deps) => - val newTarget = nodeMap(target) - mergedGraph.addEdges(deps.map(nodeMap(_)), newTarget) + val newTarget = nodeMap.getOrElse(target, target) + mergedGraph.addEdges(deps.map(d => nodeMap.getOrElse(d, d)), newTarget) } mergedGraph diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 9366998f8..ea78d3e69 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -1,5 +1,6 @@ package viper.silicon.dependencyAnalysis +import dependencyAnalysis.UserLevelDependencyAnalysisNode import viper.silicon.interfaces.Failure import viper.silicon.verifier.Verifier import viper.silver.ast @@ -26,7 +27,9 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen def initJoinCandidateNodes(): Unit = { joinCandidateNodes = dependencyGraph.getNodes.filter(node => node.isInstanceOf[AxiomAssumptionNode] || AssumptionType.joinConditionTypes.contains(node.assumptionType)) } - + + def toUserLevelNodes(nodes: Iterable[DependencyAnalysisNode]): Set[UserLevelDependencyAnalysisNode] = UserLevelDependencyAnalysisNode.from(nodes) + def getNodesByLine(line: Int): Set[DependencyAnalysisNode] = getNodes.filter(n => !AssumptionType.internalTypes.contains(n.assumptionType)).filter(node => node.sourceInfo.getLineNumber.isDefined && node.sourceInfo.getLineNumber.get == line) @@ -66,9 +69,6 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen getExplicitAssertionNodes.filter(node => allDependents.contains(node.id)) } - def filterOutNodesBySourceInfo(nodes: Set[DependencyAnalysisNode], excludeInfos: Set[AnalysisSourceInfo]): Set[DependencyAnalysisNode] = - nodes filterNot (node => excludeInfos.exists(i => i.getTopLevelSource.toString.equals(node.getUserLevelRepresentation))) - def getNonInternalAssumptionNodes: Set[DependencyAnalysisNode] = getNodes filter (node => (node.isInstanceOf[GeneralAssumptionNode] && !AssumptionType.internalTypes.contains(node.assumptionType)) || AssumptionType.postconditionTypes.contains(node.assumptionType) // postconditions act as assumptions for callers @@ -81,18 +81,12 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen def hasAnyDependency(nodesToAnalyze: Set[DependencyAnalysisNode], includeInfeasibilityNodes: Boolean = true): Boolean = nodesToAnalyze.intersect(getNonInternalAssumptionNodes) .exists(node => dependencyGraph.existsAnyDependency(Set(node.id), nodesToAnalyze map (_.id) filter (_ != node.id), includeInfeasibilityNodes)) - - def getNonInternalAssumptionNodesPerSource: Map[String, Set[DependencyAnalysisNode]] = - getNonInternalAssumptionNodes.groupBy(_.getUserLevelRepresentation) - - + + def getNonInternalAssertionNodes: Set[DependencyAnalysisNode] = getNodes filter (node => node.isInstanceOf[GeneralAssertionNode] && !AssumptionType.internalTypes.contains(node.assumptionType) ) - def getNonInternalAssertionNodesPerSource: Map[String, Set[DependencyAnalysisNode]] = - getNonInternalAssertionNodes.groupBy(_.getUserLevelRepresentation) - def getExplicitAssertionNodes: Set[DependencyAnalysisNode] = getNonInternalAssertionNodes.filter(node => AssumptionType.explicitAssertionTypes.contains(node.assumptionType)) @@ -105,8 +99,8 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen } private def getNodesWithIdenticalSource(nodes: Set[DependencyAnalysisNode]): Set[DependencyAnalysisNode] = { - val sourceInfos = nodes map (_.getUserLevelRepresentation) - getNodes filter (node => sourceInfos.contains(node.getUserLevelRepresentation)) + val sourceInfos = nodes map (_.sourceInfo.getTopLevelSource) + getNodes filter (node => sourceInfos.contains(node.sourceInfo.getTopLevelSource)) } def computeProofCoverage(): (Double, Set[String]) = { @@ -118,14 +112,15 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen val assertionNodeIds = assertionNodes map (_.id) val dependencies = dependencyGraph.getAllDependencies(assertionNodeIds, includeInfeasibilityNodes=true) val coveredNodes = dependencies ++ assertionNodeIds - val nodesPerSourceInfo = getNonInternalAssumptionNodes.filterNot(_.isInstanceOf[AxiomAssumptionNode]).groupBy(_.getUserLevelRepresentation) - if(nodesPerSourceInfo.isEmpty) return (Double.NaN, Set()) - - val uncoveredSources = (nodesPerSourceInfo filter { case (_, nodes) => - coveredNodes.intersect(nodes map (_.id)).isEmpty - }).keys.toSet - val proofCoverage = 1.0 - (uncoveredSources.size.toDouble / nodesPerSourceInfo.size.toDouble) - (proofCoverage, uncoveredSources) + + val userLevelNodes = toUserLevelNodes(getNonInternalAssumptionNodes.filterNot(_.isInstanceOf[AxiomAssumptionNode])) + if(userLevelNodes.isEmpty) return (Double.NaN, Set()) + + val uncoveredUserLevelNodes = userLevelNodes filter (node => + coveredNodes.intersect(node.lowerLevelNodes.map(_.id)).isEmpty + ) + val proofCoverage = 1.0 - (uncoveredUserLevelNodes.size.toDouble / userLevelNodes.size.toDouble) + (proofCoverage, uncoveredUserLevelNodes.map(_.toString)) } def getPrunedProgram(crucialNodes: Set[DependencyAnalysisNode], program: ast.Program): (ast.Program, Double) = { @@ -220,72 +215,72 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen } def computeVerificationProgress(): (Double, Double, String) = { - val assumptionsPerSource = getNonInternalAssumptionNodes.groupBy(_.getUserLevelRepresentation) - if(assumptionsPerSource.isEmpty) return (Double.NaN, Double.NaN, "Error: no assumptions found") - - val allAssertions = getNonInternalAssertionNodes.groupBy(_.getUserLevelRepresentation) + val allAssertions = toUserLevelNodes(getNonInternalAssertionNodes) val relevantDependenciesPerAssertion = allAssertions - .view.mapValues(g => getAllNonInternalDependencies(getNodesWithIdenticalSource(g).map(_.id))) - - // TODO ake: make sure to handle failing assertions properly! - val nonTrivialAssertionsGroupedPerAssertion = relevantDependenciesPerAssertion.filter{case (_, assumptions) => assumptions.nonEmpty} - - val assertionsWithoutExplicitAssumptions = nonTrivialAssertionsGroupedPerAssertion.filter { case (_, deps) => - !deps.exists(dep => AssumptionType.explicitAssumptionTypes.contains(dep.assumptionType)) - }.keys + .map(ass => (ass, getAllNonInternalDependencies(getNodesWithIdenticalSource(ass.lowerLevelNodes).map(_.id)))).filter{case (_, assumptions) => assumptions.nonEmpty}.toMap + val relevantDependencies = toUserLevelNodes(relevantDependenciesPerAssertion.flatMap(_._2)) - val relevantDependencies = relevantDependenciesPerAssertion.flatMap(_._2) + val coveredExplicitSources = UserLevelDependencyAnalysisNode.extractExplicitAssumptionNodes(relevantDependencies) + val coveredVerificationAnnotations = UserLevelDependencyAnalysisNode.extractVerificationAnnotationNodes(relevantDependencies).diff(coveredExplicitSources) + val coveredSourceCodeStmts = relevantDependencies.diff(coveredExplicitSources).diff(coveredVerificationAnnotations) - def filterAndGroup(nodes: Iterable[DependencyAnalysisNode], filterCondition: DependencyAnalysisNode => Boolean) = { - nodes.filter(filterCondition).groupBy(_.getUserLevelRepresentation).keys.toSet - } - - val coveredExplicitSources = filterAndGroup(relevantDependencies, node => AssumptionType.explicitAssumptionTypes.contains(node.assumptionType)) - val coveredVerificationAnnotations = filterAndGroup(relevantDependencies, node => AssumptionType.verificationAnnotationTypes.contains(node.assumptionType)).diff(coveredExplicitSources) - val coveredSourceCodeStmts = filterAndGroup(relevantDependencies, _ => true).diff(coveredExplicitSources).diff(coveredVerificationAnnotations) - - def getUncoveredNodesAndGroup(nodes: Iterable[DependencyAnalysisNode]) = { - nodes.groupBy(_.getUserLevelRepresentation).keys.toSet.diff(relevantDependencies.groupBy(_.getUserLevelRepresentation).keys.toSet) - } - - val uncoveredExplicitSources = getUncoveredNodesAndGroup(getExplicitAssumptionNodes) - val uncoveredVerificationAnnotations = getUncoveredNodesAndGroup(getNonInternalAssumptionNodes.filter(node => AssumptionType.verificationAnnotationTypes.contains(node.assumptionType))).diff(uncoveredExplicitSources) - val uncoveredSourceCodeStmts = getUncoveredNodesAndGroup(getNonInternalAssumptionNodes).diff(uncoveredExplicitSources).diff(uncoveredVerificationAnnotations) + val uncoveredNodes = toUserLevelNodes(getNonInternalAssumptionNodes).diff(relevantDependencies) + val uncoveredExplicitSources = UserLevelDependencyAnalysisNode.extractExplicitAssumptionNodes(uncoveredNodes) + val uncoveredVerificationAnnotations = UserLevelDependencyAnalysisNode.extractVerificationAnnotationNodes(uncoveredNodes).diff(uncoveredExplicitSources) + val uncoveredSourceCodeStmts = uncoveredNodes.diff(uncoveredExplicitSources).diff(uncoveredVerificationAnnotations) // Peter's metric + // TODO ake: make sure to handle failing assertions properly! + val assertionsWithoutExplicitAssumptions = relevantDependenciesPerAssertion.filter { case (_, deps) => + !deps.exists(dep => AssumptionType.explicitAssumptionTypes.contains(dep.assumptionType)) + }.keys val specQuality = coveredSourceCodeStmts.size.toDouble / (coveredSourceCodeStmts.size.toDouble + uncoveredSourceCodeStmts.size.toDouble) - val proofQualityPeter = (assertionsWithoutExplicitAssumptions.size.toDouble - errors.size.toDouble) / nonTrivialAssertionsGroupedPerAssertion.size.toDouble + val proofQualityPeter = (assertionsWithoutExplicitAssumptions.size.toDouble - errors.size.toDouble) / relevantDependenciesPerAssertion.size.toDouble val verificationProgressPeter = specQuality * proofQualityPeter // Lea's metric - val proofQualityNumerator = nonTrivialAssertionsGroupedPerAssertion.map{case (_, assumptions) => filterAndGroup(assumptions, node => !AssumptionType.explicitAssumptionTypes.contains(node.assumptionType)).size.toDouble / assumptions.groupBy(_.getUserLevelRepresentation).keys.size.toDouble}.sum - val proofQualityLea = proofQualityNumerator / nonTrivialAssertionsGroupedPerAssertion.keys.size.toDouble + val proofQualityNumerator = relevantDependenciesPerAssertion.map { case (_, assumptions) => + val userLevelAssumptions = toUserLevelNodes(assumptions) + UserLevelDependencyAnalysisNode.extractNonExplicitAssumptionNodes(userLevelAssumptions).size.toDouble / userLevelAssumptions.size.toDouble + // TODO ake: take failing assertions into account! + }.sum + val proofQualityLea = proofQualityNumerator / relevantDependenciesPerAssertion.keys.size.toDouble val verificationProgressLea = specQuality * proofQualityLea val info = { s"Covered\n" + - s"\tExplicit Assumptions:\n\t\t${coveredExplicitSources.mkString("\n\t\t")}" + "\n" + - s"\tVerification Annotations:\n\t\t${coveredVerificationAnnotations.mkString("\n\t\t")}" + "\n" + - s"\tSource Code:\n\t\t${coveredSourceCodeStmts.mkString("\n\t\t")}" + "\n" + + s"\tExplicit Assumptions:\n\t\t${UserLevelDependencyAnalysisNode.mkString(coveredExplicitSources, "\n\t\t")}" + "\n" + + s"\tVerification Annotations:\n\t\t${UserLevelDependencyAnalysisNode.mkString(coveredVerificationAnnotations, "\n\t\t")}" + "\n" + + s"\tSource Code:\n\t\t${UserLevelDependencyAnalysisNode.mkString(coveredSourceCodeStmts, "\n\t\t")}" + "\n" + "\n" + s"Uncovered\n" + - s"\tExplicit Assumptions:\n\t\t${uncoveredExplicitSources.mkString("\n\t\t")}" + "\n" + - s"\tVerification Annotations:\n\t\t${uncoveredVerificationAnnotations.mkString("\n\t\t")}" + "\n" + - s"\tSource Code:\n\t\t${uncoveredSourceCodeStmts.mkString("\n\t\t")}" + "\n" + + s"\tExplicit Assumptions:\n\t\t${UserLevelDependencyAnalysisNode.mkString(uncoveredExplicitSources, "\n\t\t")}" + "\n" + + s"\tVerification Annotations:\n\t\t${UserLevelDependencyAnalysisNode.mkString(uncoveredVerificationAnnotations, "\n\t\t")}" + "\n" + + s"\tSource Code:\n\t\t${UserLevelDependencyAnalysisNode.mkString(uncoveredSourceCodeStmts, "\n\t\t")}" + "\n" + "\n" + s"Fully verified assertions:\n\t${assertionsWithoutExplicitAssumptions.mkString("\n\t")}" + "\n\n" + - s"Assertions depending on explicit assumptions:\n\t${nonTrivialAssertionsGroupedPerAssertion.keySet.diff(assertionsWithoutExplicitAssumptions.toSet).mkString("\n\t")}" + "\n\n" + + s"Assertions depending on explicit assumptions:\n\t${relevantDependenciesPerAssertion.keySet.diff(assertionsWithoutExplicitAssumptions.toSet).mkString("\n\t")}" + "\n\n" + s"Failing assertions:\n\tTODO" + "\n\n" + // TODO ake "\n" + s"#Verification Errors: ${errors.size}" + "\n\n" + "\n" + s"Verification Progress (Peter):\n\t${coveredSourceCodeStmts.size}/(${coveredSourceCodeStmts.size + uncoveredSourceCodeStmts.size}) * " + - s"${assertionsWithoutExplicitAssumptions.size - errors.size.toDouble}/${nonTrivialAssertionsGroupedPerAssertion.size} = $verificationProgressPeter" + "\n" + + s"${assertionsWithoutExplicitAssumptions.size - errors.size.toDouble}/${relevantDependenciesPerAssertion.size} = $verificationProgressPeter" + "\n" + s"Verification Progress (Lea):\n\t${coveredSourceCodeStmts.size}/(${coveredSourceCodeStmts.size + uncoveredSourceCodeStmts.size}) * " + - s"$proofQualityNumerator/${nonTrivialAssertionsGroupedPerAssertion.keys.size} = $verificationProgressLea" + "\n" + s"$proofQualityNumerator/${relevantDependenciesPerAssertion.keys.size} = $verificationProgressLea" + "\n" } (verificationProgressPeter, verificationProgressLea, info) } + + def provideVerificationGuidance() = { + + + // return (String, Int) = Assumption, #deps + // return (String, Int) = function/method, #uncovered stmts + // return (String, Int) = failing assertions, #dependents + } + + } diff --git a/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala b/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala new file mode 100644 index 000000000..973cabd07 --- /dev/null +++ b/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala @@ -0,0 +1,68 @@ +package dependencyAnalysis + +import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyAnalysisNode, GeneralAssertionNode, GeneralAssumptionNode} +import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType +import viper.silicon.state.terms.{And, Term} +import viper.silver.ast.Position + +object UserLevelDependencyAnalysisNode { + + def from(dependencyNodes: Iterable[DependencyAnalysisNode]): Set[UserLevelDependencyAnalysisNode] = { + val res = dependencyNodes.groupBy(_.sourceInfo.getTopLevelSource).map { case (topLevelSource, nodes) => + UserLevelDependencyAnalysisNode(topLevelSource, nodes.toSet) + }.toSet + res + } + + def extractExplicitAssumptionNodes(nodes: Set[UserLevelDependencyAnalysisNode]): Set[UserLevelDependencyAnalysisNode] = { + extractByAssumptionType(nodes, AssumptionType.explicitAssumptionTypes) + } + + def extractNonExplicitAssumptionNodes(nodes: Set[UserLevelDependencyAnalysisNode]): Set[UserLevelDependencyAnalysisNode] = { + nodes.diff(extractExplicitAssumptionNodes(nodes)) + } + + def extractVerificationAnnotationNodes(nodes: Set[UserLevelDependencyAnalysisNode]): Set[UserLevelDependencyAnalysisNode] = { + extractByAssumptionType(nodes, AssumptionType.verificationAnnotationTypes) + } + + def extractByAssumptionType(nodes: Set[UserLevelDependencyAnalysisNode], assumptionTypes: Set[AssumptionType]): Set[UserLevelDependencyAnalysisNode] = { + nodes.filter(node => assumptionTypes.intersect(node.assumptionTypes).nonEmpty) + } + + def extractByAssertionType(nodes: Set[UserLevelDependencyAnalysisNode], assertionTypes: Set[AssumptionType]): Set[UserLevelDependencyAnalysisNode] = { + nodes.filter(node => assertionTypes.intersect(node.assertionTypes).nonEmpty) + } + + def mkString(nodes: Set[UserLevelDependencyAnalysisNode], sep: String = "\n"): String = { + nodes.toList.sortBy(_.source.getLineNumber).mkString(sep) + } + + def mkUserLevelString(nodes: Set[DependencyAnalysisNode], sep: String = "\n"): String = { + mkString(from(nodes), sep) + } +} + +case class UserLevelDependencyAnalysisNode(source: AnalysisSourceInfo, lowerLevelNodes: Set[DependencyAnalysisNode]) { + + def position: Position = source.getPosition + + def assumptionTypes: Set[AssumptionType] = lowLevelAssumptionNodes.map(_.assumptionType) + def assertionTypes: Set[AssumptionType] = lowLevelAssertionNodes.map(_.assumptionType) + + lazy val lowLevelAssumptionNodes: Set[DependencyAnalysisNode] = lowerLevelNodes.filter(_.isInstanceOf[GeneralAssumptionNode]) + lazy val lowLevelAssertionNodes: Set[DependencyAnalysisNode] = lowerLevelNodes.filter(_.isInstanceOf[GeneralAssertionNode]) + + lazy val assumptionTerm: Term = And(lowLevelAssumptionNodes.map(_.getTerm)) + lazy val assertionTerm: Term = And(lowLevelAssertionNodes.map(_.getTerm)) + + + override def toString: String = source.toString + + override def hashCode(): Int = source.hashCode() + + override def equals(obj: Any): Boolean = obj match { + case node: UserLevelDependencyAnalysisNode => this.source.getTopLevelSource.equals(node.source.getTopLevelSource) + case _ => false + } +} diff --git a/src/test/scala/DependencyAnalysisTests.scala b/src/test/scala/DependencyAnalysisTests.scala index 2a4307ca6..63d707153 100644 --- a/src/test/scala/DependencyAnalysisTests.scala +++ b/src/test/scala/DependencyAnalysisTests.scala @@ -1,5 +1,6 @@ package viper.silicon.tests +import dependencyAnalysis.UserLevelDependencyAnalysisNode import org.scalatest.funsuite.AnyFunSuite import viper.silicon.dependencyAnalysis._ import viper.silver.ast._ @@ -106,8 +107,8 @@ class DependencyAnalysisTests extends AnyFunSuite with DependencyAnalysisTestFra proofCoverageWriter.println(filePrefix + "/" + fileName) dependencyGraphInterpreters foreach (memberInterpreter => { - memberInterpreter.getExplicitAssertionNodes.groupBy(_.getUserLevelRepresentation) foreach {case (source, nodes) => - proofCoverageWriter.println(memberInterpreter.getName + " " + source.replace("\n", " ") + " ---> " + memberInterpreter.computeProofCoverage(nodes)._1)} + UserLevelDependencyAnalysisNode.from(memberInterpreter.getExplicitAssertionNodes) foreach {node => + proofCoverageWriter.println(memberInterpreter.getName + " " + node.source.toString.replace("\n", " ") + " ---> " + memberInterpreter.computeProofCoverage(node.lowerLevelNodes)._1)} proofCoverageWriter.println("overall " + memberInterpreter.getName + " ---> + " + memberInterpreter.computeProofCoverage()._1) }) proofCoverageWriter.println() From 5c0dfce3e5bfca1eecdf9220d03fb1756fcfb48d Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 12 Jan 2026 11:51:59 +0100 Subject: [PATCH 295/474] renaming --- .../DependencyAnalyzer.scala | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index d4e7784fc..1789c77cc 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -12,7 +12,7 @@ import scala.collection.mutable trait DependencyAnalyzer { - protected val assumptionGraph: DependencyGraph = new DependencyGraph() + protected val dependencyGraph: DependencyGraph = new DependencyGraph() protected var isClosed_ = false def disableTransitiveEdges(): Unit = { @@ -219,10 +219,10 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { override def getMember: Option[ast.Member] = Some(member) - override def getNodes: Iterable[DependencyAnalysisNode] = assumptionGraph.nodes + override def getNodes: Iterable[DependencyAnalysisNode] = dependencyGraph.nodes override def getChunkInhaleNode(chunk: Chunk): Option[PermissionInhaleNode] = { - val inhaleNode = assumptionGraph.nodes + val inhaleNode = dependencyGraph.nodes .filter(c => c.isInstanceOf[PermissionInhaleNode] && chunk.equals(c.asInstanceOf[ChunkAnalysisInfo].getChunk)) .map(_.asInstanceOf[PermissionInhaleNode]) assert(inhaleNode.size == 1) @@ -230,24 +230,24 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { } private def getChunkNodeIds(oldChunks: Set[Chunk]): Set[Int] = { - assumptionGraph.nodes + dependencyGraph.nodes .filter(c => c.isInstanceOf[PermissionInhaleNode] && oldChunks.contains(c.asInstanceOf[ChunkAnalysisInfo].getChunk)) .map(_.id).toSet } private def getNodeIdsByTerm(terms: Set[Term]): Set[Int] = { - assumptionGraph.nodes + dependencyGraph.nodes .filter(t => terms.contains(t.getTerm)) .map(_.id).toSet } override def addNodes(nodes: Iterable[DependencyAnalysisNode]): Unit = { - assumptionGraph.addNodes(nodes) + dependencyGraph.addNodes(nodes) } override def addNode(node: DependencyAnalysisNode): Unit = { - assumptionGraph.addNode(node) + dependencyGraph.addNode(node) } // adding assumption nodes @@ -300,7 +300,7 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { override def createLabelNode(label: Var, sourceChunks: Iterable[Chunk], sourceTerms: Iterable[Term]): Option[LabelNode] = { val labelNode = LabelNode(label) addNode(labelNode) - assumptionGraph.addEdges(getChunkNodeIds(sourceChunks.toSet) ++ getNodeIdsByTerm(sourceTerms.toSet), labelNode.id) + dependencyGraph.addEdges(getChunkNodeIds(sourceChunks.toSet) ++ getNodeIdsByTerm(sourceTerms.toSet), labelNode.id) Some(labelNode) } @@ -334,7 +334,7 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { // adding dependencies override def addDependency(source: Option[Int], dest: Option[Int]): Unit = { if(source.isDefined && dest.isDefined) - assumptionGraph.addEdges(source.get, Set(dest.get)) + dependencyGraph.addEdges(source.get, Set(dest.get)) } override def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit = { @@ -344,9 +344,9 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { val assertionIdsFromUnsatCore = assumptionLabels.filter(DependencyAnalyzer.isAssertionLabel).map(DependencyAnalyzer.getIdFromLabel) val assertionIdFromLabel = DependencyAnalyzer.getIdFromLabel(assertionLabel) val assertionIds = assertionIdFromLabel +: assertionIdsFromUnsatCore - assumptionGraph.addEdges(assumptionIds, assertionIds) + dependencyGraph.addEdges(assumptionIds, assertionIds) val axiomIds = assumptionLabels.filter(DependencyAnalyzer.isAxiomLabel).map(DependencyAnalyzer.getIdFromLabel) - assumptionGraph.addEdges(axiomIds, assertionIds) + dependencyGraph.addEdges(axiomIds, assertionIds) stopTimeMeasurementAndAddToTotal(startTime, timeToProcessUnsatCore) } @@ -354,12 +354,12 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { if(newChunkNodeId.isEmpty) return val sourceNodeIds = getChunkNodeIds(sourceChunks).filter(id => id != newChunkNodeId.get) ++ getNodeIdsByTerm(sourceTerms) - assumptionGraph.addEdges(sourceNodeIds, newChunkNodeId.get) + dependencyGraph.addEdges(sourceNodeIds, newChunkNodeId.get) } override def addPermissionDependencies(sourceChunks: Set[Chunk], sourceTerms: Set[Term], newChunk: Chunk): Unit = { val startTime = startTimeMeasurement() - val newChunkId = assumptionGraph.nodes + val newChunkId = dependencyGraph.nodes .filter(c => c.isInstanceOf[PermissionInhaleNode] && c.isInstanceOf[ChunkAnalysisInfo] && newChunk.equals(c.asInstanceOf[ChunkAnalysisInfo].getChunk)) .map(_.id).toSet addPermissionDependencies(sourceChunks, sourceTerms, newChunkId.headOption) @@ -376,7 +376,7 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { override def addDependenciesForExplicitPostconditions(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp]): Unit = { val sourceNodeIds = sourceExps.flatMap(e => addAssumption(True, ExpAnalysisSourceInfo(e), AssumptionType.Precondition, None)) val targetNodes = targetExps.flatMap(e => addAssertNode(True, AssumptionType.ExplicitPostcondition, ExpAnalysisSourceInfo(e))) - assumptionGraph.addEdges(sourceNodeIds, targetNodes) + dependencyGraph.addEdges(sourceNodeIds, targetNodes) } /** @@ -387,8 +387,8 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { * Further, this operation removes unnecessary details from the graph by, for example, removing label nodes and merging identical nodes. */ override def buildFinalGraph(): Option[DependencyGraph] = { - assumptionGraph.removeLabelNodes() - val mergedGraph = if(Verifier.config.enableDependencyAnalysisDebugging()) assumptionGraph else buildAndGetMergedGraph() + dependencyGraph.removeLabelNodes() + val mergedGraph = if(Verifier.config.enableDependencyAnalysisDebugging()) dependencyGraph else buildAndGetMergedGraph() mergedGraph.addTransitiveEdges() Some(mergedGraph) } @@ -398,7 +398,7 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { val postcondNodes = getNodes.filter(n => AssumptionType.postconditionTypes.contains(n.assumptionType)) axiomNodes foreach {aNode => val pNodes = postcondNodes filter (_.sourceInfo.toString.equals(aNode.sourceInfo.toString)) map (_.id) - assumptionGraph.addEdges(pNodes, aNode.id) + dependencyGraph.addEdges(pNodes, aNode.id) } } @@ -414,12 +414,12 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { val mergedGraph = new DependencyGraph val nodeMap = mutable.HashMap[Int, Int]() - assumptionGraph.getNodes.filter(keepNode).foreach { n => + dependencyGraph.getNodes.filter(keepNode).foreach { n => nodeMap.put(n.id, n.id) mergedGraph.addNode(n) } - val nodesBySource = assumptionGraph.getNodes.filter(!keepNode(_)) + val nodesBySource = dependencyGraph.getNodes.filter(!keepNode(_)) .groupBy(n => (n.sourceInfo.getSourceForTransitiveEdges.toString, n.sourceInfo, n.sourceInfo.getFineGrainedSource.toString, n.assumptionType)) nodesBySource foreach { case ((_, sourceInfo, _, assumptionType), nodes) => @@ -440,7 +440,7 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { } } - assumptionGraph.getAllEdges foreach { case (target, deps) => + dependencyGraph.getAllEdges foreach { case (target, deps) => val newTarget = nodeMap.getOrElse(target, target) mergedGraph.addEdges(deps.map(d => nodeMap.getOrElse(d, d)), newTarget) } From acbcca6bfb244cec68dbd1337224dee2fb8023bd Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 12 Jan 2026 11:52:45 +0100 Subject: [PATCH 296/474] implement verification guidance --- .../DependencyAnalysisUserTool.scala | 12 ++ .../DependencyGraphInterpreter.scala | 16 +- .../UserLevelDependencyAnalysisNode.scala | 4 + .../verificationProgress/guidance.vpr | 142 ++++++++++++++++++ 4 files changed, 167 insertions(+), 7 deletions(-) create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgress/guidance.vpr diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala index ee58843f8..ae7053e16 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala @@ -18,6 +18,7 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete "\n\t'cov [members]' to print proof coverage of given member or" + "\n\t'covL member [line numbers]' to print proof coverage of given lines of given member or" + "\n\t'progress' to compute the verification progress of the program or" + + "\n\t'guide' to compute verification guidance or" + "\n\t'prune [line numbers]' to prune the program with respect to the given line numbers and export the new program or" + "\n\t'q' to quit" @@ -59,6 +60,8 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete handleProofCoverageLineQuery(inputParts.tail) }else if (inputParts.head.equalsIgnoreCase("progress") || inputParts.head.equalsIgnoreCase("prog")) { handleVerificationProgressQuery() + }else if (inputParts.head.equalsIgnoreCase("guidance") || inputParts.head.equalsIgnoreCase("guide")) { + handleVerificationGuidanceQuery() }else if(inputParts.head.equalsIgnoreCase("prune")) { handlePruningRequest(inputParts.tail) }else if(inputParts.head.equalsIgnoreCase("benchmark")) { @@ -259,6 +262,15 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete } writer.close() + } + def handleVerificationGuidanceQuery(): Unit = { + val assumptionRanking = fullGraphInterpreter.computeAssumptionRanking() + println(s"Assumptions and the number of dependencies:\n\t${assumptionRanking.mkString("\n\t")}\n") + val memberCoverageRanking = memberInterpreters.filter(mInterpreter => mInterpreter.getMember.isDefined && mInterpreter.getMember.get.isInstanceOf[Method]) + .map(mInterpreter => (mInterpreter.getMember.get.name, mInterpreter.computeUncoveredStatements())) + .toList.sortBy(_._2).reverse + println(s"Members and the number of uncovered statements:\n\t${memberCoverageRanking.mkString("\n\t")}") + println(s"Errors:\n${verificationErrors.mkString("\n\t")}") } } diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index ea78d3e69..887d04dce 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -274,13 +274,15 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen (verificationProgressPeter, verificationProgressLea, info) } - def provideVerificationGuidance() = { - - - // return (String, Int) = Assumption, #deps - // return (String, Int) = function/method, #uncovered stmts - // return (String, Int) = failing assertions, #dependents + /* returns an ordered list of (Assumption, #dependents) */ + def computeAssumptionRanking(): List[(String, Int)] = { + toUserLevelNodes(getExplicitAssumptionNodes).map(node => (node.toString, getAllNonInternalDependents(node.lowerLevelNodes.map(_.id)).size)) + .toList.sortBy(_._2).reverse } - + def computeUncoveredStatements(): Int = { + val allSourceCodeStmts = UserLevelDependencyAnalysisNode.extractSourceCodeNodes(toUserLevelNodes(getNonInternalAssumptionNodes)) + val coveredSourceCodeStmts = toUserLevelNodes(getAllNonInternalDependencies(getNodesWithIdenticalSource(getNonInternalAssertionNodes).map(_.id))) + allSourceCodeStmts.diff(coveredSourceCodeStmts).size + } } diff --git a/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala b/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala index 973cabd07..012be3449 100644 --- a/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala +++ b/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala @@ -26,6 +26,10 @@ object UserLevelDependencyAnalysisNode { extractByAssumptionType(nodes, AssumptionType.verificationAnnotationTypes) } + def extractSourceCodeNodes(nodes: Set[UserLevelDependencyAnalysisNode]): Set[UserLevelDependencyAnalysisNode] = { + nodes.diff(extractExplicitAssumptionNodes(nodes)).diff(extractVerificationAnnotationNodes(nodes)) + } + def extractByAssumptionType(nodes: Set[UserLevelDependencyAnalysisNode], assumptionTypes: Set[AssumptionType]): Set[UserLevelDependencyAnalysisNode] = { nodes.filter(node => assumptionTypes.intersect(node.assumptionTypes).nonEmpty) } diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgress/guidance.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgress/guidance.vpr new file mode 100644 index 000000000..4ad939a7e --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgress/guidance.vpr @@ -0,0 +1,142 @@ + +field f: Int + +method add(a: Int, b: Int) returns (res: Int) + ensures res == a + b + ensures res > 0 +{ + assume a > 0 && b > 0 + res := a + b +} + +method client() +{ + var a: Int, b: Int, c: Int + c := 0 + b := 10 + c := add(a, b) + + assert c > 0 +} + +method gaussianSimple(n: Int) returns (res: Int) + requires n <= 5 + ensures res == n * (n + 1) / 2 +{ + assume n > 0 + res := 0 + var i: Int := 0 + while(i <= n) + invariant i <= (n + 1) + invariant i <= 6 + invariant res == (i - 1) * i / 2 + { + res := res + i + i := i + 1 + } + i := 0 +} + +method inc(x: Ref) returns (res:Int) + requires acc(x.f, 1/2) + ensures acc(x.f, 1/2) + ensures res == x.f + 1 + ensures res > 0 + +method client1() +{ + var res: Int + var x: Ref := new(f) + + res := inc(x) + + assert res > 0 +} + +method client2() +{ + var res: Int + var x: Ref := new(f) + + res := inc(x) + + assert res == x.f + 1 +} + +method branches1(a: Int, b: Int) +{ + var n:Int, c: Bool + + assume a > 0 + assume b > 4 + + assume c ==> a < 100 + + if(c){ + n := a + 1 + }else{ + n := b + 1 + } + + assert n > 1 +} + +method branches2(a: Int, b: Int) +{ + var n:Int, c: Bool + + assume a > 0 + assume a < 100 + assume a < b + assume b < 50 + + var x: Int + if(a >= n){ + x := a + b + }else{ + x := n + 1 + assert x > 1 + } +} + +method branches3(a: Int, b: Int) +{ + var n:Int, c: Bool + + assume b > 0 + + var x: Int + if(a >= n){ + x := a + b + }else{ + x := n + 1 + } + + assert x > n +} + + +method nestedBranches1(a: Int, b: Int) +{ + var n:Int, c: Bool + + assume a > 0 + assume a < 100 + assume b > 0 + assume b < 50 + assume c ==> a > 5 + + if(c){ + if(a > b){ + n := a - b + }else{ + n := a + b + } + n := n - 1 + }else{ + n := a + b + } + + assert n <= a + b + assert c ==> n < a + b +} From c990fa690bb8fa17e43d5f25fcfb37b3fd943766 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 12 Jan 2026 14:39:02 +0100 Subject: [PATCH 297/474] handle verification failures in progress metric --- src/main/scala/decider/Decider.scala | 39 +++++++------ .../DependencyAnalysisNode.scala | 16 +++++- .../DependencyAnalysisUserTool.scala | 7 ++- .../DependencyGraphInterpreter.scala | 56 ++++++++++++------- .../UserLevelDependencyAnalysisNode.scala | 2 + 5 files changed, 76 insertions(+), 44 deletions(-) diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 6b85f4b09..0ede38574 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -476,22 +476,21 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => /* Asserting facts */ def checkSmoke(isAssert: Boolean = false, assumptionType: AssumptionType=AssumptionType.Implicit): Boolean = { - val (label, checkNodeId) = if(Verifier.config.enableDependencyAnalysis()){ - val nodeId = dependencyAnalyzer.addAssertFalseNode(!isAssert, assumptionType, analysisSourceInfoStack.getFullSourceInfo) // TODO ake: add node only if it can be verified - (DependencyAnalyzer.createAssertionLabel(nodeId), nodeId) - }else{ ("", None) } + val checkNode = dependencyAnalyzer.createAssertOrCheckNode(False, assumptionType, analysisSourceInfoStack.getFullSourceInfo, !isAssert) + val label = DependencyAnalyzer.createAssertionLabel(checkNode.map(_.id)) val timeout = if (isAssert) Verifier.config.assertTimeout.toOption else Verifier.config.checkTimeout.toOption val result = isPathInfeasible() || prover.check(timeout, label) == Unsat - if(result) { - if(pcs.getCurrentInfeasibilityNode.isDefined){ - dependencyAnalyzer.addDependency(pcs.getCurrentInfeasibilityNode, checkNodeId) - }else { - dependencyAnalyzer.processUnsatCoreAndAddDependencies(prover.getLastUnsatCore, label) - val infeasibleNodeId = dependencyAnalyzer.addInfeasibilityNode(!isAssert, analysisSourceInfoStack.getFullSourceInfo) - dependencyAnalyzer.addDependency(checkNodeId, infeasibleNodeId) - pcs.setCurrentInfeasibilityNode(checkNodeId) - } + + if(isPathInfeasible()){ + checkNode foreach dependencyAnalyzer.addNode + dependencyAnalyzer.addDependency(pcs.getCurrentInfeasibilityNode, checkNode.map(_.id)) + }else if(result){ + checkNode foreach dependencyAnalyzer.addNode + dependencyAnalyzer.processUnsatCoreAndAddDependencies(prover.getLastUnsatCore, label) + val infeasibleNodeId = dependencyAnalyzer.addInfeasibilityNode(!isAssert, analysisSourceInfoStack.getFullSourceInfo) + dependencyAnalyzer.addDependency(checkNode.map(_.id), infeasibleNodeId) + pcs.setCurrentInfeasibilityNode(checkNode.map(_.id)) } result } @@ -538,7 +537,11 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => val result = asserted || proverAssert(t, timeout, DependencyAnalyzer.createAssertionLabel(assertNode map (_.id))) - if(result) assertNode foreach dependencyAnalyzer.addNode + if(result){ + assertNode foreach dependencyAnalyzer.addNode + }else if(!isCheck){ // TODO ake: only for asserts? + assertNode foreach {node => dependencyAnalyzer.addNode(node.getAssertFailedNode())} + } symbExLog.closeScope(sepIdentifier) (result, assertNode) @@ -558,12 +561,8 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => val result = isPathInfeasible() || prover.assert(t, timeout, label) - if(result) - if(pcs.getCurrentInfeasibilityNode.isDefined) { // TODO ake: should be checked before calling prover.assert - dependencyAnalyzer.addDependency(pcs.getCurrentInfeasibilityNode, Some(DependencyAnalyzer.getIdFromLabel(label))) - }else{ - dependencyAnalyzer.processUnsatCoreAndAddDependencies(prover.getLastUnsatCore, label) - } + if(isPathInfeasible()) dependencyAnalyzer.addDependency(pcs.getCurrentInfeasibilityNode, Some(DependencyAnalyzer.getIdFromLabel(label))) + if(result) dependencyAnalyzer.processUnsatCoreAndAddDependencies(prover.getLastUnsatCore, label) symbExLog.whenEnabled { assertRecord.statistics = Some(symbExLog.deltaStatistics(prover.statistics())) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala index 2b861ce6c..0101188f9 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala @@ -61,6 +61,10 @@ trait GeneralAssumptionNode extends DependencyAnalysisNode { } trait GeneralAssertionNode extends DependencyAnalysisNode { override def getNodeType: String = "Assertion" + + val hasFailed: Boolean + + def getAssertFailedNode(): GeneralAssertionNode } trait ChunkAnalysisInfo { @@ -76,13 +80,17 @@ case class AxiomAssumptionNode(term: Term, description: Option[String], sourceIn override def getNodeString: String = "assume axiom " + term.toString + description.map(" (" + _ + ")").getOrElse("") } -case class SimpleAssertionNode(term: Term, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo, isClosed: Boolean) extends GeneralAssertionNode { +case class SimpleAssertionNode(term: Term, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo, isClosed: Boolean, hasFailed: Boolean = false) extends GeneralAssertionNode { override def getNodeString: String = "assert " + term.toString + + override def getAssertFailedNode(): GeneralAssertionNode = SimpleAssertionNode(term, assumptionType, sourceInfo, isClosed, hasFailed=true) } -case class SimpleCheckNode(term: Term, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo, isClosed: Boolean) extends GeneralAssertionNode { +case class SimpleCheckNode(term: Term, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo, isClosed: Boolean, hasFailed: Boolean = false) extends GeneralAssertionNode { override def getNodeString: String = "check " + term override def getNodeType: String = "Check" + + override def getAssertFailedNode(): GeneralAssertionNode = SimpleCheckNode(term, assumptionType, sourceInfo, isClosed, hasFailed=true) } case class PermissionInhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean, labelNode: LabelNode) extends GeneralAssumptionNode with ChunkAnalysisInfo { @@ -90,9 +98,11 @@ case class PermissionInhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSo override def getNodeType: String = "Inhale" } -case class PermissionExhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean) extends GeneralAssertionNode with ChunkAnalysisInfo { +case class PermissionExhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean, hasFailed: Boolean = false) extends GeneralAssertionNode with ChunkAnalysisInfo { override def getNodeType: String = "Exhale" override def getNodeString: String = "exhale " + chunk.toString + + override def getAssertFailedNode(): GeneralAssertionNode = PermissionExhaleNode(chunk, term, sourceInfo, assumptionType, isClosed, hasFailed=true) } /** diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala index ae7053e16..00cf4e03a 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala @@ -267,10 +267,13 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete def handleVerificationGuidanceQuery(): Unit = { val assumptionRanking = fullGraphInterpreter.computeAssumptionRanking() println(s"Assumptions and the number of dependencies:\n\t${assumptionRanking.mkString("\n\t")}\n") + val memberCoverageRanking = memberInterpreters.filter(mInterpreter => mInterpreter.getMember.isDefined && mInterpreter.getMember.get.isInstanceOf[Method]) .map(mInterpreter => (mInterpreter.getMember.get.name, mInterpreter.computeUncoveredStatements())) .toList.sortBy(_._2).reverse - println(s"Members and the number of uncovered statements:\n\t${memberCoverageRanking.mkString("\n\t")}") - println(s"Errors:\n${verificationErrors.mkString("\n\t")}") + println(s"Members and the number of uncovered statements:\n\t${memberCoverageRanking.mkString("\n\t")}\n") + + val errorRanking = fullGraphInterpreter.computeFailureRanking() + println(s"Errors:\n\t${errorRanking.mkString("\n\t")}\n") } } diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 887d04dce..3c797680c 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -90,6 +90,8 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen def getExplicitAssertionNodes: Set[DependencyAnalysisNode] = getNonInternalAssertionNodes.filter(node => AssumptionType.explicitAssertionTypes.contains(node.assumptionType)) + def getAssertionNodesWithFailures: Set[GeneralAssertionNode] = + getNonInternalAssertionNodes.filter(_.isInstanceOf[GeneralAssertionNode]).map(_.asInstanceOf[GeneralAssertionNode]).filter(_.hasFailed) def exportGraph(): Unit = { if(Verifier.config.dependencyAnalysisExportPath.isEmpty) return @@ -218,35 +220,45 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen val allAssertions = toUserLevelNodes(getNonInternalAssertionNodes) val relevantDependenciesPerAssertion = allAssertions - .map(ass => (ass, getAllNonInternalDependencies(getNodesWithIdenticalSource(ass.lowerLevelNodes).map(_.id)))).filter{case (_, assumptions) => assumptions.nonEmpty}.toMap + .map(ass => (ass, getAllNonInternalDependencies(getNodesWithIdenticalSource(ass.lowerLevelNodes).map(_.id)))).toMap + .filter{case (assertion, assumptions) => assumptions.nonEmpty || assertion.hasFailures} // filter out trivial assertions like `assert true` + + // TODO ake: what to do with partially verified assertions? Currently, we take them into account for spec quality but + // consider the assertion to not be verified at all for proof quality. val relevantDependencies = toUserLevelNodes(relevantDependenciesPerAssertion.flatMap(_._2)) + // covered val coveredExplicitSources = UserLevelDependencyAnalysisNode.extractExplicitAssumptionNodes(relevantDependencies) val coveredVerificationAnnotations = UserLevelDependencyAnalysisNode.extractVerificationAnnotationNodes(relevantDependencies).diff(coveredExplicitSources) val coveredSourceCodeStmts = relevantDependencies.diff(coveredExplicitSources).diff(coveredVerificationAnnotations) + // uncovered val uncoveredNodes = toUserLevelNodes(getNonInternalAssumptionNodes).diff(relevantDependencies) val uncoveredExplicitSources = UserLevelDependencyAnalysisNode.extractExplicitAssumptionNodes(uncoveredNodes) val uncoveredVerificationAnnotations = UserLevelDependencyAnalysisNode.extractVerificationAnnotationNodes(uncoveredNodes).diff(uncoveredExplicitSources) val uncoveredSourceCodeStmts = uncoveredNodes.diff(uncoveredExplicitSources).diff(uncoveredVerificationAnnotations) + // assertions + val assertionsWithFailures = relevantDependenciesPerAssertion.keySet.filter(_.hasFailures) + val assertionsWithExplicitDeps = relevantDependenciesPerAssertion.filter(deps => deps._2.exists(d => AssumptionType.explicitAssumptionTypes.contains(d.assumptionType))).keySet.diff(assertionsWithFailures) + val fullyVerifiedAssertions = relevantDependenciesPerAssertion.keySet.diff(assertionsWithFailures).diff(assertionsWithExplicitDeps) + // Peter's metric - // TODO ake: make sure to handle failing assertions properly! - val assertionsWithoutExplicitAssumptions = relevantDependenciesPerAssertion.filter { case (_, deps) => - !deps.exists(dep => AssumptionType.explicitAssumptionTypes.contains(dep.assumptionType)) - }.keys val specQuality = coveredSourceCodeStmts.size.toDouble / (coveredSourceCodeStmts.size.toDouble + uncoveredSourceCodeStmts.size.toDouble) - val proofQualityPeter = (assertionsWithoutExplicitAssumptions.size.toDouble - errors.size.toDouble) / relevantDependenciesPerAssertion.size.toDouble + val proofQualityPeter = fullyVerifiedAssertions.size.toDouble / relevantDependenciesPerAssertion.keySet.size.toDouble val verificationProgressPeter = specQuality * proofQualityPeter // Lea's metric - val proofQualityNumerator = relevantDependenciesPerAssertion.map { case (_, assumptions) => - val userLevelAssumptions = toUserLevelNodes(assumptions) - UserLevelDependencyAnalysisNode.extractNonExplicitAssumptionNodes(userLevelAssumptions).size.toDouble / userLevelAssumptions.size.toDouble - // TODO ake: take failing assertions into account! - }.sum - val proofQualityLea = proofQualityNumerator / relevantDependenciesPerAssertion.keys.size.toDouble + val proofQualityPerAssertion = relevantDependenciesPerAssertion.map { case (assertion, assumptions) => + if(assertion.hasFailures) 0.0 + else{ + val userLevelAssumptions = toUserLevelNodes(assumptions) + UserLevelDependencyAnalysisNode.extractNonExplicitAssumptionNodes(userLevelAssumptions).size.toDouble / userLevelAssumptions.size.toDouble + } + } + + val proofQualityLea = proofQualityPerAssertion.sum / relevantDependenciesPerAssertion.keys.size.toDouble val verificationProgressLea = specQuality * proofQualityLea val info = { @@ -260,16 +272,16 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen s"\tVerification Annotations:\n\t\t${UserLevelDependencyAnalysisNode.mkString(uncoveredVerificationAnnotations, "\n\t\t")}" + "\n" + s"\tSource Code:\n\t\t${UserLevelDependencyAnalysisNode.mkString(uncoveredSourceCodeStmts, "\n\t\t")}" + "\n" + "\n" + - s"Fully verified assertions:\n\t${assertionsWithoutExplicitAssumptions.mkString("\n\t")}" + "\n\n" + - s"Assertions depending on explicit assumptions:\n\t${relevantDependenciesPerAssertion.keySet.diff(assertionsWithoutExplicitAssumptions.toSet).mkString("\n\t")}" + "\n\n" + - s"Failing assertions:\n\tTODO" + "\n\n" + // TODO ake + s"Fully verified assertions:\n\t${UserLevelDependencyAnalysisNode.mkString(fullyVerifiedAssertions, "\n\t")}" + "\n\n" + + s"Assertions depending on explicit assumptions:\n\t${UserLevelDependencyAnalysisNode.mkString(assertionsWithExplicitDeps, "\n\t")}" + "\n\n" + + s"Failing assertions:\n\t${UserLevelDependencyAnalysisNode.mkString(assertionsWithFailures, "\n\t")}\n\n" + "\n" + s"#Verification Errors: ${errors.size}" + "\n\n" + "\n" + - s"Verification Progress (Peter):\n\t${coveredSourceCodeStmts.size}/(${coveredSourceCodeStmts.size + uncoveredSourceCodeStmts.size}) * " + - s"${assertionsWithoutExplicitAssumptions.size - errors.size.toDouble}/${relevantDependenciesPerAssertion.size} = $verificationProgressPeter" + "\n" + - s"Verification Progress (Lea):\n\t${coveredSourceCodeStmts.size}/(${coveredSourceCodeStmts.size + uncoveredSourceCodeStmts.size}) * " + - s"$proofQualityNumerator/${relevantDependenciesPerAssertion.keys.size} = $verificationProgressLea" + "\n" + s"Verification Progress (Peter):\n\t${coveredSourceCodeStmts.size}/${coveredSourceCodeStmts.size + uncoveredSourceCodeStmts.size} * " + + s"${fullyVerifiedAssertions.size}/${relevantDependenciesPerAssertion.keySet.size} = $verificationProgressPeter" + "\n" + + s"Verification Progress (Lea):\n\t${coveredSourceCodeStmts.size}/${coveredSourceCodeStmts.size + uncoveredSourceCodeStmts.size} * " + + s"${proofQualityPerAssertion.sum}/${relevantDependenciesPerAssertion.keys.size} = $verificationProgressLea" + "\n" } (verificationProgressPeter, verificationProgressLea, info) } @@ -280,6 +292,12 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen .toList.sortBy(_._2).reverse } + def computeFailureRanking(): List[(String, Int)] = { + toUserLevelNodes(getNodesWithIdenticalSource(getAssertionNodesWithFailures.map(_.asInstanceOf[DependencyAnalysisNode]))) + .map(node => (node.toString, getAllNonInternalDependents(node.lowerLevelNodes.map(_.id)).size)) + .toList.sortBy(_._2).reverse + } + def computeUncoveredStatements(): Int = { val allSourceCodeStmts = UserLevelDependencyAnalysisNode.extractSourceCodeNodes(toUserLevelNodes(getNonInternalAssumptionNodes)) val coveredSourceCodeStmts = toUserLevelNodes(getAllNonInternalDependencies(getNodesWithIdenticalSource(getNonInternalAssertionNodes).map(_.id))) diff --git a/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala b/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala index 012be3449..472f995a4 100644 --- a/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala +++ b/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala @@ -60,6 +60,8 @@ case class UserLevelDependencyAnalysisNode(source: AnalysisSourceInfo, lowerLeve lazy val assumptionTerm: Term = And(lowLevelAssumptionNodes.map(_.getTerm)) lazy val assertionTerm: Term = And(lowLevelAssertionNodes.map(_.getTerm)) + lazy val hasFailures: Boolean = lowerLevelNodes.filter(_.isInstanceOf[GeneralAssertionNode]).map(_.asInstanceOf[GeneralAssertionNode]).exists(_.hasFailed) + override def toString: String = source.toString From c75b61798305cce3a5b92b76573c706b499201b1 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 12 Jan 2026 16:14:02 +0100 Subject: [PATCH 298/474] fix for Gobra --- .../dependencyAnalysis/UserLevelDependencyAnalysisNode.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala b/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala index 472f995a4..dbab591d3 100644 --- a/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala +++ b/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala @@ -8,8 +8,8 @@ import viper.silver.ast.Position object UserLevelDependencyAnalysisNode { def from(dependencyNodes: Iterable[DependencyAnalysisNode]): Set[UserLevelDependencyAnalysisNode] = { - val res = dependencyNodes.groupBy(_.sourceInfo.getTopLevelSource).map { case (topLevelSource, nodes) => - UserLevelDependencyAnalysisNode(topLevelSource, nodes.toSet) + val res = dependencyNodes.groupBy(_.sourceInfo.getTopLevelSource.toString).map { case (_, nodes) => + UserLevelDependencyAnalysisNode(nodes.head.sourceInfo.getTopLevelSource, nodes.toSet) // TODO ake }.toSet res } From 4ce35a341222f27c3b62b4e617d728f25bc5b1e3 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 13 Jan 2026 10:57:33 +0100 Subject: [PATCH 299/474] handle verification failures via infeasible path and assert failed nodes --- src/main/scala/Config.scala | 2 ++ src/main/scala/decider/Decider.scala | 16 ++++++++++++- .../DependencyAnalysisNode.scala | 15 ++++++++++-- .../DependencyAnalyzer.scala | 16 +++++++++++++ .../DependencyGraphInterpreter.scala | 6 +++++ src/main/scala/rules/ChunkSupporter.scala | 16 +++++++++++-- src/main/scala/rules/Evaluator.scala | 24 ++++++++++++++++--- src/main/scala/rules/Executor.scala | 16 ++++++++++--- src/main/scala/rules/MagicWandSupporter.scala | 4 +++- .../scala/rules/QuantifiedChunkSupport.scala | 22 +++++++++++++---- 10 files changed, 121 insertions(+), 16 deletions(-) diff --git a/src/main/scala/Config.scala b/src/main/scala/Config.scala index 535eac819..20d650f94 100644 --- a/src/main/scala/Config.scala +++ b/src/main/scala/Config.scala @@ -729,6 +729,8 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { noshort = true ) + lazy val enableDependencyAnalysisFailureHandling: Boolean = enableDependencyAnalysis() && disableInfeasibilityChecks() && numberOfErrorsToReport() == 0 + val dependencyAnalysisPostProcessingMode: ScallopOption[Int] = opt[Int]("dependencyAnalysisPostProcessingMode", descr = "Postprocessing mode: 0=default, 1=disable memory footprint optimizations, 2=disable joining of graphs and all of 1 (does not compute dependencies between methods), 3=disable transitive edges and all of 2 (UNSOUND)", default = Some(0), diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 0ede38574..fb516836e 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -76,6 +76,7 @@ trait Decider { def check(t: Term, timeout: Int, assumptionType: AssumptionType=AssumptionType.Implicit): Boolean def checkAndGetInfeasibilityNode(t: Term, timeout: Int, assumptionType: AssumptionType=AssumptionType.Implicit): (Boolean, Option[Int]) + def handleVerificationFailure(assertionType: AssumptionType): Unit /* TODO: Consider changing assert such that * 1. It passes State and Operations to the continuation @@ -512,6 +513,19 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => (success, infeasibilityNodeId) } + /* Verification failures are represented by an "assert failed" node and an "assume false" node which is used as a dependency for the remainder of the path */ + def handleVerificationFailure(assertionType: AssumptionType): Unit = { + if(!Verifier.config.enableDependencyAnalysis()) return + + val assertFailedNodeId = dependencyAnalyzer.addAssertFailedNode(analysisSourceInfoStack.getFullSourceInfo, assertionType) + + val assumeFalseNodeId = dependencyAnalyzer.addInfeasibilityNode(isCheck = false, analysisSourceInfoStack.getFullSourceInfo, AssumptionType.Explicit) + assumeWithoutSmokeChecks(InsertionOrderedSet((False, DependencyAnalyzer.createAssumptionLabel(assumeFalseNodeId)))) + + dependencyAnalyzer.addDependency(assertFailedNodeId, assumeFalseNodeId) + if(pathConditions.getCurrentInfeasibilityNode.isEmpty) pathConditions.setCurrentInfeasibilityNode(assumeFalseNodeId) + } + def assert(t: Term, assumptionType: AssumptionType=AssumptionType.Implicit, timeout: Option[Int] = Verifier.config.assertTimeout.toOption)(Q: Boolean => VerificationResult): VerificationResult = { val (success, _) = deciderAssert(t, assumptionType, timeout) @@ -562,7 +576,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => val result = isPathInfeasible() || prover.assert(t, timeout, label) if(isPathInfeasible()) dependencyAnalyzer.addDependency(pcs.getCurrentInfeasibilityNode, Some(DependencyAnalyzer.getIdFromLabel(label))) - if(result) dependencyAnalyzer.processUnsatCoreAndAddDependencies(prover.getLastUnsatCore, label) + else if(result) dependencyAnalyzer.processUnsatCoreAndAddDependencies(prover.getLastUnsatCore, label) symbExLog.whenEnabled { assertRecord.statistics = Some(symbExLog.deltaStatistics(prover.statistics())) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala index 0101188f9..7cdcb69b9 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala @@ -125,12 +125,23 @@ case class LabelNode(term: Var) extends GeneralAssumptionNode { * Infeasibility nodes allow to distinguish between dependencies coming from the fact that the assertion is not reachable * on a given path and dependencies used to prove the assertion on feasible paths. */ -case class InfeasibilityNode(sourceInfo: AnalysisSourceInfo) extends GeneralAssumptionNode { +case class InfeasibilityNode(sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Implicit) extends GeneralAssumptionNode { val term: Term = False - val assumptionType: AssumptionType = AssumptionType.Implicit val isClosed: Boolean = true val description: String = "False" override def getNodeType: String = "Infeasible" override def getNodeString: String = "infeasible" } + +case class AssertFailedNode(sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Implicit) extends GeneralAssertionNode { + val term: Term = False + val isClosed: Boolean = true + val description: String = "Assert failed" + val hasFailed: Boolean = true + + override def getNodeType: String = "Failed" + override def getNodeString: String = "failed" + + override def getAssertFailedNode(): GeneralAssertionNode = this +} \ No newline at end of file diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index 1789c77cc..72f173ed1 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -39,6 +39,8 @@ trait DependencyAnalyzer { def createAssertOrCheckNode(term: Term, assumptionType: AssumptionType, analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] def addAssertFalseNode(isCheck: Boolean, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo): Option[Int] def addInfeasibilityNode(isCheck: Boolean, sourceInfo: AnalysisSourceInfo): Option[Int] + def addInfeasibilityNode(isCheck: Boolean, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] + def addAssertFailedNode(sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] def addDependency(source: Option[Int], dest: Option[Int]): Unit def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit @@ -330,6 +332,18 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { Some(node.id) } + override def addInfeasibilityNode(isCheck: Boolean, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { + val node = InfeasibilityNode(sourceInfo, assumptionType) + addNode(node) + Some(node.id) + } + + override def addAssertFailedNode(sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { + val node = AssertFailedNode(sourceInfo, assumptionType) + addNode(node) + Some(node.id) + } + // adding dependencies override def addDependency(source: Option[Int], dest: Option[Int]): Unit = { @@ -482,6 +496,8 @@ class NoDependencyAnalyzer extends DependencyAnalyzer { override def createAssertOrCheckNode(term: Term, assumptionType: AssumptionType, analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] = None override def addAssertFalseNode(isCheck: Boolean, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo): Option[Int] = None override def addInfeasibilityNode(isCheck: Boolean, sourceInfo: AnalysisSourceInfo): Option[Int] = None + override def addInfeasibilityNode(isCheck: Boolean, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None + override def addAssertFailedNode(sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None override def addDependency(source: Option[Int], dest: Option[Int]): Unit = {} override def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit = {} diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 3c797680c..9d3be5fc3 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -219,10 +219,16 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen def computeVerificationProgress(): (Double, Double, String) = { val allAssertions = toUserLevelNodes(getNonInternalAssertionNodes) + println(s"#assertions: ${allAssertions.size}") + + val startTime = System.nanoTime() + // TODO ake: this is suuuper slow. Can we reuse previously computed results? Caching? val relevantDependenciesPerAssertion = allAssertions .map(ass => (ass, getAllNonInternalDependencies(getNodesWithIdenticalSource(ass.lowerLevelNodes).map(_.id)))).toMap .filter{case (assertion, assumptions) => assumptions.nonEmpty || assertion.hasFailures} // filter out trivial assertions like `assert true` + val endTime = System.nanoTime() + println(s"Runtime of computing dependencies per assertion: ${(endTime-startTime)/1e6}ms") // TODO ake: what to do with partially verified assertions? Currently, we take them into account for spec quality but // consider the assertion to not be verified at all for proof quality. diff --git a/src/main/scala/rules/ChunkSupporter.scala b/src/main/scala/rules/ChunkSupporter.scala index c75423feb..e943dcf1a 100644 --- a/src/main/scala/rules/ChunkSupporter.scala +++ b/src/main/scala/rules/ChunkSupporter.scala @@ -151,7 +151,13 @@ object chunkSupporter extends ChunkSupportRules { else Success() // TODO: Mark branch as dead? case _ => - createFailure(ve, v1, s1, "consuming chunk", true) + val failure = createFailure(ve, v1, s1, "consuming chunk", true) + if(Verifier.config.enableDependencyAnalysisFailureHandling){ + v1.decider.handleVerificationFailure(assumptionType) + failure combine QS(s1, s1.h, None, v1) + }else{ + failure + } } } )(Q) @@ -275,7 +281,13 @@ object chunkSupporter extends ChunkSupportRules { Success() // TODO: Mark branch as dead? } case _ => - createFailure(ve, v, s, "looking up chunk", true) + val failure = createFailure(ve, v, s, "looking up chunk", true) + if(Verifier.config.enableDependencyAnalysisFailureHandling){ + v.decider.handleVerificationFailure(assumptionType) + failure combine Q(s, False /* FIXME ake */, v) + }else{ + failure + } } } diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index ddf0489f1..0f5a236d1 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -240,7 +240,13 @@ object evaluator extends EvaluationRules { val toAssert = IsPositive(totalPermissions.replace(`?r`, tRcvr)) v1.decider.assert(toAssert) { case false => - createFailure(pve dueTo InsufficientPermission(fa), v1, s1, toAssert, Option.when(withExp)(perms.IsPositive(ast.CurrentPerm(fa)())())) + val failure = createFailure(pve dueTo InsufficientPermission(fa), v1, s1, toAssert, Option.when(withExp)(perms.IsPositive(ast.CurrentPerm(fa)())())) + if(Verifier.config.enableDependencyAnalysisFailureHandling){ + v1.decider.handleVerificationFailure(AssumptionType.Implicit) + failure combine Q(s, Lookup(fa.field.name, fvfDef.sm, tRcvr), newFa, v1) + }else{ + failure + } case true => val fvfLookup = Lookup(fa.field.name, fvfDef.sm, tRcvr) val fr1 = s1.functionRecorder.recordSnapshot(fa, v1.decider.pcs.branchConditions, fvfLookup).recordFvfAndDomain(fvfDef) @@ -270,7 +276,13 @@ object evaluator extends EvaluationRules { } v1.decider.assert(permCheck) { case false => - createFailure(pve dueTo InsufficientPermission(fa), v1, s1, permCheck, permCheckExp) + val failure = createFailure(pve dueTo InsufficientPermission(fa), v1, s1, permCheck, permCheckExp) + if(Verifier.config.enableDependencyAnalysisFailureHandling){ + v1.decider.handleVerificationFailure(AssumptionType.Implicit) + failure combine Q(s, Lookup(fa.field.name, relevantChunks.head.fvf, tRcvr), newFa, v1) + }else{ + failure + } case true => val smLookup = Lookup(fa.field.name, relevantChunks.head.fvf, tRcvr) val fr2 = @@ -302,7 +314,13 @@ object evaluator extends EvaluationRules { } v1.decider.assert(permCheck) { case false => - createFailure(pve dueTo InsufficientPermission(fa), v1, s2, permCheck, permCheckExp) + val failure = createFailure(pve dueTo InsufficientPermission(fa), v1, s2, permCheck, permCheckExp) + if(Verifier.config.enableDependencyAnalysisFailureHandling){ + v1.decider.handleVerificationFailure(AssumptionType.Implicit) + failure combine Q(s, Lookup(fa.field.name, smDef1.sm, tRcvr), newFa, v1) + }else{ + failure + } case true => val smLookup = Lookup(fa.field.name, smDef1.sm, tRcvr) val fr2 = diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index d4c8dd8b2..618f74fa3 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -333,8 +333,11 @@ object executor extends ExecutionRules { val sepIdentifier = v.symbExLog.openScope(new ExecuteRecord(stmt, s, v.decider.pcs)) val sourceInfo = StmtAnalysisSourceInfo(stmt) v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo) - if(Verifier.config.disableInfeasibilityChecks() && Verifier.config.enableDependencyAnalysis() && - v.decider.pcs.getCurrentInfeasibilityNode.isDefined && !alwaysExecute(stmt)){ + if(false /* TODO ake: Verifier.config.disableInfeasibilityChecks() && Verifier.config.enableDependencyAnalysis() && + v.decider.pcs.getCurrentInfeasibilityNode.isDefined && !alwaysExecute(stmt)*/){ + // TODO ake: this might be used as a performance optimization but then we would need to figure out + // which statements enforce some kind of proof obligations and which do not. Currently, all statements + // are considered to enforce some proof obligation. v.decider.dependencyAnalyzer.addInfeasibilityDepToStmt(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, AssumptionType.Implicit) v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) @@ -454,7 +457,14 @@ object executor extends ExecutionRules { val s5 = if (withExp) s4.copy(oldHeaps = s4.oldHeaps + (debugHeapName -> magicWandSupporter.getEvalHeap(s4))) else s4 Q(s5, v2) case (Incomplete(_, _), s3, _) => - createFailure(pve dueTo InsufficientPermission(fa), v2, s3, "sufficient permission")}})) + val failure = createFailure(pve dueTo InsufficientPermission(fa), v2, s3, "sufficient permission") + if(Verifier.config.enableDependencyAnalysisFailureHandling){ + v2.decider.handleVerificationFailure(AssumptionType.Implicit) + failure combine Q(s3, v2) + }else{ + failure + } + }})) case ass @ ast.FieldAssign(fa @ ast.FieldAccess(eRcvr, field), rhs) => assert(!s.exhaleExt) diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 46a33b49a..aaffde4e1 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -403,7 +403,7 @@ object magicWandSupporter extends SymbolicExecutionRules { // The proof script should transform the current state such that we can consume the wand's RHS. val prevSourceInfo = v2.decider.analysisSourceInfoStack.getAnalysisSourceInfos v2.decider.analysisSourceInfoStack.setAnalysisSourceInfo(List.empty) - executor.exec(s2, proofScriptCfg, v2)((proofScriptState, proofScriptVerifier) => { + val result = executor.exec(s2, proofScriptCfg, v2)((proofScriptState, proofScriptVerifier) => { v2.decider.analysisSourceInfoStack.setAnalysisSourceInfo(prevSourceInfo) // Consume the wand's RHS and produce a snapshot which records all the values of variables on the RHS. // This part indirectly calls the methods `this.transfer` and `this.consumeFromMultipleHeaps`. @@ -415,6 +415,8 @@ object magicWandSupporter extends SymbolicExecutionRules { createWandChunkAndRecordResults(s3.copy(exhaleExt = false, oldHeaps = s.oldHeaps), freshSnapRoot, snapRhs.get, v3, assumptionType) }) }) + v2.decider.analysisSourceInfoStack.setAnalysisSourceInfo(prevSourceInfo) + result }) }) diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 0b02bfc87..584ea68c4 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -1443,8 +1443,16 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { } else { Q(s2, h3, None, v) } - case (Incomplete(_, _), s2, _) => - createFailure(pve dueTo insufficientPermissionReason, v, s2, "QP consume")} + case (Incomplete(_, _), s2, _) => { + val failure = createFailure(pve dueTo insufficientPermissionReason, v, s2, "QP consume") + if (Verifier.config.enableDependencyAnalysisFailureHandling) { + v.decider.handleVerificationFailure(assumptionType) + failure combine Q(s2, s2.h, None, v) + } else { + failure + } + } + } } case false => createFailure(pve dueTo notInjectiveReason, v, s, receiverInjectivityCheck, "QP receiver injective")} @@ -1574,12 +1582,18 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { } else { Q(s1, h1, None, v) } - case (Incomplete(_, _), _, _) => - resourceAccess match { + case (Incomplete(_, _), s1, _) => + val failure = resourceAccess match { case locAcc: ast.LocationAccess => createFailure(pve dueTo InsufficientPermission(locAcc), v, s, "single QP consume") case wand: ast.MagicWand => createFailure(pve dueTo MagicWandChunkNotFound(wand), v, s, "single QP consume") case _ => sys.error(s"Found resource $resourceAccess, which is not yet supported as a quantified resource.") } + if(Verifier.config.enableDependencyAnalysisFailureHandling){ + v.decider.handleVerificationFailure(assumptionType) + failure combine Q(s1, s1.h, None, v) + }else{ + failure + } } } } From 9964848605c2af0fa6b6e67282e486f558d8fc1b Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 13 Jan 2026 13:53:12 +0100 Subject: [PATCH 300/474] minor fix --- .../DependencyAnalysisUserTool.scala | 2 +- .../DependencyGraphInterpreter.scala | 17 +++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala index 00cf4e03a..a26f6600a 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala @@ -274,6 +274,6 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete println(s"Members and the number of uncovered statements:\n\t${memberCoverageRanking.mkString("\n\t")}\n") val errorRanking = fullGraphInterpreter.computeFailureRanking() - println(s"Errors:\n\t${errorRanking.mkString("\n\t")}\n") + println(s"Errors and the number of dependents:\n\t${errorRanking.mkString("\n\t")}\n") } } diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 9d3be5fc3..32911abc0 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -246,17 +246,18 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen val uncoveredSourceCodeStmts = uncoveredNodes.diff(uncoveredExplicitSources).diff(uncoveredVerificationAnnotations) // assertions - val assertionsWithFailures = relevantDependenciesPerAssertion.keySet.filter(_.hasFailures) - val assertionsWithExplicitDeps = relevantDependenciesPerAssertion.filter(deps => deps._2.exists(d => AssumptionType.explicitAssumptionTypes.contains(d.assumptionType))).keySet.diff(assertionsWithFailures) - val fullyVerifiedAssertions = relevantDependenciesPerAssertion.keySet.diff(assertionsWithFailures).diff(assertionsWithExplicitDeps) + val relevantAssertions = relevantDependenciesPerAssertion // TODO ake: maybe .filter(_._1.lowLevelAssertionNodes.exists(n => !n.isInstanceOf[SimpleCheckNode])) + val assertionsWithFailures = relevantAssertions.keySet.filter(_.hasFailures) + val assertionsWithExplicitDeps = relevantAssertions.filter(deps => deps._2.exists(d => AssumptionType.explicitAssumptionTypes.contains(d.assumptionType))).keySet.diff(assertionsWithFailures) + val fullyVerifiedAssertions = relevantAssertions.keySet.diff(assertionsWithFailures).diff(assertionsWithExplicitDeps) // Peter's metric val specQuality = coveredSourceCodeStmts.size.toDouble / (coveredSourceCodeStmts.size.toDouble + uncoveredSourceCodeStmts.size.toDouble) - val proofQualityPeter = fullyVerifiedAssertions.size.toDouble / relevantDependenciesPerAssertion.keySet.size.toDouble + val proofQualityPeter = fullyVerifiedAssertions.size.toDouble / relevantAssertions.keySet.size.toDouble val verificationProgressPeter = specQuality * proofQualityPeter // Lea's metric - val proofQualityPerAssertion = relevantDependenciesPerAssertion.map { case (assertion, assumptions) => + val proofQualityPerAssertion = relevantAssertions.map { case (assertion, assumptions) => if(assertion.hasFailures) 0.0 else{ val userLevelAssumptions = toUserLevelNodes(assumptions) @@ -264,7 +265,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen } } - val proofQualityLea = proofQualityPerAssertion.sum / relevantDependenciesPerAssertion.keys.size.toDouble + val proofQualityLea = proofQualityPerAssertion.sum / relevantAssertions.keys.size.toDouble val verificationProgressLea = specQuality * proofQualityLea val info = { @@ -285,9 +286,9 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen s"#Verification Errors: ${errors.size}" + "\n\n" + "\n" + s"Verification Progress (Peter):\n\t${coveredSourceCodeStmts.size}/${coveredSourceCodeStmts.size + uncoveredSourceCodeStmts.size} * " + - s"${fullyVerifiedAssertions.size}/${relevantDependenciesPerAssertion.keySet.size} = $verificationProgressPeter" + "\n" + + s"${fullyVerifiedAssertions.size}/${relevantAssertions.keySet.size} = $verificationProgressPeter" + "\n" + s"Verification Progress (Lea):\n\t${coveredSourceCodeStmts.size}/${coveredSourceCodeStmts.size + uncoveredSourceCodeStmts.size} * " + - s"${proofQualityPerAssertion.sum}/${relevantDependenciesPerAssertion.keys.size} = $verificationProgressLea" + "\n" + s"${proofQualityPerAssertion.sum}/${relevantAssertions.keys.size} = $verificationProgressLea" + "\n" } (verificationProgressPeter, verificationProgressLea, info) } From af67cfd9c43c70c85f20db08eabfdf19fd4311c1 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 13 Jan 2026 15:32:09 +0100 Subject: [PATCH 301/474] minor fix --- src/main/scala/Config.scala | 2 +- .../dependencyAnalysis/DependencyAnalysisUserTool.scala | 6 ++++-- .../scala/dependencyAnalysis/DependencyAnalyzer.scala | 4 ++-- src/main/scala/rules/Executor.scala | 8 ++++++-- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/main/scala/Config.scala b/src/main/scala/Config.scala index 20d650f94..ca8e15e25 100644 --- a/src/main/scala/Config.scala +++ b/src/main/scala/Config.scala @@ -729,7 +729,7 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { noshort = true ) - lazy val enableDependencyAnalysisFailureHandling: Boolean = enableDependencyAnalysis() && disableInfeasibilityChecks() && numberOfErrorsToReport() == 0 + lazy val enableDependencyAnalysisFailureHandling: Boolean = enableDependencyAnalysis() && disableInfeasibilityChecks() // FIXME ake: && numberOfErrorsToReport() == 0 val dependencyAnalysisPostProcessingMode: ScallopOption[Int] = opt[Int]("dependencyAnalysisPostProcessingMode", descr = "Postprocessing mode: 0=default, 1=disable memory footprint optimizations, 2=disable joining of graphs and all of 1 (does not compute dependencies between methods), 3=disable transitive edges and all of 2 (UNSOUND)", diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala index a26f6600a..f4df95d50 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala @@ -177,6 +177,8 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete println(s"\nAll Dependencies (${timeAll}ms):\n\t${getSourceInfoString(allDependencies)}") println(s"\nDependencies without infeasibility (${timeWithoutInfeasibility}ms):\n\t${getSourceInfoString(allDependenciesWithoutInfeasibility)}") println(s"\nExplicit Dependencies (${timeExplicit}ms):\n\t${getSourceInfoString(explicitDependencies)}") + + if(queriedNodes.exists(_.asInstanceOf[GeneralAssertionNode].hasFailed)) println("\nQueried assertions (partially) FAILED!\n") println("Done.") } @@ -265,12 +267,12 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete } def handleVerificationGuidanceQuery(): Unit = { - val assumptionRanking = fullGraphInterpreter.computeAssumptionRanking() + val assumptionRanking = fullGraphInterpreter.computeAssumptionRanking().filter(_._2 > 0) println(s"Assumptions and the number of dependencies:\n\t${assumptionRanking.mkString("\n\t")}\n") val memberCoverageRanking = memberInterpreters.filter(mInterpreter => mInterpreter.getMember.isDefined && mInterpreter.getMember.get.isInstanceOf[Method]) .map(mInterpreter => (mInterpreter.getMember.get.name, mInterpreter.computeUncoveredStatements())) - .toList.sortBy(_._2).reverse + .toList.filter(_._2 > 0).sortBy(_._2).reverse println(s"Members and the number of uncovered statements:\n\t${memberCoverageRanking.mkString("\n\t")}\n") val errorRanking = fullGraphInterpreter.computeFailureRanking() diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index 72f173ed1..7a588df19 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -446,9 +446,9 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { } nodesBySource foreach { case ((_, sourceInfo, _, assumptionType), nodes) => - val assertionNodes = nodes.filter(_.isInstanceOf[GeneralAssertionNode]) + val assertionNodes = nodes.filter(_.isInstanceOf[GeneralAssertionNode]).map(_.asInstanceOf[GeneralAssertionNode]) if (assertionNodes.nonEmpty) { - val newNode = SimpleAssertionNode(True, assumptionType, sourceInfo, isClosed = false) + val newNode = SimpleAssertionNode(True, assumptionType, sourceInfo, isClosed = false, hasFailed=assertionNodes.exists(_.hasFailed)) assertionNodes foreach (n => nodeMap.put(n.id, newNode.id)) mergedGraph.addNode(newNode) } diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 618f74fa3..e801fdb2e 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -546,10 +546,14 @@ object executor extends ExecutionRules { case assert @ ast.Assert(a: ast.FalseLit) if !s.isInPackage => /* "assert false" triggers a smoke check. If successful, we backtrack. */ executionFlowController.tryOrFail0(s.copy(h = magicWandSupporter.getEvalHeap(s)), v)((s1, v1, QS) => { + val failure = createFailure(AssertFailed(assert) dueTo AssertionFalse(a), v1, s1, False, true, Option.when(withExp)(a)) if (v1.decider.checkSmoke(isAssert=true, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit))) QS(s1.copy(h = s.h), v1) - else - createFailure(AssertFailed(assert) dueTo AssertionFalse(a), v1, s1, False, true, Option.when(withExp)(a)) + else if (Verifier.config.enableDependencyAnalysisFailureHandling) { + v1.decider.handleVerificationFailure(AssumptionType.Explicit) + failure combine QS(s1.copy(h = s.h), v1) + } else + failure })((s2, v2) => if (Verifier.config.disableInfeasibilityChecks()) Q(s2, v2) From ca821e1d6fd80e446056c92327252cc53870632e Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 14 Jan 2026 11:28:17 +0100 Subject: [PATCH 302/474] add dependency types --- .../dependencyAnalysis/AnalysisInfo.scala | 18 +++++ .../DependencyAnalysisInfo.scala | 8 +++ .../DependencyAnalyzer.scala | 11 ++- src/main/scala/rules/Executor.scala | 41 +++++------ src/main/scala/rules/MagicWandSupporter.scala | 22 +++--- src/main/scala/rules/PredicateSupporter.scala | 28 ++++---- .../dependencyAnalysisTests/viperTest.vpr | 68 +++++++++++++++---- 7 files changed, 136 insertions(+), 60 deletions(-) create mode 100644 src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala diff --git a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala index f4c13137d..1051414da 100644 --- a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala +++ b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala @@ -1,5 +1,6 @@ package viper.silicon.dependencyAnalysis + object AssumptionType extends Enumeration { type AssumptionType = Value val Explicit, LoopInvariant, PathCondition, Rewrite, Implicit /* TODO ake: rename to Stmt? */, Internal, Trigger, ExplicitPostcondition, ImplicitPostcondition, CallPostcondition, FunctionBody, Precondition = Value @@ -19,6 +20,23 @@ object AssumptionType extends Enumeration { import viper.silicon.dependencyAnalysis.AssumptionType._ import viper.silicon.decider.Decider +object DependencyType { + + val Implicit: DependencyType = DependencyType(AssumptionType.Implicit, AssumptionType.Implicit) + val Explicit: DependencyType = DependencyType(AssumptionType.Explicit, AssumptionType.Explicit) + val ExplicitAssertion: DependencyType = DependencyType(AssumptionType.Implicit, AssumptionType.Explicit) + val ExplicitAssumption: DependencyType = DependencyType(AssumptionType.Explicit, AssumptionType.Implicit) + val PathCondition: DependencyType = DependencyType(AssumptionType.PathCondition, AssumptionType.Implicit) + val Invariant: DependencyType = DependencyType(AssumptionType.LoopInvariant, AssumptionType.LoopInvariant) + val Rewrite: DependencyType = DependencyType(AssumptionType.Rewrite, AssumptionType.Rewrite) + val Internal: DependencyType = DependencyType(AssumptionType.Internal, AssumptionType.Internal) + val Trigger: DependencyType = DependencyType(AssumptionType.Trigger, AssumptionType.Trigger) + + def make(singleType: AssumptionType): DependencyType = DependencyType(singleType, singleType) + +} + +case class DependencyType(assumptionType: AssumptionType, assertionType: AssumptionType) case class AnalysisInfo(decider: Decider, dependencyAnalyzer: DependencyAnalyzer, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType) { diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala new file mode 100644 index 000000000..efcf16248 --- /dev/null +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala @@ -0,0 +1,8 @@ +package viper.silicon.dependencyAnalysis + +import viper.silver.ast.{AbstractSourcePosition, Info} + +case class DependencyAnalysisInfo(info: String, pos: AbstractSourcePosition, dependencyType: Option[DependencyType]=None) extends Info { + override val comment = Nil + override val isCached = false +} diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index 7a588df19..e2b934324 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -122,7 +122,16 @@ object DependencyAnalyzer { def extractAssumptionTypeFromInfo(info: ast.Info): Option[AssumptionType] = { val annotation = extractAnnotationFromInfo(info, assumptionTypeAnnotationKey) - if(annotation.isDefined && annotation.get.nonEmpty) AssumptionType.fromString(annotation.get.head) else None + if(annotation.isDefined && annotation.get.nonEmpty) AssumptionType.fromString(annotation.get.head) + else None + } + + def extractDependencyTypeFromInfo(info: ast.Info): Option[DependencyType] = { + val annotation = extractAnnotationFromInfo(info, assumptionTypeAnnotationKey) + val dependencyAnalysisInfo = info.getUniqueInfo[DependencyAnalysisInfo] + if(annotation.isDefined && annotation.get.nonEmpty) AssumptionType.fromString(annotation.get.head).map(DependencyType.make) + else if(dependencyAnalysisInfo.isDefined) dependencyAnalysisInfo.get.dependencyType + else None } def extractEnableAnalysisFromInfo(info: ast.Info): Option[Boolean] = { diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index e801fdb2e..49882556d 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -8,7 +8,7 @@ package viper.silicon.rules import viper.silicon.Config.JoinMode import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType -import viper.silicon.dependencyAnalysis.{DependencyAnalyzer, AssumptionType, ExpAnalysisSourceInfo, StmtAnalysisSourceInfo, TransitivityAnalysisSourceInfo} +import viper.silicon.dependencyAnalysis.{AssumptionType, DependencyAnalyzer, DependencyType, ExpAnalysisSourceInfo, StmtAnalysisSourceInfo, TransitivityAnalysisSourceInfo} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.decider.RecordedPathConditions @@ -271,7 +271,7 @@ object executor extends ExecutionRules { })}) combine executionFlowController.locally(s, v)((s0, v0) => { v0.decider.prover.comment("Loop head block: Establish invariant") - consumes(s0, invs, false, LoopInvariantNotEstablished, v0)((sLeftover, _, v1) => { + consumes(s0, invs, false, LoopInvariantNotEstablished, v0, AssumptionType.LoopInvariant)((sLeftover, _, v1) => { v1.decider.prover.comment("Loop head block: Execute statements of loop head block (in invariant state)") phase1data.foldLeft(Success(): VerificationResult) { case (result, _) if !result.continueVerification => result @@ -305,7 +305,7 @@ object executor extends ExecutionRules { * attempting to re-establish the invariant. */ v.decider.prover.comment("Loop head block: Re-establish invariant") - consumes(s, invs, false, e => LoopInvariantNotPreserved(e), v)((_, _, _) => + consumes(s, invs, false, e => LoopInvariantNotPreserved(e), v, AssumptionType.LoopInvariant)((_, _, _) => Success()) } } @@ -370,7 +370,10 @@ object executor extends ExecutionRules { v.decider.prover.comment("[exec]") v.decider.prover.comment(stmt.toString()) } - val annotatedAssumptionTypeOpt = DependencyAnalyzer.extractAssumptionTypeFromInfo(stmt.info) + + val annotatedDependencyTypeOpt = DependencyAnalyzer.extractDependencyTypeFromInfo(stmt.info) + val annotatedAssumptionTypeOpt = annotatedDependencyTypeOpt.map(_.assumptionType) + val annotatedAssertionTypeOpt = annotatedDependencyTypeOpt.map(_.assertionType) val executed = stmt match { case ast.Seqn(stmts, _) => @@ -476,7 +479,7 @@ object executor extends ExecutionRules { val description = s"consume ${ass.pos}: $ass" val lhsSourceInfo = TransitivityAnalysisSourceInfo(v2.decider.analysisSourceInfoStack.getFullSourceInfo, ExpAnalysisSourceInfo(fa)) v2.decider.analysisSourceInfoStack.setForcedSource(lhsSourceInfo) // splitting lhs and rhs to make permission flow analysis more precise - chunkSupporter.consume(s2, s2.h, resource, Seq(tRcvr), eRcvrNew.map(Seq(_)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), false, ve, v2, description)((s3, h3, _, v3) => { + chunkSupporter.consume(s2, s2.h, resource, Seq(tRcvr), eRcvrNew.map(Seq(_)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), false, ve, v2, description, annotatedAssertionTypeOpt.getOrElse(AssumptionType.Implicit))((s3, h3, _, v3) => { v2.decider.analysisSourceInfoStack.removeForcedSource() val (tSnap, _) = ssaifyRhs(tRhs, rhs, rhsNew, field.name, field.typ, v3, s3, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit)) val id = BasicChunkIdentifier(field.name) @@ -540,14 +543,14 @@ object executor extends ExecutionRules { case exhale @ ast.Exhale(a) => val pve = ExhaleFailed(exhale) - consume(s, a, false, pve, v, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit))((s1, _, v1) => + consume(s, a, false, pve, v, annotatedAssertionTypeOpt.getOrElse(AssumptionType.Explicit))((s1, _, v1) => Q(s1, v1)) case assert @ ast.Assert(a: ast.FalseLit) if !s.isInPackage => /* "assert false" triggers a smoke check. If successful, we backtrack. */ executionFlowController.tryOrFail0(s.copy(h = magicWandSupporter.getEvalHeap(s)), v)((s1, v1, QS) => { val failure = createFailure(AssertFailed(assert) dueTo AssertionFalse(a), v1, s1, False, true, Option.when(withExp)(a)) - if (v1.decider.checkSmoke(isAssert=true, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit))) + if (v1.decider.checkSmoke(isAssert=true, annotatedAssertionTypeOpt.getOrElse(AssumptionType.Explicit))) QS(s1.copy(h = s.h), v1) else if (Verifier.config.enableDependencyAnalysisFailureHandling) { v1.decider.handleVerificationFailure(AssumptionType.Explicit) @@ -563,7 +566,7 @@ object executor extends ExecutionRules { case assert @ ast.Assert(a) if Verifier.config.disableSubsumption() => val r = - consume(s, a, false, AssertFailed(assert), v, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit))((_, _, _) => + consume(s, a, false, AssertFailed(assert), v, annotatedAssertionTypeOpt.getOrElse(AssumptionType.Explicit))((_, _, _) => Success()) r combine Q(s, v) @@ -579,11 +582,11 @@ object executor extends ExecutionRules { * hUsed (reserveHeaps.head) instead of consuming them. hUsed is later discarded and replaced * by s.h. By copying hUsed to s.h the contained permissions remain available inside the wand. */ - consume(s, a, false, pve, v, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit))((s2, _, v1) => { + consume(s, a, false, pve, v, annotatedAssertionTypeOpt.getOrElse(AssumptionType.Explicit))((s2, _, v1) => { Q(s2.copy(h = s2.reserveHeaps.head), v1) }) } else - consume(s, a, false, pve, v, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit))((s1, _, v1) => { + consume(s, a, false, pve, v, annotatedAssertionTypeOpt.getOrElse(AssumptionType.Explicit))((s1, _, v1) => { val s2 = s1.copy(h = s.h, reserveHeaps = s.reserveHeaps) Q(s2, v1)}) @@ -646,7 +649,7 @@ object executor extends ExecutionRules { tArgs zip Seq.fill(tArgs.size)(None) val s2 = s1.copy(g = Store(fargs.zip(argsWithExp)), recordVisited = true) - consumes(s2, meth.pres, false, _ => pvePre, v1)((s3, _, v2) => { + consumes(s2, meth.pres, false, _ => pvePre, v1, assumptionType=annotatedAssertionTypeOpt.getOrElse(AssumptionType.Implicit))((s3, _, v2) => { v2.symbExLog.closeScope(preCondId) val postCondLog = new CommentRecord("Postcondition", s3, v2.decider.pcs) val postCondId = v2.symbExLog.openScope(postCondLog) @@ -669,14 +672,14 @@ object executor extends ExecutionRules { v.decider.startDebugSubExp() val ePerm = pap.perm val predicate = s.program.findPredicate(predicateName) - val predicateAnnotatedAssumptionType = DependencyAnalyzer.extractAssumptionTypeFromInfo(predicate.info) - val finalAssumptionType = annotatedAssumptionTypeOpt.getOrElse(predicateAnnotatedAssumptionType.getOrElse(AssumptionType.Rewrite)) + val predicateAnnotatedAssumptionType = DependencyAnalyzer.extractAssumptionTypeFromInfo(predicate.info).map(DependencyType.make) + val finalDependencyType = annotatedDependencyTypeOpt.getOrElse(predicateAnnotatedAssumptionType.getOrElse(DependencyType.Rewrite)) val pve = FoldFailed(fold) evals(s, eArgs, _ => pve, v)((s1, tArgs, eArgsNew, v1) => eval(s1, ePerm, pve, v1)((s2, tPerm, ePermNew, v2) => permissionSupporter.assertPositive(s2, tPerm, if (withExp) ePermNew.get else ePerm, pve, v2)((s3, v3) => { val wildcards = s3.constrainableARPs -- s1.constrainableARPs - predicateSupporter.fold(s3, predicate, tArgs, eArgsNew, tPerm, ePermNew, wildcards, pve, v3, finalAssumptionType)((s4, v4) => { + predicateSupporter.fold(s3, predicate, tArgs, eArgsNew, tPerm, ePermNew, wildcards, pve, v3, finalDependencyType)((s4, v4) => { v3.decider.finishDebugSubExp(s"folded ${predAcc.toString}") Q(s4, v4) } @@ -687,8 +690,8 @@ object executor extends ExecutionRules { v.decider.startDebugSubExp() val ePerm = pap.perm val predicate = s.program.findPredicate(predicateName) - val predicateAnnotatedAssumptionType = DependencyAnalyzer.extractAssumptionTypeFromInfo(predicate.info) - val finalAssumptionType = annotatedAssumptionTypeOpt.getOrElse(predicateAnnotatedAssumptionType.getOrElse(AssumptionType.Rewrite)) + val predicateAnnotatedAssumptionType = DependencyAnalyzer.extractAssumptionTypeFromInfo(predicate.info).map(DependencyType.make) + val finalDependencyType = annotatedDependencyTypeOpt.getOrElse(predicateAnnotatedAssumptionType.getOrElse(DependencyType.Rewrite)) val pve = UnfoldFailed(unfold) evals(s, eArgs, _ => pve, v)((s1, tArgs, eArgsNew, v1) => eval(s1, ePerm, pve, v1)((s2, tPerm, ePermNew, v2) => { @@ -710,7 +713,7 @@ object executor extends ExecutionRules { permissionSupporter.assertPositive(s2, tPerm, if (withExp) ePermNew.get else ePerm, pve, v2)((s3, v3) => { val wildcards = s3.constrainableARPs -- s1.constrainableARPs - predicateSupporter.unfold(s3.copy(smCache = smCache1), predicate, tArgs, eArgsNew, tPerm, ePermNew, wildcards, pve, v3, pa, finalAssumptionType)( + predicateSupporter.unfold(s3.copy(smCache = smCache1), predicate, tArgs, eArgsNew, tPerm, ePermNew, wildcards, pve, v3, pa, finalDependencyType)( (s4, v4) => { v2.decider.finishDebugSubExp(s"unfolded ${pa.toString}") Q(s4, v4) @@ -720,7 +723,7 @@ object executor extends ExecutionRules { case pckg @ ast.Package(wand, proofScript) => val pve = PackageFailed(pckg) - magicWandSupporter.packageWand(s.copy(isInPackage = true), wand, proofScript, pve, v, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Rewrite))((s1, chWand, v1) => { + magicWandSupporter.packageWand(s.copy(isInPackage = true), wand, proofScript, pve, v, annotatedDependencyTypeOpt.getOrElse(DependencyType.Rewrite))((s1, chWand, v1) => { val hOps = s1.reserveHeaps.head + chWand assert(s.exhaleExt || s1.reserveHeaps.length == 1) @@ -764,7 +767,7 @@ object executor extends ExecutionRules { case apply @ ast.Apply(e) => val pve = ApplyFailed(apply) - magicWandSupporter.applyWand(s, e, pve, v, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Rewrite))(Q) + magicWandSupporter.applyWand(s, e, pve, v, annotatedDependencyTypeOpt.getOrElse(DependencyType.Rewrite))(Q) case havoc: ast.Quasihavoc => havocSupporter.execHavoc(havoc, v, s, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit))(Q) diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index aaffde4e1..0f10a4453 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -7,11 +7,11 @@ package viper.silicon.rules import viper.silicon._ -import viper.silicon.dependencyAnalysis.AssumptionType -import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.decider.RecordedPathConditions +import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType +import viper.silicon.dependencyAnalysis.{AssumptionType, DependencyType} import viper.silicon.interfaces._ import viper.silicon.interfaces.state._ import viper.silicon.state._ @@ -239,7 +239,7 @@ object magicWandSupporter extends SymbolicExecutionRules { proofScript: ast.Seqn, pve: PartialVerificationError, v: Verifier, - assumptionType: AssumptionType = AssumptionType.Rewrite) + dependencyType: DependencyType = DependencyType.Rewrite) (Q: (State, Chunk, Verifier) => VerificationResult) : VerificationResult = { @@ -409,10 +409,10 @@ object magicWandSupporter extends SymbolicExecutionRules { // This part indirectly calls the methods `this.transfer` and `this.consumeFromMultipleHeaps`. consume( proofScriptState.copy(oldHeaps = s2.oldHeaps, reserveCfgs = proofScriptState.reserveCfgs.tail), - wand.right, true, pve, proofScriptVerifier + wand.right, true, pve, proofScriptVerifier, dependencyType.assertionType )((s3, snapRhs, v3) => { analysisLabels = v.decider.pcs.after(prePackageMark).analysisLabels - createWandChunkAndRecordResults(s3.copy(exhaleExt = false, oldHeaps = s.oldHeaps), freshSnapRoot, snapRhs.get, v3, assumptionType) + createWandChunkAndRecordResults(s3.copy(exhaleExt = false, oldHeaps = s.oldHeaps), freshSnapRoot, snapRhs.get, v3, dependencyType.assumptionType) }) }) v2.decider.analysisSourceInfoStack.setAnalysisSourceInfo(prevSourceInfo) @@ -425,7 +425,7 @@ object magicWandSupporter extends SymbolicExecutionRules { // and thus, that no wand chunk was created. In order to continue, we create one now. // Moreover, we need to set reserveHeaps to structurally match [State RHS] below. val s1 = sEmp.copy(reserveHeaps = Heap() +: Heap() +: Heap() +: s.reserveHeaps.tail) - createWandChunkAndRecordResults(s1, freshSnap(sorts.Snap, v), freshSnap(sorts.Snap, v), v, assumptionType) + createWandChunkAndRecordResults(s1, freshSnap(sorts.Snap, v), freshSnap(sorts.Snap, v), v, dependencyType.assumptionType) } // some of the analysis labels, introduced while verifying the package statement, might be needed later on -> reassume them @@ -472,13 +472,13 @@ object magicWandSupporter extends SymbolicExecutionRules { wand: ast.MagicWand, pve: PartialVerificationError, v: Verifier, - assumptionType: AssumptionType = AssumptionType.Rewrite) + dependencyType: DependencyType = DependencyType.Rewrite) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { // Consume the magic wand instance "A --* B". - consume(s, wand, true, pve, v)((s1, snapWand, v1) => { + consume(s, wand, true, pve, v, dependencyType.assertionType)((s1, snapWand, v1) => { // Consume the wand's LHS "A". - consume(s1, wand.left, true, pve, v1)((s2, snapLhs, v2) => { + consume(s1, wand.left, true, pve, v1, dependencyType.assertionType)((s2, snapLhs, v2) => { /* It is assumed that snap and MagicWandSnapshot.abstractLhs are structurally the same. * Equating the two snapshots is sound iff a wand is applied only once. * The old solution in this case did use this assumption: @@ -495,13 +495,13 @@ object magicWandSupporter extends SymbolicExecutionRules { case SortWrapper(snapshot: MagicWandSnapshot, _) => snapshot.applyToMWSF(snapLhs.get) // Fallback solution for quantified magic wands case predicateLookup: PredicateLookup => - v2.decider.assume(snapLhs.get === First(snapWand.get), Option.when(withExp)(DebugExp.createInstance("Magic wand snapshot", isInternal_ = true)), assumptionType) + v2.decider.assume(snapLhs.get === First(snapWand.get), Option.when(withExp)(DebugExp.createInstance("Magic wand snapshot", isInternal_ = true)), dependencyType.assumptionType) Second(predicateLookup) case _ => snapWand.get } // Produce the wand's RHS. - produce(s3.copy(conservingSnapshotGeneration = true), toSf(magicWandSnapshotLookup), wand.right, pve, v2, assumptionType)((s4, v3) => { + produce(s3.copy(conservingSnapshotGeneration = true), toSf(magicWandSnapshotLookup), wand.right, pve, v2, dependencyType.assumptionType)((s4, v3) => { // Recreate old state without the magic wand, and the state with the oldHeap called lhs. val s5 = s4.copy(g = s1.g, conservingSnapshotGeneration = s3.conservingSnapshotGeneration) diff --git a/src/main/scala/rules/PredicateSupporter.scala b/src/main/scala/rules/PredicateSupporter.scala index ab60ed535..1e0e00ffd 100644 --- a/src/main/scala/rules/PredicateSupporter.scala +++ b/src/main/scala/rules/PredicateSupporter.scala @@ -6,10 +6,9 @@ package viper.silicon.rules -import viper.silicon.dependencyAnalysis.AssumptionType -import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp +import viper.silicon.dependencyAnalysis.{AssumptionType, DependencyType} import viper.silicon.interfaces.VerificationResult import viper.silicon.resources.PredicateID import viper.silicon.state._ @@ -30,7 +29,7 @@ trait PredicateSupportRules extends SymbolicExecutionRules { constrainableWildcards: InsertionOrderedSet[Var], pve: PartialVerificationError, v: Verifier, - assumptionType: AssumptionType = AssumptionType.Rewrite) + dependencyType: DependencyType = DependencyType.Rewrite) (Q: (State, Verifier) => VerificationResult) : VerificationResult @@ -44,7 +43,7 @@ trait PredicateSupportRules extends SymbolicExecutionRules { pve: PartialVerificationError, v: Verifier, pa: ast.PredicateAccess, - assumptionType: AssumptionType = AssumptionType.Rewrite) + dependencyType: DependencyType = DependencyType.Rewrite) (Q: (State, Verifier) => VerificationResult) : VerificationResult } @@ -62,7 +61,7 @@ object predicateSupporter extends PredicateSupportRules { constrainableWildcards: InsertionOrderedSet[Var], pve: PartialVerificationError, v: Verifier, - assumptionType: AssumptionType = AssumptionType.Rewrite) + dependencyType: DependencyType = DependencyType.Rewrite) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -75,7 +74,7 @@ object predicateSupporter extends PredicateSupportRules { val s1 = s.copy(g = gIns, smDomainNeeded = true) .scalePermissionFactor(tPerm, ePerm) - consume(s1, body, true, pve, v)((s1a, snap, v1) => { + consume(s1, body, true, pve, v, dependencyType.assertionType)((s1a, snap, v1) => { if (!Verifier.config.disableFunctionUnfoldTrigger()) { val predTrigger = App(s1a.predicateData(predicate).triggerFunction, snap.get.convert(terms.sorts.Snap) +: tArgs) @@ -90,10 +89,10 @@ object predicateSupporter extends PredicateSupportRules { quantifiedChunkSupporter.singletonSnapshotMap(s2, predicate, tArgs, predSnap, v1) v1.decider.prover.comment("Definitional axioms for singleton-SM's value") val debugExp = Option.when(withExp)(DebugExp.createInstance("Definitional axioms for singleton-SM's value", true)) - v1.decider.assumeDefinition(smValueDef, debugExp, assumptionType) + v1.decider.assumeDefinition(smValueDef, debugExp, dependencyType.assumptionType) val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk( - formalArgs, Option.when(withExp)(predicate.formalArgs), predicate, tArgs, eArgs, tPerm, ePerm, sm, s.program, v1, assumptionType, isExhale=false) + formalArgs, Option.when(withExp)(predicate.formalArgs), predicate, tArgs, eArgs, tPerm, ePerm, sm, s.program, v1, dependencyType.assumptionType, isExhale=false) val h3 = s2.h + ch val smDef = SnapshotMapDefinition(predicate, sm, Seq(smValueDef), Seq()) val smCache = if (s2.heapDependentTriggers.contains(predicate)) { @@ -118,7 +117,7 @@ object predicateSupporter extends PredicateSupportRules { functionRecorder = s2.functionRecorder.recordFvfAndDomain(smDef)) Q(s3, v1) } else { - val ch = BasicChunk(PredicateID, BasicChunkIdentifier(predicate.name), tArgs, eArgs, snap.get.convert(sorts.Snap), None, tPerm, ePerm, v1.decider.getAnalysisInfo(assumptionType)) + val ch = BasicChunk(PredicateID, BasicChunkIdentifier(predicate.name), tArgs, eArgs, snap.get.convert(sorts.Snap), None, tPerm, ePerm, v1.decider.getAnalysisInfo(dependencyType.assumptionType)) val s3 = s2.copy(g = s.g, smDomainNeeded = s.smDomainNeeded, permissionScalingFactor = s.permissionScalingFactor, @@ -139,7 +138,7 @@ object predicateSupporter extends PredicateSupportRules { pve: PartialVerificationError, v: Verifier, pa: ast.PredicateAccess, - assumptionType: AssumptionType = AssumptionType.Rewrite) + dependencyType: DependencyType = DependencyType.Rewrite) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -165,11 +164,12 @@ object predicateSupporter extends PredicateSupportRules { true, None, pve, - v + v, + assumptionType=dependencyType.assumptionType )((s2, h2, snap, v1) => { val s3 = s2.copy(g = gIns, h = h2) .setConstrainable(constrainableWildcards, false) - produce(s3, toSf(snap.get), body, pve, v1, assumptionType)((s4, v2) => { + produce(s3, toSf(snap.get), body, pve, v1, dependencyType.assertionType)((s4, v2) => { v2.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterUnfold) if (!Verifier.config.disableFunctionUnfoldTrigger()) { val predicateTrigger = @@ -186,10 +186,10 @@ object predicateSupporter extends PredicateSupportRules { } else { val ve = pve dueTo InsufficientPermission(pa) val description = s"consume ${pa.pos}: $pa" - chunkSupporter.consume(s1, s1.h, predicate, tArgs, eArgs, s1.permissionScalingFactor, s1.permissionScalingFactorExp, true, ve, v, description)((s2, h1, snap, v1) => { + chunkSupporter.consume(s1, s1.h, predicate, tArgs, eArgs, s1.permissionScalingFactor, s1.permissionScalingFactorExp, true, ve, v, description, dependencyType.assertionType)((s2, h1, snap, v1) => { val s3 = s2.copy(g = gIns, h = h1) .setConstrainable(constrainableWildcards, false) - produce(s3, toSf(snap.get), body, pve, v1, assumptionType)((s4, v2) => { + produce(s3, toSf(snap.get), body, pve, v1, dependencyType.assertionType)((s4, v2) => { v2.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterUnfold) if (!Verifier.config.disableFunctionUnfoldTrigger()) { val predicateTrigger = diff --git a/src/test/resources/dependencyAnalysisTests/viperTest.vpr b/src/test/resources/dependencyAnalysisTests/viperTest.vpr index 4d2b3b073..2dac2bf9f 100644 --- a/src/test/resources/dependencyAnalysisTests/viperTest.vpr +++ b/src/test/resources/dependencyAnalysisTests/viperTest.vpr @@ -1,25 +1,63 @@ +field f: Int +method foo(a: Int, x: Ref) returns (res: Int) + ensures res >= 0 +{ + if(a < 0){ + res := x.f + }else{ + res := a + } +} -method foo(a: Int) returns (res: Int) - requires a > 0 - ensures res > 0 +method aTest() + ensures false { - res := a + 1 + assert false } +method bar() +{ + var a: Int + assert a > 0 -method bar(a: Int) returns (res: Int) - requires a > 0 - ensures res > 0 + assert a >= 0 + + a := 0 + + assert a == 0 +} + +method permTest(x: Ref) + requires acc(x.f) { - res := foo(a+1) - assert res >= 0 + x.f := 0 +} + +predicate p(a: Int, x: Ref){ + acc(x.f, 1/2) && a == x.f +} + +method pTest(x: Ref, a: Int) + requires p(a, x) + requires a == 0 +{ + unfold p(a, x) - var b: Int - assume b > 0 - assume b < 100 - assert b >= 0 + assert a == x.f - b := 0 - b := 1 + assert a > 0 + + var i: Int + i := 0 + assert i == 0 } + +method unreachTest(a: Int, x: Ref) + requires a > 0 +{ + if(a < 0){ + x.f := a + assert false + } +} \ No newline at end of file From e15ae1f39757c717c9eb3b6796f6bfec9896a69f Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 14 Jan 2026 11:37:45 +0100 Subject: [PATCH 303/474] revert --- src/main/scala/Config.scala | 2 -- src/main/scala/decider/Decider.scala | 14 ---------- .../DependencyAnalysisNode.scala | 15 ++--------- .../DependencyAnalyzer.scala | 16 ------------ src/main/scala/rules/ChunkSupporter.scala | 16 ++---------- src/main/scala/rules/Evaluator.scala | 24 +++-------------- src/main/scala/rules/Executor.scala | 26 +++++-------------- src/main/scala/rules/MagicWandSupporter.scala | 4 +-- .../scala/rules/QuantifiedChunkSupport.scala | 22 +++------------- 9 files changed, 18 insertions(+), 121 deletions(-) diff --git a/src/main/scala/Config.scala b/src/main/scala/Config.scala index ca8e15e25..535eac819 100644 --- a/src/main/scala/Config.scala +++ b/src/main/scala/Config.scala @@ -729,8 +729,6 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { noshort = true ) - lazy val enableDependencyAnalysisFailureHandling: Boolean = enableDependencyAnalysis() && disableInfeasibilityChecks() // FIXME ake: && numberOfErrorsToReport() == 0 - val dependencyAnalysisPostProcessingMode: ScallopOption[Int] = opt[Int]("dependencyAnalysisPostProcessingMode", descr = "Postprocessing mode: 0=default, 1=disable memory footprint optimizations, 2=disable joining of graphs and all of 1 (does not compute dependencies between methods), 3=disable transitive edges and all of 2 (UNSOUND)", default = Some(0), diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index fb516836e..66f7baf64 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -76,7 +76,6 @@ trait Decider { def check(t: Term, timeout: Int, assumptionType: AssumptionType=AssumptionType.Implicit): Boolean def checkAndGetInfeasibilityNode(t: Term, timeout: Int, assumptionType: AssumptionType=AssumptionType.Implicit): (Boolean, Option[Int]) - def handleVerificationFailure(assertionType: AssumptionType): Unit /* TODO: Consider changing assert such that * 1. It passes State and Operations to the continuation @@ -513,19 +512,6 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => (success, infeasibilityNodeId) } - /* Verification failures are represented by an "assert failed" node and an "assume false" node which is used as a dependency for the remainder of the path */ - def handleVerificationFailure(assertionType: AssumptionType): Unit = { - if(!Verifier.config.enableDependencyAnalysis()) return - - val assertFailedNodeId = dependencyAnalyzer.addAssertFailedNode(analysisSourceInfoStack.getFullSourceInfo, assertionType) - - val assumeFalseNodeId = dependencyAnalyzer.addInfeasibilityNode(isCheck = false, analysisSourceInfoStack.getFullSourceInfo, AssumptionType.Explicit) - assumeWithoutSmokeChecks(InsertionOrderedSet((False, DependencyAnalyzer.createAssumptionLabel(assumeFalseNodeId)))) - - dependencyAnalyzer.addDependency(assertFailedNodeId, assumeFalseNodeId) - if(pathConditions.getCurrentInfeasibilityNode.isEmpty) pathConditions.setCurrentInfeasibilityNode(assumeFalseNodeId) - } - def assert(t: Term, assumptionType: AssumptionType=AssumptionType.Implicit, timeout: Option[Int] = Verifier.config.assertTimeout.toOption)(Q: Boolean => VerificationResult): VerificationResult = { val (success, _) = deciderAssert(t, assumptionType, timeout) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala index 7cdcb69b9..0101188f9 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala @@ -125,23 +125,12 @@ case class LabelNode(term: Var) extends GeneralAssumptionNode { * Infeasibility nodes allow to distinguish between dependencies coming from the fact that the assertion is not reachable * on a given path and dependencies used to prove the assertion on feasible paths. */ -case class InfeasibilityNode(sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Implicit) extends GeneralAssumptionNode { +case class InfeasibilityNode(sourceInfo: AnalysisSourceInfo) extends GeneralAssumptionNode { val term: Term = False + val assumptionType: AssumptionType = AssumptionType.Implicit val isClosed: Boolean = true val description: String = "False" override def getNodeType: String = "Infeasible" override def getNodeString: String = "infeasible" } - -case class AssertFailedNode(sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType = AssumptionType.Implicit) extends GeneralAssertionNode { - val term: Term = False - val isClosed: Boolean = true - val description: String = "Assert failed" - val hasFailed: Boolean = true - - override def getNodeType: String = "Failed" - override def getNodeString: String = "failed" - - override def getAssertFailedNode(): GeneralAssertionNode = this -} \ No newline at end of file diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index e2b934324..e3baac883 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -39,8 +39,6 @@ trait DependencyAnalyzer { def createAssertOrCheckNode(term: Term, assumptionType: AssumptionType, analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] def addAssertFalseNode(isCheck: Boolean, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo): Option[Int] def addInfeasibilityNode(isCheck: Boolean, sourceInfo: AnalysisSourceInfo): Option[Int] - def addInfeasibilityNode(isCheck: Boolean, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] - def addAssertFailedNode(sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] def addDependency(source: Option[Int], dest: Option[Int]): Unit def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit @@ -341,18 +339,6 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { Some(node.id) } - override def addInfeasibilityNode(isCheck: Boolean, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { - val node = InfeasibilityNode(sourceInfo, assumptionType) - addNode(node) - Some(node.id) - } - - override def addAssertFailedNode(sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { - val node = AssertFailedNode(sourceInfo, assumptionType) - addNode(node) - Some(node.id) - } - // adding dependencies override def addDependency(source: Option[Int], dest: Option[Int]): Unit = { @@ -505,8 +491,6 @@ class NoDependencyAnalyzer extends DependencyAnalyzer { override def createAssertOrCheckNode(term: Term, assumptionType: AssumptionType, analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] = None override def addAssertFalseNode(isCheck: Boolean, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo): Option[Int] = None override def addInfeasibilityNode(isCheck: Boolean, sourceInfo: AnalysisSourceInfo): Option[Int] = None - override def addInfeasibilityNode(isCheck: Boolean, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None - override def addAssertFailedNode(sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None override def addDependency(source: Option[Int], dest: Option[Int]): Unit = {} override def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit = {} diff --git a/src/main/scala/rules/ChunkSupporter.scala b/src/main/scala/rules/ChunkSupporter.scala index e943dcf1a..c75423feb 100644 --- a/src/main/scala/rules/ChunkSupporter.scala +++ b/src/main/scala/rules/ChunkSupporter.scala @@ -151,13 +151,7 @@ object chunkSupporter extends ChunkSupportRules { else Success() // TODO: Mark branch as dead? case _ => - val failure = createFailure(ve, v1, s1, "consuming chunk", true) - if(Verifier.config.enableDependencyAnalysisFailureHandling){ - v1.decider.handleVerificationFailure(assumptionType) - failure combine QS(s1, s1.h, None, v1) - }else{ - failure - } + createFailure(ve, v1, s1, "consuming chunk", true) } } )(Q) @@ -281,13 +275,7 @@ object chunkSupporter extends ChunkSupportRules { Success() // TODO: Mark branch as dead? } case _ => - val failure = createFailure(ve, v, s, "looking up chunk", true) - if(Verifier.config.enableDependencyAnalysisFailureHandling){ - v.decider.handleVerificationFailure(assumptionType) - failure combine Q(s, False /* FIXME ake */, v) - }else{ - failure - } + createFailure(ve, v, s, "looking up chunk", true) } } diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 0f5a236d1..ddf0489f1 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -240,13 +240,7 @@ object evaluator extends EvaluationRules { val toAssert = IsPositive(totalPermissions.replace(`?r`, tRcvr)) v1.decider.assert(toAssert) { case false => - val failure = createFailure(pve dueTo InsufficientPermission(fa), v1, s1, toAssert, Option.when(withExp)(perms.IsPositive(ast.CurrentPerm(fa)())())) - if(Verifier.config.enableDependencyAnalysisFailureHandling){ - v1.decider.handleVerificationFailure(AssumptionType.Implicit) - failure combine Q(s, Lookup(fa.field.name, fvfDef.sm, tRcvr), newFa, v1) - }else{ - failure - } + createFailure(pve dueTo InsufficientPermission(fa), v1, s1, toAssert, Option.when(withExp)(perms.IsPositive(ast.CurrentPerm(fa)())())) case true => val fvfLookup = Lookup(fa.field.name, fvfDef.sm, tRcvr) val fr1 = s1.functionRecorder.recordSnapshot(fa, v1.decider.pcs.branchConditions, fvfLookup).recordFvfAndDomain(fvfDef) @@ -276,13 +270,7 @@ object evaluator extends EvaluationRules { } v1.decider.assert(permCheck) { case false => - val failure = createFailure(pve dueTo InsufficientPermission(fa), v1, s1, permCheck, permCheckExp) - if(Verifier.config.enableDependencyAnalysisFailureHandling){ - v1.decider.handleVerificationFailure(AssumptionType.Implicit) - failure combine Q(s, Lookup(fa.field.name, relevantChunks.head.fvf, tRcvr), newFa, v1) - }else{ - failure - } + createFailure(pve dueTo InsufficientPermission(fa), v1, s1, permCheck, permCheckExp) case true => val smLookup = Lookup(fa.field.name, relevantChunks.head.fvf, tRcvr) val fr2 = @@ -314,13 +302,7 @@ object evaluator extends EvaluationRules { } v1.decider.assert(permCheck) { case false => - val failure = createFailure(pve dueTo InsufficientPermission(fa), v1, s2, permCheck, permCheckExp) - if(Verifier.config.enableDependencyAnalysisFailureHandling){ - v1.decider.handleVerificationFailure(AssumptionType.Implicit) - failure combine Q(s, Lookup(fa.field.name, smDef1.sm, tRcvr), newFa, v1) - }else{ - failure - } + createFailure(pve dueTo InsufficientPermission(fa), v1, s2, permCheck, permCheckExp) case true => val smLookup = Lookup(fa.field.name, smDef1.sm, tRcvr) val fr2 = diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 49882556d..990a3d3f1 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -333,11 +333,8 @@ object executor extends ExecutionRules { val sepIdentifier = v.symbExLog.openScope(new ExecuteRecord(stmt, s, v.decider.pcs)) val sourceInfo = StmtAnalysisSourceInfo(stmt) v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo) - if(false /* TODO ake: Verifier.config.disableInfeasibilityChecks() && Verifier.config.enableDependencyAnalysis() && - v.decider.pcs.getCurrentInfeasibilityNode.isDefined && !alwaysExecute(stmt)*/){ - // TODO ake: this might be used as a performance optimization but then we would need to figure out - // which statements enforce some kind of proof obligations and which do not. Currently, all statements - // are considered to enforce some proof obligation. + if(Verifier.config.disableInfeasibilityChecks() && Verifier.config.enableDependencyAnalysis() && + v.decider.pcs.getCurrentInfeasibilityNode.isDefined && !alwaysExecute(stmt)){ v.decider.dependencyAnalyzer.addInfeasibilityDepToStmt(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, AssumptionType.Implicit) v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) @@ -460,14 +457,7 @@ object executor extends ExecutionRules { val s5 = if (withExp) s4.copy(oldHeaps = s4.oldHeaps + (debugHeapName -> magicWandSupporter.getEvalHeap(s4))) else s4 Q(s5, v2) case (Incomplete(_, _), s3, _) => - val failure = createFailure(pve dueTo InsufficientPermission(fa), v2, s3, "sufficient permission") - if(Verifier.config.enableDependencyAnalysisFailureHandling){ - v2.decider.handleVerificationFailure(AssumptionType.Implicit) - failure combine Q(s3, v2) - }else{ - failure - } - }})) + createFailure(pve dueTo InsufficientPermission(fa), v2, s3, "sufficient permission")}})) case ass @ ast.FieldAssign(fa @ ast.FieldAccess(eRcvr, field), rhs) => assert(!s.exhaleExt) @@ -549,14 +539,10 @@ object executor extends ExecutionRules { case assert @ ast.Assert(a: ast.FalseLit) if !s.isInPackage => /* "assert false" triggers a smoke check. If successful, we backtrack. */ executionFlowController.tryOrFail0(s.copy(h = magicWandSupporter.getEvalHeap(s)), v)((s1, v1, QS) => { - val failure = createFailure(AssertFailed(assert) dueTo AssertionFalse(a), v1, s1, False, true, Option.when(withExp)(a)) - if (v1.decider.checkSmoke(isAssert=true, annotatedAssertionTypeOpt.getOrElse(AssumptionType.Explicit))) + if (v1.decider.checkSmoke(isAssert=true, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit))) QS(s1.copy(h = s.h), v1) - else if (Verifier.config.enableDependencyAnalysisFailureHandling) { - v1.decider.handleVerificationFailure(AssumptionType.Explicit) - failure combine QS(s1.copy(h = s.h), v1) - } else - failure + else + createFailure(AssertFailed(assert) dueTo AssertionFalse(a), v1, s1, False, true, Option.when(withExp)(a)) })((s2, v2) => if (Verifier.config.disableInfeasibilityChecks()) Q(s2, v2) diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 0f10a4453..5cc97dde6 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -403,7 +403,7 @@ object magicWandSupporter extends SymbolicExecutionRules { // The proof script should transform the current state such that we can consume the wand's RHS. val prevSourceInfo = v2.decider.analysisSourceInfoStack.getAnalysisSourceInfos v2.decider.analysisSourceInfoStack.setAnalysisSourceInfo(List.empty) - val result = executor.exec(s2, proofScriptCfg, v2)((proofScriptState, proofScriptVerifier) => { + executor.exec(s2, proofScriptCfg, v2)((proofScriptState, proofScriptVerifier) => { v2.decider.analysisSourceInfoStack.setAnalysisSourceInfo(prevSourceInfo) // Consume the wand's RHS and produce a snapshot which records all the values of variables on the RHS. // This part indirectly calls the methods `this.transfer` and `this.consumeFromMultipleHeaps`. @@ -415,8 +415,6 @@ object magicWandSupporter extends SymbolicExecutionRules { createWandChunkAndRecordResults(s3.copy(exhaleExt = false, oldHeaps = s.oldHeaps), freshSnapRoot, snapRhs.get, v3, dependencyType.assumptionType) }) }) - v2.decider.analysisSourceInfoStack.setAnalysisSourceInfo(prevSourceInfo) - result }) }) diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 584ea68c4..0b02bfc87 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -1443,16 +1443,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { } else { Q(s2, h3, None, v) } - case (Incomplete(_, _), s2, _) => { - val failure = createFailure(pve dueTo insufficientPermissionReason, v, s2, "QP consume") - if (Verifier.config.enableDependencyAnalysisFailureHandling) { - v.decider.handleVerificationFailure(assumptionType) - failure combine Q(s2, s2.h, None, v) - } else { - failure - } - } - } + case (Incomplete(_, _), s2, _) => + createFailure(pve dueTo insufficientPermissionReason, v, s2, "QP consume")} } case false => createFailure(pve dueTo notInjectiveReason, v, s, receiverInjectivityCheck, "QP receiver injective")} @@ -1582,18 +1574,12 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { } else { Q(s1, h1, None, v) } - case (Incomplete(_, _), s1, _) => - val failure = resourceAccess match { + case (Incomplete(_, _), _, _) => + resourceAccess match { case locAcc: ast.LocationAccess => createFailure(pve dueTo InsufficientPermission(locAcc), v, s, "single QP consume") case wand: ast.MagicWand => createFailure(pve dueTo MagicWandChunkNotFound(wand), v, s, "single QP consume") case _ => sys.error(s"Found resource $resourceAccess, which is not yet supported as a quantified resource.") } - if(Verifier.config.enableDependencyAnalysisFailureHandling){ - v.decider.handleVerificationFailure(assumptionType) - failure combine Q(s1, s1.h, None, v) - }else{ - failure - } } } } From c769b794e63ff2a85e6dfd83d1ad76789f07cdcd Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 14 Jan 2026 15:04:40 +0100 Subject: [PATCH 304/474] fix source info "equals" --- .../AnalysisSourceInfo.scala | 38 ++++--------------- 1 file changed, 7 insertions(+), 31 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala b/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala index a13fc28a1..fb6565ff0 100644 --- a/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala +++ b/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala @@ -48,13 +48,16 @@ abstract class AnalysisSourceInfo { val dependencyAnalysisInfo: Option[DependencyAnalysisInfo] = None - override def equals(obj: Any): Boolean = obj match { - case other: AnalysisSourceInfo => this.getPosition.equals(other.getPosition) && this.toString.equals(other.toString) - case _ => false + override def equals(obj: Any): Boolean = { + obj match { + case info: AnalysisSourceInfo => + info.getTopLevelSource.toString.equals(this.getTopLevelSource.toString) && info.getPosition.equals(this.getPosition) + case _ => false + } } override def hashCode(): Int = - (this.toString + this.getPosition.toString).hashCode + (this.getTopLevelSource.toString + this.getPosition.toString).hashCode } case class NoAnalysisSourceInfo() extends AnalysisSourceInfo { @@ -71,14 +74,6 @@ case class ExpAnalysisSourceInfo(source: ast.Exp) extends AnalysisSourceInfo { override def getPosition: Position = if(dependencyAnalysisInfo.isDefined) dependencyAnalysisInfo.get.pos else source.pos - override def equals(obj: Any): Boolean = { - obj match { - case info: ExpAnalysisSourceInfo => - info.source.equals(this.source) && info.getPosition.equals(this.getPosition) - case _ => false - } - } - override def isAnalysisEnabled: Boolean = DependencyAnalyzer.extractEnableAnalysisFromInfo(source.info).getOrElse(true) } @@ -89,27 +84,12 @@ case class StmtAnalysisSourceInfo(source: ast.Stmt) extends AnalysisSourceInfo { " (" + super.toString + ")" override def getPosition: Position = if(dependencyAnalysisInfo.isDefined) dependencyAnalysisInfo.get.pos else source.pos - override def equals(obj: Any): Boolean = { - obj match { - case info: StmtAnalysisSourceInfo => - info.source.equals(this.source) && info.getPosition.equals(this.getPosition) - case _ => false - } - } - override def isAnalysisEnabled: Boolean = DependencyAnalyzer.extractEnableAnalysisFromInfo(source.info).getOrElse(true) } case class StringAnalysisSourceInfo(description: String, position: Position) extends AnalysisSourceInfo { override def toString: String = description.replaceAll("\n", "\t") + " (" + super.toString + ")" override def getPosition: Position = position - - override def equals(obj: Any): Boolean = - obj match { - case info: StringAnalysisSourceInfo => - info.description.equals(this.description) && info.getPosition.equals(this.getPosition) - case _ => false - } } case class TransitivityAnalysisSourceInfo(actualSource: AnalysisSourceInfo, transitivitySource: AnalysisSourceInfo) extends AnalysisSourceInfo { @@ -117,8 +97,6 @@ case class TransitivityAnalysisSourceInfo(actualSource: AnalysisSourceInfo, tran override def getPosition: Position = actualSource.getPosition override def toString: String = getTopLevelSource.toString - override def equals(obj: Any): Boolean = actualSource.equals(obj) - override def getSourceForTransitiveEdges: AnalysisSourceInfo = transitivitySource.getTopLevelSource override def getTopLevelSource: AnalysisSourceInfo = actualSource.getTopLevelSource override def getFineGrainedSource: AnalysisSourceInfo = actualSource.getFineGrainedSource @@ -129,8 +107,6 @@ case class CompositeAnalysisSourceInfo(coarseGrainedSource: AnalysisSourceInfo, override def toString: String = getTopLevelSource.toString override def getPosition: Position = coarseGrainedSource.getPosition - override def equals(obj: Any): Boolean = coarseGrainedSource.equals(obj) - override def getTopLevelSource: AnalysisSourceInfo = coarseGrainedSource.getTopLevelSource override def getFineGrainedSource: AnalysisSourceInfo = fineGrainedSource.getFineGrainedSource From c87a0f71a7d544f34cd3d39d759d85b3fa1d214e Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 14 Jan 2026 15:05:27 +0100 Subject: [PATCH 305/474] fix function axioms of disabled functions --- src/main/scala/supporters/functions/FunctionData.scala | 2 +- .../scala/supporters/functions/FunctionVerificationUnit.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/scala/supporters/functions/FunctionData.scala b/src/main/scala/supporters/functions/FunctionData.scala index 41469bcda..b9c75c851 100644 --- a/src/main/scala/supporters/functions/FunctionData.scala +++ b/src/main/scala/supporters/functions/FunctionData.scala @@ -216,7 +216,7 @@ class FunctionData(val programFunction: ast.Function, def wrapBody(body: Term): Term = Let(toMap(bodyBindings), body) - if(Verifier.config.enableDependencyAnalysis()){ + if(isAnalysisEnabled){ val assumptionType = if(programFunction.body.isDefined) AssumptionType.ImplicitPostcondition else AssumptionType.ExplicitPostcondition (Forall(arguments, wrapBody(And(generateNestedDefinitionalAxioms)), Trigger(limitedFunctionApplication)), Option.empty[(AnalysisSourceInfo, AssumptionType)]) +: programFunction.posts.flatMap(_.topLevelConjuncts).map({p => diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index 2540085e1..4abdd007f 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -299,7 +299,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver val cleanAxiom = if(!Verifier.config.enableDependencyAnalysis()) axiom else axiom.map(a => (a._1.transform{ - case Var(name, _, _) if name.name.startsWith(DependencyAnalyzer.analysisLabelName) => True + case Var(name, _, _) if name.name.startsWith(DependencyAnalyzer.analysisLabelName) => True // replace dependency analysis labels by True to avoid errors }(), a._2)) decider.prover.assumeAxiomsWithAnalysisInfo(InsertionOrderedSet(cleanAxiom), "Function axioms") From 9863770bfd5453ce4de628a28c132bfaa17d324f Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 14 Jan 2026 15:06:11 +0100 Subject: [PATCH 306/474] progress metric: require verified program --- .../DependencyAnalysisUserTool.scala | 11 +++++++---- .../DependencyGraphInterpreter.scala | 8 -------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala index f4df95d50..a404aff42 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala @@ -139,7 +139,8 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete } private def handleVerificationProgressQuery(): Unit = { - println("Computing verification progress...") + if(verificationErrors.nonEmpty) println(s"Fix verification failures first!") + val ((progress, _, info), time) = measureTime(fullGraphInterpreter.computeVerificationProgress()) println(s"Overall verification progress: $progress") println(s"$info") @@ -267,6 +268,11 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete } def handleVerificationGuidanceQuery(): Unit = { + if(verificationErrors.nonEmpty) { + println(s"Fix verification failures first!") + return + } + val assumptionRanking = fullGraphInterpreter.computeAssumptionRanking().filter(_._2 > 0) println(s"Assumptions and the number of dependencies:\n\t${assumptionRanking.mkString("\n\t")}\n") @@ -274,8 +280,5 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete .map(mInterpreter => (mInterpreter.getMember.get.name, mInterpreter.computeUncoveredStatements())) .toList.filter(_._2 > 0).sortBy(_._2).reverse println(s"Members and the number of uncovered statements:\n\t${memberCoverageRanking.mkString("\n\t")}\n") - - val errorRanking = fullGraphInterpreter.computeFailureRanking() - println(s"Errors and the number of dependents:\n\t${errorRanking.mkString("\n\t")}\n") } } diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 32911abc0..c6e2b3095 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -229,8 +229,6 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen val endTime = System.nanoTime() println(s"Runtime of computing dependencies per assertion: ${(endTime-startTime)/1e6}ms") - // TODO ake: what to do with partially verified assertions? Currently, we take them into account for spec quality but - // consider the assertion to not be verified at all for proof quality. val relevantDependencies = toUserLevelNodes(relevantDependenciesPerAssertion.flatMap(_._2)) @@ -299,12 +297,6 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen .toList.sortBy(_._2).reverse } - def computeFailureRanking(): List[(String, Int)] = { - toUserLevelNodes(getNodesWithIdenticalSource(getAssertionNodesWithFailures.map(_.asInstanceOf[DependencyAnalysisNode]))) - .map(node => (node.toString, getAllNonInternalDependents(node.lowerLevelNodes.map(_.id)).size)) - .toList.sortBy(_._2).reverse - } - def computeUncoveredStatements(): Int = { val allSourceCodeStmts = UserLevelDependencyAnalysisNode.extractSourceCodeNodes(toUserLevelNodes(getNonInternalAssumptionNodes)) val coveredSourceCodeStmts = toUserLevelNodes(getAllNonInternalDependencies(getNodesWithIdenticalSource(getNonInternalAssertionNodes).map(_.id))) From 08233c9ef473527361d9d07f2e0f1aaae6d66c41 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 14 Jan 2026 15:23:10 +0100 Subject: [PATCH 307/474] progress metric: exclude trivial assertions --- .../DependencyGraphInterpreter.scala | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index c6e2b3095..6d51e4fb6 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -224,13 +224,13 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen val startTime = System.nanoTime() // TODO ake: this is suuuper slow. Can we reuse previously computed results? Caching? val relevantDependenciesPerAssertion = allAssertions - .map(ass => (ass, getAllNonInternalDependencies(getNodesWithIdenticalSource(ass.lowerLevelNodes).map(_.id)))).toMap + .map(ass => (ass, toUserLevelNodes(getAllNonInternalDependencies(getNodesWithIdenticalSource(ass.lowerLevelNodes).map(_.id))).diff(Set(ass)))).toMap .filter{case (assertion, assumptions) => assumptions.nonEmpty || assertion.hasFailures} // filter out trivial assertions like `assert true` val endTime = System.nanoTime() println(s"Runtime of computing dependencies per assertion: ${(endTime-startTime)/1e6}ms") - val relevantDependencies = toUserLevelNodes(relevantDependenciesPerAssertion.flatMap(_._2)) + val relevantDependencies = relevantDependenciesPerAssertion.flatMap(_._2).toSet // covered val coveredExplicitSources = UserLevelDependencyAnalysisNode.extractExplicitAssumptionNodes(relevantDependencies) @@ -245,8 +245,8 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen // assertions val relevantAssertions = relevantDependenciesPerAssertion // TODO ake: maybe .filter(_._1.lowLevelAssertionNodes.exists(n => !n.isInstanceOf[SimpleCheckNode])) - val assertionsWithFailures = relevantAssertions.keySet.filter(_.hasFailures) - val assertionsWithExplicitDeps = relevantAssertions.filter(deps => deps._2.exists(d => AssumptionType.explicitAssumptionTypes.contains(d.assumptionType))).keySet.diff(assertionsWithFailures) + val assertionsWithFailures = relevantAssertions.keySet.filter(_.hasFailures) // should be empty to get a reasonable progress metric + val assertionsWithExplicitDeps = relevantAssertions.filter(deps => deps._2.exists(d => AssumptionType.explicitAssumptionTypes.intersect(d.assumptionTypes).nonEmpty)).keySet.diff(assertionsWithFailures) val fullyVerifiedAssertions = relevantAssertions.keySet.diff(assertionsWithFailures).diff(assertionsWithExplicitDeps) // Peter's metric @@ -257,10 +257,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen // Lea's metric val proofQualityPerAssertion = relevantAssertions.map { case (assertion, assumptions) => if(assertion.hasFailures) 0.0 - else{ - val userLevelAssumptions = toUserLevelNodes(assumptions) - UserLevelDependencyAnalysisNode.extractNonExplicitAssumptionNodes(userLevelAssumptions).size.toDouble / userLevelAssumptions.size.toDouble - } + else UserLevelDependencyAnalysisNode.extractNonExplicitAssumptionNodes(assumptions).size.toDouble / assumptions.size.toDouble } val proofQualityLea = proofQualityPerAssertion.sum / relevantAssertions.keys.size.toDouble From 69ed391f235fa555be2ddca6b6233be0ef5bfb42 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 14 Jan 2026 15:34:22 +0100 Subject: [PATCH 308/474] test --- src/main/scala/rules/Executor.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 990a3d3f1..bda32ca4f 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -333,7 +333,7 @@ object executor extends ExecutionRules { val sepIdentifier = v.symbExLog.openScope(new ExecuteRecord(stmt, s, v.decider.pcs)) val sourceInfo = StmtAnalysisSourceInfo(stmt) v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo) - if(Verifier.config.disableInfeasibilityChecks() && Verifier.config.enableDependencyAnalysis() && + if(false /* TODO ake */ || Verifier.config.disableInfeasibilityChecks() && Verifier.config.enableDependencyAnalysis() && v.decider.pcs.getCurrentInfeasibilityNode.isDefined && !alwaysExecute(stmt)){ v.decider.dependencyAnalyzer.addInfeasibilityDepToStmt(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, AssumptionType.Implicit) From b3e6f21c4dfd0c6cdfd16605920015cf628070b1 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 14 Jan 2026 16:03:21 +0100 Subject: [PATCH 309/474] Revert "fix source info "equals"" This reverts commit c769b794e63ff2a85e6dfd83d1ad76789f07cdcd. --- .../AnalysisSourceInfo.scala | 38 +++++++++++++++---- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala b/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala index fb6565ff0..a13fc28a1 100644 --- a/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala +++ b/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala @@ -48,16 +48,13 @@ abstract class AnalysisSourceInfo { val dependencyAnalysisInfo: Option[DependencyAnalysisInfo] = None - override def equals(obj: Any): Boolean = { - obj match { - case info: AnalysisSourceInfo => - info.getTopLevelSource.toString.equals(this.getTopLevelSource.toString) && info.getPosition.equals(this.getPosition) - case _ => false - } + override def equals(obj: Any): Boolean = obj match { + case other: AnalysisSourceInfo => this.getPosition.equals(other.getPosition) && this.toString.equals(other.toString) + case _ => false } override def hashCode(): Int = - (this.getTopLevelSource.toString + this.getPosition.toString).hashCode + (this.toString + this.getPosition.toString).hashCode } case class NoAnalysisSourceInfo() extends AnalysisSourceInfo { @@ -74,6 +71,14 @@ case class ExpAnalysisSourceInfo(source: ast.Exp) extends AnalysisSourceInfo { override def getPosition: Position = if(dependencyAnalysisInfo.isDefined) dependencyAnalysisInfo.get.pos else source.pos + override def equals(obj: Any): Boolean = { + obj match { + case info: ExpAnalysisSourceInfo => + info.source.equals(this.source) && info.getPosition.equals(this.getPosition) + case _ => false + } + } + override def isAnalysisEnabled: Boolean = DependencyAnalyzer.extractEnableAnalysisFromInfo(source.info).getOrElse(true) } @@ -84,12 +89,27 @@ case class StmtAnalysisSourceInfo(source: ast.Stmt) extends AnalysisSourceInfo { " (" + super.toString + ")" override def getPosition: Position = if(dependencyAnalysisInfo.isDefined) dependencyAnalysisInfo.get.pos else source.pos + override def equals(obj: Any): Boolean = { + obj match { + case info: StmtAnalysisSourceInfo => + info.source.equals(this.source) && info.getPosition.equals(this.getPosition) + case _ => false + } + } + override def isAnalysisEnabled: Boolean = DependencyAnalyzer.extractEnableAnalysisFromInfo(source.info).getOrElse(true) } case class StringAnalysisSourceInfo(description: String, position: Position) extends AnalysisSourceInfo { override def toString: String = description.replaceAll("\n", "\t") + " (" + super.toString + ")" override def getPosition: Position = position + + override def equals(obj: Any): Boolean = + obj match { + case info: StringAnalysisSourceInfo => + info.description.equals(this.description) && info.getPosition.equals(this.getPosition) + case _ => false + } } case class TransitivityAnalysisSourceInfo(actualSource: AnalysisSourceInfo, transitivitySource: AnalysisSourceInfo) extends AnalysisSourceInfo { @@ -97,6 +117,8 @@ case class TransitivityAnalysisSourceInfo(actualSource: AnalysisSourceInfo, tran override def getPosition: Position = actualSource.getPosition override def toString: String = getTopLevelSource.toString + override def equals(obj: Any): Boolean = actualSource.equals(obj) + override def getSourceForTransitiveEdges: AnalysisSourceInfo = transitivitySource.getTopLevelSource override def getTopLevelSource: AnalysisSourceInfo = actualSource.getTopLevelSource override def getFineGrainedSource: AnalysisSourceInfo = actualSource.getFineGrainedSource @@ -107,6 +129,8 @@ case class CompositeAnalysisSourceInfo(coarseGrainedSource: AnalysisSourceInfo, override def toString: String = getTopLevelSource.toString override def getPosition: Position = coarseGrainedSource.getPosition + override def equals(obj: Any): Boolean = coarseGrainedSource.equals(obj) + override def getTopLevelSource: AnalysisSourceInfo = coarseGrainedSource.getTopLevelSource override def getFineGrainedSource: AnalysisSourceInfo = fineGrainedSource.getFineGrainedSource From ffb25e3ecbd065f1fc5ce730052f86af7df7ce2a Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 14 Jan 2026 17:06:25 +0100 Subject: [PATCH 310/474] fix UserLevelDependencyAnalysisNode creation --- .../AnalysisSourceInfo.scala | 22 ++++++++++++++----- .../DependencyAnalysisUserTool.scala | 2 +- .../UserLevelDependencyAnalysisNode.scala | 8 ++++--- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala b/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala index a13fc28a1..9f334888d 100644 --- a/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala +++ b/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala @@ -17,6 +17,8 @@ object AnalysisSourceInfo { abstract class AnalysisSourceInfo { override def toString: String = getPositionString + def getDescription: String + def getLineNumber: Option[Int] = getPosition match { case column: HasLineColumn => Some(column.line) case _ => None @@ -60,14 +62,15 @@ abstract class AnalysisSourceInfo { case class NoAnalysisSourceInfo() extends AnalysisSourceInfo { override def getPosition: Position = NoPosition + override def getDescription: String = "" + override def equals(obj: Any): Boolean = false } case class ExpAnalysisSourceInfo(source: ast.Exp) extends AnalysisSourceInfo { override val dependencyAnalysisInfo: Option[DependencyAnalysisInfo] = source.info.getUniqueInfo[DependencyAnalysisInfo] - override def toString: String = (if(dependencyAnalysisInfo.isDefined) dependencyAnalysisInfo.get.info else source.toString).replaceAll("\n", "\t") + - " (" + super.toString + ")" + override def toString: String = getDescription + " (" + super.toString + ")" override def getPosition: Position = if(dependencyAnalysisInfo.isDefined) dependencyAnalysisInfo.get.pos else source.pos @@ -80,13 +83,14 @@ case class ExpAnalysisSourceInfo(source: ast.Exp) extends AnalysisSourceInfo { } override def isAnalysisEnabled: Boolean = DependencyAnalyzer.extractEnableAnalysisFromInfo(source.info).getOrElse(true) + + override def getDescription: String = (if(dependencyAnalysisInfo.isDefined) dependencyAnalysisInfo.get.info else source.toString).replaceAll("\n", "\t") } case class StmtAnalysisSourceInfo(source: ast.Stmt) extends AnalysisSourceInfo { override val dependencyAnalysisInfo: Option[DependencyAnalysisInfo] = source.info.getUniqueInfo[DependencyAnalysisInfo] - override def toString: String = (if(dependencyAnalysisInfo.isDefined) dependencyAnalysisInfo.get.info else source.toString()).replaceAll("\n", "\t") + - " (" + super.toString + ")" + override def toString: String = getDescription + " (" + super.toString + ")" override def getPosition: Position = if(dependencyAnalysisInfo.isDefined) dependencyAnalysisInfo.get.pos else source.pos override def equals(obj: Any): Boolean = { @@ -98,10 +102,12 @@ case class StmtAnalysisSourceInfo(source: ast.Stmt) extends AnalysisSourceInfo { } override def isAnalysisEnabled: Boolean = DependencyAnalyzer.extractEnableAnalysisFromInfo(source.info).getOrElse(true) + + override def getDescription: String = (if(dependencyAnalysisInfo.isDefined) dependencyAnalysisInfo.get.info else source.toString()).replaceAll("\n", "\t") } case class StringAnalysisSourceInfo(description: String, position: Position) extends AnalysisSourceInfo { - override def toString: String = description.replaceAll("\n", "\t") + " (" + super.toString + ")" + override def toString: String = getDescription + " (" + getPositionString + ")" override def getPosition: Position = position override def equals(obj: Any): Boolean = @@ -110,6 +116,8 @@ case class StringAnalysisSourceInfo(description: String, position: Position) ext info.description.equals(this.description) && info.getPosition.equals(this.getPosition) case _ => false } + + override def getDescription: String = description.replaceAll("\n", "\t") } case class TransitivityAnalysisSourceInfo(actualSource: AnalysisSourceInfo, transitivitySource: AnalysisSourceInfo) extends AnalysisSourceInfo { @@ -123,6 +131,8 @@ case class TransitivityAnalysisSourceInfo(actualSource: AnalysisSourceInfo, tran override def getTopLevelSource: AnalysisSourceInfo = actualSource.getTopLevelSource override def getFineGrainedSource: AnalysisSourceInfo = actualSource.getFineGrainedSource override def isAnalysisEnabled: Boolean = actualSource.isAnalysisEnabled + + override def getDescription: String = actualSource.getDescription } case class CompositeAnalysisSourceInfo(coarseGrainedSource: AnalysisSourceInfo, fineGrainedSource: AnalysisSourceInfo) extends AnalysisSourceInfo { @@ -135,4 +145,6 @@ case class CompositeAnalysisSourceInfo(coarseGrainedSource: AnalysisSourceInfo, override def getFineGrainedSource: AnalysisSourceInfo = fineGrainedSource.getFineGrainedSource override def isAnalysisEnabled: Boolean = coarseGrainedSource.isAnalysisEnabled && fineGrainedSource.isAnalysisEnabled + + override def getDescription: String = coarseGrainedSource.getDescription } diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala index a404aff42..52eaa6cb9 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala @@ -142,7 +142,7 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete if(verificationErrors.nonEmpty) println(s"Fix verification failures first!") val ((progress, _, info), time) = measureTime(fullGraphInterpreter.computeVerificationProgress()) - println(s"Overall verification progress: $progress") +// println(s"Overall verification progress: $progress") println(s"$info") println(s"Finished in ${time}ms") } diff --git a/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala b/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala index dbab591d3..141795f1e 100644 --- a/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala +++ b/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala @@ -1,6 +1,6 @@ package dependencyAnalysis -import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyAnalysisNode, GeneralAssertionNode, GeneralAssumptionNode} +import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyAnalysisNode, GeneralAssertionNode, GeneralAssumptionNode, StringAnalysisSourceInfo} import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType import viper.silicon.state.terms.{And, Term} import viper.silver.ast.Position @@ -8,8 +8,10 @@ import viper.silver.ast.Position object UserLevelDependencyAnalysisNode { def from(dependencyNodes: Iterable[DependencyAnalysisNode]): Set[UserLevelDependencyAnalysisNode] = { - val res = dependencyNodes.groupBy(_.sourceInfo.getTopLevelSource.toString).map { case (_, nodes) => - UserLevelDependencyAnalysisNode(nodes.head.sourceInfo.getTopLevelSource, nodes.toSet) // TODO ake + val res = dependencyNodes + .map(n => (StringAnalysisSourceInfo(n.sourceInfo.getDescription, n.sourceInfo.getPosition), n)) + .groupBy(_._1).map { case (key, nodes) => + UserLevelDependencyAnalysisNode(key, nodes.map(_._2).toSet) }.toSet res } From b1d20cc0d2701d586f56f666e4ff80f65dc9d501 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 14 Jan 2026 17:50:59 +0100 Subject: [PATCH 311/474] minor fix --- .../scala/dependencyAnalysis/DependencyGraphInterpreter.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 6d51e4fb6..804ac06dd 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -281,9 +281,9 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen s"#Verification Errors: ${errors.size}" + "\n\n" + "\n" + s"Verification Progress (Peter):\n\t${coveredSourceCodeStmts.size}/${coveredSourceCodeStmts.size + uncoveredSourceCodeStmts.size} * " + - s"${fullyVerifiedAssertions.size}/${relevantAssertions.keySet.size} = $verificationProgressPeter" + "\n" + + s"${fullyVerifiedAssertions.size}/${relevantAssertions.keySet.size} = ${"%.2f".format(verificationProgressPeter)}" + "\n" + s"Verification Progress (Lea):\n\t${coveredSourceCodeStmts.size}/${coveredSourceCodeStmts.size + uncoveredSourceCodeStmts.size} * " + - s"${proofQualityPerAssertion.sum}/${relevantAssertions.keys.size} = $verificationProgressLea" + "\n" + f"${"%.2f".format(proofQualityPerAssertion.sum)}/${relevantAssertions.keys.size} = ${"%.2f".format(verificationProgressLea)}" + "\n" } (verificationProgressPeter, verificationProgressLea, info) } From 9d67732d5a0e14647a9caed936dd0d08dc1349ad Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 15 Jan 2026 08:53:57 +0100 Subject: [PATCH 312/474] temporary fix graph join for frontends --- src/main/scala/rules/Executor.scala | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index bda32ca4f..ba92c0a16 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -612,9 +612,8 @@ object executor extends ExecutionRules { val pveCall = CallFailed(call) val pveCallTransformed = pveCall.withReasonNodeTransformed(reasonTransformer) - val methodAnnotatedAssumptionType = DependencyAnalyzer.extractAssumptionTypeFromInfo(meth.info) // TODO: make sure the join can still be made? - val defaultAssumptionType = AssumptionType.CallPostcondition - val finalAssumptionType = annotatedAssumptionTypeOpt.getOrElse(methodAnnotatedAssumptionType.getOrElse(defaultAssumptionType)) + // TODO: make sure the join can still be made! + val finalAssumptionType = annotatedAssumptionTypeOpt.getOrElse(AssumptionType.CallPostcondition) val mcLog = new MethodCallRecord(call, s, v.decider.pcs) val sepIdentifier = v.symbExLog.openScope(mcLog) @@ -642,7 +641,7 @@ object executor extends ExecutionRules { val outs = meth.formalReturns.map(_.localVar) val gOuts = Store(outs.map(x => (x, v2.decider.fresh(x))).toMap) val s4 = s3.copy(g = s3.g + gOuts, oldHeaps = s3.oldHeaps + (Verifier.PRE_STATE_LABEL -> magicWandSupporter.getEvalHeap(s1))) - produces(s4, freshSnap, meth.posts, _ => pveCallTransformed, v2, finalAssumptionType)((s5, v3) => { + produces(s4, freshSnap, meth.posts, _ => pveCallTransformed, v2, AssumptionType.CallPostcondition)((s5, v3) => { v3.symbExLog.closeScope(postCondId) v3.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) val gLhs = Store(lhs.zip(outs) From 3afb7bcb68aba5ce20fdb6487e4c8083f1f633cc Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 15 Jan 2026 08:58:23 +0100 Subject: [PATCH 313/474] fix UserLevelDependencyAnalysisNode --- .../DependencyGraphInterpreter.scala | 49 ++++++++++--------- .../UserLevelDependencyAnalysisNode.scala | 18 +++++-- 2 files changed, 40 insertions(+), 27 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 804ac06dd..6711dc800 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -224,7 +224,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen val startTime = System.nanoTime() // TODO ake: this is suuuper slow. Can we reuse previously computed results? Caching? val relevantDependenciesPerAssertion = allAssertions - .map(ass => (ass, toUserLevelNodes(getAllNonInternalDependencies(getNodesWithIdenticalSource(ass.lowerLevelNodes).map(_.id))).diff(Set(ass)))).toMap + .map(ass => (ass, toUserLevelNodes(getAllNonInternalDependencies(getNodesWithIdenticalSource(ass.lowerLevelNodes).map(_.id))).diffBySource(Set(ass)))).toMap .filter{case (assertion, assumptions) => assumptions.nonEmpty || assertion.hasFailures} // filter out trivial assertions like `assert true` val endTime = System.nanoTime() @@ -233,21 +233,21 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen val relevantDependencies = relevantDependenciesPerAssertion.flatMap(_._2).toSet // covered - val coveredExplicitSources = UserLevelDependencyAnalysisNode.extractExplicitAssumptionNodes(relevantDependencies) - val coveredVerificationAnnotations = UserLevelDependencyAnalysisNode.extractVerificationAnnotationNodes(relevantDependencies).diff(coveredExplicitSources) - val coveredSourceCodeStmts = relevantDependencies.diff(coveredExplicitSources).diff(coveredVerificationAnnotations) + val coveredExplicitSources = UserLevelDependencyAnalysisNode.extractExplicitAssumptionNodes(relevantDependencies).getSourceSet() + val coveredVerificationAnnotations = UserLevelDependencyAnalysisNode.extractVerificationAnnotationNodes(relevantDependencies).getSourceSet().diff(coveredExplicitSources) + val coveredSourceCodeStmts = relevantDependencies.getSourceSet().diff(coveredExplicitSources).diff(coveredVerificationAnnotations) // uncovered - val uncoveredNodes = toUserLevelNodes(getNonInternalAssumptionNodes).diff(relevantDependencies) - val uncoveredExplicitSources = UserLevelDependencyAnalysisNode.extractExplicitAssumptionNodes(uncoveredNodes) - val uncoveredVerificationAnnotations = UserLevelDependencyAnalysisNode.extractVerificationAnnotationNodes(uncoveredNodes).diff(uncoveredExplicitSources) - val uncoveredSourceCodeStmts = uncoveredNodes.diff(uncoveredExplicitSources).diff(uncoveredVerificationAnnotations) + val uncoveredNodes = toUserLevelNodes(getNonInternalAssumptionNodes).diffBySource(relevantDependencies) + val uncoveredExplicitSources = UserLevelDependencyAnalysisNode.extractExplicitAssumptionNodes(uncoveredNodes).getSourceSet() + val uncoveredVerificationAnnotations = UserLevelDependencyAnalysisNode.extractVerificationAnnotationNodes(uncoveredNodes).getSourceSet().diff(uncoveredExplicitSources) + val uncoveredSourceCodeStmts = uncoveredNodes.getSourceSet().diff(uncoveredExplicitSources).diff(uncoveredVerificationAnnotations) // assertions val relevantAssertions = relevantDependenciesPerAssertion // TODO ake: maybe .filter(_._1.lowLevelAssertionNodes.exists(n => !n.isInstanceOf[SimpleCheckNode])) - val assertionsWithFailures = relevantAssertions.keySet.filter(_.hasFailures) // should be empty to get a reasonable progress metric - val assertionsWithExplicitDeps = relevantAssertions.filter(deps => deps._2.exists(d => AssumptionType.explicitAssumptionTypes.intersect(d.assumptionTypes).nonEmpty)).keySet.diff(assertionsWithFailures) - val fullyVerifiedAssertions = relevantAssertions.keySet.diff(assertionsWithFailures).diff(assertionsWithExplicitDeps) + val assertionsWithFailures = relevantAssertions.keySet.filter(_.hasFailures).getSourceSet() // should be empty to get a reasonable progress metric + val assertionsWithExplicitDeps = relevantAssertions.filter(deps => deps._2.exists(d => AssumptionType.explicitAssumptionTypes.intersect(d.assumptionTypes).nonEmpty)).keySet.getSourceSet().diff(assertionsWithFailures) + val fullyVerifiedAssertions = relevantAssertions.keySet.getSourceSet().diff(assertionsWithFailures).diff(assertionsWithExplicitDeps) // Peter's metric val specQuality = coveredSourceCodeStmts.size.toDouble / (coveredSourceCodeStmts.size.toDouble + uncoveredSourceCodeStmts.size.toDouble) @@ -263,20 +263,25 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen val proofQualityLea = proofQualityPerAssertion.sum / relevantAssertions.keys.size.toDouble val verificationProgressLea = specQuality * proofQualityLea + + def getString(nodes: Set[AnalysisSourceInfo]): String = { + nodes.toList.sortBy(_.getLineNumber).mkString(("\n\t\t")) + } + val info = { s"Covered\n" + - s"\tExplicit Assumptions:\n\t\t${UserLevelDependencyAnalysisNode.mkString(coveredExplicitSources, "\n\t\t")}" + "\n" + - s"\tVerification Annotations:\n\t\t${UserLevelDependencyAnalysisNode.mkString(coveredVerificationAnnotations, "\n\t\t")}" + "\n" + - s"\tSource Code:\n\t\t${UserLevelDependencyAnalysisNode.mkString(coveredSourceCodeStmts, "\n\t\t")}" + "\n" + + s"\tExplicit Assumptions:\n\t\t${getString(coveredExplicitSources)}" + "\n" + + s"\tVerification Annotations:\n\t\t${getString(coveredVerificationAnnotations)}" + "\n" + + s"\tSource Code:\n\t\t${getString(coveredSourceCodeStmts)}" + "\n" + "\n" + s"Uncovered\n" + - s"\tExplicit Assumptions:\n\t\t${UserLevelDependencyAnalysisNode.mkString(uncoveredExplicitSources, "\n\t\t")}" + "\n" + - s"\tVerification Annotations:\n\t\t${UserLevelDependencyAnalysisNode.mkString(uncoveredVerificationAnnotations, "\n\t\t")}" + "\n" + - s"\tSource Code:\n\t\t${UserLevelDependencyAnalysisNode.mkString(uncoveredSourceCodeStmts, "\n\t\t")}" + "\n" + + s"\tExplicit Assumptions:\n\t\t${getString(uncoveredExplicitSources)}" + "\n" + + s"\tVerification Annotations:\n\t\t${getString(uncoveredVerificationAnnotations)}" + "\n" + + s"\tSource Code:\n\t\t${getString(uncoveredSourceCodeStmts)}" + "\n" + "\n" + - s"Fully verified assertions:\n\t${UserLevelDependencyAnalysisNode.mkString(fullyVerifiedAssertions, "\n\t")}" + "\n\n" + - s"Assertions depending on explicit assumptions:\n\t${UserLevelDependencyAnalysisNode.mkString(assertionsWithExplicitDeps, "\n\t")}" + "\n\n" + - s"Failing assertions:\n\t${UserLevelDependencyAnalysisNode.mkString(assertionsWithFailures, "\n\t")}\n\n" + + s"Fully verified assertions:\n\t\t${getString(fullyVerifiedAssertions)}" + "\n\n" + + s"Assertions depending on explicit assumptions:\n\t\t${getString(assertionsWithExplicitDeps)}" + "\n\n" + + s"Failing assertions:\n\t\t${getString(assertionsWithFailures)}\n\n" + "\n" + s"#Verification Errors: ${errors.size}" + "\n\n" + "\n" + @@ -295,8 +300,8 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen } def computeUncoveredStatements(): Int = { - val allSourceCodeStmts = UserLevelDependencyAnalysisNode.extractSourceCodeNodes(toUserLevelNodes(getNonInternalAssumptionNodes)) - val coveredSourceCodeStmts = toUserLevelNodes(getAllNonInternalDependencies(getNodesWithIdenticalSource(getNonInternalAssertionNodes).map(_.id))) + val allSourceCodeStmts = UserLevelDependencyAnalysisNode.extractSourceCodeNodes(toUserLevelNodes(getNonInternalAssumptionNodes)).getSourceSet() + val coveredSourceCodeStmts = toUserLevelNodes(getAllNonInternalDependencies(getNodesWithIdenticalSource(getNonInternalAssertionNodes).map(_.id))).getSourceSet() allSourceCodeStmts.diff(coveredSourceCodeStmts).size } } diff --git a/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala b/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala index 141795f1e..2a0a21383 100644 --- a/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala +++ b/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala @@ -47,6 +47,17 @@ object UserLevelDependencyAnalysisNode { def mkUserLevelString(nodes: Set[DependencyAnalysisNode], sep: String = "\n"): String = { mkString(from(nodes), sep) } + + implicit class SetNodeOps(private val left: Set[UserLevelDependencyAnalysisNode]) extends AnyVal { + def diffBySource(right: Set[UserLevelDependencyAnalysisNode]): Set[UserLevelDependencyAnalysisNode] = { + val sources = right.map(_.groupingCondition) + left.filterNot(n => sources.contains(n.groupingCondition)) + } + + def getSourceSet(): Set[AnalysisSourceInfo] = { + left.map(_.source) + } + } } case class UserLevelDependencyAnalysisNode(source: AnalysisSourceInfo, lowerLevelNodes: Set[DependencyAnalysisNode]) { @@ -67,10 +78,7 @@ case class UserLevelDependencyAnalysisNode(source: AnalysisSourceInfo, lowerLeve override def toString: String = source.toString - override def hashCode(): Int = source.hashCode() + def groupingCondition: (String, Position) = (source.toString, position) + - override def equals(obj: Any): Boolean = obj match { - case node: UserLevelDependencyAnalysisNode => this.source.getTopLevelSource.equals(node.source.getTopLevelSource) - case _ => false - } } From 6672a9ddaa1f55525df225ab7c946cddad10fb2f Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 15 Jan 2026 09:11:24 +0100 Subject: [PATCH 314/474] fix AnalysisSourceInfo --- .../AnalysisSourceInfo.scala | 66 ++++++------------- .../DependencyAnalysisInfo.scala | 8 --- .../DependencyAnalyzer.scala | 17 +++-- .../dependencyAnalysis/DependencyGraph.scala | 4 +- .../FrontendDependencyAnalysisInfo.scala | 14 ++++ src/main/scala/rules/Brancher.scala | 9 +-- src/main/scala/rules/Consumer.scala | 4 +- src/main/scala/rules/Evaluator.scala | 4 +- src/main/scala/rules/Executor.scala | 8 +-- src/main/scala/rules/Producer.scala | 4 +- src/main/scala/supporters/Domains.scala | 2 +- .../supporters/functions/FunctionData.scala | 4 +- .../functions/FunctionVerificationUnit.scala | 2 +- 13 files changed, 64 insertions(+), 82 deletions(-) delete mode 100644 src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala create mode 100644 src/main/scala/dependencyAnalysis/FrontendDependencyAnalysisInfo.scala diff --git a/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala b/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala index 9f334888d..df500c65d 100644 --- a/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala +++ b/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala @@ -12,6 +12,21 @@ object AnalysisSourceInfo { case VirtualPosition(identifier) => "label " + identifier } } + + def createAnalysisSourceInfo(exp: ast.Exp): AnalysisSourceInfo = { + val depInfo = exp.info.getUniqueInfo[FrontendDependencyAnalysisInfo] + if(depInfo.isDefined) depInfo.get.createAnalysisSourceInfo() + else ExpAnalysisSourceInfo(exp, exp.pos) + } + + def createAnalysisSourceInfo(stmt: ast.Stmt): AnalysisSourceInfo = { + val depInfo = stmt.info.getUniqueInfo[FrontendDependencyAnalysisInfo] + if(depInfo.isDefined) depInfo.get.createAnalysisSourceInfo() + else StmtAnalysisSourceInfo(stmt, stmt.pos) + } + + def createAnalysisSourceInfo(description: String, pos: Position): AnalysisSourceInfo = StringAnalysisSourceInfo(description, pos) + } abstract class AnalysisSourceInfo { @@ -47,16 +62,6 @@ abstract class AnalysisSourceInfo { def getFineGrainedSource: AnalysisSourceInfo = this def isAnalysisEnabled: Boolean = true - - val dependencyAnalysisInfo: Option[DependencyAnalysisInfo] = None - - override def equals(obj: Any): Boolean = obj match { - case other: AnalysisSourceInfo => this.getPosition.equals(other.getPosition) && this.toString.equals(other.toString) - case _ => false - } - - override def hashCode(): Int = - (this.toString + this.getPosition.toString).hashCode } case class NoAnalysisSourceInfo() extends AnalysisSourceInfo { @@ -67,56 +72,31 @@ case class NoAnalysisSourceInfo() extends AnalysisSourceInfo { override def equals(obj: Any): Boolean = false } -case class ExpAnalysisSourceInfo(source: ast.Exp) extends AnalysisSourceInfo { - override val dependencyAnalysisInfo: Option[DependencyAnalysisInfo] = source.info.getUniqueInfo[DependencyAnalysisInfo] +case class ExpAnalysisSourceInfo(source: ast.Exp, pos: Position) extends AnalysisSourceInfo { override def toString: String = getDescription + " (" + super.toString + ")" - override def getPosition: Position = if(dependencyAnalysisInfo.isDefined) dependencyAnalysisInfo.get.pos else source.pos - - override def equals(obj: Any): Boolean = { - obj match { - case info: ExpAnalysisSourceInfo => - info.source.equals(this.source) && info.getPosition.equals(this.getPosition) - case _ => false - } - } + override def getPosition: Position = pos override def isAnalysisEnabled: Boolean = DependencyAnalyzer.extractEnableAnalysisFromInfo(source.info).getOrElse(true) - override def getDescription: String = (if(dependencyAnalysisInfo.isDefined) dependencyAnalysisInfo.get.info else source.toString).replaceAll("\n", "\t") + override def getDescription: String = source.toString.replaceAll("\n", "\t") } -case class StmtAnalysisSourceInfo(source: ast.Stmt) extends AnalysisSourceInfo { - override val dependencyAnalysisInfo: Option[DependencyAnalysisInfo] = source.info.getUniqueInfo[DependencyAnalysisInfo] +case class StmtAnalysisSourceInfo(source: ast.Stmt, pos: Position) extends AnalysisSourceInfo { override def toString: String = getDescription + " (" + super.toString + ")" - override def getPosition: Position = if(dependencyAnalysisInfo.isDefined) dependencyAnalysisInfo.get.pos else source.pos - - override def equals(obj: Any): Boolean = { - obj match { - case info: StmtAnalysisSourceInfo => - info.source.equals(this.source) && info.getPosition.equals(this.getPosition) - case _ => false - } - } + override def getPosition: Position = pos override def isAnalysisEnabled: Boolean = DependencyAnalyzer.extractEnableAnalysisFromInfo(source.info).getOrElse(true) - override def getDescription: String = (if(dependencyAnalysisInfo.isDefined) dependencyAnalysisInfo.get.info else source.toString()).replaceAll("\n", "\t") + override def getDescription: String = source.toString().replaceAll("\n", "\t") } case class StringAnalysisSourceInfo(description: String, position: Position) extends AnalysisSourceInfo { override def toString: String = getDescription + " (" + getPositionString + ")" override def getPosition: Position = position - override def equals(obj: Any): Boolean = - obj match { - case info: StringAnalysisSourceInfo => - info.description.equals(this.description) && info.getPosition.equals(this.getPosition) - case _ => false - } - override def getDescription: String = description.replaceAll("\n", "\t") } @@ -125,8 +105,6 @@ case class TransitivityAnalysisSourceInfo(actualSource: AnalysisSourceInfo, tran override def getPosition: Position = actualSource.getPosition override def toString: String = getTopLevelSource.toString - override def equals(obj: Any): Boolean = actualSource.equals(obj) - override def getSourceForTransitiveEdges: AnalysisSourceInfo = transitivitySource.getTopLevelSource override def getTopLevelSource: AnalysisSourceInfo = actualSource.getTopLevelSource override def getFineGrainedSource: AnalysisSourceInfo = actualSource.getFineGrainedSource @@ -139,8 +117,6 @@ case class CompositeAnalysisSourceInfo(coarseGrainedSource: AnalysisSourceInfo, override def toString: String = getTopLevelSource.toString override def getPosition: Position = coarseGrainedSource.getPosition - override def equals(obj: Any): Boolean = coarseGrainedSource.equals(obj) - override def getTopLevelSource: AnalysisSourceInfo = coarseGrainedSource.getTopLevelSource override def getFineGrainedSource: AnalysisSourceInfo = fineGrainedSource.getFineGrainedSource diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala deleted file mode 100644 index efcf16248..000000000 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala +++ /dev/null @@ -1,8 +0,0 @@ -package viper.silicon.dependencyAnalysis - -import viper.silver.ast.{AbstractSourcePosition, Info} - -case class DependencyAnalysisInfo(info: String, pos: AbstractSourcePosition, dependencyType: Option[DependencyType]=None) extends Info { - override val comment = Nil - override val isCached = false -} diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index e3baac883..2280c1bc8 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -126,7 +126,7 @@ object DependencyAnalyzer { def extractDependencyTypeFromInfo(info: ast.Info): Option[DependencyType] = { val annotation = extractAnnotationFromInfo(info, assumptionTypeAnnotationKey) - val dependencyAnalysisInfo = info.getUniqueInfo[DependencyAnalysisInfo] + val dependencyAnalysisInfo = info.getUniqueInfo[FrontendDependencyAnalysisInfo] if(annotation.isDefined && annotation.get.nonEmpty) AssumptionType.fromString(annotation.get.head).map(DependencyType.make) else if(dependencyAnalysisInfo.isDefined) dependencyAnalysisInfo.get.dependencyType else None @@ -192,12 +192,12 @@ object DependencyAnalyzer { val axiomAssertionNodes = joinCandidateNodes .filter(n => (n.isInstanceOf[GeneralAssertionNode] && AssumptionType.postconditionTypes.contains(n.assumptionType)) || AssumptionType.FunctionBody.equals(n.assumptionType)) - .groupBy(_.getUserLevelRepresentation) + .groupBy(_.sourceInfo.getTopLevelSource) .view.mapValues(_.map(_.id)) .toMap joinCandidateNodes.filter(_.isInstanceOf[AxiomAssumptionNode]) .groupBy(n => n.sourceInfo) - .map{case (sourceInfo, axiomNodes) => (axiomNodes.map(_.id), axiomAssertionNodes.getOrElse(sourceInfo.toString, Seq.empty))} + .map{case (sourceInfo, axiomNodes) => (axiomNodes.map(_.id), axiomAssertionNodes.getOrElse(sourceInfo.getTopLevelSource, Seq.empty))} .foreach{case (axiomNodeIds, assertionNodeIds) => newGraph.addEdges(assertionNodeIds, axiomNodeIds) // TODO ake: maybe we could merge the axiom nodes here since they represent the same axiom? } @@ -383,8 +383,8 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { } override def addDependenciesForExplicitPostconditions(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp]): Unit = { - val sourceNodeIds = sourceExps.flatMap(e => addAssumption(True, ExpAnalysisSourceInfo(e), AssumptionType.Precondition, None)) - val targetNodes = targetExps.flatMap(e => addAssertNode(True, AssumptionType.ExplicitPostcondition, ExpAnalysisSourceInfo(e))) + val sourceNodeIds = sourceExps.flatMap(e => addAssumption(True, AnalysisSourceInfo.createAnalysisSourceInfo(e), AssumptionType.Precondition, None)) + val targetNodes = targetExps.flatMap(e => addAssertNode(True, AssumptionType.ExplicitPostcondition, AnalysisSourceInfo.createAnalysisSourceInfo(e))) dependencyGraph.addEdges(sourceNodeIds, targetNodes) } @@ -428,10 +428,9 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { mergedGraph.addNode(n) } - val nodesBySource = dependencyGraph.getNodes.filter(!keepNode(_)) - .groupBy(n => (n.sourceInfo.getSourceForTransitiveEdges.toString, n.sourceInfo, n.sourceInfo.getFineGrainedSource.toString, n.assumptionType)) + val nodesBySource = dependencyGraph.getNodes.filter(!keepNode(_)).groupBy(n => (n.sourceInfo, n.assumptionType)) - nodesBySource foreach { case ((_, sourceInfo, _, assumptionType), nodes) => + nodesBySource foreach { case ((sourceInfo, assumptionType), nodes) => val assumptionNodes = nodes.filter(_.isInstanceOf[GeneralAssumptionNode]) if (assumptionNodes.nonEmpty) { val newNode = SimpleAssumptionNode(True, None, sourceInfo, assumptionType, isClosed = false) @@ -440,7 +439,7 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { } } - nodesBySource foreach { case ((_, sourceInfo, _, assumptionType), nodes) => + nodesBySource foreach { case ((sourceInfo, assumptionType), nodes) => val assertionNodes = nodes.filter(_.isInstanceOf[GeneralAssertionNode]).map(_.asInstanceOf[GeneralAssertionNode]) if (assertionNodes.nonEmpty) { val newNode = SimpleAssertionNode(True, assumptionType, sourceInfo, isClosed = false, hasFailed=assertionNodes.exists(_.hasFailed)) diff --git a/src/main/scala/dependencyAnalysis/DependencyGraph.scala b/src/main/scala/dependencyAnalysis/DependencyGraph.scala index edf8a8113..db0aa646a 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraph.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraph.scala @@ -128,8 +128,8 @@ class DependencyGraph extends ReadOnlyDependencyGraph { } // TODO ake: maybe move to DependencyAnalyzer? - private def getNodesPerTransitivitySourceInfo: Map[String, Seq[DependencyAnalysisNode]] = { - getNodes.groupBy(_.sourceInfo.getSourceForTransitiveEdges.toString) + private def getNodesPerTransitivitySourceInfo: Map[AnalysisSourceInfo, Seq[DependencyAnalysisNode]] = { + getNodes.groupBy(_.sourceInfo.getSourceForTransitiveEdges) } // TODO ake: maybe move to DependencyAnalyzer? diff --git a/src/main/scala/dependencyAnalysis/FrontendDependencyAnalysisInfo.scala b/src/main/scala/dependencyAnalysis/FrontendDependencyAnalysisInfo.scala new file mode 100644 index 000000000..f1ef6f296 --- /dev/null +++ b/src/main/scala/dependencyAnalysis/FrontendDependencyAnalysisInfo.scala @@ -0,0 +1,14 @@ +package viper.silicon.dependencyAnalysis + +import viper.silver.ast.{AbstractSourcePosition, Info} + +abstract class FrontendDependencyAnalysisInfo extends Info { + override val comment = Nil + override val isCached = false + + val info: String + val pos: AbstractSourcePosition + val dependencyType: Option[DependencyType]=None + + def createAnalysisSourceInfo(): AnalysisSourceInfo +} diff --git a/src/main/scala/rules/Brancher.scala b/src/main/scala/rules/Brancher.scala index 187283f9b..42676a4bd 100644 --- a/src/main/scala/rules/Brancher.scala +++ b/src/main/scala/rules/Brancher.scala @@ -6,7 +6,8 @@ package viper.silicon.rules -import viper.silicon.dependencyAnalysis.{ExpAnalysisSourceInfo} +import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, ExpAnalysisSourceInfo} + import java.util.concurrent._ import viper.silicon.common.concurrency._ import viper.silicon.decider.PathConditionStack @@ -57,7 +58,7 @@ object brancher extends BranchingRules { && s.quantifiedVariables.map(_._1).exists(condition.freeVariables.contains)) ) - val sourceInfo = ExpAnalysisSourceInfo(conditionExp._1) + val sourceInfo = AnalysisSourceInfo.createAnalysisSourceInfo(conditionExp._1) v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo) /* True if the then-branch is to be explored */ val executeThenBranch = ( @@ -159,7 +160,7 @@ object brancher extends BranchingRules { v0.decider.analysisSourceInfoStack.setAnalysisSourceInfo(currentAnalysisSourceInfos) executionFlowController.locally(s, v0)((s1, v1) => { v1.decider.prover.comment(s"[else-branch: $cnt | $negatedCondition]") - val sourceInfo = ExpAnalysisSourceInfo(conditionExp._1) + val sourceInfo = AnalysisSourceInfo.createAnalysisSourceInfo(conditionExp._1) v1.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo) v1.decider.pcs.setCurrentInfeasibilityNode(elseInfeasibilityNode) v1.decider.setCurrentBranchCondition(negatedCondition, (negatedConditionExp, negatedConditionExpNew)) @@ -214,7 +215,7 @@ object brancher extends BranchingRules { v.decider.analysisSourceInfoStack.setAnalysisSourceInfo(currentAnalysisSourceInfos) executionFlowController.locally(s, v)((s1, v1) => { v1.decider.prover.comment(s"[then-branch: $cnt | $condition]") - val sourceInfo = ExpAnalysisSourceInfo(conditionExp._1) + val sourceInfo = AnalysisSourceInfo.createAnalysisSourceInfo(conditionExp._1) v1.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo) v1.decider.pcs.setCurrentInfeasibilityNode(thenInfeasibilityNode) v1.decider.setCurrentBranchCondition(condition, conditionExp) diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index 23de50054..534dbb4b2 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -8,7 +8,7 @@ package viper.silicon.rules import viper.silicon.Config.JoinMode import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType -import viper.silicon.dependencyAnalysis.{AnalysisInfo, AssumptionType, ExpAnalysisSourceInfo, StringAnalysisSourceInfo} +import viper.silicon.dependencyAnalysis.{AnalysisInfo, AnalysisSourceInfo, AssumptionType, ExpAnalysisSourceInfo, StringAnalysisSourceInfo} import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.VerificationResult import viper.silicon.interfaces.state.Chunk @@ -183,7 +183,7 @@ object consumer extends ConsumptionRules { val s1 = s0.copy(h = s.h) /* s1 is s, but the retrying flag might be set */ val sepIdentifier = v1.symbExLog.openScope(new ConsumeRecord(a, s1, v.decider.pcs)) - val sourceInfo = ExpAnalysisSourceInfo(a) + val sourceInfo = AnalysisSourceInfo.createAnalysisSourceInfo(a) v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo) consumeTlc(s1, h0, a, returnSnap, pve, v1, assumptionType)((s2, h2, snap2, v2) => { diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index ddf0489f1..67109878b 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -7,7 +7,7 @@ package viper.silicon.rules import viper.silicon.Config.JoinMode -import viper.silicon.dependencyAnalysis.{DependencyAnalyzer, AssumptionType, ExpAnalysisSourceInfo} +import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyAnalyzer, ExpAnalysisSourceInfo} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.interfaces._ @@ -93,7 +93,7 @@ object evaluator extends EvaluationRules { : VerificationResult = { val sepIdentifier = v.symbExLog.openScope(new EvaluateRecord(e, s, v.decider.pcs)) - val sourceInfo = ExpAnalysisSourceInfo(e) + val sourceInfo = AnalysisSourceInfo.createAnalysisSourceInfo(e) v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo) eval3(s, e, pve, v)((s1, t, eNew, v1) => { v1.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index ba92c0a16..0a0949089 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -8,7 +8,7 @@ package viper.silicon.rules import viper.silicon.Config.JoinMode import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType -import viper.silicon.dependencyAnalysis.{AssumptionType, DependencyAnalyzer, DependencyType, ExpAnalysisSourceInfo, StmtAnalysisSourceInfo, TransitivityAnalysisSourceInfo} +import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyAnalyzer, DependencyType, ExpAnalysisSourceInfo, StmtAnalysisSourceInfo, TransitivityAnalysisSourceInfo} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.decider.RecordedPathConditions @@ -331,7 +331,7 @@ object executor extends ExecutionRules { (Q: (State, Verifier) => VerificationResult) : VerificationResult = { val sepIdentifier = v.symbExLog.openScope(new ExecuteRecord(stmt, s, v.decider.pcs)) - val sourceInfo = StmtAnalysisSourceInfo(stmt) + val sourceInfo = AnalysisSourceInfo.createAnalysisSourceInfo(stmt) v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo) if(false /* TODO ake */ || Verifier.config.disableInfeasibilityChecks() && Verifier.config.enableDependencyAnalysis() && v.decider.pcs.getCurrentInfeasibilityNode.isDefined && !alwaysExecute(stmt)){ @@ -419,7 +419,7 @@ object executor extends ExecutionRules { s2 } v2.decider.clearModel() - val lhsSourceInfo = TransitivityAnalysisSourceInfo(v2.decider.analysisSourceInfoStack.getFullSourceInfo, ExpAnalysisSourceInfo(fa)) + val lhsSourceInfo = TransitivityAnalysisSourceInfo(v2.decider.analysisSourceInfoStack.getFullSourceInfo, AnalysisSourceInfo.createAnalysisSourceInfo(fa)) v2.decider.analysisSourceInfoStack.setForcedSource(lhsSourceInfo) // splitting lhs and rhs to make permission flow analysis more precise val result = quantifiedChunkSupporter.removePermissions( s2p, @@ -467,7 +467,7 @@ object executor extends ExecutionRules { val resource = fa.res(s.program) val ve = pve dueTo InsufficientPermission(fa) val description = s"consume ${ass.pos}: $ass" - val lhsSourceInfo = TransitivityAnalysisSourceInfo(v2.decider.analysisSourceInfoStack.getFullSourceInfo, ExpAnalysisSourceInfo(fa)) + val lhsSourceInfo = TransitivityAnalysisSourceInfo(v2.decider.analysisSourceInfoStack.getFullSourceInfo, AnalysisSourceInfo.createAnalysisSourceInfo(fa)) v2.decider.analysisSourceInfoStack.setForcedSource(lhsSourceInfo) // splitting lhs and rhs to make permission flow analysis more precise chunkSupporter.consume(s2, s2.h, resource, Seq(tRcvr), eRcvrNew.map(Seq(_)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), false, ve, v2, description, annotatedAssertionTypeOpt.getOrElse(AssumptionType.Implicit))((s3, h3, _, v3) => { v2.decider.analysisSourceInfoStack.removeForcedSource() diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index 03d075148..d6a6e50d7 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -7,7 +7,7 @@ package viper.silicon.rules import viper.silicon.Config.JoinMode -import viper.silicon.dependencyAnalysis.{DependencyAnalyzer, AssumptionType, ExpAnalysisSourceInfo} +import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyAnalyzer, ExpAnalysisSourceInfo} import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.{Unreachable, VerificationResult} @@ -154,7 +154,7 @@ object producer extends ProductionRules { val a = as.head.whenInhaling val pve = pves.head - val sourceInfo = ExpAnalysisSourceInfo(a) + val sourceInfo = AnalysisSourceInfo.createAnalysisSourceInfo(a) v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo) if (as.tail.isEmpty) wrappedProduceTlc(s, sf, a, pve, v, assumptionType)((s1, v1) => { diff --git a/src/main/scala/supporters/Domains.scala b/src/main/scala/supporters/Domains.scala index 82184c211..3d643753f 100644 --- a/src/main/scala/supporters/Domains.scala +++ b/src/main/scala/supporters/Domains.scala @@ -106,7 +106,7 @@ class DefaultDomainsContributor(symbolConverter: SymbolConverter, val tAx = domainTranslator.translateAxiom(axiom, symbolConverter.toSort) val tAxPres = FunctionPreconditionTransformer.transform(tAx, program) val enableAnalysis = DependencyAnalyzer.extractEnableAnalysisFromInfo(axiom.info).getOrElse(isAnalysisForDomainEnabled) - collectedAxioms = collectedAxioms.incl((terms.And(tAxPres, tAx), Option.when(enableAnalysis)((ExpAnalysisSourceInfo(axiom.exp), AssumptionType.Explicit)))) + collectedAxioms = collectedAxioms.incl((terms.And(tAxPres, tAx), Option.when(enableAnalysis)((AnalysisSourceInfo.createAnalysisSourceInfo(axiom.exp), AssumptionType.Explicit)))) }) }) } diff --git a/src/main/scala/supporters/functions/FunctionData.scala b/src/main/scala/supporters/functions/FunctionData.scala index b9c75c851..be81fcfbf 100644 --- a/src/main/scala/supporters/functions/FunctionData.scala +++ b/src/main/scala/supporters/functions/FunctionData.scala @@ -221,7 +221,7 @@ class FunctionData(val programFunction: ast.Function, (Forall(arguments, wrapBody(And(generateNestedDefinitionalAxioms)), Trigger(limitedFunctionApplication)), Option.empty[(AnalysisSourceInfo, AssumptionType)]) +: programFunction.posts.flatMap(_.topLevelConjuncts).map({p => val terms = expressionTranslator.translatePostcondition(program, Seq(p), this) - (And(Forall(arguments, wrapBody(Implies(pre, And(terms))), Trigger(limitedFunctionApplication)), True), Some((ExpAnalysisSourceInfo(p), assumptionType))) + (And(Forall(arguments, wrapBody(Implies(pre, And(terms))), Trigger(limitedFunctionApplication)), True), Some((AnalysisSourceInfo.createAnalysisSourceInfo(p), assumptionType))) }) }else{ val innermostBody = And(generateNestedDefinitionalAxioms ++ List(Implies(pre, And(translatedPosts)))) @@ -297,7 +297,7 @@ class FunctionData(val programFunction: ast.Function, val allTriggers = ( Seq(Trigger(functionApplication)) ++ actualPredicateTriggers) - (Forall(arguments, body, allTriggers), Option.when(isAnalysisEnabled)((ExpAnalysisSourceInfo(programFunction.body.get), AssumptionType.Implicit))) + (Forall(arguments, body, allTriggers), Option.when(isAnalysisEnabled)((AnalysisSourceInfo.createAnalysisSourceInfo(programFunction.body.get), AssumptionType.Implicit))) }) } diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index 4abdd007f..b8087e482 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -283,7 +283,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver val eNew = ast.EqCmp(ast.Result(function.typ)(), bodyNew.get)(function.pos, function.info, function.errT) Some(DebugExp.createInstance(e, eNew)) } else { None } - decider.analysisSourceInfoStack.setForcedSource(ExpAnalysisSourceInfo(body)) + decider.analysisSourceInfoStack.setForcedSource(AnalysisSourceInfo.createAnalysisSourceInfo(body)) decider.assume(BuiltinEquals(data.formalResult, tBody), debugExp, AssumptionType.FunctionBody) decider.analysisSourceInfoStack.removeForcedSource() consumes(s2, posts, false, postconditionViolated, v, postConditionType)((s3, _, _) => { From 34b6c1871a2a47b20612d6ec361042c04bb20ead Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 15 Jan 2026 10:20:59 +0100 Subject: [PATCH 315/474] minor fix --- src/main/scala/dependencyAnalysis/AnalysisInfo.scala | 4 +--- .../dependencyAnalysis/DependencyGraphInterpreter.scala | 9 +++------ src/main/scala/supporters/MethodSupporter.scala | 3 +-- .../supporters/functions/FunctionVerificationUnit.scala | 1 - 4 files changed, 5 insertions(+), 12 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala index 1051414da..a64836d92 100644 --- a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala +++ b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala @@ -11,10 +11,8 @@ object AssumptionType extends Enumeration { def postconditionTypes: Set[AssumptionType] = Set(ImplicitPostcondition, ExplicitPostcondition, CallPostcondition) // used to join graphs via postconditions def explicitAssertionTypes: Set[AssumptionType] = Set(Explicit, ImplicitPostcondition, ExplicitPostcondition) def internalTypes: Set[AssumptionType] = Set(Internal) // will always be hidden from user - def implicitTypes: Set[AssumptionType] = AssumptionType.values.diff(explicitAssumptionTypes).diff(internalTypes) def joinConditionTypes: Set[AssumptionType] = postconditionTypes ++ Set(FunctionBody) def verificationAnnotationTypes: Set[AssumptionType] = Set(LoopInvariant, Rewrite, ExplicitPostcondition, ImplicitPostcondition, Precondition, Explicit) - def sourceCodeStatementTypes: Set[AssumptionType] = Set(PathCondition, Implicit, CallPostcondition, FunctionBody) } import viper.silicon.dependencyAnalysis.AssumptionType._ @@ -24,7 +22,7 @@ object DependencyType { val Implicit: DependencyType = DependencyType(AssumptionType.Implicit, AssumptionType.Implicit) val Explicit: DependencyType = DependencyType(AssumptionType.Explicit, AssumptionType.Explicit) - val ExplicitAssertion: DependencyType = DependencyType(AssumptionType.Implicit, AssumptionType.Explicit) + val ExplicitAssertion: DependencyType = DependencyType(AssumptionType.Internal, AssumptionType.Explicit) val ExplicitAssumption: DependencyType = DependencyType(AssumptionType.Explicit, AssumptionType.Implicit) val PathCondition: DependencyType = DependencyType(AssumptionType.PathCondition, AssumptionType.Implicit) val Invariant: DependencyType = DependencyType(AssumptionType.LoopInvariant, AssumptionType.LoopInvariant) diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 6711dc800..12d469304 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -14,7 +14,6 @@ import java.nio.file.Paths class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependencyGraph, errors: List[Failure], member: Option[ast.Member]=None) extends AbstractDependencyGraphInterpreter{ - protected var joinCandidateNodes: Seq[DependencyAnalysisNode] = Seq.empty def getGraph: ReadOnlyDependencyGraph = dependencyGraph def getName: String = name @@ -24,11 +23,9 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen def getJoinCandidateNodes: Iterable[DependencyAnalysisNode] = joinCandidateNodes - def initJoinCandidateNodes(): Unit = { - joinCandidateNodes = dependencyGraph.getNodes.filter(node => node.isInstanceOf[AxiomAssumptionNode] || AssumptionType.joinConditionTypes.contains(node.assumptionType)) - } + protected lazy val joinCandidateNodes: Seq[DependencyAnalysisNode] = dependencyGraph.getNodes.filter(node => node.isInstanceOf[AxiomAssumptionNode] || AssumptionType.joinConditionTypes.contains(node.assumptionType)) - def toUserLevelNodes(nodes: Iterable[DependencyAnalysisNode]): Set[UserLevelDependencyAnalysisNode] = UserLevelDependencyAnalysisNode.from(nodes) + private def toUserLevelNodes(nodes: Iterable[DependencyAnalysisNode]): Set[UserLevelDependencyAnalysisNode] = UserLevelDependencyAnalysisNode.from(nodes) def getNodesByLine(line: Int): Set[DependencyAnalysisNode] = getNodes.filter(n => !AssumptionType.internalTypes.contains(n.assumptionType)).filter(node => node.sourceInfo.getLineNumber.isDefined && node.sourceInfo.getLineNumber.get == line) @@ -230,7 +227,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen val endTime = System.nanoTime() println(s"Runtime of computing dependencies per assertion: ${(endTime-startTime)/1e6}ms") - val relevantDependencies = relevantDependenciesPerAssertion.flatMap(_._2).toSet + val relevantDependencies = relevantDependenciesPerAssertion.flatMap(_._2).filter(_.assumptionTypes.nonEmpty).toSet // covered val coveredExplicitSources = UserLevelDependencyAnalysisNode.extractExplicitAssumptionNodes(relevantDependencies).getSourceSet() diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index 52d618153..d09a5b063 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -109,7 +109,7 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif val s4 = s3.copy(h = Heap()) val impLog = new WellformednessCheckRecord(posts, s, v.decider.pcs) val sepIdentifier = symbExLog.openScope(impLog) - produces(s4, freshSnap, posts, ContractNotWellformed, v3, annotatedAssumptionTypeOpt.getOrElse(postConditionType))((_, _) => { + produces(s4, freshSnap, posts, ContractNotWellformed, v3, postConditionType)((_, _) => { symbExLog.closeScope(sepIdentifier) Success()})}) && { @@ -128,7 +128,6 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif val allErrors = (result :: result.previous.toList).filter(_.isInstanceOf[Failure]).map(_.asInstanceOf[Failure]) result.dependencyGraphInterpreter = v.decider.dependencyAnalyzer.buildFinalGraph().map(new DependencyGraphInterpreter(method.name, _, allErrors, Some(method))) - result.dependencyGraphInterpreter.foreach(_.initJoinCandidateNodes()) v.decider.resetProverOptions() diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index b8087e482..e45a64bf7 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -167,7 +167,6 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver res.dependencyGraphInterpreter = v.decider.dependencyAnalyzer.buildFinalGraph().map(new DependencyGraphInterpreter(function.name, _, allErrors, Some(function))) - res.dependencyGraphInterpreter.foreach(_.initJoinCandidateNodes()) Seq(res) } From 53d3051221599ce9eafd72a451d09588c10a9c56 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 15 Jan 2026 11:32:38 +0100 Subject: [PATCH 316/474] DependencyAnalysisTestFramework: make storing pruned programs optional --- src/test/scala/DependencyAnalysisTestFramework.scala | 3 ++- src/test/scala/DependencyAnalysisTests.scala | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/test/scala/DependencyAnalysisTestFramework.scala b/src/test/scala/DependencyAnalysisTestFramework.scala index fa6457f50..4e1e67f83 100644 --- a/src/test/scala/DependencyAnalysisTestFramework.scala +++ b/src/test/scala/DependencyAnalysisTestFramework.scala @@ -16,6 +16,7 @@ trait DependencyAnalysisTestFramework { val irrelevantKeyword = "irrelevant" val dependencyKeyword = "dependency" val testAssertionKeyword = "testAssertion" + val EXPORT_PRUNED_PROGRAMS = false val ignores: Seq[String] var baseCommandLineArguments: Seq[String] = Seq("--timeout", "300" /* seconds */) @@ -98,7 +99,7 @@ trait DependencyAnalysisTestFramework { val crucialNodes = relevantNodes ++ dependencies val (newProgram, pruningFactor) = fullGraphInterpreter.getPrunedProgram(crucialNodes, program) val result = frontend.verifier.verify(newProgram) - exportPrunedProgram(exportFileName, newProgram, pruningFactor, result) + if(EXPORT_PRUNED_PROGRAMS) exportPrunedProgram(exportFileName, newProgram, pruningFactor, result) assert(!result.isInstanceOf[verifier.Failure], s"Failed to verify new program ${newProgram.toString()}") } diff --git a/src/test/scala/DependencyAnalysisTests.scala b/src/test/scala/DependencyAnalysisTests.scala index 63d707153..b734a46a9 100644 --- a/src/test/scala/DependencyAnalysisTests.scala +++ b/src/test/scala/DependencyAnalysisTests.scala @@ -18,6 +18,7 @@ class DependencyAnalysisTests extends AnyFunSuite with DependencyAnalysisTestFra val CHECK_PRECISION = false val EXECUTE_TEST = true val EXECUTE_PERFORMANCE_BENCHMARK = false + override val EXPORT_PRUNED_PROGRAMS: Boolean = false val ignores: Seq[String] = Seq() val testDirectories: Seq[String] = Seq( "dependencyAnalysisTests/all", From bf5f1ca648e97f6c5bc5b032913c40913f3ed71c Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 15 Jan 2026 12:58:35 +0100 Subject: [PATCH 317/474] update submodules --- silver | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/silver b/silver index e61c768a6..0f3f92702 160000 --- a/silver +++ b/silver @@ -1 +1 @@ -Subproject commit e61c768a68ad18fd6f87946ba078275b4f011e81 +Subproject commit 0f3f92702b5a468215ec25978c449311b7d3f8f1 From d0a0f07ef382b650c5e99ae341cb0c968db503e3 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 15 Jan 2026 15:15:11 +0100 Subject: [PATCH 318/474] add progress test --- .../DependencyGraphInterpreter.scala | 8 +++---- .../DependencyAnalysisProgressTests.scala | 23 +++++++++++++------ 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 12d469304..89b2d5017 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -216,16 +216,16 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen def computeVerificationProgress(): (Double, Double, String) = { val allAssertions = toUserLevelNodes(getNonInternalAssertionNodes) - println(s"#assertions: ${allAssertions.size}") +// println(s"#assertions: ${allAssertions.size}") - val startTime = System.nanoTime() +// val startTime = System.nanoTime() // TODO ake: this is suuuper slow. Can we reuse previously computed results? Caching? val relevantDependenciesPerAssertion = allAssertions .map(ass => (ass, toUserLevelNodes(getAllNonInternalDependencies(getNodesWithIdenticalSource(ass.lowerLevelNodes).map(_.id))).diffBySource(Set(ass)))).toMap .filter{case (assertion, assumptions) => assumptions.nonEmpty || assertion.hasFailures} // filter out trivial assertions like `assert true` - val endTime = System.nanoTime() - println(s"Runtime of computing dependencies per assertion: ${(endTime-startTime)/1e6}ms") +// val endTime = System.nanoTime() +// println(s"Runtime of computing dependencies per assertion: ${(endTime-startTime)/1e6}ms") val relevantDependencies = relevantDependenciesPerAssertion.flatMap(_._2).filter(_.assumptionTypes.nonEmpty).toSet diff --git a/src/test/scala/DependencyAnalysisProgressTests.scala b/src/test/scala/DependencyAnalysisProgressTests.scala index ed2e9b127..76a00831a 100644 --- a/src/test/scala/DependencyAnalysisProgressTests.scala +++ b/src/test/scala/DependencyAnalysisProgressTests.scala @@ -1,13 +1,17 @@ +package viper.silicon.tests + +import org.scalatest.DoNotDiscover import org.scalatest.funsuite.AnyFunSuite import viper.silicon.dependencyAnalysis.DependencyAnalysisReporter -import viper.silicon.tests.DependencyAnalysisTestFramework import viper.silver.ast.Program import viper.silver.frontend.SilFrontend import viper.silver.verifier.{Failure, VerificationResult} +import java.io.PrintWriter import java.nio.file.{Files, Path, Paths} import scala.collection.convert.ImplicitConversions.`iterable AsScalaIterable` +@DoNotDiscover class DependencyAnalysisProgressTests extends AnyFunSuite with DependencyAnalysisTestFramework { val ignores: Seq[String] = Seq.empty @@ -19,9 +23,12 @@ class DependencyAnalysisProgressTests extends AnyFunSuite with DependencyAnalysi def createSingleTest(dirName: String, fileName: String): Unit = { test(dirName) { + val writer = new PrintWriter("src/test/resources/" + dirName + "/results_" + System.currentTimeMillis() + ".csv") try{ - val directoryStream = Files.newDirectoryStream(Paths.get(getClass.getClassLoader.getResource(dirName).toURI)) + val dirPath = Paths.get(getClass.getClassLoader.getResource(dirName).toURI) + val directoryStream = Files.newDirectoryStream(dirPath) val dirContent = directoryStream.toList + writer.println("File name,Progress (Peter),Progress (Lea)") for (filePath: Path <- dirContent.sorted if Files.isReadable(filePath)) { @@ -30,9 +37,10 @@ class DependencyAnalysisProgressTests extends AnyFunSuite with DependencyAnalysi val vprFileName = rawFileName.replace(".vpr", "") if (!ignores.contains(vprFileName)) resetFrontend() - executeTest(dirName + "/", vprFileName, frontend) + executeTest(dirName + "/", vprFileName, frontend, writer) } } + writer.close() }catch{ case t: Throwable => fail(t.toString) } @@ -42,7 +50,8 @@ class DependencyAnalysisProgressTests extends AnyFunSuite with DependencyAnalysi def executeTest(filePrefix: String, fileName: String, - frontend: SilFrontend): Unit = { + frontend: SilFrontend, + writer: PrintWriter): Unit = { val program: Program = tests.loadProgram(filePrefix, fileName, frontend) val result: VerificationResult = frontend.verifier.verify(program) @@ -53,9 +62,9 @@ class DependencyAnalysisProgressTests extends AnyFunSuite with DependencyAnalysi val joinedDependencyGraphInterpreter = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].joinedDependencyGraphInterpreter - val (progressA, progressB, info) = joinedDependencyGraphInterpreter.get.computeVerificationProgress() + val (progressPeter, progressLea, _) = joinedDependencyGraphInterpreter.get.computeVerificationProgress() - println(s"$filePrefix$fileName:\n\tProgress A = $progressA\t\t\tProgress P = $progressB") - println(info) + println(s"$filePrefix$fileName:\n\tProgress (Peter) = $progressPeter\t\t\tProgress (Lea) = $progressLea\n") + writer.println(s"$fileName,\t$progressPeter,\t$progressLea") } } From 9ed781e5ddaf46ddfb30fb2320a392a081e0e94e Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 16 Jan 2026 09:57:45 +0100 Subject: [PATCH 319/474] fix method join --- src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala | 2 +- src/main/scala/rules/Executor.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index 2280c1bc8..af226c1d1 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -212,7 +212,7 @@ object DependencyAnalyzer { .groupBy(_.sourceInfo.getFineGrainedSource) .view.mapValues(_.map(_.id)) .toMap - joinCandidateNodes.filter(node => node.isInstanceOf[GeneralAssertionNode] && AssumptionType.postconditionTypes.contains(node.assumptionType)) + joinCandidateNodes.filter(node => AssumptionType.postconditionTypes.contains(node.assumptionType)) .map(node => (node.id, relevantAssumptionNodes.getOrElse(node.sourceInfo.getTopLevelSource, Seq.empty))) .foreach { case (src, targets) => newGraph.addEdges(src, targets)} diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 0a0949089..71273e023 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -514,7 +514,7 @@ object executor extends ExecutionRules { val esNew = eRcvrNew.map(rcvr => BigAnd(viper.silicon.state.utils.computeReferenceDisjointnessesExp(s, rcvr))) val s1 = s.copy(g = s.g + (x, (tRcvr, eRcvrNew)), h = s.h + Heap(newChunks)) val s2 = if (withExp) s1.copy(oldHeaps = s1.oldHeaps + (debugHeapName -> magicWandSupporter.getEvalHeap(s1))) else s1 - v.decider.assume(ts, Option.when(withExp)(DebugExp.createInstance(Some("Reference Disjointness"), esNew, esNew, InsertionOrderedSet.empty)), enforceAssumption = false, assumptionType=AssumptionType.Implicit) + v.decider.assume(ts, Option.when(withExp)(DebugExp.createInstance(Some("Reference Disjointness"), esNew, esNew, InsertionOrderedSet.empty)), enforceAssumption = false, assumptionType=annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit)) Q(s2, v) case inhale @ ast.Inhale(a) => a match { From 3d41f980a85d78002beb203fb21dec4ae3ce8a3b Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 16 Jan 2026 09:58:28 +0100 Subject: [PATCH 320/474] implement VerificationProgressRunner --- .../DependencyAnalysisProgressTests.scala | 70 ------------------- .../scala/VerificationProgressRunner.scala | 62 ++++++++++++++++ 2 files changed, 62 insertions(+), 70 deletions(-) delete mode 100644 src/test/scala/DependencyAnalysisProgressTests.scala create mode 100644 src/test/scala/VerificationProgressRunner.scala diff --git a/src/test/scala/DependencyAnalysisProgressTests.scala b/src/test/scala/DependencyAnalysisProgressTests.scala deleted file mode 100644 index 76a00831a..000000000 --- a/src/test/scala/DependencyAnalysisProgressTests.scala +++ /dev/null @@ -1,70 +0,0 @@ -package viper.silicon.tests - -import org.scalatest.DoNotDiscover -import org.scalatest.funsuite.AnyFunSuite -import viper.silicon.dependencyAnalysis.DependencyAnalysisReporter -import viper.silver.ast.Program -import viper.silver.frontend.SilFrontend -import viper.silver.verifier.{Failure, VerificationResult} - -import java.io.PrintWriter -import java.nio.file.{Files, Path, Paths} -import scala.collection.convert.ImplicitConversions.`iterable AsScalaIterable` - -@DoNotDiscover -class DependencyAnalysisProgressTests extends AnyFunSuite with DependencyAnalysisTestFramework { - - val ignores: Seq[String] = Seq.empty - val testDirectories: Seq[String] = Seq( - "dependencyAnalysisTests/verificationProgress/incrRand" - ) - - testDirectories foreach (dir => createSingleTest(dir, "")) - - def createSingleTest(dirName: String, fileName: String): Unit = { - test(dirName) { - val writer = new PrintWriter("src/test/resources/" + dirName + "/results_" + System.currentTimeMillis() + ".csv") - try{ - val dirPath = Paths.get(getClass.getClassLoader.getResource(dirName).toURI) - val directoryStream = Files.newDirectoryStream(dirPath) - val dirContent = directoryStream.toList - writer.println("File name,Progress (Peter),Progress (Lea)") - - for (filePath: Path <- dirContent.sorted - if Files.isReadable(filePath)) { - val rawFileName = filePath.getFileName.toString - if (rawFileName.endsWith(".vpr")) { - val vprFileName = rawFileName.replace(".vpr", "") - if (!ignores.contains(vprFileName)) - resetFrontend() - executeTest(dirName + "/", vprFileName, frontend, writer) - } - } - writer.close() - }catch{ - case t: Throwable => fail(t.toString) - } - } - } - - - def executeTest(filePrefix: String, - fileName: String, - frontend: SilFrontend, - writer: PrintWriter): Unit = { - - val program: Program = tests.loadProgram(filePrefix, fileName, frontend) - val result: VerificationResult = frontend.verifier.verify(program) - val errors = result match { - case failure: Failure => failure.errors - case _ => List.empty - } - - val joinedDependencyGraphInterpreter = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].joinedDependencyGraphInterpreter - - val (progressPeter, progressLea, _) = joinedDependencyGraphInterpreter.get.computeVerificationProgress() - - println(s"$filePrefix$fileName:\n\tProgress (Peter) = $progressPeter\t\t\tProgress (Lea) = $progressLea\n") - writer.println(s"$fileName,\t$progressPeter,\t$progressLea") - } -} diff --git a/src/test/scala/VerificationProgressRunner.scala b/src/test/scala/VerificationProgressRunner.scala new file mode 100644 index 000000000..c39cacde0 --- /dev/null +++ b/src/test/scala/VerificationProgressRunner.scala @@ -0,0 +1,62 @@ +package viper.silicon.tests + +import viper.silicon.dependencyAnalysis.DependencyAnalysisReporter +import viper.silver.ast.Program +import viper.silver.frontend.SilFrontend +import viper.silver.verifier.VerificationResult + +import java.io.PrintWriter +import java.nio.file.{Files, Path, Paths} +import scala.collection.convert.ImplicitConversions.`iterable AsScalaIterable` + + +object VerificationProgressRunner extends DependencyAnalysisTestFramework { + + val ignores: Seq[String] = Seq.empty + val pathToTests: String = "src/test/resources/" + val testDirectories: Seq[String] = Seq( + "dependencyAnalysisTests/verificationProgress/incrRand" + ) + + def main(args: Array[String]): Unit = { + testDirectories foreach computeProgressForDir + } + + def computeProgressForDir(dirName: String): Unit = { + val writer = new PrintWriter(pathToTests + dirName + "/results_" + System.currentTimeMillis() + ".csv") + + val directoryStream = Files.newDirectoryStream(Paths.get(pathToTests, dirName)) + val dirContent = directoryStream.toList + writer.println("File name,Progress (Peter),Progress (Lea)") + println("File name,Progress (Peter),Progress (Lea)") + + for (filePath: Path <- dirContent.sorted + if Files.isReadable(filePath)) { + val rawFileName = filePath.getFileName.toString + if (rawFileName.endsWith(".vpr")) { + val vprFileName = rawFileName.replace(".vpr", "") + if (!ignores.contains(vprFileName)) + resetFrontend() + computeProgress(dirName + "/", vprFileName, frontend, writer) + } + } + writer.close() + } + + + def computeProgress(filePrefix: String, + fileName: String, + frontend: SilFrontend, + writer: PrintWriter): Unit = { + + val program: Program = tests.loadProgram(filePrefix, fileName, frontend) + val _: VerificationResult = frontend.verifier.verify(program) + + val joinedDependencyGraphInterpreter = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].joinedDependencyGraphInterpreter + + val (progressPeter, progressLea, _) = joinedDependencyGraphInterpreter.get.computeVerificationProgress() + + writer.println(s"$fileName,\t$progressPeter,\t$progressLea") + println(s"$fileName,\t$progressPeter,\t$progressLea") + } +} From 36fa7eb71476c502b3b7e022b2b531176ddcb89b Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 16 Jan 2026 10:41:49 +0100 Subject: [PATCH 321/474] add verification progress examples --- .../DependencyGraphInterpreter.scala | 6 +++-- .../incrRand/incrRand-1.vpr | 10 ++++++++ .../incrRand/incrRand-2.vpr | 11 +++++++++ .../incrRand/incrRand-3.vpr | 13 +++++++++++ .../incrRand/incrRand-4.vpr | 16 +++++++++++++ .../incrRand/incrRand-5.vpr | 16 +++++++++++++ .../incrRand/incrRand-6a.vpr | 17 ++++++++++++++ .../incrRand/incrRand-6b.vpr | 17 ++++++++++++++ .../incrRand/incrRand-7.vpr | 16 +++++++++++++ .../incrRand/incrRand-8a.vpr | 1 - .../incrRand/incrRand-8b.vpr | 1 - .../incrRand/incrRand-8c.vpr | 1 - .../incrRand/results_1768556239635.csv | 12 ++++++++++ .../verificationProgress/perms/perms-1.vpr | 19 +++++++++++++++ .../verificationProgress/perms/perms-3.vpr | 20 ++++++++++++++++ .../verificationProgress/perms/perms-4.vpr | 21 +++++++++++++++++ .../verificationProgress/perms/perms-5.vpr | 23 +++++++++++++++++++ .../perms/perms-abstract.vpr | 20 ++++++++++++++++ .../perms/results_1768556253265.csv | 6 +++++ .../scala/VerificationProgressRunner.scala | 17 +++++++++----- 20 files changed, 252 insertions(+), 11 deletions(-) create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-1.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-2.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-3.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-4.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-5.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-6a.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-6b.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-7.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/results_1768556239635.csv create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgress/perms/perms-1.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgress/perms/perms-3.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgress/perms/perms-4.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgress/perms/perms-5.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgress/perms/perms-abstract.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgress/perms/results_1768556253265.csv diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 89b2d5017..44aad81a0 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -246,9 +246,11 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen val assertionsWithExplicitDeps = relevantAssertions.filter(deps => deps._2.exists(d => AssumptionType.explicitAssumptionTypes.intersect(d.assumptionTypes).nonEmpty)).keySet.getSourceSet().diff(assertionsWithFailures) val fullyVerifiedAssertions = relevantAssertions.keySet.getSourceSet().diff(assertionsWithFailures).diff(assertionsWithExplicitDeps) + val numRelevantAssertions = relevantAssertions.keySet.size.toDouble + // Peter's metric val specQuality = coveredSourceCodeStmts.size.toDouble / (coveredSourceCodeStmts.size.toDouble + uncoveredSourceCodeStmts.size.toDouble) - val proofQualityPeter = fullyVerifiedAssertions.size.toDouble / relevantAssertions.keySet.size.toDouble + val proofQualityPeter = if(numRelevantAssertions > 0) fullyVerifiedAssertions.size.toDouble / numRelevantAssertions else 1.0 val verificationProgressPeter = specQuality * proofQualityPeter // Lea's metric @@ -257,7 +259,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen else UserLevelDependencyAnalysisNode.extractNonExplicitAssumptionNodes(assumptions).size.toDouble / assumptions.size.toDouble } - val proofQualityLea = proofQualityPerAssertion.sum / relevantAssertions.keys.size.toDouble + val proofQualityLea = if(numRelevantAssertions > 0) proofQualityPerAssertion.sum / numRelevantAssertions else 1.0 val verificationProgressLea = specQuality * proofQualityLea diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-1.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-1.vpr new file mode 100644 index 000000000..eb94f77f0 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-1.vpr @@ -0,0 +1,10 @@ + +method rand() returns (res: Int) + ensures res > 0 + + +method incrRand(a: Int) returns (res: Int) +{ + var r: Int := rand() + res := a + r +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-2.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-2.vpr new file mode 100644 index 000000000..1b264ab16 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-2.vpr @@ -0,0 +1,11 @@ + +method rand() returns (res: Int) + ensures res > 0 + + +method incrRand(a: Int) returns (res: Int) + ensures res > a +{ + var r: Int := rand() + res := a + r +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-3.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-3.vpr new file mode 100644 index 000000000..b850d092f --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-3.vpr @@ -0,0 +1,13 @@ + +method rand() returns (res: Int) + ensures res > 0 + + +method incrRand(a: Int) returns (res: Int) + requires a >= 0 + ensures res > a + ensures res > 0 +{ + var r: Int := rand() + res := a + r +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-4.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-4.vpr new file mode 100644 index 000000000..0fba565c0 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-4.vpr @@ -0,0 +1,16 @@ + +method rand() returns (res: Int) + ensures res > 0 +{ + assume false +} + + +method incrRand(a: Int) returns (res: Int) + requires a >= 0 + ensures res > a + ensures res > 0 +{ + var r: Int := rand() + res := a + r +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-5.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-5.vpr new file mode 100644 index 000000000..03bfd9450 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-5.vpr @@ -0,0 +1,16 @@ + +method rand() returns (res: Int) + ensures res > 0 +{ + assume res > 0 +} + + +method incrRand(a: Int) returns (res: Int) + requires a >= 0 + ensures res > a + ensures res > 0 +{ + var r: Int := rand() + res := a + r +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-6a.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-6a.vpr new file mode 100644 index 000000000..37f0c4a17 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-6a.vpr @@ -0,0 +1,17 @@ + +method rand() returns (res: Int) + ensures res > 0 +{ + assume res > 0 // unnecessary + res := 10 +} + + +method incrRand(a: Int) returns (res: Int) + requires a >= 0 + ensures res > a + ensures res > 0 +{ + var r: Int := rand() + res := a + r +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-6b.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-6b.vpr new file mode 100644 index 000000000..38c512221 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-6b.vpr @@ -0,0 +1,17 @@ + +method rand() returns (res: Int) + ensures res > 0 +{ + assume res >= 0 + res := res + 1 +} + + +method incrRand(a: Int) returns (res: Int) + requires a >= 0 + ensures res > a + ensures res > 0 +{ + var r: Int := rand() + res := a + r +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-7.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-7.vpr new file mode 100644 index 000000000..75b46d06c --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-7.vpr @@ -0,0 +1,16 @@ + +method rand() returns (res: Int) + ensures res > 0 +{ + res := 10 +} + + +method incrRand(a: Int) returns (res: Int) + requires a >= 0 + ensures res > a + ensures res > 0 +{ + var r: Int := rand() + res := a + r +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-8a.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-8a.vpr index 1324d817f..d5b9067e5 100644 --- a/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-8a.vpr +++ b/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-8a.vpr @@ -1,4 +1,3 @@ -// Verification Progress: 1.0 (A), 1.0 (P) method rand() returns (res: Int) ensures res > 0 diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-8b.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-8b.vpr index 680dc146f..002363ad5 100644 --- a/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-8b.vpr +++ b/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-8b.vpr @@ -1,4 +1,3 @@ -// Verification Progress: 0.83 (A), 0.83 (P) method rand() returns (res: Int) ensures res > 0 diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-8c.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-8c.vpr index b3f0c1ef5..b282ef3f3 100644 --- a/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-8c.vpr +++ b/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/incrRand-8c.vpr @@ -1,4 +1,3 @@ -// Verification Progress: 0.83 (A), 0.86 (P) method rand() returns (res: Int) ensures res > 0 diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/results_1768556239635.csv b/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/results_1768556239635.csv new file mode 100644 index 000000000..18090cb76 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgress/incrRand/results_1768556239635.csv @@ -0,0 +1,12 @@ +File name Progress (Peter) Progress (Lea) verification successful? +incrRand-1 0.000 0.000 true +incrRand-2 0.000 0.667 true +incrRand-3 0.000 0.708 true +incrRand-4 0.000 0.517 true +incrRand-5 0.000 0.517 true +incrRand-6a 1.000 1.000 true +incrRand-6b 0.000 0.711 true +incrRand-7 1.000 1.000 true +incrRand-8a 1.000 1.000 true +incrRand-8b 0.800 0.800 true +incrRand-8c 0.857 0.929 true diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgress/perms/perms-1.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgress/perms/perms-1.vpr new file mode 100644 index 000000000..a118357b9 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgress/perms/perms-1.vpr @@ -0,0 +1,19 @@ +field f: Int + + +method store(x: Ref, a: Int) +{ + inhale acc(x.f) + x.f := a +} + + +method client() +{ + var x: Ref + x := new(f) + + store(x, 0) + + x.f := x.f + 1 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgress/perms/perms-3.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgress/perms/perms-3.vpr new file mode 100644 index 000000000..619029f46 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgress/perms/perms-3.vpr @@ -0,0 +1,20 @@ +field f: Int + + +method store(x: Ref, a: Int) + requires acc(x.f) + ensures acc(x.f) +{ + x.f := a +} + + +method client() +{ + var x: Ref + x := new(f) + + store(x, 0) + + x.f := x.f + 1 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgress/perms/perms-4.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgress/perms/perms-4.vpr new file mode 100644 index 000000000..6dd1d327c --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgress/perms/perms-4.vpr @@ -0,0 +1,21 @@ +field f: Int + + +method store(x: Ref, a: Int) + requires acc(x.f) + ensures acc(x.f) + ensures x.f == a +{ + x.f := a +} + + +method client() +{ + var x: Ref + x := new(f) + + store(x, 0) + + x.f := x.f + 1 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgress/perms/perms-5.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgress/perms/perms-5.vpr new file mode 100644 index 000000000..4044d4570 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgress/perms/perms-5.vpr @@ -0,0 +1,23 @@ +field f: Int + + +method store(x: Ref, a: Int) + requires acc(x.f) + ensures acc(x.f) + ensures x.f == a +{ + x.f := a +} + + +method client() +{ + var x: Ref + x := new(f) + + store(x, 0) + + x.f := x.f + 1 + + assert x.f == 1 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgress/perms/perms-abstract.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgress/perms/perms-abstract.vpr new file mode 100644 index 000000000..68ba1940b --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgress/perms/perms-abstract.vpr @@ -0,0 +1,20 @@ +field f: Int + + +method store(x: Ref, a: Int) + requires acc(x.f) + ensures acc(x.f) + ensures x.f == a + + +method client() +{ + var x: Ref + x := new(f) + + store(x, 0) + + x.f := x.f + 1 + + assert x.f == 1 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgress/perms/results_1768556253265.csv b/src/test/resources/dependencyAnalysisTests/verificationProgress/perms/results_1768556253265.csv new file mode 100644 index 000000000..c63d3bd97 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgress/perms/results_1768556253265.csv @@ -0,0 +1,6 @@ +File name Progress (Peter) Progress (Lea) verification successful? +perms-1 0.167 0.167 true +perms-3 0.500 0.500 true +perms-4 0.750 0.750 true +perms-5 1.000 1.000 true +perms-abstract 0.200 0.683 true diff --git a/src/test/scala/VerificationProgressRunner.scala b/src/test/scala/VerificationProgressRunner.scala index c39cacde0..f483c99b1 100644 --- a/src/test/scala/VerificationProgressRunner.scala +++ b/src/test/scala/VerificationProgressRunner.scala @@ -3,6 +3,7 @@ package viper.silicon.tests import viper.silicon.dependencyAnalysis.DependencyAnalysisReporter import viper.silver.ast.Program import viper.silver.frontend.SilFrontend +import viper.silver.verifier import viper.silver.verifier.VerificationResult import java.io.PrintWriter @@ -15,7 +16,8 @@ object VerificationProgressRunner extends DependencyAnalysisTestFramework { val ignores: Seq[String] = Seq.empty val pathToTests: String = "src/test/resources/" val testDirectories: Seq[String] = Seq( - "dependencyAnalysisTests/verificationProgress/incrRand" + "dependencyAnalysisTests/verificationProgress/incrRand", + "dependencyAnalysisTests/verificationProgress/perms" ) def main(args: Array[String]): Unit = { @@ -27,8 +29,9 @@ object VerificationProgressRunner extends DependencyAnalysisTestFramework { val directoryStream = Files.newDirectoryStream(Paths.get(pathToTests, dirName)) val dirContent = directoryStream.toList - writer.println("File name,Progress (Peter),Progress (Lea)") - println("File name,Progress (Peter),Progress (Lea)") + writer.println("File name\tProgress (Peter)\tProgress (Lea)\tverification successful?") + println(s"\n$dirName") + println("File name\tProgress (Peter)\tProgress (Lea)\tverification successful?") for (filePath: Path <- dirContent.sorted if Files.isReadable(filePath)) { @@ -50,13 +53,15 @@ object VerificationProgressRunner extends DependencyAnalysisTestFramework { writer: PrintWriter): Unit = { val program: Program = tests.loadProgram(filePrefix, fileName, frontend) - val _: VerificationResult = frontend.verifier.verify(program) + val result: VerificationResult = frontend.verifier.verify(program) + + val hasFailures = result.isInstanceOf[verifier.Failure] val joinedDependencyGraphInterpreter = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].joinedDependencyGraphInterpreter val (progressPeter, progressLea, _) = joinedDependencyGraphInterpreter.get.computeVerificationProgress() - writer.println(s"$fileName,\t$progressPeter,\t$progressLea") - println(s"$fileName,\t$progressPeter,\t$progressLea") + writer.println(f"$fileName\t$progressPeter%.3f\t$progressLea%.3f\t${!hasFailures}") + println(f"$fileName\t$progressPeter%.3f\t$progressLea%.3f\t${!hasFailures}") } } From 74a2a1eca94a5cd9184c565751732aaa4e92cd0a Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 16 Jan 2026 11:24:55 +0100 Subject: [PATCH 322/474] update Readme --- src/main/scala/dependencyAnalysis/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/scala/dependencyAnalysis/README.md b/src/main/scala/dependencyAnalysis/README.md index b7a59c5c3..dda8f5c8b 100644 --- a/src/main/scala/dependencyAnalysis/README.md +++ b/src/main/scala/dependencyAnalysis/README.md @@ -42,6 +42,10 @@ Example queries for program `src/test/resources/dependencyAnalysisTests/unitTest - `prune 66 71` - Exports the program pruned with respect to lines 66 and 71. - exportFileName: path and file name for the pruned program (e.g. `prunedPrograms/test.vpr` +- `progress` + - Prints the verification progress of the program +- `guide` + - Prints verification guidance # Neo4j Scripts and Usage From 71b955fcadcf58d37c5f20342851d2a935e5e0c7 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 16 Jan 2026 11:33:21 +0100 Subject: [PATCH 323/474] fix typos --- .../scala/dependencyAnalysis/DependencyAnalysisUserTool.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala index 52eaa6cb9..34da78f2d 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala @@ -274,11 +274,11 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete } val assumptionRanking = fullGraphInterpreter.computeAssumptionRanking().filter(_._2 > 0) - println(s"Assumptions and the number of dependencies:\n\t${assumptionRanking.mkString("\n\t")}\n") + println(s"Assumptions and the number of dependents:\n\t${assumptionRanking.mkString("\n\t")}\n") val memberCoverageRanking = memberInterpreters.filter(mInterpreter => mInterpreter.getMember.isDefined && mInterpreter.getMember.get.isInstanceOf[Method]) .map(mInterpreter => (mInterpreter.getMember.get.name, mInterpreter.computeUncoveredStatements())) .toList.filter(_._2 > 0).sortBy(_._2).reverse - println(s"Members and the number of uncovered statements:\n\t${memberCoverageRanking.mkString("\n\t")}\n") + println(s"Methods and the number of uncovered statements:\n\t${memberCoverageRanking.mkString("\n\t")}\n") } } From 7b45a02e4b20cdea32aabcd34b135a5ffb616644 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 26 Jan 2026 16:49:59 +0100 Subject: [PATCH 324/474] fix after merge --- src/main/scala/rules/Consumer.scala | 2 +- src/main/scala/rules/Executor.scala | 8 +- src/main/scala/rules/HeapSupporter.scala | 110 +++++++++++------- src/main/scala/rules/MagicWandSupporter.scala | 6 +- src/main/scala/rules/PredicateSupporter.scala | 16 +-- src/main/scala/rules/Producer.scala | 4 +- .../scala/rules/QuantifiedChunkSupport.scala | 2 +- src/main/scala/state/Chunks.scala | 54 --------- .../functions/FunctionVerificationUnit.scala | 2 +- 9 files changed, 89 insertions(+), 115 deletions(-) diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index 288a4bed2..454077a9e 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -270,7 +270,7 @@ object consumer extends ConsumptionRules { WildcardSimplifyingPermTimes(tPerm, s2.permissionScalingFactor) val lossExp = ePermNew.map(p => ast.PermMul(p, s2.permissionScalingFactorExp.get)(p.pos, p.info, p.errT)) val s3 = v2.heapSupporter.triggerResourceIfNeeded(s2, accPred.loc, tArgs, eArgsNew, v2) - v2.heapSupporter.consumeSingle(s3, h, accPred.loc, tArgs, eArgsNew, loss, lossExp, returnSnap, pve, v2)((s4, h4, snap, v4) => { + v2.heapSupporter.consumeSingle(s3, h, accPred.loc, tArgs, eArgsNew, loss, lossExp, returnSnap, pve, v2, assumptionType)((s4, h4, snap, v4) => { val s5 = s4.copy(constrainableARPs = s.constrainableARPs, partiallyConsumedHeap = Some(h4)) Q(s5, h4, snap, v4) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index b81a2da3f..f9cc21449 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -409,8 +409,8 @@ object executor extends ExecutionRules { val pve = AssignmentFailed(ass) eval(s, eRcvr, pve, v)((s1, tRcvr, eRcvrNew, v1) => { eval(s1, rhs, pve, v1)((s2, tRhs, eRhsNew, v2) => { - val (tSnap, _) = ssaifyRhs(tRhs, rhs, eRhsNew, field.name, field.typ, v2, s2) - v2.heapSupporter.execFieldAssign(s2, ass, tRcvr, eRcvrNew, tSnap, eRhsNew, pve, v2)(Q) + val (tSnap, _) = ssaifyRhs(tRhs, rhs, eRhsNew, field.name, field.typ, v2, s2, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit)) + v2.heapSupporter.execFieldAssign(s2, ass, tRcvr, eRcvrNew, tSnap, eRhsNew, pve, v2, annotatedDependencyTypeOpt.getOrElse(DependencyType.Implicit))(Q) }) }) @@ -433,7 +433,7 @@ object executor extends ExecutionRules { val fld = flds.head val snap = v.decider.fresh(fld.name, v.symbolConverter.toSort(fld.typ), Option.when(withExp)(extractPTypeFromExp(x))) val snapExp = Option.when(withExp)(ast.DebugLabelledOld(ast.FieldAccess(eRcvrNew.get, fld)(), debugLabel)(stmt.pos, stmt.info, stmt.errT)) - v.heapSupporter.produceSingle(s, fld, Seq(tRcvr), eRcvr, snap, snapExp, p, pExp, NullPartialVerificationError, false, v)((s1, v1) => { + v.heapSupporter.produceSingle(s, fld, Seq(tRcvr), eRcvr, snap, snapExp, p, pExp, NullPartialVerificationError, false, v, annotatedDependencyTypeOpt.getOrElse(DependencyType.Implicit))((s1, v1) => { addFieldPerms(s1, flds.tail, v1)(QB) }) } @@ -443,7 +443,7 @@ object executor extends ExecutionRules { addFieldPerms(s, fields, v)((s0, v0) => { val s1 = s0.copy(g = s0.g + (x, (tRcvr, eRcvrNew))) val s2 = if (withExp) s1.copy(oldHeaps = s1.oldHeaps + (debugHeapName -> magicWandSupporter.getEvalHeap(s1))) else s1 - v0.decider.assume(ts, Option.when(withExp)(DebugExp.createInstance(Some("Reference Disjointness"), esNew, esNew, InsertionOrderedSet.empty)), enforceAssumption = false) + v0.decider.assume(ts, Option.when(withExp)(DebugExp.createInstance(Some("Reference Disjointness"), esNew, esNew, InsertionOrderedSet.empty)), enforceAssumption = false, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit)) Q(s2, v0) }) diff --git a/src/main/scala/rules/HeapSupporter.scala b/src/main/scala/rules/HeapSupporter.scala index 3f93212e4..545fbb5a5 100644 --- a/src/main/scala/rules/HeapSupporter.scala +++ b/src/main/scala/rules/HeapSupporter.scala @@ -9,8 +9,10 @@ package viper.silicon.rules import viper.silicon import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp +import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType +import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyType, TransitivityAnalysisSourceInfo} import viper.silicon.interfaces.VerificationResult -import viper.silicon.interfaces.state.{ChunkIdentifer, NonQuantifiedChunk} +import viper.silicon.interfaces.state.{ChunkIdentifer, NonQuantifiedChunk, QuantifiedChunk} import viper.silicon.resources.{FieldID, PredicateID} import viper.silicon.rules.havocSupporter.{HavocHelperData, HavocOneData, HavocallData} import viper.silicon.rules.quantifiedChunkSupporter.freshSnapshotMap @@ -57,7 +59,8 @@ trait HeapSupportRules extends SymbolicExecutionRules { tRhs: Term, eRhsNew: Option[ast.Exp], pve: PartialVerificationError, - v: Verifier) + v: Verifier, + dependencyType: DependencyType) (Q: (State, Verifier) => VerificationResult) : VerificationResult @@ -76,7 +79,8 @@ trait HeapSupportRules extends SymbolicExecutionRules { ePerm: Option[ast.Exp], returnSnap: Boolean, pve: PartialVerificationError, - v: Verifier) + v: Verifier, + assumptionType: AssumptionType) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult): VerificationResult def consumeQuantified(s: State, @@ -104,7 +108,8 @@ trait HeapSupportRules extends SymbolicExecutionRules { negativePermissionReason: => ErrorReason, notInjectiveReason: => ErrorReason, insufficientPermissionReason: => ErrorReason, - v: Verifier) + v: Verifier, + assumptionType: AssumptionType) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult): VerificationResult def produceSingle(s: State, @@ -117,7 +122,8 @@ trait HeapSupportRules extends SymbolicExecutionRules { ePerm: Option[ast.Exp], pve: PartialVerificationError, mergeAndTrigger: Boolean, - v: Verifier) + v: Verifier, + dependencyType: DependencyType) (Q: (State, Verifier) => VerificationResult): VerificationResult def produceQuantified(s: State, @@ -144,14 +150,16 @@ trait HeapSupportRules extends SymbolicExecutionRules { pve: PartialVerificationError, negativePermissionReason: => ErrorReason, notInjectiveReason: => ErrorReason, - v: Verifier) + v: Verifier, + assumptionType: AssumptionType) (Q: (State, Verifier) => VerificationResult): VerificationResult def havocResource(s: State, lhs: Term, resource: ast.Resource, condInfo: HavocHelperData, - v: Verifier): Heap + v: Verifier, + assumptionType: AssumptionType): Heap def collectForPermConditions(s: State, resource: ast.Resource, @@ -175,7 +183,8 @@ class DefaultHeapSupportRules extends HeapSupportRules { tRhs: Term, eRhsNew: Option[ast.Exp], pve: PartialVerificationError, - v: Verifier) + v: Verifier, + dependencyType: DependencyType) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { val field = ass.lhs.field @@ -187,6 +196,8 @@ class DefaultHeapSupportRules extends HeapSupportRules { val chunkOrderHeuristics = quantifiedChunkSupporter.singleReceiverChunkOrderHeuristic(Seq(tRcvr), hints, v) val s2 = triggerResourceIfNeeded(s, ass.lhs, Seq(tRcvr), eRcvrNew.map(Seq(_)), v) v.decider.clearModel() + val lhsSourceInfo = TransitivityAnalysisSourceInfo(v.decider.analysisSourceInfoStack.getFullSourceInfo, AnalysisSourceInfo.createAnalysisSourceInfo(ass.lhs)) + v.decider.analysisSourceInfoStack.setForcedSource(lhsSourceInfo) // splitting lhs and rhs to make permission flow analysis more precise val result = quantifiedChunkSupporter.removePermissions( s2, relevantChunks, @@ -201,19 +212,23 @@ class DefaultHeapSupportRules extends HeapSupportRules { chunkOrderHeuristics, v ) + v.decider.analysisSourceInfoStack.removeForcedSource() result match { case (Complete(), s3, remainingChunks) => val h3 = Heap(remainingChunks ++ otherChunks) val (sm, smValueDef) = quantifiedChunkSupporter.singletonSnapshotMap(s3, field, Seq(tRcvr), tRhs, v) v.decider.prover.comment("Definitional axioms for singleton-FVF's value") val debugExp = Option.when(withExp)(DebugExp.createInstance("Definitional axioms for singleton-FVF's value", isInternal_ = true)) - v.decider.assumeDefinition(smValueDef, debugExp) + v.decider.assumeDefinition(smValueDef, debugExp, dependencyType.assumptionType) + v.decider.analysisSourceInfoStack.setForcedSource(lhsSourceInfo) // splitting lhs and rhs to make permission flow analysis more precise val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(Seq(`?r`), Option.when(withExp)(Seq(ast.LocalVarDecl("r", ast.Ref)(ass.pos, ass.info, ass.errT))), - field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), sm, s.program) + field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), sm, s.program, v, AssumptionType.Internal, isExhale=false) if (s3.heapDependentTriggers.contains(field)) { - val debugExp2 = Option.when(withExp)(DebugExp.createInstance(s"FieldTrigger(${eRcvrNew.toString()}.${field.name})")) - v.decider.assume(FieldTrigger(field.name, sm, tRcvr), debugExp2) + val debugExp2 = Option.when(withExp)(DebugExp.createInstance(s"FieldTrigger(${eRcvrNew.toString}.${field.name})")) + v.decider.assume(FieldTrigger(field.name, sm, tRcvr), debugExp2, AssumptionType.Trigger) } + v.decider.analysisSourceInfoStack.removeForcedSource() + v.decider.dependencyAnalyzer.addCustomTransitiveDependency(lhsSourceInfo, v.decider.analysisSourceInfoStack.getFullSourceInfo) val s4 = s3.copy(h = h3 + ch) val (debugHeapName, _) = v.getDebugOldLabel(s4, ass.lhs.pos) val s5 = if (withExp) s4.copy(oldHeaps = s4.oldHeaps + (debugHeapName -> magicWandSupporter.getEvalHeap(s4))) else s4 @@ -223,13 +238,17 @@ class DefaultHeapSupportRules extends HeapSupportRules { } } else { val description = s"consume ${ass.pos}: $ass" - chunkSupporter.consume(s, s.h, field, Seq(tRcvr), eRcvrNew.map(Seq(_)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), false, ve, v, description)((s3, h3, _, v3) => { + val lhsSourceInfo = TransitivityAnalysisSourceInfo(v.decider.analysisSourceInfoStack.getFullSourceInfo, AnalysisSourceInfo.createAnalysisSourceInfo(ass.lhs)) + v.decider.analysisSourceInfoStack.setForcedSource(lhsSourceInfo) // splitting lhs and rhs to make permission flow analysis more precise + chunkSupporter.consume(s, s.h, field, Seq(tRcvr), eRcvrNew.map(Seq(_)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), false, ve, v, description, dependencyType.assumptionType)((s3, h3, _, v3) => { val id = BasicChunkIdentifier(field.name) - val newChunk = BasicChunk(FieldID, id, Seq(tRcvr), eRcvrNew.map(Seq(_)), tRhs, eRhsNew, FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT))) + val newChunk = BasicChunk.apply(FieldID, id, Seq(tRcvr), eRcvrNew.map(Seq(_)), tRhs, eRhsNew, FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), v3.decider.getAnalysisInfo(AssumptionType.Internal)) + v.decider.analysisSourceInfoStack.removeForcedSource() chunkSupporter.produce(s3, h3, newChunk, v3)((s4, h4, v4) => { val s5 = s4.copy(h = h4) val (debugHeapName, _) = v4.getDebugOldLabel(s5, ass.lhs.pos) val s6 = if (withExp) s5.copy(oldHeaps = s5.oldHeaps + (debugHeapName -> magicWandSupporter.getEvalHeap(s5))) else s5 + v4.decider.dependencyAnalyzer.addCustomTransitiveDependency(lhsSourceInfo, v4.decider.analysisSourceInfoStack.getFullSourceInfo) Q(s6, v4) }) }) @@ -270,7 +289,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { case w: ast.MagicWand => MagicWandIdentifier(w, s2.program).toString } DebugExp.createInstance(s"Resource trigger(${name}($argsString))", isInternal_ = true) - })) + }), AssumptionType.Trigger) } val currentPermAmount = ResourcePermissionLookup(res, pmDef.pm, tArgs, s2.program) @@ -280,7 +299,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { v.decider.prover.comment(s"perm($resAcc) ~~> assume upper permission bound") val (debugHeapName, debugLabel) = v.getDebugOldLabel(s2, resAcc.pos, Some(h)) val exp = Option.when(withExp)(ast.PermLeCmp(ast.DebugLabelledOld(ast.CurrentPerm(resAcc)(), debugLabel)(), ast.FullPerm()())()) - v.decider.assume(PermAtMost(currentPermAmount, FullPerm), exp, exp.map(s2.substituteVarsInExp(_))) + v.decider.assume(PermAtMost(currentPermAmount, FullPerm), exp, exp.map(s2.substituteVarsInExp(_)), AssumptionType.Internal) val s3 = if (Verifier.config.enableDebugging()) s2.copy(oldHeaps = s2.oldHeaps + (debugHeapName -> h)) else s2 s3 case _ => s2 @@ -320,11 +339,11 @@ class DefaultHeapSupportRules extends HeapSupportRules { * quantifier in whose body field 'fa.field' was accessed) * which is protected by a trigger term that we currently don't have. */ - v.decider.assume(And(fvfDef.valueDefinitions), Option.when(withExp)(DebugExp.createInstance("Value definitions", isInternal_ = true))) + v.decider.assume(And(fvfDef.valueDefinitions), Option.when(withExp)(DebugExp.createInstance("Value definitions", isInternal_ = true)), AssumptionType.Internal) if (s.heapDependentTriggers.contains(fa.field)) { val trigger = FieldTrigger(fa.field.name, fvfDef.sm, tRcvr) val triggerExp = Option.when(withExp)(DebugExp.createInstance(s"FieldTrigger(${eRcvr.toString()}.${fa.field.name})")) - v.decider.assume(trigger, triggerExp) + v.decider.assume(trigger, triggerExp, AssumptionType.Trigger) } if (s.triggerExp) { val fvfLookup = Lookup(fa.field.name, fvfDef.sm, tRcvr) @@ -368,7 +387,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { if (s2.heapDependentTriggers.contains(fa.field)) { val trigger = FieldTrigger(fa.field.name, sm, tRcvr) val triggerExp = Option.when(withExp)(DebugExp.createInstance(s"FieldTrigger(${eRcvr.toString()}.${fa.field.name})")) - v.decider.assume(trigger, triggerExp) + v.decider.assume(trigger, triggerExp, AssumptionType.Trigger) } val (permCheck, permCheckExp, s3) = if (s2.triggerExp) { @@ -431,7 +450,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { val eArgsStr = eArgs.mkString(", ") val debugExp = Option.when(withExp)(DebugExp.createInstance(Some(s"Resource trigger(${name}($eArgsStr))"), Some(resAcc), Some(resAcc), None, isInternal_ = true, InsertionOrderedSet.empty)) - v.decider.assume(trigger(smDef1.sm), debugExp) + v.decider.assume(trigger(smDef1.sm), debugExp, AssumptionType.Trigger) s.copy(smCache = smCache1, functionRecorder = s.functionRecorder.recordFvfAndDomain(smDef1)) } else { s @@ -448,7 +467,8 @@ class DefaultHeapSupportRules extends HeapSupportRules { ePerm: Option[ast.Exp], pve: PartialVerificationError, mergeAndTrigger: Boolean, - v: Verifier) + v: Verifier, + dependencyType: DependencyType) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { val useQPs = s.isQuantifiedResource(resource) if (useQPs) { @@ -456,17 +476,17 @@ class DefaultHeapSupportRules extends HeapSupportRules { val tFormalArgs = s.getFormalArgVars(resource, v) val eFormalArgs = Option.when(withExp)(s.getFormalArgDecls(resource)) quantifiedChunkSupporter.produceSingleLocation( - s, resource, tFormalArgs, eFormalArgs, tArgs, eArgs, tSnap, tPerm, ePerm, trigger, mergeAndTrigger, v)(Q) + s, resource, tFormalArgs, eFormalArgs, tArgs, eArgs, tSnap, tPerm, ePerm, trigger, mergeAndTrigger, v, dependencyType.assumptionType)(Q) } else { resource match { case w: ast.MagicWand => - magicWandSupporter.createChunk(s, w, MagicWandSnapshot(tSnap), pve, v)((s2, chWand, v2) => + magicWandSupporter.createChunk(s, w, MagicWandSnapshot(tSnap), pve, v, dependencyType.assumptionType)((s2, chWand, v2) => chunkSupporter.produce(s2, s2.h, chWand, v2)((s3, h3, v3) => Q(s3.copy(h = h3), v3))) case _ => val chunkId = ChunkIdentifier(resource, s.program) val (resId, snap1) = if (resource.isInstanceOf[ast.Field]) (FieldID, tSnap) else (PredicateID, tSnap.convert(sorts.Snap)) - val ch = BasicChunk(resId, chunkId.asInstanceOf[BasicChunkIdentifier], tArgs, eArgs, snap1, eSnap, tPerm, ePerm) + val ch = BasicChunk.apply(resId, chunkId.asInstanceOf[BasicChunkIdentifier], tArgs, eArgs, snap1, eSnap, tPerm, ePerm, v.decider.getAnalysisInfo(dependencyType.assumptionType)) if (mergeAndTrigger) { chunkSupporter.produce(s, s.h, ch, v)((s2, h2, v2) => { if (resource.isInstanceOf[ast.Predicate] && Verifier.config.enablePredicateTriggersOnInhale() && s2.functionRecorder == NoopFunctionRecorder @@ -474,7 +494,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { val predicate = resource.asInstanceOf[ast.Predicate] val argsString = eArgs.mkString(", ") val debugExp = Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}($argsString))", isInternal_ = true)) - v2.decider.assume(App(s2.predicateData(predicate.name).triggerFunction, snap1 +: tArgs), debugExp) + v2.decider.assume(App(s2.predicateData(predicate.name).triggerFunction, snap1 +: tArgs), debugExp, AssumptionType.Trigger) } Q(s2.copy(h = h2), v2) }) @@ -494,7 +514,8 @@ class DefaultHeapSupportRules extends HeapSupportRules { ePerm: Option[ast.Exp], returnSnap: Boolean, pve: PartialVerificationError, - v: Verifier) + v: Verifier, + assumptionType: AssumptionType) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult): VerificationResult = { val resource = resAcc.res(s.program) val useQPs = s.isQuantifiedResource(resource) @@ -502,14 +523,14 @@ class DefaultHeapSupportRules extends HeapSupportRules { val tFormalArgs = s.getFormalArgVars(resource, v) val eFormalArgs = Option.when(withExp)(s.getFormalArgDecls(resource)) quantifiedChunkSupporter.consumeSingleLocation( - s, h, tFormalArgs, eFormalArgs, tArgs, eArgs, resAcc, tPerm, ePerm, returnSnap, None, pve, v)(Q) + s, h, tFormalArgs, eFormalArgs, tArgs, eArgs, resAcc, tPerm, ePerm, returnSnap, None, pve, v, assumptionType)(Q) } else { val ve = resAcc match { case l: ast.LocationAccess => pve dueTo InsufficientPermission(l) case w: ast.MagicWand => pve dueTo MagicWandChunkNotFound(w) } val description = s"consume ${resAcc.pos}: $resAcc" - chunkSupporter.consume(s, h, resource, tArgs, eArgs, tPerm, ePerm, returnSnap, ve, v, description)(Q) + chunkSupporter.consume(s, h, resource, tArgs, eArgs, tPerm, ePerm, returnSnap, ve, v, description, assumptionType)(Q) } } @@ -537,7 +558,8 @@ class DefaultHeapSupportRules extends HeapSupportRules { pve: PartialVerificationError, negativePermissionReason: => ErrorReason, notInjectiveReason: => ErrorReason, - v: Verifier) + v: Verifier, + assumptionType: AssumptionType) (Q: (State, Verifier) => VerificationResult): VerificationResult = { val tSnap = resource match { case f: ast.Field => @@ -573,7 +595,8 @@ class DefaultHeapSupportRules extends HeapSupportRules { pve, negativePermissionReason, notInjectiveReason, - v + v, + assumptionType )(Q) } @@ -602,7 +625,8 @@ class DefaultHeapSupportRules extends HeapSupportRules { negativePermissionReason: => ErrorReason, notInjectiveReason: => ErrorReason, insufficientPermissionReason: => ErrorReason, - v: Verifier) + v: Verifier, + assumptionType: AssumptionType) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult): VerificationResult = { quantifiedChunkSupporter.consume( s, @@ -630,7 +654,8 @@ class DefaultHeapSupportRules extends HeapSupportRules { negativePermissionReason, notInjectiveReason, insufficientPermissionReason, - v + v, + assumptionType )(Q) } @@ -638,11 +663,12 @@ class DefaultHeapSupportRules extends HeapSupportRules { lhs: Term, resource: ast.Resource, condInfo: HavocHelperData, - v: Verifier): Heap = { + v: Verifier, + assumptionType: AssumptionType): Heap = { if (s.isQuantifiedResource(resource)) { - havocQuantifiedResource(s, lhs, resource, condInfo, v) + havocQuantifiedResource(s, lhs, resource, condInfo, v, assumptionType) } else { - havocNonQuantifiedResource(s, lhs, resource, condInfo, v) + havocNonQuantifiedResource(s, lhs, resource, condInfo, v, assumptionType) } } @@ -663,7 +689,8 @@ class DefaultHeapSupportRules extends HeapSupportRules { lhs: Term, resource: ast.Resource, condInfo: HavocHelperData, - v: Verifier) + v: Verifier, + assumptionType: AssumptionType) : Heap = { val id = ChunkIdentifier(resource, s.program) @@ -674,12 +701,12 @@ class DefaultHeapSupportRules extends HeapSupportRules { val havockedSnap = v.decider.fresh("mwsf", sorts.MagicWandSnapFunction, Option.when(withExp)(PUnknown())) val cond = replacementCond(lhs, ch.args, condInfo) val magicWandSnapshot = MagicWandSnapshot(Ite(cond, havockedSnap, ch.snap.mwsf)) - ch.withSnap(magicWandSnapshot, None) + NonQuantifiedChunk.withSnap(ch, magicWandSnapshot, None, v.decider.getAnalysisInfo(assumptionType)) case ch => val havockedSnap = freshSnap(ch.snap.sort, v) val cond = replacementCond(lhs, ch.args, condInfo) - ch.withSnap(Ite(cond, havockedSnap, ch.snap), None) + NonQuantifiedChunk.withSnap(ch, Ite(cond, havockedSnap, ch.snap), None, v.decider.getAnalysisInfo(assumptionType)) } Heap(otherChunks ++ newChunks) } @@ -705,7 +732,8 @@ class DefaultHeapSupportRules extends HeapSupportRules { lhs: Term, resource: ast.Resource, condInfo: HavocHelperData, - v: Verifier) : Heap = { + v: Verifier, + assumptionType: AssumptionType) : Heap = { // Quantified field chunks are of the form R(r; sm, pm). // Conceptually, quantified predicate/wand chunks look like R(r1, ..., rn; sm, pm). @@ -758,9 +786,9 @@ class DefaultHeapSupportRules extends HeapSupportRules { v.decider.prover.comment("axiomatized snapshot map after havoc") val debugExp = Option.when(withExp)(DebugExp.createInstance("havoc new axiom", isInternal_ = true)) - v.decider.assume(newAxiom, debugExp) + v.decider.assume(newAxiom, debugExp, assumptionType) - ch.withSnapshotMap(newSm) + QuantifiedChunk.withSnapshotMap(ch, newSm, v.decider.getAnalysisInfo(assumptionType)) } Heap(newChunks ++ otherChunks) } diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 2989a44a5..b76eb9ca4 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -332,12 +332,12 @@ object magicWandSupporter extends SymbolicExecutionRules { val debugExp = Option.when(withExp)(DebugExp.createInstance("Definitional axioms for singleton-SM's value", true)) v2.decider.assumeDefinition(smValueDef, debugExp, assumptionType) val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(formalVars, formalVarExps, wand, tArgs, - eArgsNew, FullPerm, Option.when(withExp)(ast.FullPerm()()), sm, s.program, assumptionType) + eArgsNew, FullPerm, Option.when(withExp)(ast.FullPerm()()), sm, s.program, v, assumptionType, isExhale=false) val conservedPcs = s2.conservedPcs.head :+ v2.decider.pcs.after(preMark).definitionsOnly (s2, ch, conservedPcs.flatMap(_.conditionalized), Option.when(withExp)(conservedPcs.flatMap(_.conditionalizedExp)), v2) } else { - val ch = MagicWandChunk(MagicWandIdentifier(wand, s.program), s2.g.values, tArgs, eArgsNew, wandSnapshot, FullPerm, - Option.when(withExp)(ast.FullPerm()(wand.pos, wand.info, wand.errT))) + val ch = MagicWandChunk.apply(MagicWandIdentifier(wand, s.program), s2.g.values, tArgs, eArgsNew, wandSnapshot, FullPerm, + Option.when(withExp)(ast.FullPerm()(wand.pos, wand.info, wand.errT)), v.decider.getAnalysisInfo(assumptionType)) val conservedPcs = s2.conservedPcs.head :+ v2.decider.pcs.after(preMark).definitionsOnly // Partition path conditions into a set which include the freshSnapRoot and those which do not val (pcsWithFreshSnapRoot, pcsWithoutFreshSnapRoot) = conservedPcs.flatMap(pcs => pcs.conditionalized).partition(_.contains(freshSnapRoot)) diff --git a/src/main/scala/rules/PredicateSupporter.scala b/src/main/scala/rules/PredicateSupporter.scala index 6939b41c3..6edd0e10b 100644 --- a/src/main/scala/rules/PredicateSupporter.scala +++ b/src/main/scala/rules/PredicateSupporter.scala @@ -92,8 +92,8 @@ object predicateSupporter extends PredicateSupportRules { permissionScalingFactor = s.permissionScalingFactor, permissionScalingFactorExp = s.permissionScalingFactorExp).setConstrainable(constrainableWildcards, false) - v1.heapSupporter.produceSingle(s2, predicate, tArgs, eArgs, snap.get.convert(s2.predicateSnapMap(predicate.name)), None, tPerm, ePerm, pve, true, v1, dependencyType.assumptionType)((s3, v3) => { - val s4 = v3.heapSupporter.triggerResourceIfNeeded(s3, pa, tArgs, eArgs, v3, dependencyType.assumptionType) + v1.heapSupporter.produceSingle(s2, predicate, tArgs, eArgs, snap.get.convert(s2.predicateSnapMap(predicate.name)), None, tPerm, ePerm, pve, true, v1, dependencyType)((s3, v3) => { + val s4 = v3.heapSupporter.triggerResourceIfNeeded(s3, pa, tArgs, eArgs, v3) Q(s4, v3) }) }) @@ -105,8 +105,8 @@ object predicateSupporter extends PredicateSupportRules { tree match { case PredicateLeafNode(h, assumptions) => val debugExp = Option.when(withExp)(DebugExp.createInstance("Assumption from unfolded predicate body")) - v.decider.assume(assumptions.map(a => (a.replace(toReplace), debugExp)).toSeq) - val substChunks = h.values.map(_.substitute(toReplace).asInstanceOf[GeneralChunk].permScale(s.permissionScalingFactor, s.permissionScalingFactorExp)) + assumptions.foreach(a => v.decider.assume(a.replace(toReplace), debugExp, AssumptionType.Implicit)) // TODO ake + val substChunks = h.values.map(_.substitute(toReplace).asInstanceOf[GeneralChunk].permScale(s.permissionScalingFactor, s.permissionScalingFactorExp)) // TODO ake val quantifiedResourceIdentifiers: Set[ChunkIdentifer] = s.qpPredicates.map(p => BasicChunkIdentifier(p.name)) ++ s.qpFields.map(f => BasicChunkIdentifier(f.name)) ++ s.qpMagicWands @@ -120,21 +120,21 @@ object predicateSupporter extends PredicateSupportRules { case _ => s.program.findPredicate(bc.id.name) } val (sm, smValueDef) = quantifiedChunkSupporter.singletonSnapshotMap(s, resource, bc.args, bc.snap, v) - v.decider.assumeDefinition(smValueDef, None) + v.decider.assumeDefinition(smValueDef, None, AssumptionType.Internal) val codQvars = bc.resourceID match { case FieldID => Seq(`?r`) case _ => s.predicateFormalVarMap(resource.asInstanceOf[ast.Predicate].name) } newFr = newFr.recordFvfAndDomain(SnapshotMapDefinition(resource, sm, Seq(smValueDef), Seq())) - quantifiedChunkSupporter.createSingletonQuantifiedChunk(codQvars, None, resource, bc.args, None, bc.perm, None, sm, s.program) + quantifiedChunkSupporter.createSingletonQuantifiedChunk(codQvars, None, resource, bc.args, None, bc.perm, None, sm, s.program, v, AssumptionType.Rewrite, isExhale=false) // TODO ake case mwc: MagicWandChunk => val wand = mwc.id.ghostFreeWand val bodyVars = wand.subexpressionsToEvaluate(s.program) val codQvars = bodyVars.indices.toList.map(i => Var(Identifier(s"x$i"), v.symbolConverter.toSort(bodyVars(i).typ), false)) val (sm, smValueDef) = quantifiedChunkSupporter.singletonSnapshotMap(s, wand, mwc.args, mwc.snap, v) - v.decider.assumeDefinition(smValueDef, None) + v.decider.assumeDefinition(smValueDef, None, AssumptionType.Internal) newFr = newFr.recordFvfAndDomain(SnapshotMapDefinition(wand, sm, Seq(smValueDef), Seq())) - quantifiedChunkSupporter.createSingletonQuantifiedChunk(codQvars, None, wand, mwc.args, None, mwc.perm, None, sm, s.program) + quantifiedChunkSupporter.createSingletonQuantifiedChunk(codQvars, None, wand, mwc.args, None, mwc.perm, None, sm, s.program, v, AssumptionType.Rewrite, isExhale=false) // TODO ake } } else { c diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index 99ee063c3..db1f896b5 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -7,7 +7,7 @@ package viper.silicon.rules import viper.silicon.Config.JoinMode -import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyAnalyzer, ExpAnalysisSourceInfo} +import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyAnalyzer, DependencyType, ExpAnalysisSourceInfo} import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.{Unreachable, VerificationResult} @@ -349,7 +349,7 @@ object producer extends ProductionRules { else WildcardSimplifyingPermTimes(tPerm, s2.permissionScalingFactor) val gainExp = ePermNew.map(p => ast.PermMul(p, s2.permissionScalingFactorExp.get)(p.pos, p.info, p.errT)) - v2.heapSupporter.produceSingle(s2, resource, tArgs, eArgsNew, snap, None, gain, gainExp, pve, true, v2, assumptionType)(Q) + v2.heapSupporter.produceSingle(s2, resource, tArgs, eArgsNew, snap, None, gain, gainExp, pve, true, v2, DependencyType.make(assumptionType))(Q) }))) diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 44b6fa189..4e2461e1c 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -1087,7 +1087,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val conservedPcs = if (s.recordPcs) (s.conservedPcs.head :+ v.decider.pcs.after(definitionalAxiomMark)) +: s.conservedPcs.tail else s.conservedPcs - val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(formalQVars, formalQVarsExp, resource, tArgs, eArgs, tPerm, ePerm, sm, s.program, assumptionType, isExhale=false) + val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(formalQVars, formalQVarsExp, resource, tArgs, eArgs, tPerm, ePerm, sm, s.program, v, assumptionType, isExhale=false) val s1 = if (mergeAndTrigger) { val (fr1, h1) = v.stateConsolidator(s).merge(s.functionRecorder, s, s.h, Heap(Seq(ch)), v) diff --git a/src/main/scala/state/Chunks.scala b/src/main/scala/state/Chunks.scala index fab9b8b79..33b9b3ab3 100644 --- a/src/main/scala/state/Chunks.scala +++ b/src/main/scala/state/Chunks.scala @@ -32,12 +32,6 @@ case class BasicChunkIdentifier(name: String) extends ChunkIdentifer { } object BasicChunk { - private def apply(resourceID: BaseID, id: BasicChunkIdentifier, - args: Seq[Term], argsExp: Option[Seq[ast.Exp]], - snap: Term, snapExp: Option[ast.Exp], - perm: Term, permExp: Option[ast.Exp]): BasicChunk = { - new BasicChunk(resourceID, id, args, argsExp, snap, snapExp, perm, permExp) - } def apply(resourceID: BaseID, id: BasicChunkIdentifier, args: Seq[Term], argsExp: Option[Seq[ast.Exp]], @@ -111,18 +105,6 @@ sealed trait QuantifiedBasicChunk extends QuantifiedChunk { } object QuantifiedFieldChunk { - private def apply(id: BasicChunkIdentifier, - fvf: Term, - condition: Term, - conditionExp: Option[ast.Exp], - permValue: Term, - permValueExp: Option[ast.Exp], - invs: Option[InverseFunctions], - singletonRcvr: Option[Term], - singletonRcvrExp: Option[ast.Exp], - hints: Seq[Term]): QuantifiedFieldChunk = { - new QuantifiedFieldChunk(id, fvf, condition, conditionExp, permValue, permValueExp, invs, singletonRcvr, singletonRcvrExp, hints) - } def apply(id: BasicChunkIdentifier, fvf: Term, @@ -204,21 +186,6 @@ case class QuantifiedFieldChunk private(id: BasicChunkIdentifier, } object QuantifiedPredicateChunk { - private def apply(id: BasicChunkIdentifier, - quantifiedVars: Seq[Var], - quantifiedVarExps: Option[Seq[ast.LocalVarDecl]], - psf: Term, - condition: Term, - conditionExp: Option[ast.Exp], - permValue: Term, - permValueExp: Option[ast.Exp], - invs: Option[InverseFunctions], - singletonArgs: Option[Seq[Term]], - singletonArgExps: Option[Seq[ast.Exp]], - hints: Seq[Term]): QuantifiedPredicateChunk = { - new QuantifiedPredicateChunk(id, quantifiedVars, quantifiedVarExps, psf, condition, conditionExp, permValue, permValueExp, invs, singletonArgs, singletonArgExps, hints) - } - def apply(id: BasicChunkIdentifier, quantifiedVars: Seq[Var], @@ -290,18 +257,6 @@ case class QuantifiedPredicateChunk private(id: BasicChunkIdentifier, } object QuantifiedMagicWandChunk { - private def apply(id: MagicWandIdentifier, - quantifiedVars: Seq[Var], - quantifiedVarExps: Option[Seq[ast.LocalVarDecl]], - wsf: Term, - perm: Term, - permExp: Option[ast.Exp], - invs: Option[InverseFunctions], - singletonArgs: Option[Seq[Term]], - singletonArgExps: Option[Seq[ast.Exp]], - hints: Seq[Term]): QuantifiedMagicWandChunk = { - new QuantifiedMagicWandChunk(id, quantifiedVars, quantifiedVarExps, wsf, perm, permExp, invs, singletonArgs, singletonArgExps, hints) - } def apply(id: MagicWandIdentifier, quantifiedVars: Seq[Var], @@ -383,15 +338,6 @@ object MagicWandIdentifier { } object MagicWandChunk { - private def apply(id: MagicWandIdentifier, - bindings: Map[ast.AbstractLocalVar, (Term, Option[ast.Exp])], - args: Seq[Term], - argsExp: Option[Seq[ast.Exp]], - snap: MagicWandSnapshot, - perm: Term, - permExp: Option[ast.Exp]): MagicWandChunk = { - new MagicWandChunk(id, bindings, args, argsExp, snap, perm, permExp) - } def apply(id: MagicWandIdentifier, bindings: Map[ast.AbstractLocalVar, (Term, Option[ast.Exp])], diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index d871ca6ac..b7a2a036e 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -170,7 +170,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver data.formalArgs.values foreach (v => decider.prover.declare(ConstDecl(v))) decider.prover.declare(ConstDecl(data.formalResult)) - val res = Seq(handleFunction(sInit, function)) + var res = handleFunction(sInit, function) v.decider.resetProverOptions() symbExLog.closeMemberScope() From c1231a14af76240d55322de21ca4552d20f7ae24 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 27 Jan 2026 09:42:05 +0100 Subject: [PATCH 325/474] fix assumption types --- src/main/scala/decider/Decider.scala | 9 +- .../dependencyAnalysis/AnalysisInfo.scala | 11 ++- .../DependencyAnalysisNode.scala | 2 +- src/main/scala/rules/Brancher.scala | 7 +- src/main/scala/rules/ChunkSupporter.scala | 38 ++++----- src/main/scala/rules/Consumer.scala | 79 +++++++++-------- src/main/scala/rules/Evaluator.scala | 26 +++--- src/main/scala/rules/Executor.scala | 84 +++++++++---------- src/main/scala/rules/HeapSupporter.scala | 16 ++-- src/main/scala/rules/MagicWandSupporter.scala | 17 ++-- .../rules/MoreCompleteExhaleSupporter.scala | 53 ++++++------ src/main/scala/rules/PredicateSupporter.scala | 30 +++---- src/main/scala/rules/Producer.scala | 8 +- .../scala/rules/QuantifiedChunkSupport.scala | 52 ++++++------ src/main/scala/rules/StateConsolidator.scala | 2 +- src/main/scala/supporters/Domains.scala | 2 +- .../scala/supporters/MethodSupporter.scala | 4 +- .../PredicateVerificationUnit.scala | 2 +- .../supporters/functions/FunctionData.scala | 4 +- .../functions/FunctionVerificationUnit.scala | 4 +- .../dependencyAnalysisTests/all/aliasing.vpr | 8 +- .../dependencyAnalysisTests/all/branches.vpr | 20 ++--- .../dependencyAnalysisTests/all/divBy0.vpr | 8 +- .../all/function-sum.vpr | 4 +- .../all/function_vs_method.vpr | 8 +- .../dependencyAnalysisTests/all/functions.vpr | 14 ++-- .../all/imprecision-fixed.vpr | 10 +-- .../all/imprecision.vpr | 10 +-- .../all/infeasible.vpr | 2 +- .../all/invariant_ordering_imprecise.vpr | 16 ++-- .../all/method-sum.vpr | 10 +-- .../all/quantified-perm.vpr | 8 +- .../all/quasihavoc.vpr | 2 +- .../dependencyAnalysisTests/all/sequences.vpr | 6 +- .../dependencyAnalysisTests/all/sum-loops.vpr | 24 +++--- .../dependencyAnalysisTests/all/tuples.vpr | 10 +-- .../dependencyAnalysisTests/all/unfolding.vpr | 8 +- .../real-world-examples/gaussian.vpr | 20 ++--- .../real-world-examples/listAppend.vpr | 36 ++++---- .../unitTests/B-permissions.vpr | 6 +- .../unitTests/D-quantifiedPermissions.vpr | 10 +-- .../unitTests/E-function-call.vpr | 12 +-- .../unitTests/F-method-call.vpr | 16 ++-- .../unitTests/G-predicates.vpr | 22 ++--- .../unitTests/H-magicWands.vpr | 14 ++-- .../unitTests/I-branches.vpr | 14 ++-- .../unitTests/J-loops.vpr | 32 +++---- .../unitTests/K-domain.vpr | 4 +- 48 files changed, 403 insertions(+), 401 deletions(-) diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 66f7baf64..1c892ac85 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -54,7 +54,7 @@ trait Decider { def checkSmoke(isAssert: Boolean = false, assumptionType: AssumptionType=AssumptionType.Implicit): Boolean - def setCurrentBranchCondition(t: Term, te: (ast.Exp, Option[ast.Exp])): Unit + def setCurrentBranchCondition(t: Term, te: (ast.Exp, Option[ast.Exp]), assumptionType: AssumptionType): Unit def setPathConditionMark(): Mark def finishDebugSubExp(description : String): Unit @@ -114,7 +114,6 @@ trait Decider { var analysisSourceInfoStack: AnalysisSourceInfoStack def initDependencyAnalyzer(member: Member, preambleNodes: Iterable[DependencyAnalysisNode]): Unit def removeDependencyAnalyzer(): Unit - def getAnalysisInfo: AnalysisInfo def getAnalysisInfo(assumptionType: AssumptionType): AnalysisInfo } @@ -164,8 +163,6 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => analysisSourceInfoStack = AnalysisSourceInfoStack() } - def getAnalysisInfo: AnalysisInfo = getAnalysisInfo(AssumptionType.Implicit) - def getAnalysisInfo(assumptionType: AssumptionType): AnalysisInfo = AnalysisInfo(this, dependencyAnalyzer, analysisSourceInfoStack.getFullSourceInfo, assumptionType) def functionDecls: Set[FunctionDecl] = _declaredFreshFunctions @@ -287,9 +284,9 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => //symbExLog.closeScope(sepIdentifier) } - def setCurrentBranchCondition(t: Term, te: (ast.Exp, Option[ast.Exp])): Unit = { + def setCurrentBranchCondition(t: Term, te: (ast.Exp, Option[ast.Exp]), assumptionType: AssumptionType): Unit = { pathConditions.setCurrentBranchCondition(t, te) - assume(t, Option.when(te._2.isDefined)(te._1), te._2, AssumptionType.PathCondition) + assume(t, Option.when(te._2.isDefined)(te._1), te._2, assumptionType) } def setPathConditionMark(): Mark = pathConditions.mark() diff --git a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala index a64836d92..f14ad30d6 100644 --- a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala +++ b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala @@ -3,16 +3,17 @@ package viper.silicon.dependencyAnalysis object AssumptionType extends Enumeration { type AssumptionType = Value - val Explicit, LoopInvariant, PathCondition, Rewrite, Implicit /* TODO ake: rename to Stmt? */, Internal, Trigger, ExplicitPostcondition, ImplicitPostcondition, CallPostcondition, FunctionBody, Precondition = Value + val Explicit, LoopInvariant, PathCondition, Rewrite, SourceCode, DomainAxiom, Implicit, Internal, Trigger, ExplicitPostcondition, ImplicitPostcondition, MethodCall, FunctionBody, Precondition = Value def fromString(s: String): Option[Value] = values.find(_.toString == s) - def explicitAssumptionTypes: Set[AssumptionType] = Set(Explicit, ExplicitPostcondition) - def postconditionTypes: Set[AssumptionType] = Set(ImplicitPostcondition, ExplicitPostcondition, CallPostcondition) // used to join graphs via postconditions + def explicitAssumptionTypes: Set[AssumptionType] = Set(Explicit, ExplicitPostcondition, DomainAxiom) + def postconditionTypes: Set[AssumptionType] = Set(ImplicitPostcondition, ExplicitPostcondition, MethodCall) // used to join graphs via postconditions def explicitAssertionTypes: Set[AssumptionType] = Set(Explicit, ImplicitPostcondition, ExplicitPostcondition) - def internalTypes: Set[AssumptionType] = Set(Internal) // will always be hidden from user + def internalTypes: Set[AssumptionType] = Set(Internal, Trigger) // will always be hidden from user def joinConditionTypes: Set[AssumptionType] = postconditionTypes ++ Set(FunctionBody) def verificationAnnotationTypes: Set[AssumptionType] = Set(LoopInvariant, Rewrite, ExplicitPostcondition, ImplicitPostcondition, Precondition, Explicit) + def sourceCodeTypes: Set[AssumptionType] = Set(SourceCode, PathCondition, MethodCall, FunctionBody, Implicit) } import viper.silicon.dependencyAnalysis.AssumptionType._ @@ -21,6 +22,7 @@ import viper.silicon.decider.Decider object DependencyType { val Implicit: DependencyType = DependencyType(AssumptionType.Implicit, AssumptionType.Implicit) + val SourceCode: DependencyType = DependencyType(AssumptionType.SourceCode, AssumptionType.SourceCode) val Explicit: DependencyType = DependencyType(AssumptionType.Explicit, AssumptionType.Explicit) val ExplicitAssertion: DependencyType = DependencyType(AssumptionType.Internal, AssumptionType.Explicit) val ExplicitAssumption: DependencyType = DependencyType(AssumptionType.Explicit, AssumptionType.Implicit) @@ -29,6 +31,7 @@ object DependencyType { val Rewrite: DependencyType = DependencyType(AssumptionType.Rewrite, AssumptionType.Rewrite) val Internal: DependencyType = DependencyType(AssumptionType.Internal, AssumptionType.Internal) val Trigger: DependencyType = DependencyType(AssumptionType.Trigger, AssumptionType.Trigger) + val MethodCall: DependencyType = DependencyType(AssumptionType.MethodCall, AssumptionType.MethodCall) def make(singleType: AssumptionType): DependencyType = DependencyType(singleType, singleType) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala index 0101188f9..ffc77c088 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala @@ -127,7 +127,7 @@ case class LabelNode(term: Var) extends GeneralAssumptionNode { */ case class InfeasibilityNode(sourceInfo: AnalysisSourceInfo) extends GeneralAssumptionNode { val term: Term = False - val assumptionType: AssumptionType = AssumptionType.Implicit + val assumptionType: AssumptionType = AssumptionType.Implicit // TODO ake: assumption type? val isClosed: Boolean = true val description: String = "False" diff --git a/src/main/scala/rules/Brancher.scala b/src/main/scala/rules/Brancher.scala index 20ea4bd0a..fdf7f4133 100644 --- a/src/main/scala/rules/Brancher.scala +++ b/src/main/scala/rules/Brancher.scala @@ -11,6 +11,7 @@ import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, ExpAnalysisSourceIn import java.util.concurrent._ import viper.silicon.common.concurrency._ import viper.silicon.decider.PathConditionStack +import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType import viper.silicon.interfaces.{Unreachable, VerificationResult} import viper.silicon.reporting.condenseToViperResult import viper.silicon.state.State @@ -27,6 +28,7 @@ trait BranchingRules extends SymbolicExecutionRules { condition: Term, conditionExp: (ast.Exp, Option[ast.Exp]), v: Verifier, + assumptionType: AssumptionType, fromShortCircuitingAnd: Boolean = false) (fTrue: (State, Verifier) => VerificationResult, fFalse: (State, Verifier) => VerificationResult) @@ -38,6 +40,7 @@ object brancher extends BranchingRules { condition: Term, conditionExp: (ast.Exp, Option[ast.Exp]), v: Verifier, + assumptionType: AssumptionType, fromShortCircuitingAnd: Boolean = false) (fThen: (State, Verifier) => VerificationResult, fElse: (State, Verifier) => VerificationResult) @@ -163,7 +166,7 @@ object brancher extends BranchingRules { val sourceInfo = AnalysisSourceInfo.createAnalysisSourceInfo(conditionExp._1) v1.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo) v1.decider.pcs.setCurrentInfeasibilityNode(elseInfeasibilityNode) - v1.decider.setCurrentBranchCondition(negatedCondition, (negatedConditionExp, negatedConditionExpNew)) + v1.decider.setCurrentBranchCondition(negatedCondition, (negatedConditionExp, negatedConditionExpNew), assumptionType) v1.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) var functionsOfElseBranchdDeciderBefore: Set[FunctionDecl] = null @@ -218,7 +221,7 @@ object brancher extends BranchingRules { val sourceInfo = AnalysisSourceInfo.createAnalysisSourceInfo(conditionExp._1) v1.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo) v1.decider.pcs.setCurrentInfeasibilityNode(thenInfeasibilityNode) - v1.decider.setCurrentBranchCondition(condition, conditionExp) + v1.decider.setCurrentBranchCondition(condition, conditionExp, assumptionType) v1.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) fThen(v1.stateConsolidator(s1).consolidateOptionally(s1, v1), v1) diff --git a/src/main/scala/rules/ChunkSupporter.scala b/src/main/scala/rules/ChunkSupporter.scala index 58549fea0..bacd235e3 100644 --- a/src/main/scala/rules/ChunkSupporter.scala +++ b/src/main/scala/rules/ChunkSupporter.scala @@ -6,7 +6,7 @@ package viper.silicon.rules -import viper.silicon.dependencyAnalysis.AssumptionType +import viper.silicon.dependencyAnalysis.{AssumptionType, DependencyType} import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.state._ @@ -35,7 +35,7 @@ trait ChunkSupportRules extends SymbolicExecutionRules { ve: VerificationError, v: Verifier, description: String, - assumptionType: AssumptionType=AssumptionType.Implicit) + dependencyType: DependencyType=DependencyType.Implicit) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult @@ -80,11 +80,11 @@ object chunkSupporter extends ChunkSupportRules { ve: VerificationError, v: Verifier, description: String, - assumptionType: AssumptionType=AssumptionType.Implicit) + dependencyType: DependencyType=DependencyType.Implicit) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { - consume2(s, h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, assumptionType)((s2, h2, optSnap, v2) => + consume2(s, h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, dependencyType)((s2, h2, optSnap, v2) => optSnap match { case Some(snap) => Q(s2, h2, Some(snap.convert(sorts.Snap)), v2) @@ -113,14 +113,14 @@ object chunkSupporter extends ChunkSupportRules { returnSnap: Boolean, ve: VerificationError, v: Verifier, - assumptionType: AssumptionType) + dependencyType: DependencyType) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { val id = ChunkIdentifier(resource, s.program) if (s.exhaleExt) { val failure = createFailure(ve, v, s, "chunk consume in package") - magicWandSupporter.transfer(s, perms, permsExp, failure, Seq(), v)(consumeGreedy(_, _, id, args, _, _, _, assumptionType))((s1, optCh, v1) => + magicWandSupporter.transfer(s, perms, permsExp, failure, Seq(), v)(consumeGreedy(_, _, id, args, _, _, _, dependencyType))((s1, optCh, v1) => if (returnSnap){ Q(s1, h, optCh.flatMap(ch => Some(ch.snap)), v1) } else { @@ -129,11 +129,11 @@ object chunkSupporter extends ChunkSupportRules { } else { executionFlowController.tryOrFail2[Heap, Option[Term]](s.copy(h = h), v)((s1, v1, QS) => if (s1.moreCompleteExhale) { - moreCompleteExhaleSupporter.consumeComplete(s1, s1.h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v1, assumptionType)((s2, h2, snap2, v2) => { + moreCompleteExhaleSupporter.consumeComplete(s1, s1.h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v1, dependencyType)((s2, h2, snap2, v2) => { QS(s2.copy(h = s.h), h2, snap2, v2) }) } else { - consumeGreedy(s1, s1.h, id, args, perms, permsExp, v1, assumptionType) match { + consumeGreedy(s1, s1.h, id, args, perms, permsExp, v1, dependencyType) match { case (Complete(), s2, h2, optCh2) => val snap = optCh2 match { case Some(ch) if returnSnap => @@ -145,7 +145,7 @@ object chunkSupporter extends ChunkSupportRules { case _ => None } QS(s2.copy(h = s.h), h2, snap, v1) - case (_, s2, h2, _) if v1.decider.checkSmoke(isAssert=true, assumptionType) => + case (_, s2, h2, _) if v1.decider.checkSmoke(isAssert=true, dependencyType.assertionType) => if(Verifier.config.disableInfeasibilityChecks()) QS(s2.copy(h = s.h), h2, None, v1) else @@ -165,7 +165,7 @@ object chunkSupporter extends ChunkSupportRules { perms: Term, permsExp: Option[ast.Exp], v: Verifier, - assumptionType: AssumptionType) + dependencyType: DependencyType) : (ConsumptionResult, State, Heap, Option[NonQuantifiedChunk]) = { val consumeExact = terms.utils.consumeExactRead(perms, s.constrainableARPs) @@ -174,13 +174,13 @@ object chunkSupporter extends ChunkSupportRules { val interpreter = new NonQuantifiedPropertyInterpreter(heap.values, v) val resource = Resources.resourceDescriptions(chunk.resourceID) val pathCond = interpreter.buildPathConditionsForChunk(chunk, resource.instanceProperties(s.mayAssumeUpperBounds)) - pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), AssumptionType.PathCondition)) + pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), dependencyType.assumptionType)) } findChunk[NonQuantifiedChunk](h.values, id, args, v) match { case Some(ch) => if (s.assertReadAccessOnly) { - if (v.decider.check(Implies(IsPositive(perms), IsPositive(ch.perm)), Verifier.config.assertTimeout.getOrElse(0), assumptionType)) { + if (v.decider.check(Implies(IsPositive(perms), IsPositive(ch.perm)), Verifier.config.assertTimeout.getOrElse(0), dependencyType.assertionType)) { (Complete(), s, h, Some(ch)) } else { (Incomplete(perms, permsExp), s, h, None) @@ -189,8 +189,8 @@ object chunkSupporter extends ChunkSupportRules { val toTake = PermMin(ch.perm, perms) val toTakeExp = permsExp.map(pe => buildMinExp(Seq(ch.permExp.get, pe), ast.Perm)) val newPermExp = permsExp.map(pe => ast.PermSub(ch.permExp.get, toTakeExp.get)(pe.pos, pe.info, pe.errT)) - val newChunk = NonQuantifiedChunk.withPerm(ch, PermMinus(ch.perm, toTake), newPermExp, v.decider.getAnalysisInfo(AssumptionType.Implicit)) - val takenChunk = Some(NonQuantifiedChunk.withPerm(ch, toTake, toTakeExp, v.decider.getAnalysisInfo(assumptionType), isExhale=true)) + val newChunk = NonQuantifiedChunk.withPerm(ch, PermMinus(ch.perm, toTake), newPermExp, v.decider.getAnalysisInfo(dependencyType.assumptionType)) + val takenChunk = Some(NonQuantifiedChunk.withPerm(ch, toTake, toTakeExp, v.decider.getAnalysisInfo(dependencyType.assertionType), isExhale=true)) var newHeap = h - ch if (!v.decider.check(newChunk.perm === NoPerm, Verifier.config.checkTimeout(), AssumptionType.Internal)) { newHeap = newHeap + newChunk @@ -199,12 +199,12 @@ object chunkSupporter extends ChunkSupportRules { val remainingExp = permsExp.map(pe => ast.PermSub(pe, toTakeExp.get)(pe.pos, pe.info, pe.errT)) (ConsumptionResult(PermMinus(perms, toTake), remainingExp, Seq(), v, 0), s, newHeap, takenChunk) } else { - if (v.decider.check(ch.perm !== NoPerm, Verifier.config.checkTimeout(), assumptionType)) { + if (v.decider.check(ch.perm !== NoPerm, Verifier.config.checkTimeout(), dependencyType.assertionType)) { val constraintExp = permsExp.map(pe => ast.PermLtCmp(pe, ch.permExp.get)(pe.pos, pe.info, pe.errT)) - v.decider.assume(PermLess(perms, ch.perm), Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp)), AssumptionType.Implicit) + v.decider.assume(PermLess(perms, ch.perm), Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp)), dependencyType.assumptionType) val newPermExp = permsExp.map(pe => ast.PermSub(ch.permExp.get, pe)(pe.pos, pe.info, pe.errT)) - val newChunk = NonQuantifiedChunk.withPerm(ch, PermMinus(ch.perm, perms), newPermExp, v.decider.getAnalysisInfo(AssumptionType.Implicit)) - val takenChunk = NonQuantifiedChunk.withPerm(ch, perms, permsExp, v.decider.getAnalysisInfo(assumptionType), isExhale=true) + val newChunk = NonQuantifiedChunk.withPerm(ch, PermMinus(ch.perm, perms), newPermExp, v.decider.getAnalysisInfo(dependencyType.assumptionType)) + val takenChunk = NonQuantifiedChunk.withPerm(ch, perms, permsExp, v.decider.getAnalysisInfo(dependencyType.assertionType), isExhale=true) val newHeap = h - ch + newChunk assumeProperties(newChunk, newHeap) (Complete(), s, newHeap, Some(takenChunk)) @@ -213,7 +213,7 @@ object chunkSupporter extends ChunkSupportRules { } } case None => - if (consumeExact && s.retrying && v.decider.check(perms === NoPerm, Verifier.config.checkTimeout(), assumptionType)) { + if (consumeExact && s.retrying && v.decider.check(perms === NoPerm, Verifier.config.checkTimeout(), dependencyType.assertionType)) { (Complete(), s, h, None) } else { (Incomplete(perms, permsExp), s, h, None) diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index 454077a9e..908a127fe 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -7,11 +7,8 @@ package viper.silicon.rules import viper.silicon.Config.JoinMode -import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType -import viper.silicon.dependencyAnalysis.{AnalysisInfo, AnalysisSourceInfo, AssumptionType, ExpAnalysisSourceInfo, StringAnalysisSourceInfo} -import viper.silicon.debugger.DebugExp +import viper.silicon.dependencyAnalysis._ import viper.silicon.interfaces.VerificationResult -import viper.silicon.interfaces.state.Chunk import viper.silicon.logger.records.data.{CondExpRecord, ConsumeRecord, ImpliesRecord} import viper.silicon.state._ import viper.silicon.state.terms._ @@ -33,14 +30,14 @@ trait ConsumptionRules extends SymbolicExecutionRules { * @param returnSnap Whether a snapshot should be returned or not. * @param pve The error to report in case the consumption fails. * @param v The verifier to use. - * @param assumptionType The assumption type used for dependency analysis and proof coverage + * @param dependencyType The assumption type used for dependency analysis and proof coverage * @param Q The continuation to invoke if the consumption succeeded, with the following * arguments: state (1st argument) and verifier (3rd argument) resulting from the * consumption, and a heap snapshot (2bd argument )representing the values of the * consumed partial heap. * @return The result of the continuation. */ - def consume(s: State, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, assumptionType: AssumptionType=AssumptionType.Implicit) + def consume(s: State, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, dependencyType: DependencyType=DependencyType.Implicit) (Q: (State, Option[Term], Verifier) => VerificationResult) : VerificationResult @@ -56,7 +53,7 @@ trait ConsumptionRules extends SymbolicExecutionRules { * @param pvef The error to report in case a consumption fails. Given assertions `as`, an error * `pvef(as_i)` will be reported if consuming assertion `as_i` fails. * @param v @see [[consume]] - * @param assumptionType @see [[consume]] + * @param dependencyType @see [[consume]] * @param Q @see [[consume]] * @return @see [[consume]] */ @@ -65,7 +62,7 @@ trait ConsumptionRules extends SymbolicExecutionRules { returnSnap: Boolean, pvef: ast.Exp => PartialVerificationError, v: Verifier, - assumptionType: AssumptionType=AssumptionType.Implicit) + dependencyType: DependencyType=DependencyType.Implicit) (Q: (State, Option[Term], Verifier) => VerificationResult) : VerificationResult } @@ -79,11 +76,11 @@ object consumer extends ConsumptionRules { */ /** @inheritdoc */ - def consume(s: State, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, assumptionType: AssumptionType=AssumptionType.Implicit) + def consume(s: State, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, dependencyType: DependencyType=DependencyType.Implicit) (Q: (State, Option[Term], Verifier) => VerificationResult) : VerificationResult = { - consumeR(s, s.h, a.whenExhaling, returnSnap, pve, v, assumptionType)((s1, h1, snap, v1) => { + consumeR(s, s.h, a.whenExhaling, returnSnap, pve, v, dependencyType)((s1, h1, snap, v1) => { val s2 = s1.copy(h = h1, partiallyConsumedHeap = s.partiallyConsumedHeap) Q(s2, snap, v1)}) @@ -95,7 +92,7 @@ object consumer extends ConsumptionRules { returnSnap: Boolean, pvef: ast.Exp => PartialVerificationError, v: Verifier, - assumptionType: AssumptionType=AssumptionType.Implicit) + dependencyType: DependencyType=DependencyType.Implicit) (Q: (State, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -110,7 +107,7 @@ object consumer extends ConsumptionRules { allPves ++= pves }) - consumeTlcs(s, s.h, allTlcs.result(), returnSnap, allPves.result(), v, assumptionType)((s1, h1, snap1, v1) => { + consumeTlcs(s, s.h, allTlcs.result(), returnSnap, allPves.result(), v, dependencyType)((s1, h1, snap1, v1) => { val s2 = s1.copy(h = h1, partiallyConsumedHeap = s.partiallyConsumedHeap) Q(s2, snap1, v1) @@ -123,7 +120,7 @@ object consumer extends ConsumptionRules { returnSnap: Boolean, pves: Seq[PartialVerificationError], v: Verifier, - assumptionType: AssumptionType) + dependencyType: DependencyType) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -134,10 +131,10 @@ object consumer extends ConsumptionRules { val pve = pves.head if (tlcs.tail.isEmpty) - wrappedConsumeTlc(s, h, a, returnSnap, pve, v, assumptionType)(Q) + wrappedConsumeTlc(s, h, a, returnSnap, pve, v, dependencyType)(Q) else - wrappedConsumeTlc(s, h, a, returnSnap, pve, v, assumptionType)((s1, h1, snap1, v1) => { - consumeTlcs(s1, h1, tlcs.tail, returnSnap, pves.tail, v1, assumptionType)((s2, h2, snap2, v2) => + wrappedConsumeTlc(s, h, a, returnSnap, pve, v, dependencyType)((s1, h1, snap1, v1) => { + consumeTlcs(s1, h1, tlcs.tail, returnSnap, pves.tail, v1, dependencyType)((s2, h2, snap2, v2) => (snap1, snap2) match { case (Some(sn1), Some(sn2)) if returnSnap => Q(s2, h2, Some(Combine(sn1, sn2)), v2) @@ -148,14 +145,14 @@ object consumer extends ConsumptionRules { } } - private def consumeR(s: State, h: Heap, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, assumptionType: AssumptionType=AssumptionType.Implicit) + private def consumeR(s: State, h: Heap, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, dependencyType: DependencyType=DependencyType.Implicit) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { val tlcs = a.topLevelConjuncts val pves = Seq.fill(tlcs.length)(pve) - consumeTlcs(s, h, tlcs, returnSnap, pves, v, assumptionType)(Q) + consumeTlcs(s, h, tlcs, returnSnap, pves, v, dependencyType)(Q) } /** Wrapper/decorator for consume that injects the following operations: @@ -168,7 +165,7 @@ object consumer extends ConsumptionRules { returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, - assumptionType: AssumptionType) + dependencyType: DependencyType) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -185,14 +182,14 @@ object consumer extends ConsumptionRules { val sourceInfo = AnalysisSourceInfo.createAnalysisSourceInfo(a) v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo) - consumeTlc(s1, h0, a, returnSnap, pve, v1, assumptionType)((s2, h2, snap2, v2) => { + consumeTlc(s1, h0, a, returnSnap, pve, v1, dependencyType)((s2, h2, snap2, v2) => { v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) v2.symbExLog.closeScope(sepIdentifier) QS(s2, h2, snap2, v2)}) })(Q) } - private def consumeTlc(s: State, h: Heap, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, assumptionType: AssumptionType) + private def consumeTlc(s: State, h: Heap, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, dependencyType: DependencyType) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -204,7 +201,7 @@ object consumer extends ConsumptionRules { */ if(v.decider.isPathInfeasible()){ - v.decider.dependencyAnalyzer.addInfeasibilityDepToStmt(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, assumptionType) + v.decider.dependencyAnalyzer.addInfeasibilityDepToStmt(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, dependencyType.assertionType) return Q(s, h, Option.when(returnSnap)(Unit), v) } @@ -218,15 +215,15 @@ object consumer extends ConsumptionRules { case imp @ ast.Implies(e0, a0) if !a.isPure && s.moreJoins.id >= JoinMode.Impure.id => val impliesRecord = new ImpliesRecord(imp, s, v.decider.pcs, "consume") val uidImplies = v.symbExLog.openScope(impliesRecord) - consumeConditionalTlcMoreJoins(s, h, e0, a0, None, uidImplies, returnSnap, pve, v, assumptionType)(Q) + consumeConditionalTlcMoreJoins(s, h, e0, a0, None, uidImplies, returnSnap, pve, v, dependencyType)(Q) case imp @ ast.Implies(e0, a0) if !a.isPure => val impliesRecord = new ImpliesRecord(imp, s, v.decider.pcs, "consume") val uidImplies = v.symbExLog.openScope(impliesRecord) evaluator.eval(s, e0, pve, v)((s1, t0, e0New, v1) => - branch(s1, t0, (e0, e0New), v1)( - (s2, v2) => consumeR(s2, h, a0, returnSnap, pve, v2, assumptionType)((s3, h1, t1, v3) => { + branch(s1, t0, (e0, e0New), v1, dependencyType.assumptionType)( + (s2, v2) => consumeR(s2, h, a0, returnSnap, pve, v2, dependencyType)((s3, h1, t1, v3) => { v3.symbExLog.closeScope(uidImplies) Q(s3, h1, t1, v3) }), @@ -238,19 +235,19 @@ object consumer extends ConsumptionRules { case ite @ ast.CondExp(e0, a1, a2) if !a.isPure && s.moreJoins.id >= JoinMode.Impure.id => val condExpRecord = new CondExpRecord(ite, s, v.decider.pcs, "consume") val uidCondExp = v.symbExLog.openScope(condExpRecord) - consumeConditionalTlcMoreJoins(s, h, e0, a1, Some(a2), uidCondExp, returnSnap, pve, v, assumptionType)(Q) + consumeConditionalTlcMoreJoins(s, h, e0, a1, Some(a2), uidCondExp, returnSnap, pve, v, dependencyType)(Q) case ite @ ast.CondExp(e0, a1, a2) if !a.isPure => val condExpRecord = new CondExpRecord(ite, s, v.decider.pcs, "consume") val uidCondExp = v.symbExLog.openScope(condExpRecord) eval(s, e0, pve, v)((s1, t0, e0New, v1) => - branch(s1, t0, (e0, e0New), v1)( - (s2, v2) => consumeR(s2, h, a1, returnSnap, pve, v2, assumptionType)((s3, h1, t1, v3) => { + branch(s1, t0, (e0, e0New), v1, dependencyType.assumptionType)( + (s2, v2) => consumeR(s2, h, a1, returnSnap, pve, v2, dependencyType)((s3, h1, t1, v3) => { v3.symbExLog.closeScope(uidCondExp) Q(s3, h1, t1, v3) }), - (s2, v2) => consumeR(s2, h, a2, returnSnap, pve, v2, assumptionType)((s3, h1, t1, v3) => { + (s2, v2) => consumeR(s2, h, a2, returnSnap, pve, v2, dependencyType)((s3, h1, t1, v3) => { v3.symbExLog.closeScope(uidCondExp) Q(s3, h1, t1, v3) }))) @@ -270,7 +267,7 @@ object consumer extends ConsumptionRules { WildcardSimplifyingPermTimes(tPerm, s2.permissionScalingFactor) val lossExp = ePermNew.map(p => ast.PermMul(p, s2.permissionScalingFactorExp.get)(p.pos, p.info, p.errT)) val s3 = v2.heapSupporter.triggerResourceIfNeeded(s2, accPred.loc, tArgs, eArgsNew, v2) - v2.heapSupporter.consumeSingle(s3, h, accPred.loc, tArgs, eArgsNew, loss, lossExp, returnSnap, pve, v2, assumptionType)((s4, h4, snap, v4) => { + v2.heapSupporter.consumeSingle(s3, h, accPred.loc, tArgs, eArgsNew, loss, lossExp, returnSnap, pve, v2, dependencyType)((s4, h4, snap, v4) => { val s5 = s4.copy(constrainableARPs = s.constrainableARPs, partiallyConsumedHeap = Some(h4)) Q(s5, h4, snap, v4) @@ -329,7 +326,7 @@ object consumer extends ConsumptionRules { notInjectiveReason = QPAssertionNotInjective(resAcc), insufficientPermissionReason = insuffReason, v1, - assumptionType)((s2, h2, snap, v2) => { + dependencyType)((s2, h2, snap, v2) => { val s3 = s2.copy(constrainableARPs = s.constrainableARPs, functionRecorder = s2.functionRecorder.leaveQuantifiedExp(qpa)) Q(s3, h2, snap, v2) }) @@ -339,13 +336,13 @@ object consumer extends ConsumptionRules { case let: ast.Let if !let.isPure => letSupporter.handle[ast.Exp](s, let, pve, v)((s1, g1, body, v1) => { val s2 = s1.copy(g = s1.g + g1) - consumeR(s2, h, body, returnSnap, pve, v1, assumptionType)(Q)}) + consumeR(s2, h, body, returnSnap, pve, v1, dependencyType)(Q)}) case _: ast.InhaleExhaleExp => createFailure(viper.silicon.utils.consistency.createUnexpectedInhaleExhaleExpressionError(a), v, s, "valid AST") case _ => - evalAndAssert(s, a, returnSnap, pve, v, assumptionType)((s1, t, v1) => { + evalAndAssert(s, a, returnSnap, pve, v, dependencyType)((s1, t, v1) => { Q(s1, h, t, v1) }) } @@ -355,20 +352,20 @@ object consumer extends ConsumptionRules { private def consumeConditionalTlcMoreJoins(s: State, h: Heap, e0: ast.Exp, a1: ast.Exp, a2: Option[ast.Exp], scopeUid: Int, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, - assumptionType: AssumptionType) + dependencyType: DependencyType) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { eval(s, e0, pve, v)((s1, t0, e0New, v1) => joiner.join[(Heap, Option[Term]), (Heap, Option[Term])](s1, v1, resetState = false)((s1, v1, QB) => { - branch(s1.copy(parallelizeBranches = false), t0, (e0, e0New), v1)( + branch(s1.copy(parallelizeBranches = false), t0, (e0, e0New), v1, dependencyType.assumptionType)( (s2, v2) => - consumeR(s2.copy(parallelizeBranches = s1.parallelizeBranches), h, a1, returnSnap, pve, v2, assumptionType)((s3, h1, t1, v3) => { + consumeR(s2.copy(parallelizeBranches = s1.parallelizeBranches), h, a1, returnSnap, pve, v2, dependencyType)((s3, h1, t1, v3) => { v3.symbExLog.closeScope(scopeUid) QB(s3, (h1, t1), v3) }), (s2, v2) => a2 match { - case Some(a2) => consumeR(s2.copy(parallelizeBranches = s1.parallelizeBranches), h, a2, returnSnap, pve, v2, assumptionType)((s3, h1, t1, v3) => { + case Some(a2) => consumeR(s2.copy(parallelizeBranches = s1.parallelizeBranches), h, a2, returnSnap, pve, v2, dependencyType)((s3, h1, t1, v3) => { v3.symbExLog.closeScope(scopeUid) QB(s3, (h1, t1), v3) }) @@ -385,7 +382,7 @@ object consumer extends ConsumptionRules { State.mergeHeap( entry1.data._1, And(entry1.pathConditions.branchConditions), Option.when(withExp)(BigAnd(entry1.pathConditions.branchConditionExps.map(_._2.get))), entry2.data._1, And(entry2.pathConditions.branchConditions), Option.when(withExp)(BigAnd(entry2.pathConditions.branchConditionExps.map(_._2.get))), - AnalysisInfo(v.decider, v.decider.dependencyAnalyzer, StringAnalysisSourceInfo("conditional join", e0.pos), AssumptionType.Implicit) + AnalysisInfo(v.decider, v.decider.dependencyAnalyzer, StringAnalysisSourceInfo("conditional join", e0.pos), dependencyType.assumptionType) ), // Assume that entry1.pcs is inverse of entry2.pcs (entry1.data._2, entry2.data._2) match { @@ -406,7 +403,7 @@ object consumer extends ConsumptionRules { } - private def evalAndAssert(s: State, e: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, assumptionType: AssumptionType) + private def evalAndAssert(s: State, e: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, dependencyType: DependencyType) (Q: (State, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -429,11 +426,11 @@ object consumer extends ConsumptionRules { val termToAssert = t match { case Quantification(q, vars, body, trgs, name, isGlob, weight) => val transformed = FunctionPreconditionTransformer.transform(body, s3.program) - v2.decider.assume(Quantification(q, vars, transformed, trgs, name+"_precondition", isGlob, weight), Option.when(withExp)(e), eNew, assumptionType) + v2.decider.assume(Quantification(q, vars, transformed, trgs, name+"_precondition", isGlob, weight), Option.when(withExp)(e), eNew, dependencyType.assumptionType) Quantification(q, vars, Implies(transformed, body), trgs, name, isGlob, weight) case _ => t } - v2.decider.assert(termToAssert, assumptionType) { + v2.decider.assert(termToAssert, dependencyType.assertionType) { case true => v2.decider.assume(t, Option.when(withExp)(e), eNew, AssumptionType.Internal) QS(s3, v2) diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 10f272e9f..8998233a7 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -9,7 +9,7 @@ package viper.silicon.rules import viper.silicon import viper.silicon.debugger.DebugExp import viper.silicon.Config.JoinMode -import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyAnalyzer, ExpAnalysisSourceInfo} +import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyAnalyzer, DependencyType, ExpAnalysisSourceInfo} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.interfaces._ @@ -188,7 +188,7 @@ object evaluator extends EvaluationRules { case _: ast.WildcardPerm => val (tVar, tConstraints, eVar) = v.decider.freshARP() val constraintExp = Option.when(withExp)(DebugExp.createInstance(s"${eVar.get.toString} > none", true)) - v.decider.assumeDefinition(tConstraints, constraintExp, AssumptionType.Implicit) + v.decider.assumeDefinition(tConstraints, constraintExp, AssumptionType.Internal) /* TODO: Only record wildcards in State.constrainableARPs that are used in exhale * position. Currently, wildcards used in inhale position (only) may not be removed * from State.constrainableARPs (potentially inefficient, but should be sound). @@ -258,7 +258,7 @@ object evaluator extends EvaluationRules { eval(s, e0, pve, v)((s1, t0, e0New, v1) => { val t = v1.decider.appliedFresh("letvar", v1.symbolConverter.toSort(x.typ), s1.relevantQuantifiedVariables.map(_._1)) val debugExp = Option.when(withExp)(DebugExp.createInstance("letvar assignment", InsertionOrderedSet(DebugExp.createInstance(ast.EqCmp(x.localVar, e0)(), ast.EqCmp(x.localVar, e0New.get)())))) - v1.decider.assumeDefinition(BuiltinEquals(t, t0), debugExp, AssumptionType.Implicit) + v1.decider.assumeDefinition(BuiltinEquals(t, t0), debugExp, AssumptionType.Internal) val newFuncRec = s1.functionRecorder.recordFreshSnapshot(t.applicable.asInstanceOf[Function]).enterLet(l) val possibleTriggersBefore = if (s1.recordPossibleTriggers) s1.possibleTriggers else Map.empty eval(s1.copy(g = s1.g + (x.localVar, (t0, e0New)), functionRecorder = newFuncRec), e1, pve, v1)((s2, t2, e1New, v2) => { @@ -310,7 +310,7 @@ object evaluator extends EvaluationRules { val uidCondExp = v.symbExLog.openScope(condExpRecord) eval(s, e0, pve, v)((s1, t0, e0New, v1) => joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s1, v1)((s2, v2, QB) => - brancher.branch(s2.copy(parallelizeBranches = false), t0, (e0, e0New), v2)( + brancher.branch(s2.copy(parallelizeBranches = false), t0, (e0, e0New), v2, AssumptionType.Internal)( (s3, v3) => eval(s3.copy(parallelizeBranches = s2.parallelizeBranches), e1, pve, v3)((s4, t4, e4, v4) => QB(s4, (t4, e4), v4)), (s3, v3) => eval(s3.copy(parallelizeBranches = s2.parallelizeBranches), e2, pve, v3)((s4, t4, e4, v4) => QB(s4, (t4, e4), v4))) )(entries => { @@ -558,7 +558,7 @@ object evaluator extends EvaluationRules { val exp = ast.Forall(eQuant.variables, eTriggers, body)(sourceQuant.pos, sourceQuant.info, sourceQuant.errT) DebugExp.createInstance(exp, expNew) }) - v1.decider.assume(Quantification(Forall, tVars, FunctionPreconditionTransformer.transform(tBody, s1.program), tTriggers, name, quantWeight), debugExp, AssumptionType.Implicit) + v1.decider.assume(Quantification(Forall, tVars, FunctionPreconditionTransformer.transform(tBody, s1.program), tTriggers, name, quantWeight), debugExp, AssumptionType.Internal) } val tQuant = Quantification(qantOp, tVars, tBody, tTriggers, name, quantWeight) @@ -576,7 +576,7 @@ object evaluator extends EvaluationRules { case fapp @ ast.FuncApp(funcName, eArgs) => val func = s.program.findFunction(funcName) - val funcAssumptionType = AssumptionType.CallPostcondition + val funcAssumptionType = AssumptionType.MethodCall val assumptionType = DependencyAnalyzer.extractAssumptionTypeFromInfo(fapp.info).getOrElse(funcAssumptionType) evals2(s, eArgs, Nil, _ => pve, v)((s1, tArgs, eArgsNew, v1) => { // bookkeeper.functionApplications += 1 @@ -728,7 +728,7 @@ object evaluator extends EvaluationRules { if (!Verifier.config.disableFunctionUnfoldTrigger()) { val eArgsString = eArgsNew.mkString(", ") val debugExp = Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}($eArgsString))", isInternal_ = true)) - v4.decider.assume(App(s.predicateData(predicate.name).triggerFunction, snap.get.convert(terms.sorts.Snap) +: tArgs), debugExp, AssumptionType.Implicit) + v4.decider.assume(App(s.predicateData(predicate.name).triggerFunction, snap.get.convert(terms.sorts.Snap) +: tArgs), debugExp, AssumptionType.Trigger) } val body = predicate.body.get /* Only non-abstract predicates can be unfolded */ val s7 = s6.scalePermissionFactor(tPerm, ePermNew) @@ -738,7 +738,7 @@ object evaluator extends EvaluationRules { if (s7a.predicateData(predicate.name).predContents.isDefined) { val toReplace: silicon.Map[Term, Term] = silicon.Map.from(s7a.predicateData(predicate.name).params.get.zip(Seq(snap.get) ++ tArgs)) - predicateSupporter.producePredicateContents(s7a, s7a.predicateData(predicate.name).predContents.get, toReplace, v4, true)((s8, v5) => { + predicateSupporter.producePredicateContents(s7a, s7a.predicateData(predicate.name).predContents.get, toReplace, v4, DependencyType.Internal, true)((s8, v5) => { val s9 = s8.copy(g = s7.g, functionRecorder = s8.functionRecorder.changeDepthBy(-1), recordVisited = s3.recordVisited, @@ -896,7 +896,7 @@ object evaluator extends EvaluationRules { val exp = ast.EqCmp(ast.SeqLength(seq)(), ast.IntLit(es.size)())(seq.pos, seq.info, seq.errT) DebugExp.createInstance(exp, expNew) }) - v1.decider.assume(SeqLength(tSeq) === IntLiteral(es.size), debugExp, AssumptionType.Implicit) + v1.decider.assume(SeqLength(tSeq) === IntLiteral(es.size), debugExp, AssumptionType.Internal) Q(s1, tSeq, esNew.map(en => ast.ExplicitSeq(en)(e.pos, e.info, e.errT)), v1)}) /* Sets and multisets */ @@ -1080,7 +1080,7 @@ object evaluator extends EvaluationRules { // check succeeds, then the continuation for evals(es2) is never invoked). This caused issue #842. // In this case, we return None. val expPair = (viper.silicon.utils.ast.BigAnd(es1), es1New.map(viper.silicon.utils.ast.BigAnd(_))) - v2.decider.setCurrentBranchCondition(bc, expPair) + v2.decider.setCurrentBranchCondition(bc, expPair, AssumptionType.Internal) var es2AndTriggerTerms: Option[(Seq[Term], Option[Seq[ast.Exp]], Seq[Trigger], (Seq[Term], Seq[Quantification]), Option[(InsertionOrderedSet[DebugExp], InsertionOrderedSet[DebugExp])], Map[ast.Exp, Term])] = None var finalState = s3 val es2AndTriggerResult = evals(s3, es2, _ => pve, v2)((s4, ts2, es2New, v3) => { @@ -1118,7 +1118,7 @@ object evaluator extends EvaluationRules { : VerificationResult = { joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s, v)((s1, v1, QB) => - brancher.branch(s1.copy(parallelizeBranches = false), tLhs, eLhs, v1, fromShortCircuitingAnd = fromShortCircuitingAnd)( + brancher.branch(s1.copy(parallelizeBranches = false), tLhs, eLhs, v1, AssumptionType.Internal, fromShortCircuitingAnd = fromShortCircuitingAnd)( (s2, v2) => eval(s2.copy(parallelizeBranches = s1.parallelizeBranches), eRhs, pve, v2)((s2, tRhs, eRhsNew, v2) => QB(s2, (tRhs, eRhsNew), v2)), (s2, v2) => QB(s2.copy(parallelizeBranches = s1.parallelizeBranches), (True, Option.when(withExp)(ast.TrueLit()())), v2)) )(entries => { @@ -1374,7 +1374,7 @@ object evaluator extends EvaluationRules { (r, optRemainingTriggerTerms) match { case (Success(), Some(remainingTriggerTerms)) => // TODO ake: wrap pcDelta with labels? - v.decider.assume(pcDelta, Option.when(withExp)(DebugExp.createInstance("pcDeltaExp", children = pcDeltaExp)), enforceAssumption = false, assumptionType=AssumptionType.Implicit) + v.decider.assume(pcDelta, Option.when(withExp)(DebugExp.createInstance("pcDeltaExp", children = pcDeltaExp)), enforceAssumption = false, assumptionType=AssumptionType.Internal) Q(s, cachedTriggerTerms ++ remainingTriggerTerms, v) case _ => for (e <- remainingTriggerExpressions) @@ -1516,7 +1516,7 @@ object evaluator extends EvaluationRules { case _ => val expPair = if (constructor == Or) (exps.head, e0New) else (ast.Not(exps.head)(), e0New.map(ast.Not(_)())) joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s1, v1)((s2, v2, QB) => - brancher.branch(s2.copy(parallelizeBranches = false), if (constructor == Or) t0 else Not(t0), expPair, v2, fromShortCircuitingAnd = true)( + brancher.branch(s2.copy(parallelizeBranches = false), if (constructor == Or) t0 else Not(t0), expPair, v2, AssumptionType.Internal, fromShortCircuitingAnd = true)( (s3, v3) => QB(s3.copy(parallelizeBranches = s2.parallelizeBranches), (t0, e0New), v3), (s3, v3) => evalSeqShortCircuit(constructor, s3.copy(parallelizeBranches = s2.parallelizeBranches), exps.tail, pve, v3)((s2, t2, e2, v2) => QB(s2, (t2, e2), v2))) ){case Seq(ent) => diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index f9cc21449..5a141e8e9 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -75,11 +75,12 @@ object executor extends ExecutionRules { val condEdgeRecord = new ConditionalEdgeRecord(ce.condition, s, v.decider.pcs) val sepIdentifier = v.symbExLog.openScope(condEdgeRecord) val s1 = handleOutEdge(s, edge, v) + val dependencyType = DependencyAnalyzer.extractDependencyTypeFromInfo(ce.condition.info) eval(s1, ce.condition, IfFailed(ce.condition), v)((s2, tCond, condNew, v1) => /* Using branch(...) here ensures that the edge condition is recorded * as a branch condition on the pathcondition stack. */ - brancher.branch(s2.copy(parallelizeBranches = false), tCond, (ce.condition, condNew), v1)( + brancher.branch(s2.copy(parallelizeBranches = false), tCond, (ce.condition, condNew), v1, dependencyType.map(_.assumptionType).getOrElse(AssumptionType.PathCondition))( (s3, v3) => exec(s3.copy(parallelizeBranches = s2.parallelizeBranches), ce.target, ce.kind, v3, joinPoint)((s4, v4) => { v4.symbExLog.closeScope(sepIdentifier) @@ -151,10 +152,12 @@ object executor extends ExecutionRules { case _ => false }) + val dependencyType = DependencyAnalyzer.extractDependencyTypeFromInfo(cedge1.condition.info) + eval(s, cedge1.condition, pvef(cedge1.condition), v)((s1, t0, condNew, v1) => // The type arguments here are Null because there is no need to pass any join data. joiner.join[scala.Null, scala.Null](s1, v1, resetState = false)((s2, v2, QB) => { - brancher.branch(s2, t0, (cedge1.condition, condNew), v2)( + brancher.branch(s2, t0, (cedge1.condition, condNew), v2, dependencyType.map(_.assumptionType).getOrElse(AssumptionType.PathCondition))( // Follow only until join point. (s3, v3) => follow(s3, edge1, v3, Some(newJoinPoint))((s, v) => QB(s, null, v)), (s3, v3) => follow(s3, edge2, v3, Some(newJoinPoint))((s, v) => QB(s, null, v)) @@ -183,8 +186,9 @@ object executor extends ExecutionRules { if Verifier.config.parallelizeBranches() && cond2 == ast.Not(cond1)() => val condEdgeRecord = new ConditionalEdgeRecord(thenEdge.condition, s, v.decider.pcs) val sepIdentifier = v.symbExLog.openScope(condEdgeRecord) + val dependencyType = DependencyAnalyzer.extractDependencyTypeFromInfo(thenEdge.condition.info) val res = eval(s, thenEdge.condition, IfFailed(thenEdge.condition), v)((s2, tCond, eCondNew, v1) => - brancher.branch(s2, tCond, (thenEdge.condition, eCondNew), v1)( + brancher.branch(s2, tCond, (thenEdge.condition, eCondNew), v1, dependencyType.map(_.assumptionType).getOrElse(AssumptionType.PathCondition))( (s3, v3) => { follow(s3, thenEdge, v3, joinPoint)(Q) }, @@ -278,7 +282,7 @@ object executor extends ExecutionRules { })}) combine executionFlowController.locally(s, v)((s0, v0) => { v0.decider.prover.comment("Loop head block: Establish invariant") - consumes(s0, invs, false, LoopInvariantNotEstablished, v0, AssumptionType.LoopInvariant)((sLeftover, _, v1) => { + consumes(s0, invs, false, LoopInvariantNotEstablished, v0, DependencyType.Invariant)((sLeftover, _, v1) => { v1.decider.prover.comment("Loop head block: Execute statements of loop head block (in invariant state)") phase1data.foldLeft(Success(): VerificationResult) { case (result, _) if !result.continueVerification => result @@ -313,7 +317,7 @@ object executor extends ExecutionRules { * attempting to re-establish the invariant. */ v.decider.prover.comment("Loop head block: Re-establish invariant") - consumes(s, invs, false, e => LoopInvariantNotPreserved(e), v, AssumptionType.LoopInvariant)((_, _, _) => + consumes(s, invs, false, e => LoopInvariantNotPreserved(e), v, DependencyType.Invariant)((_, _, _) => Success()) } } @@ -341,19 +345,11 @@ object executor extends ExecutionRules { val sepIdentifier = v.symbExLog.openScope(new ExecuteRecord(stmt, s, v.decider.pcs)) val sourceInfo = AnalysisSourceInfo.createAnalysisSourceInfo(stmt) v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo) - if(false /* TODO ake */ || Verifier.config.disableInfeasibilityChecks() && Verifier.config.enableDependencyAnalysis() && - v.decider.pcs.getCurrentInfeasibilityNode.isDefined && !alwaysExecute(stmt)){ - v.decider.dependencyAnalyzer.addInfeasibilityDepToStmt(v.decider.pcs.getCurrentInfeasibilityNode, - v.decider.analysisSourceInfoStack.getFullSourceInfo, AssumptionType.Implicit) + exec2(s, stmt, v)((s1, v1) => { v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) - Q(s, v) - }else { - exec2(s, stmt, v)((s1, v1) => { - v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) - v1.symbExLog.closeScope(sepIdentifier) - Q(s1, v1) - }) - } + v1.symbExLog.closeScope(sepIdentifier) + Q(s1, v1) + }) } def exec2(state: State, stmt: ast.Stmt, v: Verifier) @@ -377,8 +373,12 @@ object executor extends ExecutionRules { } val annotatedDependencyTypeOpt = DependencyAnalyzer.extractDependencyTypeFromInfo(stmt.info) - val annotatedAssumptionTypeOpt = annotatedDependencyTypeOpt.map(_.assumptionType) - val annotatedAssertionTypeOpt = annotatedDependencyTypeOpt.map(_.assertionType) + + def getDependencyType(defaultType: DependencyType): DependencyType = annotatedDependencyTypeOpt.getOrElse(defaultType) + + def getAssumptionType(defaultType: AssumptionType): AssumptionType = annotatedDependencyTypeOpt.map(_.assumptionType).getOrElse(defaultType) + + def getAssertionType(defaultType: AssumptionType): AssumptionType = annotatedDependencyTypeOpt.map(_.assertionType).getOrElse(defaultType) val executed = stmt match { case ast.Seqn(stmts, _) => @@ -395,7 +395,7 @@ object executor extends ExecutionRules { case ass @ ast.LocalVarAssign(x, rhs) => eval(s, rhs, AssignmentFailed(ass), v)((s1, tRhs, rhsNew, v1) => { - val (t, e) = ssaifyRhs(tRhs, rhs, rhsNew, x.name, x.typ, v, s1, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit)) + val (t, e) = ssaifyRhs(tRhs, rhs, rhsNew, x.name, x.typ, v, s1, getAssumptionType(AssumptionType.SourceCode)) Q(s1.copy(g = s1.g + (x, (t, e))), v1)}) /* TODO: Encode assignments e1.f := e2 as @@ -409,8 +409,8 @@ object executor extends ExecutionRules { val pve = AssignmentFailed(ass) eval(s, eRcvr, pve, v)((s1, tRcvr, eRcvrNew, v1) => { eval(s1, rhs, pve, v1)((s2, tRhs, eRhsNew, v2) => { - val (tSnap, _) = ssaifyRhs(tRhs, rhs, eRhsNew, field.name, field.typ, v2, s2, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit)) - v2.heapSupporter.execFieldAssign(s2, ass, tRcvr, eRcvrNew, tSnap, eRhsNew, pve, v2, annotatedDependencyTypeOpt.getOrElse(DependencyType.Implicit))(Q) + val (tSnap, _) = ssaifyRhs(tRhs, rhs, eRhsNew, field.name, field.typ, v2, s2, getAssumptionType(AssumptionType.SourceCode)) + v2.heapSupporter.execFieldAssign(s2, ass, tRcvr, eRcvrNew, tSnap, eRhsNew, pve, v2, getDependencyType(DependencyType.SourceCode))(Q) }) }) @@ -419,7 +419,7 @@ object executor extends ExecutionRules { val debugExp = Option.when(withExp)(ast.NeCmp(x, ast.NullLit()())()) val debugExpSubst = Option.when(withExp)(ast.NeCmp(eRcvrNew.get, ast.NullLit()())()) val (debugHeapName, debugLabel) = v.getDebugOldLabel(s, stmt.pos) - v.decider.assume(tRcvr !== Null, debugExp, debugExpSubst, AssumptionType.Implicit) + v.decider.assume(tRcvr !== Null, debugExp, debugExpSubst, AssumptionType.SourceCode) val eRcvr = Option.when(withExp)(Seq(x)) val p = FullPerm @@ -433,7 +433,7 @@ object executor extends ExecutionRules { val fld = flds.head val snap = v.decider.fresh(fld.name, v.symbolConverter.toSort(fld.typ), Option.when(withExp)(extractPTypeFromExp(x))) val snapExp = Option.when(withExp)(ast.DebugLabelledOld(ast.FieldAccess(eRcvrNew.get, fld)(), debugLabel)(stmt.pos, stmt.info, stmt.errT)) - v.heapSupporter.produceSingle(s, fld, Seq(tRcvr), eRcvr, snap, snapExp, p, pExp, NullPartialVerificationError, false, v, annotatedDependencyTypeOpt.getOrElse(DependencyType.Implicit))((s1, v1) => { + v.heapSupporter.produceSingle(s, fld, Seq(tRcvr), eRcvr, snap, snapExp, p, pExp, NullPartialVerificationError, false, v, getDependencyType(DependencyType.SourceCode))((s1, v1) => { addFieldPerms(s1, flds.tail, v1)(QB) }) } @@ -443,7 +443,7 @@ object executor extends ExecutionRules { addFieldPerms(s, fields, v)((s0, v0) => { val s1 = s0.copy(g = s0.g + (x, (tRcvr, eRcvrNew))) val s2 = if (withExp) s1.copy(oldHeaps = s1.oldHeaps + (debugHeapName -> magicWandSupporter.getEvalHeap(s1))) else s1 - v0.decider.assume(ts, Option.when(withExp)(DebugExp.createInstance(Some("Reference Disjointness"), esNew, esNew, InsertionOrderedSet.empty)), enforceAssumption = false, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Implicit)) + v0.decider.assume(ts, Option.when(withExp)(DebugExp.createInstance(Some("Reference Disjointness"), esNew, esNew, InsertionOrderedSet.empty)), enforceAssumption = false, getAssumptionType(AssumptionType.SourceCode)) Q(s2, v0) }) @@ -452,10 +452,10 @@ object executor extends ExecutionRules { /* We're done */ Success() case _ => - produce(s, freshSnap, a, InhaleFailed(inhale), v, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit))((s1, v1) => { + produce(s, freshSnap, a, InhaleFailed(inhale), v, getAssumptionType(AssumptionType.Explicit))((s1, v1) => { v1.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterInhale) if(Verifier.config.enableDependencyAnalysis() && a.isInstanceOf[ast.FalseLit]) { - val (_, node) = v1.decider.checkAndGetInfeasibilityNode(False, Verifier.config.checkTimeout(), AssumptionType.Explicit) + val (_, node) = v1.decider.checkAndGetInfeasibilityNode(False, Verifier.config.checkTimeout(), getAssumptionType(AssumptionType.Explicit)) v1.decider.pcs.setCurrentInfeasibilityNode(node) } Q(s1, v1)}) @@ -463,13 +463,13 @@ object executor extends ExecutionRules { case exhale @ ast.Exhale(a) => val pve = ExhaleFailed(exhale) - consume(s, a, false, pve, v, annotatedAssertionTypeOpt.getOrElse(AssumptionType.Explicit))((s1, _, v1) => + consume(s, a, false, pve, v, getDependencyType(DependencyType.ExplicitAssertion))((s1, _, v1) => Q(s1, v1)) case assert @ ast.Assert(a: ast.FalseLit) if !s.isInPackage => /* "assert false" triggers a smoke check. If successful, we backtrack. */ executionFlowController.tryOrFail0(s.copy(h = magicWandSupporter.getEvalHeap(s)), v)((s1, v1, QS) => { - if (v1.decider.checkSmoke(isAssert=true, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit))) + if (v1.decider.checkSmoke(isAssert=true, getAssertionType(AssumptionType.Explicit))) QS(s1.copy(h = s.h), v1) else createFailure(AssertFailed(assert) dueTo AssertionFalse(a), v1, s1, False, true, Option.when(withExp)(a)) @@ -482,7 +482,7 @@ object executor extends ExecutionRules { case assert @ ast.Assert(a) if Verifier.config.disableSubsumption() => val r = - consume(s, a, false, AssertFailed(assert), v, annotatedAssertionTypeOpt.getOrElse(AssumptionType.Explicit))((_, _, _) => + consume(s, a, false, AssertFailed(assert), v, getDependencyType(DependencyType.ExplicitAssertion))((_, _, _) => Success()) r combine Q(s, v) @@ -498,11 +498,11 @@ object executor extends ExecutionRules { * hUsed (reserveHeaps.head) instead of consuming them. hUsed is later discarded and replaced * by s.h. By copying hUsed to s.h the contained permissions remain available inside the wand. */ - consume(s, a, false, pve, v, annotatedAssertionTypeOpt.getOrElse(AssumptionType.Explicit))((s2, _, v1) => { + consume(s, a, false, pve, v, getDependencyType(DependencyType.ExplicitAssertion))((s2, _, v1) => { Q(s2.copy(h = s2.reserveHeaps.head), v1) }) } else - consume(s, a, false, pve, v, annotatedAssertionTypeOpt.getOrElse(AssumptionType.Explicit))((s1, _, v1) => { + consume(s, a, false, pve, v, getDependencyType(DependencyType.ExplicitAssertion))((s1, _, v1) => { val s2 = s1.copy(h = s.h, reserveHeaps = s.reserveHeaps) Q(s2, v1)}) @@ -511,7 +511,7 @@ object executor extends ExecutionRules { case ast.MethodCall(methodName, _, _) if !Verifier.config.disableHavocHack407() && methodName.startsWith(hack407_method_name_prefix) => - val analysisInfo = v.decider.getAnalysisInfo(annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit)) + val analysisInfo = v.decider.getAnalysisInfo(getAssumptionType(AssumptionType.Explicit)) val resourceName = methodName.stripPrefix(hack407_method_name_prefix) val member = s.program.collectFirst { case m: ast.Field if m.name == resourceName => m @@ -543,7 +543,7 @@ object executor extends ExecutionRules { val pveCallTransformed = pveCall.withReasonNodeTransformed(reasonTransformer) // TODO: make sure the join can still be made! - val finalAssumptionType = annotatedAssumptionTypeOpt.getOrElse(AssumptionType.CallPostcondition) + val finalAssumptionType = getAssumptionType(AssumptionType.MethodCall) val mcLog = new MethodCallRecord(call, s, v.decider.pcs) val sepIdentifier = v.symbExLog.openScope(mcLog) @@ -564,14 +564,14 @@ object executor extends ExecutionRules { tArgs zip Seq.fill(tArgs.size)(None) val s2 = s1.copy(g = Store(fargs.zip(argsWithExp)), recordVisited = true) - consumes(s2, meth.pres, false, _ => pvePre, v1, assumptionType=annotatedAssertionTypeOpt.getOrElse(AssumptionType.Implicit))((s3, _, v2) => { + consumes(s2, meth.pres, false, _ => pvePre, v1, getDependencyType(DependencyType.MethodCall))((s3, _, v2) => { v2.symbExLog.closeScope(preCondId) val postCondLog = new CommentRecord("Postcondition", s3, v2.decider.pcs) val postCondId = v2.symbExLog.openScope(postCondLog) val outs = meth.formalReturns.map(_.localVar) val gOuts = Store(outs.map(x => (x, v2.decider.fresh(x))).toMap) val s4 = s3.copy(g = s3.g + gOuts, oldHeaps = s3.oldHeaps + (Verifier.PRE_STATE_LABEL -> magicWandSupporter.getEvalHeap(s1))) - produces(s4, freshSnap, meth.posts, _ => pveCallTransformed, v2, AssumptionType.CallPostcondition)((s5, v3) => { + produces(s4, freshSnap, meth.posts, _ => pveCallTransformed, v2, AssumptionType.MethodCall)((s5, v3) => { // TODO ake: assumptionType v3.symbExLog.closeScope(postCondId) v3.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) val gLhs = Store(lhs.zip(outs) @@ -588,7 +588,7 @@ object executor extends ExecutionRules { val ePerm = pap.perm val predicate = s.program.findPredicate(predicateName) val predicateAnnotatedAssumptionType = DependencyAnalyzer.extractAssumptionTypeFromInfo(predicate.info).map(DependencyType.make) - val finalDependencyType = annotatedDependencyTypeOpt.getOrElse(predicateAnnotatedAssumptionType.getOrElse(DependencyType.Rewrite)) + val finalDependencyType = getDependencyType(predicateAnnotatedAssumptionType.getOrElse(DependencyType.Rewrite)) val pve = FoldFailed(fold) evals(s, eArgs, _ => pve, v)((s1, tArgs, eArgsNew, v1) => eval(s1, ePerm, pve, v1)((s2, tPerm, ePermNew, v2) => @@ -606,7 +606,7 @@ object executor extends ExecutionRules { val ePerm = pap.perm val predicate = s.program.findPredicate(predicateName) val predicateAnnotatedAssumptionType = DependencyAnalyzer.extractAssumptionTypeFromInfo(predicate.info).map(DependencyType.make) - val finalDependencyType = annotatedDependencyTypeOpt.getOrElse(predicateAnnotatedAssumptionType.getOrElse(DependencyType.Rewrite)) + val finalDependencyType = getDependencyType(predicateAnnotatedAssumptionType.getOrElse(DependencyType.Rewrite)) val pve = UnfoldFailed(unfold) evals(s, eArgs, _ => pve, v)((s1, tArgs, eArgsNew, v1) => eval(s1, ePerm, pve, v1)((s2, tPerm, ePermNew, v2) => { @@ -624,7 +624,7 @@ object executor extends ExecutionRules { case pckg @ ast.Package(wand, proofScript) => val pve = PackageFailed(pckg) - magicWandSupporter.packageWand(s.copy(isInPackage = true), wand, proofScript, pve, v, annotatedDependencyTypeOpt.getOrElse(DependencyType.Rewrite))((s1, chWand, v1) => { + magicWandSupporter.packageWand(s.copy(isInPackage = true), wand, proofScript, pve, v, getDependencyType(DependencyType.Rewrite))((s1, chWand, v1) => { val hOps = s1.reserveHeaps.head + chWand assert(s.exhaleExt || s1.reserveHeaps.length == 1) @@ -659,13 +659,13 @@ object executor extends ExecutionRules { case apply @ ast.Apply(e) => val pve = ApplyFailed(apply) - magicWandSupporter.applyWand(s, e, pve, v, annotatedDependencyTypeOpt.getOrElse(DependencyType.Rewrite))(Q) + magicWandSupporter.applyWand(s, e, pve, v, getDependencyType(DependencyType.Rewrite))(Q) case havoc: ast.Quasihavoc => - havocSupporter.execHavoc(havoc, v, s, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit))(Q) + havocSupporter.execHavoc(havoc, v, s, getAssumptionType(AssumptionType.Explicit))(Q) case havocall: ast.Quasihavocall => - havocSupporter.execHavocall(havocall, v, s, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Explicit))(Q) + havocSupporter.execHavocall(havocall, v, s, getAssumptionType(AssumptionType.Explicit))(Q) case viper.silicon.extensions.TryBlock(body) => var bodySucceeded = false diff --git a/src/main/scala/rules/HeapSupporter.scala b/src/main/scala/rules/HeapSupporter.scala index 545fbb5a5..524d75715 100644 --- a/src/main/scala/rules/HeapSupporter.scala +++ b/src/main/scala/rules/HeapSupporter.scala @@ -80,7 +80,7 @@ trait HeapSupportRules extends SymbolicExecutionRules { returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, - assumptionType: AssumptionType) + dependencyType: DependencyType) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult): VerificationResult def consumeQuantified(s: State, @@ -109,7 +109,7 @@ trait HeapSupportRules extends SymbolicExecutionRules { notInjectiveReason: => ErrorReason, insufficientPermissionReason: => ErrorReason, v: Verifier, - assumptionType: AssumptionType) + dependencyType: DependencyType) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult): VerificationResult def produceSingle(s: State, @@ -240,7 +240,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { val description = s"consume ${ass.pos}: $ass" val lhsSourceInfo = TransitivityAnalysisSourceInfo(v.decider.analysisSourceInfoStack.getFullSourceInfo, AnalysisSourceInfo.createAnalysisSourceInfo(ass.lhs)) v.decider.analysisSourceInfoStack.setForcedSource(lhsSourceInfo) // splitting lhs and rhs to make permission flow analysis more precise - chunkSupporter.consume(s, s.h, field, Seq(tRcvr), eRcvrNew.map(Seq(_)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), false, ve, v, description, dependencyType.assumptionType)((s3, h3, _, v3) => { + chunkSupporter.consume(s, s.h, field, Seq(tRcvr), eRcvrNew.map(Seq(_)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), false, ve, v, description, dependencyType)((s3, h3, _, v3) => { val id = BasicChunkIdentifier(field.name) val newChunk = BasicChunk.apply(FieldID, id, Seq(tRcvr), eRcvrNew.map(Seq(_)), tRhs, eRhsNew, FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), v3.decider.getAnalysisInfo(AssumptionType.Internal)) v.decider.analysisSourceInfoStack.removeForcedSource() @@ -515,7 +515,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, - assumptionType: AssumptionType) + dependencyType: DependencyType) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult): VerificationResult = { val resource = resAcc.res(s.program) val useQPs = s.isQuantifiedResource(resource) @@ -523,14 +523,14 @@ class DefaultHeapSupportRules extends HeapSupportRules { val tFormalArgs = s.getFormalArgVars(resource, v) val eFormalArgs = Option.when(withExp)(s.getFormalArgDecls(resource)) quantifiedChunkSupporter.consumeSingleLocation( - s, h, tFormalArgs, eFormalArgs, tArgs, eArgs, resAcc, tPerm, ePerm, returnSnap, None, pve, v, assumptionType)(Q) + s, h, tFormalArgs, eFormalArgs, tArgs, eArgs, resAcc, tPerm, ePerm, returnSnap, None, pve, v, dependencyType)(Q) } else { val ve = resAcc match { case l: ast.LocationAccess => pve dueTo InsufficientPermission(l) case w: ast.MagicWand => pve dueTo MagicWandChunkNotFound(w) } val description = s"consume ${resAcc.pos}: $resAcc" - chunkSupporter.consume(s, h, resource, tArgs, eArgs, tPerm, ePerm, returnSnap, ve, v, description, assumptionType)(Q) + chunkSupporter.consume(s, h, resource, tArgs, eArgs, tPerm, ePerm, returnSnap, ve, v, description, dependencyType)(Q) } } @@ -626,7 +626,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { notInjectiveReason: => ErrorReason, insufficientPermissionReason: => ErrorReason, v: Verifier, - assumptionType: AssumptionType) + dependencyType: DependencyType) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult): VerificationResult = { quantifiedChunkSupporter.consume( s, @@ -655,7 +655,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { notInjectiveReason, insufficientPermissionReason, v, - assumptionType + dependencyType )(Q) } diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index b76eb9ca4..89b2ebe87 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -185,7 +185,7 @@ object magicWandSupporter extends SymbolicExecutionRules { case (Some(ch1: QuantifiedBasicChunk), Some(ch2: QuantifiedBasicChunk)) => ch1.snapshotMap === ch2.snapshotMap case _ => True } - v.decider.assume(tEq, Option.when(withExp)(DebugExp.createInstance("Snapshots", isInternal_ = true)), AssumptionType.Explicit) + v.decider.assume(tEq, Option.when(withExp)(DebugExp.createInstance("Snapshots", isInternal_ = true)), AssumptionType.Internal) /* In the future it might be worth to recheck whether the permissions needed, in the case of * success being an instance of Incomplete, are zero. @@ -243,6 +243,8 @@ object magicWandSupporter extends SymbolicExecutionRules { (Q: (State, Chunk, Verifier) => VerificationResult) : VerificationResult = { + val prevAnalysisStack = v.decider.analysisSourceInfoStack.getAnalysisSourceInfos + val s = if (state.exhaleExt) state else state.copy(reserveHeaps = v.heapSupporter.getEmptyHeap(state.program) :: state.h :: Nil) @@ -371,7 +373,7 @@ object magicWandSupporter extends SymbolicExecutionRules { val freshSnapRoot = freshSnap(sorts.Snap, v1) // Produce the wand's LHS. - produce(s1.copy(conservingSnapshotGeneration = true), toSf(freshSnapRoot), wand.left, pve, v1, AssumptionType.Implicit)((sLhs, v2) => { + produce(s1.copy(conservingSnapshotGeneration = true), toSf(freshSnapRoot), wand.left, pve, v1, dependencyType.assumptionType)((sLhs, v2) => { val proofScriptCfg = proofScript.toCfg() val emptyHeap = v2.heapSupporter.getEmptyHeap(sLhs.program) @@ -410,7 +412,7 @@ object magicWandSupporter extends SymbolicExecutionRules { // This part indirectly calls the methods `this.transfer` and `this.consumeFromMultipleHeaps`. consume( proofScriptState.copy(oldHeaps = s2.oldHeaps, reserveCfgs = proofScriptState.reserveCfgs.tail), - wand.right, true, pve, proofScriptVerifier, dependencyType.assertionType + wand.right, true, pve, proofScriptVerifier, dependencyType )((s3, snapRhs, v3) => { analysisLabels = v.decider.pcs.after(prePackageMark).analysisLabels createWandChunkAndRecordResults(s3.copy(exhaleExt = false, oldHeaps = s.oldHeaps), freshSnapRoot, snapRhs.get, v3, dependencyType.assumptionType) @@ -431,7 +433,6 @@ object magicWandSupporter extends SymbolicExecutionRules { // some of the analysis labels, introduced while verifying the package statement, might be needed later on -> reassume them analysisLabels foreach (l => v.decider.assume(v.decider.wrapWithDependencyAnalysisLabel(l, Set.empty, Set(l)), None, AssumptionType.Internal)) - val currentAnalysisSourceInfos = v.decider.analysisSourceInfoStack.getAnalysisSourceInfos recordedBranches.foldLeft(tempResult)((prevRes, recordedState) => { prevRes && { val (state, branchConditions, branchConditionsExp, conservedPcs, magicWandChunk) = recordedState @@ -442,11 +443,11 @@ object magicWandSupporter extends SymbolicExecutionRules { // We execute the continuation Q in a new scope with all branch conditions and all conserved path conditions. executionFlowController.locally(s1, v)((s2, v1) => { - v1.decider.analysisSourceInfoStack.setAnalysisSourceInfo(currentAnalysisSourceInfos) + v1.decider.analysisSourceInfoStack.setAnalysisSourceInfo(prevAnalysisStack) val exp = viper.silicon.utils.ast.BigAnd(branchConditionsExp.map(_._1)) val expNew = Option.when(withExp)(viper.silicon.utils.ast.BigAnd(branchConditionsExp.map(_._2.get))) // Set the branch conditions - v1.decider.setCurrentBranchCondition(And(branchConditions map (t => v1.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t)))), (exp, expNew)) + v1.decider.setCurrentBranchCondition(And(branchConditions map (t => v1.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t)))), (exp, expNew), dependencyType.assumptionType) // Recreate all path conditions in the Z3 proof script that we recorded for that branch v1.decider.assume(conservedPcs._1 map (t => v1.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t))), conservedPcs._2, AssumptionType.Internal) @@ -476,9 +477,9 @@ object magicWandSupporter extends SymbolicExecutionRules { (Q: (State, Verifier) => VerificationResult) : VerificationResult = { // Consume the magic wand instance "A --* B". - consume(s, wand, true, pve, v, dependencyType.assertionType)((s1, snapWand, v1) => { + consume(s, wand, true, pve, v, dependencyType)((s1, snapWand, v1) => { // Consume the wand's LHS "A". - consume(s1, wand.left, true, pve, v1, dependencyType.assertionType)((s2, snapLhs, v2) => { + consume(s1, wand.left, true, pve, v1, dependencyType)((s2, snapLhs, v2) => { /* It is assumed that snap and MagicWandSnapshot.abstractLhs are structurally the same. * Equating the two snapshots is sound iff a wand is applied only once. * The old solution in this case did use this assumption: diff --git a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala index 49198b428..49d76dc65 100644 --- a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala +++ b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala @@ -6,7 +6,7 @@ package viper.silicon.rules -import viper.silicon.dependencyAnalysis.AssumptionType +import viper.silicon.dependencyAnalysis.{AssumptionType, DependencyType} import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType import viper.silicon.debugger.DebugExp import viper.silicon.interfaces.state._ @@ -24,6 +24,7 @@ import viper.silver.ast import viper.silver.parser.PUnknown import viper.silver.verifier.VerificationError +import scala.annotation.unused import scala.collection.mutable.ListBuffer object moreCompleteExhaleSupporter extends SymbolicExecutionRules { @@ -237,14 +238,14 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { returnSnap: Boolean, ve: VerificationError, v: Verifier, - assumptionType: AssumptionType=AssumptionType.Implicit) + dependencyType: DependencyType=DependencyType.Implicit) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { if (!s.assertReadAccessOnly) - actualConsumeComplete(s, h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, assumptionType)(Q) + actualConsumeComplete(s, h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, dependencyType)(Q) else - summariseHeapAndAssertReadAccess(s, h, resource, perms, args, argsExp, returnSnap, ve, v, assumptionType)(Q) + summariseHeapAndAssertReadAccess(s, h, resource, perms, args, argsExp, returnSnap, ve, v, dependencyType.assertionType)(Q) } private def summariseHeapAndAssertReadAccess(s: State, @@ -256,7 +257,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { returnSnap: Boolean, ve: VerificationError, v: Verifier, - assumptionType: AssumptionType) + assertionType: AssumptionType) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -265,7 +266,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { if (returnSnap) { summarise(s, relevantChunks, resource, args, argsExp, None, v)((s1, snap, permSum, permSumExp, v1) => - v.decider.assert(Implies(IsPositive(perm), IsPositive(permSum)), assumptionType) { + v.decider.assert(Implies(IsPositive(perm), IsPositive(permSum)), assertionType) { case true => Q(s1, h, Some(snap), v1) case false => @@ -273,7 +274,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { }) } else { val (s1, permSum, permSumExp) = permSummariseOnly(s, relevantChunks, resource, args, argsExp) - v.decider.assert(Implies(IsPositive(perm), IsPositive(permSum)), assumptionType) { + v.decider.assert(Implies(IsPositive(perm), IsPositive(permSum)), assertionType) { case true => Q(s1, h, None, v) case false => @@ -292,7 +293,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { returnSnap: Boolean, ve: VerificationError, v: Verifier, - assumptionType: AssumptionType) + dependencyType: DependencyType) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -306,13 +307,13 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { if (relevantChunks.isEmpty) { // if no permission is exhaled, return none - v.decider.assert(perms === NoPerm, assumptionType) { + v.decider.assert(perms === NoPerm, dependencyType.assertionType) { case true => Q(s, h, None, v) case false => createFailure(ve, v, s, perms === NoPerm, permsExp.map(pe => ast.EqCmp(pe, ast.NoPerm()())(pe.pos, pe.info, pe.errT))) } } else { if (!terms.utils.consumeExactRead(perms, s.constrainableARPs)) { - actualConsumeCompleteConstrainable(s, relevantChunks, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, assumptionType)((s1, updatedChunks, optSnap, v2) => { + actualConsumeCompleteConstrainable(s, relevantChunks, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, dependencyType)((s1, updatedChunks, optSnap, v2) => { Q(s1, Heap(updatedChunks ++ otherChunks), optSnap, v2) }) } else { @@ -365,16 +366,16 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { pSum = PermPlus(pSum, Ite(eq, ch.perm, NoPerm)) pSumExp = eqExp.map(eq => ast.PermAdd(pSumExp.get, ast.CondExp(eq, ch.permExp.get, ast.NoPerm()())(eq.pos, eq.info, eq.errT))()) - val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, pTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, pTakenExp.get)(pe.pos, pe.info, pe.errT)), v.decider.getAnalysisInfo(AssumptionType.Implicit)).asInstanceOf[NonQuantifiedChunk] - val _ = GeneralChunk.withPerm(ch, pTaken, None, v.decider.getAnalysisInfo(assumptionType), isExhale=true) + val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, pTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, pTakenExp.get)(pe.pos, pe.info, pe.errT)), v.decider.getAnalysisInfo(dependencyType.assumptionType)).asInstanceOf[NonQuantifiedChunk] + val _ = GeneralChunk.withPerm(ch, pTaken, None, v.decider.getAnalysisInfo(dependencyType.assertionType), isExhale=true) pNeeded = PermMinus(pNeeded, pTaken) pNeededExp = permsExp.map(pe => ast.PermSub(pNeededExp.get, pTakenExp.get)(pe.pos, pe.info, pe.errT)) - if (!v.decider.check(IsNonPositive(newChunk.perm), Verifier.config.splitTimeout(), AssumptionType.Implicit)) { + if (!v.decider.check(IsNonPositive(newChunk.perm), Verifier.config.splitTimeout(), AssumptionType.Internal)) { newChunks.append(newChunk) } - moreNeeded = !v.decider.check(pNeeded === NoPerm, Verifier.config.splitTimeout(), AssumptionType.Implicit) + moreNeeded = !v.decider.check(pNeeded === NoPerm, Verifier.config.splitTimeout(), dependencyType.assumptionType) } else { newChunks.append(ch) } @@ -387,7 +388,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { newChunks foreach { ch => val resource = Resources.resourceDescriptions(ch.resourceID) val pathCond = interpreter.buildPathConditionsForChunk(ch, resource.instanceProperties(s.mayAssumeUpperBounds)) - pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), AssumptionType.Implicit)) + pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), dependencyType.assumptionType)) } val newHeap = Heap(allChunks) @@ -397,7 +398,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { if (returnSnap) { summarise(s0, relevantChunks.toSeq, resource, args, argsExp, Some(definiteAlias.map(_.snap)), v)((s1, snap, _, _, v1) => { - val condSnap = Some(if (v1.decider.check(IsPositive(perms), Verifier.config.checkTimeout(), AssumptionType.Implicit)) { + val condSnap = Some(if (v1.decider.check(IsPositive(perms), Verifier.config.checkTimeout(), AssumptionType.Internal)) { snap } else { Ite(IsPositive(perms), snap.convert(sorts.Snap), Unit) @@ -405,7 +406,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { if (!moreNeeded) { Q(s1, newHeap, condSnap, v1) } else { - v1.decider.assert(pNeeded === NoPerm, assumptionType) { + v1.decider.assert(pNeeded === NoPerm, dependencyType.assertionType) { case true => Q(s1, newHeap, condSnap, v1) case false => @@ -417,7 +418,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { if (!moreNeeded) { Q(s0, newHeap, None, v) } else { - v.decider.assert(pNeeded === NoPerm, assumptionType) { + v.decider.assert(pNeeded === NoPerm, dependencyType.assertionType) { case true => Q(s0, newHeap, None, v) case false => @@ -439,7 +440,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { returnSnap: Boolean, ve: VerificationError, v: Verifier, - assumptionType: AssumptionType) + dependencyType: DependencyType) (Q: (State, ListBuffer[NonQuantifiedChunk], Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -476,13 +477,13 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { ast.Implies(ast.Not(eqExp.get)(), ast.EqCmp(permTakenExp.get, ast.NoPerm()())())(pe.pos, pe.info, pe.errT)))) - v.decider.assume(constraint, Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp)), AssumptionType.Implicit) + v.decider.assume(constraint, Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp)), dependencyType.assumptionType) newFr = newFr.recordPathSymbol(permTaken.applicable.asInstanceOf[Function]).recordConstraint(constraint) @unused // required in order to ensure a sound dependency analysis - val _ = GeneralChunk.withPerm(ch, permTaken, None, v.decider.getAnalysisInfo(assumptionType), isExhale=true) - NonQuantifiedChunk.withPerm(ch, PermMinus(ch.perm, permTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, permTakenExp.get)(pe.pos, pe.info, pe.errT)), v.decider.getAnalysisInfo(AssumptionType.Implicit)) + val _ = GeneralChunk.withPerm(ch, permTaken, None, v.decider.getAnalysisInfo(dependencyType.assertionType), isExhale=true) + NonQuantifiedChunk.withPerm(ch, PermMinus(ch.perm, permTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, permTakenExp.get)(pe.pos, pe.info, pe.errT)), v.decider.getAnalysisInfo(dependencyType.assumptionType)) }) val totalTakenBounds = @@ -494,16 +495,16 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { val constraintExp = permsExp.map(pe => ast.Implies(ast.NeCmp(totalPermSumExp.get, ast.NoPerm()())(), ast.And(ast.PermLeCmp(ast.NoPerm()(), totalPermTakenExp.get)(), ast.PermLeCmp(totalPermTakenExp.get, totalPermSumExp.get)())(pe.pos, pe.info, pe.errT))()) - v.decider.assume(totalTakenBounds, constraintExp, constraintExp, AssumptionType.Implicit) + v.decider.assume(totalTakenBounds, constraintExp, constraintExp, dependencyType.assumptionType) newFr = newFr.recordConstraint(totalTakenBounds) val s1 = s.copy(functionRecorder = newFr) - v.decider.assert(Implies(PermLess(NoPerm, perms), totalPermTaken !== NoPerm), assumptionType) { + v.decider.assert(Implies(PermLess(NoPerm, perms), totalPermTaken !== NoPerm), dependencyType.assertionType) { case true => val constraintExp = permsExp.map(pe => ast.EqCmp(pe, totalPermTakenExp.get)()) - v.decider.assume(perms === totalPermTaken, Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp)), AssumptionType.Implicit) + v.decider.assume(perms === totalPermTaken, Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp)), dependencyType.assumptionType) if (returnSnap) { summarise(s1, relevantChunks.toSeq, resource, args, argsExp, None, v)((s2, snap, _, _, v1) => Q(s2, updatedChunks, Some(snap), v1)) @@ -547,7 +548,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { relevantChunks foreach (chunk => { val instantiatedPermSum = permissionSum.replace(freeReceiver, chunk.args.head) val exp = permissionSumExp.map(pse => ast.PermLeCmp(replaceVarsInExp(pse, Seq(freeReceiverExp.name), Seq(chunk.argsExp.get.head)), ast.FullPerm()())()) - v.decider.assume(PermAtMost(instantiatedPermSum, FullPerm), exp, exp, AssumptionType.Implicit) + v.decider.assume(PermAtMost(instantiatedPermSum, FullPerm), exp, exp, AssumptionType.Internal) }) } } diff --git a/src/main/scala/rules/PredicateSupporter.scala b/src/main/scala/rules/PredicateSupporter.scala index 6edd0e10b..b0aebb1c6 100644 --- a/src/main/scala/rules/PredicateSupporter.scala +++ b/src/main/scala/rules/PredicateSupporter.scala @@ -80,7 +80,7 @@ object predicateSupporter extends PredicateSupportRules { val s1 = s.copy(g = gIns, smDomainNeeded = true) .scalePermissionFactor(tPerm, ePerm) - consume(s1, body, true, pve, v, dependencyType.assertionType)((s1a, snap, v1) => { + consume(s1, body, true, pve, v, dependencyType)((s1a, snap, v1) => { if (!Verifier.config.disableFunctionUnfoldTrigger()) { val predTrigger = App(s1a.predicateData(predicate.name).triggerFunction, snap.get.convert(terms.sorts.Snap) +: tArgs) @@ -99,13 +99,13 @@ object predicateSupporter extends PredicateSupportRules { }) } - def producePredicateContents(s: State, tree: PredicateContentsTree, toReplace: silicon.Map[Term, Term], v: Verifier, isUnfolding: Boolean = false) + def producePredicateContents(s: State, tree: PredicateContentsTree, toReplace: silicon.Map[Term, Term], v: Verifier, dependencyType: DependencyType, isUnfolding: Boolean = false) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { tree match { case PredicateLeafNode(h, assumptions) => val debugExp = Option.when(withExp)(DebugExp.createInstance("Assumption from unfolded predicate body")) - assumptions.foreach(a => v.decider.assume(a.replace(toReplace), debugExp, AssumptionType.Implicit)) // TODO ake + assumptions.foreach(a => v.decider.assume(a.replace(toReplace), debugExp, dependencyType.assumptionType)) val substChunks = h.values.map(_.substitute(toReplace).asInstanceOf[GeneralChunk].permScale(s.permissionScalingFactor, s.permissionScalingFactorExp)) // TODO ake val quantifiedResourceIdentifiers: Set[ChunkIdentifer] = s.qpPredicates.map(p => BasicChunkIdentifier(p.name)) ++ s.qpFields.map(f => BasicChunkIdentifier(f.name)) ++ s.qpMagicWands @@ -120,21 +120,21 @@ object predicateSupporter extends PredicateSupportRules { case _ => s.program.findPredicate(bc.id.name) } val (sm, smValueDef) = quantifiedChunkSupporter.singletonSnapshotMap(s, resource, bc.args, bc.snap, v) - v.decider.assumeDefinition(smValueDef, None, AssumptionType.Internal) + v.decider.assumeDefinition(smValueDef, None, dependencyType.assumptionType) val codQvars = bc.resourceID match { case FieldID => Seq(`?r`) case _ => s.predicateFormalVarMap(resource.asInstanceOf[ast.Predicate].name) } newFr = newFr.recordFvfAndDomain(SnapshotMapDefinition(resource, sm, Seq(smValueDef), Seq())) - quantifiedChunkSupporter.createSingletonQuantifiedChunk(codQvars, None, resource, bc.args, None, bc.perm, None, sm, s.program, v, AssumptionType.Rewrite, isExhale=false) // TODO ake + quantifiedChunkSupporter.createSingletonQuantifiedChunk(codQvars, None, resource, bc.args, None, bc.perm, None, sm, s.program, v, dependencyType.assumptionType, isExhale=false) case mwc: MagicWandChunk => val wand = mwc.id.ghostFreeWand val bodyVars = wand.subexpressionsToEvaluate(s.program) val codQvars = bodyVars.indices.toList.map(i => Var(Identifier(s"x$i"), v.symbolConverter.toSort(bodyVars(i).typ), false)) val (sm, smValueDef) = quantifiedChunkSupporter.singletonSnapshotMap(s, wand, mwc.args, mwc.snap, v) - v.decider.assumeDefinition(smValueDef, None, AssumptionType.Internal) + v.decider.assumeDefinition(smValueDef, None, dependencyType.assumptionType) newFr = newFr.recordFvfAndDomain(SnapshotMapDefinition(wand, sm, Seq(smValueDef), Seq())) - quantifiedChunkSupporter.createSingletonQuantifiedChunk(codQvars, None, wand, mwc.args, None, mwc.perm, None, sm, s.program, v, AssumptionType.Rewrite, isExhale=false) // TODO ake + quantifiedChunkSupporter.createSingletonQuantifiedChunk(codQvars, None, wand, mwc.args, None, mwc.perm, None, sm, s.program, v, dependencyType.assumptionType, isExhale=false) } } else { c @@ -150,12 +150,12 @@ object predicateSupporter extends PredicateSupportRules { if (!isUnfolding && s.moreJoins.id >= JoinMode.Impure.id) { joiner.join[scala.Null, scala.Null](s, v, resetState = false)((s1, v1, QB) => { - brancher.branch(s1, substCond, condExp, v1)( + brancher.branch(s1, substCond, condExp, v1, dependencyType.assumptionType)( (s2, v2) => { - producePredicateContents(s2, left, toReplace, v2, isUnfolding)((s3, v3) => QB(s3, null, v3)) + producePredicateContents(s2, left, toReplace, v2, dependencyType, isUnfolding)((s3, v3) => QB(s3, null, v3)) }, (s2, v2) => { - producePredicateContents(s2, right, toReplace, v2, isUnfolding)((s3, v3) => QB(s3, null, v3)) + producePredicateContents(s2, right, toReplace, v2, dependencyType, isUnfolding)((s3, v3) => QB(s3, null, v3)) } ) }) (entries => { @@ -170,12 +170,12 @@ object predicateSupporter extends PredicateSupportRules { (s2, null) }) ((sp, _, vp) => Q(sp, vp)) } else { - brancher.branch(s, substCond, condExp, v)( + brancher.branch(s, substCond, condExp, v, dependencyType.assumptionType)( (s1, v1) => { - producePredicateContents(s1, left, toReplace, v1, isUnfolding)(Q) + producePredicateContents(s1, left, toReplace, v1, dependencyType, isUnfolding)(Q) }, (s2, v2) => { - producePredicateContents(s2, right, toReplace, v2, isUnfolding)(Q) + producePredicateContents(s2, right, toReplace, v2, dependencyType, isUnfolding)(Q) } ) } @@ -204,12 +204,12 @@ object predicateSupporter extends PredicateSupportRules { val body = predicate.body.get /* Only non-abstract predicates can be unfolded */ val s1 = s.scalePermissionFactor(tPerm, ePerm) - v.heapSupporter.consumeSingle(s1, s1.h, pa, tArgs, eArgs, tPerm, ePerm, true, pve, v, dependencyType.assertionType)((s2, h2, snap, v1) => { + v.heapSupporter.consumeSingle(s1, s1.h, pa, tArgs, eArgs, tPerm, ePerm, true, pve, v, dependencyType)((s2, h2, snap, v1) => { val s3 = s2.copy(g = gIns, h = h2) .setConstrainable(constrainableWildcards, false) if (s3.predicateData(predicate.name).predContents.isDefined) { val toReplace: silicon.Map[Term, Term] = silicon.Map.from(s3.predicateData(predicate.name).params.get.zip(Seq(snap.get) ++ tArgs)) - producePredicateContents(s3, s3.predicateData(predicate.name).predContents.get, toReplace, v1, false)((s4, v4) => { + producePredicateContents(s3, s3.predicateData(predicate.name).predContents.get, toReplace, v1, dependencyType, false)((s4, v4) => { v4.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterUnfold) if (!Verifier.config.disableFunctionUnfoldTrigger()) { val predicateTrigger = diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index db1f896b5..c80f5487a 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -240,7 +240,7 @@ object producer extends ProductionRules { eval(s, e0, pve, v)((s1, t0, e0New, v1) => // The type arguments here are Null because there is no need to pass any join data. joiner.join[scala.Null, scala.Null](s1, v1, resetState = false)((s1, v1, QB) => - branch(s1.copy(parallelizeBranches = false), t0, (e0, e0New), v1)( + branch(s1.copy(parallelizeBranches = false), t0, (e0, e0New), v1, assumptionType)( (s2, v2) => produceR(s2.copy(parallelizeBranches = s1.parallelizeBranches), sf, a0, pve, v2, assumptionType)((s3, v3) => { v3.symbExLog.closeScope(uidImplies) QB(s3, null, v3) @@ -271,7 +271,7 @@ object producer extends ProductionRules { val uidImplies = v.symbExLog.openScope(impliesRecord) eval(s, e0, pve, v)((s1, t0, e0New, v1) => - branch(s1, t0, (e0, e0New), v1)( + branch(s1, t0, (e0, e0New), v1, assumptionType)( (s2, v2) => produceR(s2, sf, a0, pve, v2, assumptionType)((s3, v3) => { v3.symbExLog.closeScope(uidImplies) Q(s3, v3) @@ -293,7 +293,7 @@ object producer extends ProductionRules { eval(s, e0, pve, v)((s1, t0, e0New, v1) => // The type arguments here are Null because there is no need to pass any join data. joiner.join[scala.Null, scala.Null](s1, v1, resetState = false)((s1, v1, QB) => - branch(s1.copy(parallelizeBranches = false), t0, (e0, e0New), v1)( + branch(s1.copy(parallelizeBranches = false), t0, (e0, e0New), v1, assumptionType)( (s2, v2) => produceR(s2.copy(parallelizeBranches = s1.parallelizeBranches), sf, a1, pve, v2, assumptionType)((s3, v3) => { v3.symbExLog.closeScope(uidCondExp) QB(s3, null, v3) @@ -319,7 +319,7 @@ object producer extends ProductionRules { val uidCondExp = v.symbExLog.openScope(condExpRecord) eval(s, e0, pve, v)((s1, t0, e0New, v1) => - branch(s1, t0, (e0, e0New), v1)( + branch(s1, t0, (e0, e0New), v1, assumptionType)( (s2, v2) => produceR(s2, sf, a1, pve, v2, assumptionType)((s3, v3) => { v3.symbExLog.closeScope(uidCondExp) Q(s3, v3) diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 4e2461e1c..78cc5daea 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -9,7 +9,7 @@ package viper.silicon.rules import viper.silicon import viper.silicon.debugger.DebugExp import viper.silicon.Map -import viper.silicon.dependencyAnalysis.AssumptionType +import viper.silicon.dependencyAnalysis.{AssumptionType, DependencyType} import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp @@ -1147,7 +1147,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { notInjectiveReason: => ErrorReason, insufficientPermissionReason: => ErrorReason, v: Verifier, - assumptionType: AssumptionType=AssumptionType.Implicit) + dependencyType: DependencyType=DependencyType.Implicit) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -1206,7 +1206,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val nonNegTerm = Forall(qvars, Implies(FunctionPreconditionTransformer.transform(nonNegImplication, s.program), nonNegImplication), Nil) val nonNegExp = qvarExps.map(qv => ast.Forall(qv, Nil, nonNegImplicationExp.get)()) // TODO: Replace by QP-analogue of permissionSupporter.assertNotNegative - v.decider.assert(nonNegTerm, assumptionType) { + v.decider.assert(nonNegTerm, dependencyType.assertionType) { case true => val hints = quantifiedChunkSupporter.extractHints(Some(tCond), tArgs) val chunkOrderHeuristics = @@ -1242,7 +1242,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { program = s.program) v.decider.prover.comment("Check receiver injectivity") val completeReceiverInjectivityCheck = Implies(FunctionPreconditionTransformer.transform(receiverInjectivityCheck, s.program), receiverInjectivityCheck) - v.decider.assert(completeReceiverInjectivityCheck, assumptionType) { + v.decider.assert(completeReceiverInjectivityCheck, dependencyType.assertionType) { case true => val qvarsToInvOfLoc = inverseFunctions.qvarsToInversesOf(formalQVars) val condOfInvOfLoc = tCond.replace(qvarsToInvOfLoc) @@ -1256,8 +1256,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.prover.comment("Definitional axioms for inverse functions") v.decider.assume(inverseFunctions.definitionalAxioms.map(a => FunctionPreconditionTransformer.transform(a, s.program)), - Option.when(withExp)(DebugExp.createInstance("Inverse Function Axioms", isInternal_ = true)), enforceAssumption = false, assumptionType=assumptionType) - v.decider.assume(inverseFunctions.definitionalAxioms, Option.when(withExp)(DebugExp.createInstance("Inverse function axiom", isInternal_ = true)), enforceAssumption = false, assumptionType=assumptionType) + Option.when(withExp)(DebugExp.createInstance("Inverse Function Axioms", isInternal_ = true)), enforceAssumption = false, assumptionType=dependencyType.assumptionType) + v.decider.assume(inverseFunctions.definitionalAxioms, Option.when(withExp)(DebugExp.createInstance("Inverse function axiom", isInternal_ = true)), enforceAssumption = false, assumptionType=dependencyType.assumptionType) if (s.isUsedAsTrigger(resource)){ v.decider.assume( @@ -1265,7 +1265,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { formalQVars, Implies(condOfInvOfLoc, ResourceTriggerFunction(resource, smDef1.get.sm, formalQVars, s.program)), Trigger(inverseFunctions.inversesOf(formalQVars)))), - Option.when(withExp)(DebugExp.createInstance("Inverse Function", isInternal_ = true)), enforceAssumption = false, assumptionType=assumptionType) + Option.when(withExp)(DebugExp.createInstance("Inverse Function", isInternal_ = true)), enforceAssumption = false, assumptionType=dependencyType.assumptionType) } @@ -1296,7 +1296,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { rPermExp, chunkOrderHeuristics, v2, - assumptionType) + dependencyType) val optSmDomainDefinitionCondition2 = if (s3.smDomainNeeded) Some(And(condOfInvOfLoc, IsPositive(lossOfInvOfLoc), And(imagesOfFormalQVars))) else None @@ -1328,15 +1328,15 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { qid, v2, s.program, - assumptionType, + dependencyType.assertionType, isExhale = true ) val debugExp = Option.when(withExp)(DebugExp.createInstance("Inverse functions for quantified permission", isInternal_ = true)) - v.decider.assume(FunctionPreconditionTransformer.transform(inverseFunctions.axiomInvertiblesOfInverses, s3.program), debugExp, assumptionType) - v.decider.assume(inverseFunctions.axiomInvertiblesOfInverses, debugExp, assumptionType) + v.decider.assume(FunctionPreconditionTransformer.transform(inverseFunctions.axiomInvertiblesOfInverses, s3.program), debugExp, dependencyType.assumptionType) + v.decider.assume(inverseFunctions.axiomInvertiblesOfInverses, debugExp, dependencyType.assumptionType) val substitutedAxiomInversesOfInvertibles = inverseFunctions.axiomInversesOfInvertibles.replace(formalQVars, tArgs) - v.decider.assume(FunctionPreconditionTransformer.transform(substitutedAxiomInversesOfInvertibles, s3.program), debugExp, assumptionType) - v.decider.assume(substitutedAxiomInversesOfInvertibles, debugExp, assumptionType) + v.decider.assume(FunctionPreconditionTransformer.transform(substitutedAxiomInversesOfInvertibles, s3.program), debugExp, dependencyType.assumptionType) + v.decider.assume(substitutedAxiomInversesOfInvertibles, debugExp, dependencyType.assumptionType) val h2 = Heap(remainingChunks ++ otherChunks) val s4 = s3.copy(smCache = smCache2, constrainableARPs = s.constrainableARPs) @@ -1365,7 +1365,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { lossExp, chunkOrderHeuristics, v, - assumptionType + dependencyType ) permissionRemovalResult match { case (Complete(), s2, remainingChunks) => @@ -1412,7 +1412,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optChunkOrderHeuristic: Option[Seq[QuantifiedBasicChunk] => Seq[QuantifiedBasicChunk]], pve: PartialVerificationError, v: Verifier, - assumptionType: AssumptionType=AssumptionType.Implicit) + dependencyType: DependencyType=DependencyType.Implicit) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -1449,7 +1449,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { rPermExp, chunkOrderHeuristics, v, - assumptionType + dependencyType ) val h2 = Heap(remainingChunks ++ otherChunks) val (smDef1, smCache1) = @@ -1468,7 +1468,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { } val consumedChunk = quantifiedChunkSupporter.createSingletonQuantifiedChunk( - codomainQVars, codomainQVarsExp, resource, arguments, argumentsExp, permsTaken, permsTakenExp, smDef1.sm, s.program, v1, assumptionType, isExhale=true) + codomainQVars, codomainQVarsExp, resource, arguments, argumentsExp, permsTaken, permsTakenExp, smDef1.sm, s.program, v1, dependencyType.assertionType, isExhale=true) val s3 = s2.copy(functionRecorder = s2.functionRecorder.recordFvfAndDomain(smDef1), smCache = smCache1) (result, s3, h2, Some(consumedChunk)) @@ -1499,7 +1499,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { permissionsExp, chunkOrderHeuristics, v, - assumptionType + dependencyType ) result match { case (Complete(), s1, remainingChunks) => @@ -1579,7 +1579,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { permsExp: Option[ast.Exp], // p(rs) chunkOrderHeuristic: Seq[QuantifiedBasicChunk] => Seq[QuantifiedBasicChunk], v: Verifier, - assumptionType: AssumptionType=AssumptionType.Implicit) + dependencyType: DependencyType=DependencyType.Implicit) : (ConsumptionResult, State, Seq[QuantifiedBasicChunk]) = { val rmPermRecord = new CommentRecord("removePermissions", s, v.decider.pcs) @@ -1596,7 +1596,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val constrainPermissions = !consumeExactRead(perms, s.constrainableARPs) if (s.assertReadAccessOnly) { - val result = assertReadPermission(s, candidates, codomainQVars, condition, perms, permsExp, v, assumptionType) + val result = assertReadPermission(s, candidates, codomainQVars, condition, perms, permsExp, v, dependencyType.assertionType) return (result, s, relevantChunks) } @@ -1660,9 +1660,9 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { if (constrainPermissions) { v.decider.prover.comment(s"Constrain original permissions $perms") - v.decider.assume(permissionConstraint, permissionConstraintExp, permissionConstraintExp, assumptionType) + v.decider.assume(permissionConstraint, permissionConstraintExp, permissionConstraintExp, dependencyType.assumptionType) remainingChunks = - remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.getAnalysisInfo(assumptionType)) + remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.getAnalysisInfo(dependencyType.assumptionType)) } else { v.decider.prover.comment(s"Chunk depleted?") val chunkDepleted = v.decider.check(depletedCheck, Verifier.config.splitTimeout(), AssumptionType.Internal) @@ -1673,10 +1673,10 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { remainingChunks = remainingChunks :+ ithChunk } else { remainingChunks = - remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.getAnalysisInfo(assumptionType)) + remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.getAnalysisInfo(dependencyType.assumptionType)) } }else{ - val _ = GeneralChunk.withPerm(ithChunk, ithPTaken, None, v.decider.getAnalysisInfo(assumptionType), isExhale=true) + val _ = GeneralChunk.withPerm(ithChunk, ithPTaken, None, v.decider.getAnalysisInfo(dependencyType.assertionType), isExhale=true) } } @@ -1689,7 +1689,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { Forall(codomainQVars, Implies(condition, ithPNeeded === NoPerm), Nil) v.decider.prover.comment(s"Intermediate check if already taken enough permissions") - success = if (v.decider.check(tookEnoughCheck, Verifier.config.splitTimeout(), assumptionType)) { + success = if (v.decider.check(tookEnoughCheck, Verifier.config.splitTimeout(), dependencyType.assertionType)) { Complete() } else { Incomplete(ithPNeeded, ithPNeededExp) @@ -1699,7 +1699,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.prover.comment("Final check if taken enough permissions") success = - if (success.isComplete || v.decider.check(tookEnoughCheck, Verifier.config.assertTimeout.getOrElse(0), assumptionType) /* This check is a must-check, i.e. an assert */) + if (success.isComplete || v.decider.check(tookEnoughCheck, Verifier.config.assertTimeout.getOrElse(0), dependencyType.assertionType) /* This check is a must-check, i.e. an assert */) Complete() else success diff --git a/src/main/scala/rules/StateConsolidator.scala b/src/main/scala/rules/StateConsolidator.scala index f8203e56e..ba0d0e146 100644 --- a/src/main/scala/rules/StateConsolidator.scala +++ b/src/main/scala/rules/StateConsolidator.scala @@ -145,7 +145,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol newlyAddedChunks.filter(_.isInstanceOf[BasicChunk]) foreach { case ch: BasicChunk => val resource = Resources.resourceDescriptions(ch.resourceID) val pathCond = interpreter.buildPathConditionsForChunk(ch, resource.instanceProperties(s.mayAssumeUpperBounds)) - pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), AssumptionType.Implicit)) + pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), AssumptionType.Internal)) } v.symbExLog.closeScope(sepIdentifier) diff --git a/src/main/scala/supporters/Domains.scala b/src/main/scala/supporters/Domains.scala index 3d643753f..00036432f 100644 --- a/src/main/scala/supporters/Domains.scala +++ b/src/main/scala/supporters/Domains.scala @@ -106,7 +106,7 @@ class DefaultDomainsContributor(symbolConverter: SymbolConverter, val tAx = domainTranslator.translateAxiom(axiom, symbolConverter.toSort) val tAxPres = FunctionPreconditionTransformer.transform(tAx, program) val enableAnalysis = DependencyAnalyzer.extractEnableAnalysisFromInfo(axiom.info).getOrElse(isAnalysisForDomainEnabled) - collectedAxioms = collectedAxioms.incl((terms.And(tAxPres, tAx), Option.when(enableAnalysis)((AnalysisSourceInfo.createAnalysisSourceInfo(axiom.exp), AssumptionType.Explicit)))) + collectedAxioms = collectedAxioms.incl((terms.And(tAxPres, tAx), Option.when(enableAnalysis)((AnalysisSourceInfo.createAnalysisSourceInfo(axiom.exp), AssumptionType.DomainAxiom)))) }) }) } diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index 8ae8c9aea..bb9c961c8 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -9,7 +9,7 @@ package viper.silicon.supporters import com.typesafe.scalalogging.Logger import viper.silicon.Map import viper.silicon.decider.Decider -import viper.silicon.dependencyAnalysis.{AssumptionType, DependencyAnalyzer, DependencyGraphInterpreter} +import viper.silicon.dependencyAnalysis.{AssumptionType, DependencyAnalyzer, DependencyGraphInterpreter, DependencyType} import viper.silicon.interfaces._ import viper.silicon.logger.records.data.WellformednessCheckRecord import viper.silicon.rules.{consumer, executionFlowController, executor, producer} @@ -102,7 +102,7 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif if(method.body.isEmpty) v3.decider.removeDependencyAnalyzer() exec(s3, body, v3)((s4, v4) => { if(method.body.isEmpty) v3.decider.dependencyAnalyzer = da - consumes(s4, posts, false, postViolated, v4, postConditionType)((_, _, _) => + consumes(s4, posts, false, postViolated, v4, DependencyType.make(postConditionType))((_, _, _) => Success())})}) } )})}) if(method.body.isEmpty){ diff --git a/src/main/scala/supporters/PredicateVerificationUnit.scala b/src/main/scala/supporters/PredicateVerificationUnit.scala index 9fec4df17..85d886cf8 100644 --- a/src/main/scala/supporters/PredicateVerificationUnit.scala +++ b/src/main/scala/supporters/PredicateVerificationUnit.scala @@ -116,7 +116,7 @@ trait DefaultPredicateVerificationUnitProvider extends VerifierComponent { v: Ve var branchResults: Seq[(Seq[Term], Seq[(ast.Exp, Option[ast.Exp])], Heap, InsertionOrderedSet[Term])] = Seq() - val assumptionType = DependencyAnalyzer.extractAssumptionTypeFromInfo(predicate.info).getOrElse(AssumptionType.Explicit) + val assumptionType = DependencyAnalyzer.extractAssumptionTypeFromInfo(predicate.info).getOrElse(AssumptionType.Rewrite) val result = predicate.body match { case None => diff --git a/src/main/scala/supporters/functions/FunctionData.scala b/src/main/scala/supporters/functions/FunctionData.scala index 14712acb7..842500918 100644 --- a/src/main/scala/supporters/functions/FunctionData.scala +++ b/src/main/scala/supporters/functions/FunctionData.scala @@ -312,7 +312,7 @@ class FunctionData(val programFunction: ast.Function, val allTriggers = ( Seq(Trigger(functionApplication)) ++ actualPredicateTriggers) - (Forall(arguments, body, allTriggers), Option.when(isAnalysisEnabled)((AnalysisSourceInfo.createAnalysisSourceInfo(programFunction.body.get), AssumptionType.Implicit))) + (Forall(arguments, body, allTriggers), Option.when(isAnalysisEnabled)((AnalysisSourceInfo.createAnalysisSourceInfo(programFunction.body.get), AssumptionType.SourceCode))) }) } @@ -321,7 +321,7 @@ class FunctionData(val programFunction: ast.Function, val bodyPreconditions = if (programFunction.body.isDefined) optBody.map(translatedBody => { val body = Implies(pre, FunctionPreconditionTransformer.transform(translatedBody, program)) (Forall(arguments, body, Seq(Trigger(functionApplication))), - Option.when(isAnalysisEnabled)((StringAnalysisSourceInfo("bodyPreconditionPropagationAxiom", programFunction.pos), AssumptionType.Implicit))) + Option.when(isAnalysisEnabled)((StringAnalysisSourceInfo("bodyPreconditionPropagationAxiom", programFunction.pos), AssumptionType.SourceCode))) }) else None bodyPreconditions.toSeq } diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index b7a2a036e..e113d54a7 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -286,7 +286,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver case (intermediateResult, Phase1Data(sPre, bcsPre, bcsPreExp, pcsPre, pcsPreExp)) => intermediateResult && executionFlowController.locally(sPre, v)((s1, _) => { val labelledBcsPre = And(bcsPre map (t => v.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t)))) - decider.setCurrentBranchCondition(labelledBcsPre, (BigAnd(bcsPreExp.map(_._1)), Option.when(wExp)(BigAnd(bcsPreExp.map(_._2.get))))) + decider.setCurrentBranchCondition(labelledBcsPre, (BigAnd(bcsPreExp.map(_._1)), Option.when(wExp)(BigAnd(bcsPreExp.map(_._2.get)))), annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Precondition)) val labelledPcsPre = pcsPre map (t => v.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t))) decider.assume(labelledPcsPre, pcsPreExp, s"precondition of ${function.name}", enforceAssumption=false, assumptionType=annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Precondition)) v.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) @@ -299,7 +299,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver decider.analysisSourceInfoStack.setForcedSource(AnalysisSourceInfo.createAnalysisSourceInfo(body)) decider.assume(BuiltinEquals(data.formalResult, tBody), debugExp, AssumptionType.FunctionBody) decider.analysisSourceInfoStack.removeForcedSource() - consumes(s2, posts, false, postconditionViolated, v, postConditionType)((s3, _, _) => { + consumes(s2, posts, false, postconditionViolated, v, DependencyType.make(postConditionType))((s3, _, _) => { recorders :+= s3.functionRecorder Success()})})})} diff --git a/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr b/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr index 397a41030..fdb5f5647 100644 --- a/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr @@ -20,11 +20,11 @@ method aliasing1(x: Ref, n: Int) requires @irrelevant("Precondition")(n > 0) requires @irrelevant("Precondition")(x.f > n) { - @dependency("Implicit") + @dependency("SourceCode") x.f := n + 1 var y: Ref - @dependency("Implicit") + @dependency("SourceCode") y := x @testAssertion("Explicit") @@ -50,11 +50,11 @@ method alias1(a: Ref, b: Ref, c: Ref) { var x: Int if (@dependency("PathCondition")(c == a)) { - @dependency("Implicit") + @dependency("SourceCode") x := c.f } if(@dependency("PathCondition")(c == b)) { - @irrelevant("Implicit") + @irrelevant("SourceCode") x := c.f } diff --git a/src/test/resources/dependencyAnalysisTests/all/branches.vpr b/src/test/resources/dependencyAnalysisTests/all/branches.vpr index 3e4ce5c8a..c21e6a358 100644 --- a/src/test/resources/dependencyAnalysisTests/all/branches.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/branches.vpr @@ -15,10 +15,10 @@ method branches1(a: Int, b: Int) assume c ==> a > 5 if(@irrelevant("PathCondition")(c)){ - @dependency("Implicit") + @dependency("SourceCode") n := a + 1 }else{ - @dependency("Implicit") + @dependency("SourceCode") n := b + 1 } @@ -41,10 +41,10 @@ method branches2(a: Int, b: Int) var x: Int if(@dependency("PathCondition")(a >= n)){ - @irrelevant("Implicit") + @irrelevant("SourceCode") x := a + b }else{ - @dependency("Implicit") + @dependency("SourceCode") x := n + 1 @testAssertion("Explicit") assert x > 1 @@ -60,10 +60,10 @@ method branches3(a: Int, b: Int) var x: Int if(@dependency("PathCondition")(a >= n)){ - @dependency("Implicit") + @dependency("SourceCode") x := a + b }else{ - @dependency("Implicit") + @dependency("SourceCode") x := n + 1 } @@ -89,16 +89,16 @@ method nestedBranches1(a: Int, b: Int) if(@irrelevant("PathCondition")(c)){ if(@irrelevant("PathCondition")(a > b)){ - @dependency("Implicit") + @dependency("SourceCode") n := a - b }else{ - @dependency("Implicit") + @dependency("SourceCode") n := a + b } - @dependency("Implicit") + @dependency("SourceCode") n := n - 1 }else{ - @dependency("Implicit") + @dependency("SourceCode") n := a + b } diff --git a/src/test/resources/dependencyAnalysisTests/all/divBy0.vpr b/src/test/resources/dependencyAnalysisTests/all/divBy0.vpr index 155883ea3..bae24a324 100644 --- a/src/test/resources/dependencyAnalysisTests/all/divBy0.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/divBy0.vpr @@ -29,7 +29,7 @@ method basic() assume y > 0 @dependency("Explicit") assume y < x - @dependency("Implicit") + @dependency("SourceCode") x := x/y @testAssertion("Explicit") assert x >= 1 @@ -45,15 +45,15 @@ method sumClient(x: Int, y: Int) @dependency("Explicit") assume x < y var n: Int - @dependency("CallPostcondition") + @dependency("MethodCall") n := sum(x, y) // the following stmt reports dependency on n := sum(x, y) because (x < y && n == x + y && n > 100 ==> y != 0) // although you could also prove it via (0 <= x && x < y ==> y != 0) var n2: Int - @dependency("CallPostcondition") + @dependency("MethodCall") n2 := sum(x/y, y) - @dependency("Implicit") + @dependency("SourceCode") n := n + n2 @testAssertion("Explicit") diff --git a/src/test/resources/dependencyAnalysisTests/all/function-sum.vpr b/src/test/resources/dependencyAnalysisTests/all/function-sum.vpr index 88e943850..e5783be54 100644 --- a/src/test/resources/dependencyAnalysisTests/all/function-sum.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/function-sum.vpr @@ -20,7 +20,7 @@ method call2(){ @dependency("Explicit") assume y > 0 - @dependency("CallPostcondition") + @dependency("MethodCall") z := sum(x, y) // $PrecisionTest: $READ_ONLY=x,y,z $INVARIANT=x>0 @@ -36,7 +36,7 @@ method call2Unv(){ @dependency("Explicit") assume y > 0 - @dependency("CallPostcondition") + @dependency("MethodCall") z := sumUnverified(x, y) // $PrecisionTest: $READ_ONLY=x,y,z $INVARIANT=x>0 diff --git a/src/test/resources/dependencyAnalysisTests/all/function_vs_method.vpr b/src/test/resources/dependencyAnalysisTests/all/function_vs_method.vpr index 54599534d..768037a47 100644 --- a/src/test/resources/dependencyAnalysisTests/all/function_vs_method.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/function_vs_method.vpr @@ -19,10 +19,10 @@ method client_func(x: Ref, a: Int) requires @dependency("Precondition")(acc(x.f)) { var b: Int - @irrelevant("Implicit") + @irrelevant("SourceCode") b := add(x, a) + add(x, a) - @irrelevant("Implicit") + @irrelevant("SourceCode") x.f := 0 @testAssertion("Explicit") @@ -33,10 +33,10 @@ method client_meth(x: Ref, a: Int) requires @dependency("Precondition")(acc(x.f)) { var b: Int - @dependency("Implicit") + @dependency("MethodCall") b := add_M(x, a) - @irrelevant("Implicit") + @irrelevant("SourceCode") x.f := 0 @testAssertion("Explicit") diff --git a/src/test/resources/dependencyAnalysisTests/all/functions.vpr b/src/test/resources/dependencyAnalysisTests/all/functions.vpr index 16ab8dba8..efd65fb2a 100644 --- a/src/test/resources/dependencyAnalysisTests/all/functions.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/functions.vpr @@ -33,11 +33,11 @@ method predicateClient(x: Ref) requires acc(x.f) { var a: Int - @dependency("Implicit") + @dependency("SourceCode") x.f := 10 @dependency("Rewrite") fold greater0(x) - @dependency("CallPostcondition") + @dependency("MethodCall") a := foo(x) @testAssertion("Explicit") @@ -48,11 +48,11 @@ method predicateClientPostcond(x: Ref) requires acc(x.f) { var a: Int - @dependency("Implicit") + @dependency("SourceCode") x.f := 10 @dependency("Rewrite") fold greater0(x) - @dependency("CallPostcondition") + @dependency("MethodCall") a := fooPostcond(x) @testAssertion("Explicit") @@ -64,7 +64,7 @@ method callDiv(){ var x: Int @dependency("Explicit") assume x > 10 - @dependency("CallPostcondition") + @dependency("MethodCall") x := div100(x) @testAssertion("Explicit") @@ -75,7 +75,7 @@ method callDivPostcond(){ var x: Int @dependency("Explicit") assume x > 10 - @dependency("CallPostcondition") + @dependency("MethodCall") x := div100Postcond(x) @testAssertion("Explicit") @@ -90,7 +90,7 @@ function fooInput(a: Int): Int method client(a: Int) { var res: Int - @dependency("CallPostcondition") + @dependency("MethodCall") res := fooInput(a) @testAssertion("Explicit") diff --git a/src/test/resources/dependencyAnalysisTests/all/imprecision-fixed.vpr b/src/test/resources/dependencyAnalysisTests/all/imprecision-fixed.vpr index acf4a0de2..64f3f6ac3 100644 --- a/src/test/resources/dependencyAnalysisTests/all/imprecision-fixed.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/imprecision-fixed.vpr @@ -6,9 +6,9 @@ method permTest(a: Ref, b: Ref, n: Int) { @dependency("Explicit") assume n > 0 - @irrelevant("Implicit") // fixed imprecision (field assign) + @irrelevant("SourceCode") // fixed imprecision (field assign) a.f := b.f + 2 - @dependency("Implicit") + @dependency("SourceCode") a.f := n @testAssertion("Explicit") assert a.f >= 0 @@ -46,7 +46,7 @@ method quantifiedPerm5(xs: Seq[Ref], ys: Seq[Ref]) @irrelevant("Explicit") inhale forall y: Ref :: y in ys ==> acc(y.f) - @irrelevant("Implicit") // fixed imprecision (field assign with qp) + @irrelevant("SourceCode") // fixed imprecision (field assign with qp) xs[0].f := ys[0].f + 2 @testAssertion("Implicit") xs[0].f := 2 @@ -79,7 +79,7 @@ method fieldAccessPrecision(){ @irrelevant("Explicit") inhale acc(y.f) - @irrelevant("Implicit") + @irrelevant("SourceCode") y.f := y.f + 1 @testAssertion("Explicit") @@ -97,7 +97,7 @@ method fieldAccessPrecision2(){ @irrelevant("Explicit") inhale acc(y.f) - @irrelevant("Implicit") + @irrelevant("SourceCode") x.f := y.f + 1 @testAssertion("Explicit") diff --git a/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr b/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr index c0ecfc25a..f00c1e030 100644 --- a/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr @@ -46,7 +46,7 @@ method infeasible2(x: Ref) if(@irrelevant("PathCondition")(x == null)){ // unexpected dependency var a: Int - @dependency("Implicit") + @dependency("SourceCode") a := 0 @testAssertion("Explicit") @@ -63,7 +63,7 @@ method exhaleImprecision(){ @dependency("Explicit") inhale x.f > 0 - @dependency("Implicit") + @dependency("SourceCode") x.f := x.f + 1 @irrelevant("Implicit") // unexpected dependency @@ -148,7 +148,7 @@ method inhaleImprecision(){ @dependency("Explicit") inhale x.f > 0 - @irrelevant("Implicit") + @irrelevant("Explicit") inhale acc(x.f, 1/2) @testAssertion("Explicit") @@ -176,10 +176,10 @@ method quantifiedFieldAssign(){ @irrelevant("Explicit") inhale |xs| > 2 // imprecise - @irrelevant("Implicit") + @irrelevant("SourceCode") idx := 0 // imprecise inhale forall xi: Ref :: xi in xs ==> acc(xi.f) - @irrelevant("Implicit") + @irrelevant("SourceCode") xs[idx].f := a // internal dependency @testAssertion("Explicit") diff --git a/src/test/resources/dependencyAnalysisTests/all/infeasible.vpr b/src/test/resources/dependencyAnalysisTests/all/infeasible.vpr index 9a8714f31..afd90bd16 100644 --- a/src/test/resources/dependencyAnalysisTests/all/infeasible.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/infeasible.vpr @@ -41,7 +41,7 @@ method infeasibleBranchNoPerm(a: Int, b: Ref) { var res: Int if(@dependency("PathCondition")(a < 0)){ - @irrelevant("Implicit") + @irrelevant("SourceCode") res := b.f + 1 @testAssertion("Explicit") assert false diff --git a/src/test/resources/dependencyAnalysisTests/all/invariant_ordering_imprecise.vpr b/src/test/resources/dependencyAnalysisTests/all/invariant_ordering_imprecise.vpr index 81f40a18c..1399c06ae 100644 --- a/src/test/resources/dependencyAnalysisTests/all/invariant_ordering_imprecise.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/invariant_ordering_imprecise.vpr @@ -10,19 +10,19 @@ method loop1(){ var i: Int var res: Int - @dependency("Implicit") + @dependency("SourceCode") res := 0 - @irrelevant("Implicit") + @irrelevant("SourceCode") i := 10 while(@dependency("PathCondition")(i > 0)) invariant @irrelevant("LoopInvariant")(i <= 10) invariant @irrelevant("LoopInvariant")(i >= 0) invariant @dependency("LoopInvariant")(res >= 0) { - @dependency("Implicit") + @dependency("SourceCode") res := res + i - @irrelevant("Implicit") + @irrelevant("SourceCode") i := i - 1 } @testAssertion("Explicit") @@ -32,19 +32,19 @@ method loop1(){ method loop1_reordered(){ var i: Int var res: Int - @dependency("Implicit") + @dependency("SourceCode") res := 0 - @irrelevant("Implicit") + @irrelevant("SourceCode") i := 10 while(@dependency("PathCondition")(i > 0)) invariant @dependency("LoopInvariant")(res >= 0) invariant @irrelevant("LoopInvariant")(i <= 10) invariant @irrelevant("LoopInvariant")(i >= 0) { - @dependency("Implicit") + @dependency("SourceCode") res := res + i - @irrelevant("Implicit") + @irrelevant("SourceCode") i := i - 1 } @testAssertion("Explicit") diff --git a/src/test/resources/dependencyAnalysisTests/all/method-sum.vpr b/src/test/resources/dependencyAnalysisTests/all/method-sum.vpr index d253f4274..b5018cb43 100644 --- a/src/test/resources/dependencyAnalysisTests/all/method-sum.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/method-sum.vpr @@ -19,15 +19,15 @@ method sumClient(x: Int, y: Int) @dependency("Explicit") assume x < y var n: Int - @dependency("CallPostcondition") + @dependency("MethodCall") n := sum(x, y) // the following stmt reports dependency on n := sum(x, y) because (x < y && n == x + y && n > 100 ==> y != 0) // although you could also prove it via (0 <= x && x < y ==> y != 0) var n2: Int - @dependency("CallPostcondition") + @dependency("MethodCall") n2 := sum(x/y, y) - @dependency("Implicit") + @dependency("SourceCode") n := n + n2 @testAssertion("Explicit") @@ -43,13 +43,13 @@ method sumClient2(x: Int, y: Int) @irrelevant("Explicit") assume x < y var n: Int - @irrelevant("CallPostcondition") + @irrelevant("MethodCall") n := sum(x, x) @irrelevant() assert n >= 100 var n2: Int - @dependency("CallPostcondition") + @dependency("MethodCall") n2 := sum(y, y) @testAssertion("Explicit") assert n2 == 2*y diff --git a/src/test/resources/dependencyAnalysisTests/all/quantified-perm.vpr b/src/test/resources/dependencyAnalysisTests/all/quantified-perm.vpr index f33242e99..d711c0c37 100644 --- a/src/test/resources/dependencyAnalysisTests/all/quantified-perm.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/quantified-perm.vpr @@ -15,9 +15,9 @@ method quantifiedWritePerm(nodes: Set[Ref], x: Ref) { var y : Ref if(@dependency("PathCondition")(x.second != null)) { - @dependency("Implicit") + @dependency("SourceCode") y := x.second // permissions covered by preconditions - @dependency("Implicit") + @dependency("SourceCode") y.second := y @testAssertion("Explicit") assert x.second.second == x.second @@ -33,10 +33,10 @@ method quantifiedSum(nodes: Set[Ref], x: Ref) requires @dependency("Precondition")(x in nodes) { var a: Int - @dependency("Implicit") + @dependency("SourceCode") a := x.f if(@dependency("PathCondition")(x.first != null)) { - @dependency("Implicit") + @dependency("SourceCode") a := a + x.first.f } @testAssertion("Explicit") diff --git a/src/test/resources/dependencyAnalysisTests/all/quasihavoc.vpr b/src/test/resources/dependencyAnalysisTests/all/quasihavoc.vpr index 56ac9c3eb..5adda6930 100644 --- a/src/test/resources/dependencyAnalysisTests/all/quasihavoc.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/quasihavoc.vpr @@ -28,7 +28,7 @@ method quasihavoc3(x: Ref) { @irrelevant() quasihavoc x.f - @dependency("Implicit") + @dependency("SourceCode") x.f := 3 @testAssertion("Explicit") assert x.f == 3 diff --git a/src/test/resources/dependencyAnalysisTests/all/sequences.vpr b/src/test/resources/dependencyAnalysisTests/all/sequences.vpr index b133ba99f..7853b518d 100644 --- a/src/test/resources/dependencyAnalysisTests/all/sequences.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/sequences.vpr @@ -8,7 +8,7 @@ method sequenceUpdate(xs: Seq[Int], ys: Seq[Int]) returns (res: Seq[Int]) @irrelevant("Explicit") inhale forall y: Int :: y in ys ==> y >= 0 - @dependency("Implicit") + @dependency("SourceCode") res := xs[0 := 1] @testAssertion("Explicit") @@ -24,7 +24,7 @@ method sequence1(xs: Seq[Int], ys: Seq[Int]) returns (res: Seq[Int]) @irrelevant("Explicit") inhale forall y: Int :: y in ys ==> y >= 0 - @dependency("Implicit") + @dependency("SourceCode") res := xs[0 := 1] @testAssertion("Explicit") @@ -40,7 +40,7 @@ method sequence2(xs: Seq[Int], ys: Seq[Int]) returns (res: Seq[Int]) @dependency("Explicit") inhale forall y: Int :: y in ys ==> y >= 0 - @irrelevant("Implicit") + @irrelevant("SourceCode") res := xs[0 := 1] @testAssertion("Explicit") diff --git a/src/test/resources/dependencyAnalysisTests/all/sum-loops.vpr b/src/test/resources/dependencyAnalysisTests/all/sum-loops.vpr index 4f5f6a1a3..fab0707f9 100644 --- a/src/test/resources/dependencyAnalysisTests/all/sum-loops.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/sum-loops.vpr @@ -4,18 +4,18 @@ method sum(n: Int) returns (res: Int) requires @dependency("Precondition")(0 <= n) ensures res == n * (n + 1) / 2 { - @dependency("Implicit") + @dependency("SourceCode") res := 0 var i: Int - @dependency("Implicit") + @dependency("SourceCode") i := 0; while(@dependency("PathCondition")(i <= n)) invariant @dependency("LoopInvariant")(i <= (n + 1)) invariant @dependency("LoopInvariant")(res == (i - 1) * i / 2) { - @dependency("Implicit") + @dependency("SourceCode") res := res + i - @dependency("Implicit") + @dependency("SourceCode") i := i + 1 } @@ -30,19 +30,19 @@ method sumPerm1(n: Int, res: Ref) ensures acc(res.f) ensures res.f == n * (n + 1) / 2 { - @dependency("Implicit") + @dependency("SourceCode") res.f := 0 var i: Int - @dependency("Implicit") + @dependency("SourceCode") i := 0 while(@dependency("PathCondition")(i <= n)) invariant @dependency("LoopInvariant")(acc(res.f)) invariant @dependency("LoopInvariant")(i <= (n + 1)) invariant @dependency("LoopInvariant")(res.f == (i - 1) * i / 2) { - @dependency("Implicit") + @dependency("SourceCode") res.f := res.f + i - @dependency("Implicit") + @dependency("SourceCode") i := i + 1 } @@ -55,19 +55,19 @@ method sumPerm2(n: Int, res: Ref) requires @irrelevant("Precondition")(0 <= n) ensures acc(res.f) { - @irrelevant("Implicit") + @irrelevant("SourceCode") res.f := 0 var i: Int - @irrelevant("Implicit") + @irrelevant("SourceCode") i := 0; while(@irrelevant("PathCondition")(i <= n)) invariant @dependency("LoopInvariant")(acc(res.f)) invariant @irrelevant("LoopInvariant")(i <= (n + 1)) invariant @irrelevant("LoopInvariant")(res.f == (i - 1) * i / 2) { - @irrelevant("Implicit") + @irrelevant("SourceCode") res.f := res.f + i - @irrelevant("Implicit") + @irrelevant("SourceCode") i := i + 1 } diff --git a/src/test/resources/dependencyAnalysisTests/all/tuples.vpr b/src/test/resources/dependencyAnalysisTests/all/tuples.vpr index 115f341e4..028843ba8 100644 --- a/src/test/resources/dependencyAnalysisTests/all/tuples.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/tuples.vpr @@ -9,11 +9,11 @@ method setTuple(this: Ref, l: Int, r: Int) requires @dependency("Precondition")(tuple(this)) ensures tuple(this) { - @dependency("Implicit") + @dependency("Rewrite") unfold tuple(this) - @irrelevant("Implicit") + @irrelevant("SourceCode") this.left := l - @irrelevant("Implicit") + @irrelevant("SourceCode") this.right := r @testAssertion("Implicit") fold tuple(this) @@ -23,9 +23,9 @@ method addTuple(this: Ref) returns (sum: Int) requires @dependency("Precondition")(acc(tuple(this), 1/2)) ensures acc(tuple(this), 1/2) { - @dependency("Implicit") + @dependency("Rewrite") unfold acc(tuple(this), 1/2) - @irrelevant("Implicit") + @irrelevant("SourceCode") sum := this.left + this.right @testAssertion("Implicit") fold acc(tuple(this), 1/2) diff --git a/src/test/resources/dependencyAnalysisTests/all/unfolding.vpr b/src/test/resources/dependencyAnalysisTests/all/unfolding.vpr index 6dc918fb7..588add9c9 100644 --- a/src/test/resources/dependencyAnalysisTests/all/unfolding.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/unfolding.vpr @@ -26,10 +26,10 @@ method client1(x: Ref) @dependency("Rewrite") fold pred(x) - @dependency("Implicit") + @dependency("MethodCall") b := foo(x) - @irrelevant("Implicit") + @irrelevant("SourceCode") b := unfolding pred(x) in x.f @irrelevant() @@ -49,10 +49,10 @@ method client1(x: Ref) @dependency("Rewrite") fold pred(x) - @dependency("Implicit") + @dependency("SourceCode") b := foo(x) - @dependency("Implicit") + @dependency("SourceCode") b := unfolding pred(x) in x.f @testAssertion("Explicit") diff --git a/src/test/resources/dependencyAnalysisTests/real-world-examples/gaussian.vpr b/src/test/resources/dependencyAnalysisTests/real-world-examples/gaussian.vpr index 1860359f1..177a9e277 100644 --- a/src/test/resources/dependencyAnalysisTests/real-world-examples/gaussian.vpr +++ b/src/test/resources/dependencyAnalysisTests/real-world-examples/gaussian.vpr @@ -11,9 +11,9 @@ method gaussianSimple(n: Int) returns (res: Int) invariant @irrelevant("LoopInvariant")(i <= 6) invariant @dependency("LoopInvariant")(res == (i - 1) * i / 2) { - @dependency("Implicit") + @dependency("SourceCode") res := res + i - @dependency("Implicit") + @dependency("SourceCode") i := i + 1 } @@ -37,9 +37,9 @@ method gaussianPerm(a: Ref, p: Perm) returns (res: Int) invariant i <= 6 invariant @dependency("LoopInvariant")(res == (i - 1) * i / 2) { - @dependency("Implicit") + @dependency("SourceCode") res := res + i - @dependency("Implicit") + @dependency("SourceCode") i := i + 1 } @@ -57,22 +57,22 @@ method gaussianPred(n: Int) returns (res: Int) { res := 0 var i: Int - @dependency("Implicit") + @dependency("SourceCode") i := 0 - @dependency("Implicit") + @dependency("Rewrite") fold gaussianEq(res, i) while(i <= n) invariant i <= (n + 1) invariant i <= 6 invariant @dependency("LoopInvariant")(gaussianEq(res, i)) { - @dependency("Implicit") + @dependency("Rewrite") unfold gaussianEq(res, i) - @dependency("Implicit") + @dependency("SourceCode") res := res + i - @dependency("Implicit") + @dependency("SourceCode") i := i + 1 - @dependency("Implicit") + @dependency("Rewrite") fold gaussianEq(res, i) } assert i == n+1 diff --git a/src/test/resources/dependencyAnalysisTests/real-world-examples/listAppend.vpr b/src/test/resources/dependencyAnalysisTests/real-world-examples/listAppend.vpr index c46f3a566..eee87ac15 100644 --- a/src/test/resources/dependencyAnalysisTests/real-world-examples/listAppend.vpr +++ b/src/test/resources/dependencyAnalysisTests/real-world-examples/listAppend.vpr @@ -18,7 +18,7 @@ method appendList1(this: Ref, e: Int) requires @irrelevant("Precondition")(0 <= e && e < 100) ensures list(this) { - @irrelevant("Implicit") + @irrelevant("Rewrite") unfold list(this) @irrelevant("Explicit") assume 0 <= this.elem && this.elem < 100 @@ -26,13 +26,13 @@ method appendList1(this: Ref, e: Int) if (@irrelevant("PathCondition")(this.next == null)) { var n: Ref - @dependency("Implicit") + @dependency("SourceCode") n := new(elem, next) - @irrelevant("Implicit") + @irrelevant("SourceCode") n.elem := e - @dependency("Implicit") + @dependency("SourceCode") n.next := null - @irrelevant("Implicit") + @irrelevant("SourceCode") this.next := n @testAssertion("Implicit") fold list(n) @@ -48,7 +48,7 @@ method appendList2(this: Ref, e: Int) requires @dependency("Precondition")(0 <= e && e < 100) ensures list(this) { - @dependency("Implicit") + @dependency("Rewrite") unfold list(this) @irrelevant("Explicit") assume 0 <= this.elem && this.elem < 100 @@ -56,15 +56,15 @@ method appendList2(this: Ref, e: Int) if (@dependency("PathCondition")(this.next == null)) { var n: Ref - @irrelevant("Implicit") + @irrelevant("SourceCode") n := new(elem, next) - @irrelevant("Implicit") + @irrelevant("SourceCode") n.elem := e - @irrelevant("Implicit") + @irrelevant("SourceCode") n.next := null - @irrelevant("Implicit") + @irrelevant("SourceCode") this.next := n - @irrelevant("Implicit") + @irrelevant("Rewrite") fold list(n) } else { @testAssertion("Implicit") @@ -79,7 +79,7 @@ method appendListFull(this: Ref, e: Int) requires @dependency("Precondition")(0 <= e && e < 100) ensures list(this) { - @dependency("Implicit") + @dependency("Rewrite") unfold list(this) @irrelevant("Explicit") assume 0 <= this.elem && this.elem < 100 @@ -87,18 +87,18 @@ method appendListFull(this: Ref, e: Int) if (@dependency("PathCondition")(this.next == null)) { var n: Ref - @dependency("Implicit") + @dependency("SourceCode") n := new(elem, next) - @irrelevant("Implicit") + @irrelevant("SourceCode") n.elem := e - @dependency("Implicit") + @dependency("SourceCode") n.next := null - @dependency("Implicit") + @dependency("SourceCode") this.next := n - @dependency("Implicit") + @dependency("Rewrite") fold list(n) } else { - @dependency("Implicit") + @dependency("MethodCall") appendListFull(this.next, e) } diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/B-permissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/B-permissions.vpr index 6aa629f8d..8496aab60 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/B-permissions.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/B-permissions.vpr @@ -11,7 +11,7 @@ method currentPerm(x: Ref) method perm1(){ var x: Ref - @dependency("Implicit") + @dependency("SourceCode") x := new(f) // $PrecisionTest: $READ_WRITE=x.f $ACC_INVARIANT=acc(x.f) @@ -50,7 +50,7 @@ method perm4(){ @dependency("Explicit") inhale x.f > 0 - @dependency("Implicit") + @dependency("SourceCode") x.f := x.f + 1 // $PrecisionTest: $READ_ONLY=x.f $INVARIANT=x.f>0 $ACC_INVARIANT=acc(x.f,1/4) @@ -63,7 +63,7 @@ method perm5(){ var x: Ref @dependency("Explicit") inhale acc(x.f) - @irrelevant("Implicit") + @irrelevant("SourceCode") x.f := x.f + 1 // $PrecisionTest: $READ_WRITE=x.f $ACC_INVARIANT=acc(x.f) diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/D-quantifiedPermissions.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/D-quantifiedPermissions.vpr index 9844d6545..52ba571c1 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/D-quantifiedPermissions.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/D-quantifiedPermissions.vpr @@ -7,7 +7,7 @@ method quantifiedPerm1(xs: Seq[Ref]) { inhale xs[0] != xs[1] var i: Int - @irrelevant("Implicit") + @irrelevant("SourceCode") i := 0 // $PrecisionTest: $READ_WRITE=xs[i].f,xs[1].f $INVARIANT=|xs|>0 $ACC_INVARIANT=forall$_$x:Ref::x$_$in$_$xs$_$==>$_$acc(x.f) @@ -47,7 +47,7 @@ method quantifiedExhalePartiallyTest(xs: Seq[Ref]) { inhale xs[0] != xs[4] var i: Int - @irrelevant("Implicit") + @irrelevant("SourceCode") i := 0 // $PrecisionTest: $READ_WRITE=xs[i].f,xs[4].f $INVARIANT=|xs|>0 $ACC_INVARIANT=forall$_$x:Ref::x$_$in$_$xs$_$==>$_$acc(x.f) @@ -63,7 +63,7 @@ method quantifiedExhalePartially(xs: Seq[Ref]) { exhale forall x: Ref :: x in xs ==> acc(x.f, 1/2) var i: Int - @irrelevant("Implicit") + @irrelevant("SourceCode") i := 1 // $PrecisionTest: $READ_WRITE=res $READ_ONLY=xs[i].f,xs[0].f,xs[2].f $INVARIANT=|xs|>0 $ACC_INVARIANT=forall$_$x:Ref::x$_$in$_$xs$_$==>$_$acc(x.f,1/4) @@ -104,7 +104,7 @@ method quantifiedPermWrite2(xs: Seq[Ref]) { @dependency("Explicit") inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) - @dependency("Implicit") + @dependency("SourceCode") xs[0].f := 0 inhale xs[0] != xs[2] @@ -121,7 +121,7 @@ method quantifiedPermWrite3(xs: Seq[Ref]) { @dependency("Explicit") inhale forall x: Ref :: x in xs ==> (acc(x.f) && x.f > 0) - @irrelevant("Implicit") + @irrelevant("SourceCode") xs[0].f := 0 // $PrecisionTest: $READ_WRITE=xs[0].f $READ_ONLY=xs[1].f,xs[2].f $INVARIANT=xs[1].f>=0 $ACC_INVARIANT=|xs|>1&&acc(xs[0].f,1/2)&&(forall$_$x:Ref::x$_$in$_$xs$_$==>$_$acc(x.f,1/4)) diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/E-function-call.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/E-function-call.vpr index dbff5e6c3..6488774c1 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/E-function-call.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/E-function-call.vpr @@ -32,7 +32,7 @@ method call2(){ @dependency("Explicit") assume y > 0 - @dependency("CallPostcondition") + @dependency("MethodCall") z := sum(x, y) // $PrecisionTest: $READ_ONLY=x,y,z $INVARIANT=x>0 @@ -49,14 +49,14 @@ method call3(x: Int, y: Int) assume y > 0 var n: Int, n2: Int - @dependency("CallPostcondition") + @dependency("MethodCall") n := sum(x, y) // $PrecisionTest: $READ_WRITE=n2 $READ_ONLY=x,y,n $INVARIANT=x>0 - @dependency("Implicit") + @dependency("SourceCode") n2 := sum(x, y) - @dependency("Implicit") + @dependency("SourceCode") n := n + n2 @testAssertion("Explicit") @@ -75,12 +75,12 @@ method call4(x: Int, y: Int, z: Int) assume z > 0 var n: Int, n2: Int - @dependency("CallPostcondition") + @dependency("MethodCall") n := sumUnverified(x, y) // $PrecisionTest: $READ_WRITE=n2 $READ_ONLY=n,z,x,y $INVARIANT=x>0 - @irrelevant("CallPostcondition") + @irrelevant("MethodCall") n2 := sumUnverified(x, z) @testAssertion("Explicit") diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/F-method-call.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/F-method-call.vpr index d31fd81f2..e7358e062 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/F-method-call.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/F-method-call.vpr @@ -31,7 +31,7 @@ method call1(){ // $PrecisionTest: $READ_ONLY=x,y $INVARIANT=y>-5 - @testAssertion("CallPostcondition") + @testAssertion("Implicit") x := sum(x, y) } @@ -42,7 +42,7 @@ method call2(){ @dependency("Explicit") assume y > 0 - @dependency("CallPostcondition") + @dependency("MethodCall") z := sum(x, y) // $PrecisionTest: $READ_ONLY=x,y,z $INVARIANT=x>0 @@ -59,14 +59,14 @@ method call3(x: Int, y: Int) assume y > 0 var n: Int, n2: Int - @dependency("CallPostcondition") + @dependency("MethodCall") n := sumUnverified(x, y) // $PrecisionTest: $READ_WRITE=n2 $READ_ONLY=x,y,n $INVARIANT=x>0 - @dependency("CallPostcondition") + @dependency("MethodCall") n2 := sumUnverified(x, y) - @dependency("Implicit") + @dependency("SourceCode") n := n + n2 @testAssertion("Explicit") @@ -85,12 +85,12 @@ method call4(x: Int, y: Int, z: Int) assume z > 0 var n: Int, n2: Int - @dependency("CallPostcondition") + @dependency("MethodCall") n := sum(x, y) // $PrecisionTest: $READ_WRITE=n2 $READ_ONLY=n,z,x,y $INVARIANT=x>0 - @irrelevant("CallPostcondition") + @irrelevant("MethodCall") n2 := sum(x, z) @testAssertion("Explicit") @@ -101,7 +101,7 @@ method absClient1(x: Ref) requires @dependency("Precondition")(acc(x.f)) requires @irrelevant("Precondition")(x.f > 0) { - @dependency("CallPostcondition") + @dependency("MethodCall") abs(x) @testAssertion("Explicit") diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/G-predicates.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/G-predicates.vpr index 1e732f1a2..66e632ea0 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/G-predicates.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/G-predicates.vpr @@ -12,7 +12,7 @@ method foldP(n: Int) requires @dependency("Precondition")(n > 10) { var x: Int - @dependency("Implicit") + @dependency("SourceCode") x := 1 // $PrecisionTest: $READ_ONLY=n,x $INVARIANT=n>0 @@ -25,11 +25,11 @@ method unfoldP(n: Int) requires @dependency("Precondition")(greater0(n)) { var x: Int - @dependency("Implicit") + @dependency("SourceCode") x := 1 - @dependency("Implicit") + @dependency("Rewrite") unfold greater0(n) - @dependency("Implicit") + @dependency("SourceCode") x := n + x // $PrecisionTest: $READ_ONLY=x,n $INVARIANT=x>n @@ -42,9 +42,9 @@ method unfoldFoldP(a: Int, b: Int) requires @dependency("Precondition")(greater0(a)) && @dependency("Precondition")(greater5(b)) ensures greater5(a + b) { - @dependency("Implicit") + @dependency("Rewrite") unfold greater0(a) - @dependency("Implicit") + @dependency("Rewrite") unfold greater5(b) // $PrecisionTest: $READ_ONLY=a,b $INVARIANT=b>0 @@ -57,7 +57,7 @@ method callWithPredicate(a: Int, b: Int) requires @dependency("Precondition")(greater0(a)) && @dependency("Precondition")(greater5(b)) ensures greater5(a + b) { - @dependency("Implicit") + @dependency("MethodCall") unfoldFoldP(a, b) // $PrecisionTest: $READ_ONLY=a,b $INVARIANT=(unfolding$_$greater5(a+b)$_$in$_$a+b>0) $ACC_INVARIANT=greater5(a+b) @@ -74,9 +74,9 @@ method unfoldPrecision(a: Int, b: Int) // $PrecisionTest: $READ_ONLY=a,b $INVARIANT=(unfolding$_$greater0(a)$_$in$_$a>0) $ACC_INVARIANT=greater0(a) - @irrelevant("Implicit") + @irrelevant("Rewrite") unfold greater0(a) - @dependency("Implicit") + @dependency("Rewrite") unfold greater5(b) @testAssertion("Implicit") @@ -113,7 +113,7 @@ method unfoldWithImpl(a: Int, x: Ref) @dependency("Rewrite") unfold implPred(a, x) if(@dependency("PathCondition")(a > 5)){ - @irrelevant("Implicit") + @irrelevant("SourceCode") x.f := a // $PrecisionTest: $READ_ONLY=a $READ_WRITE=x.f $INVARIANT=a>=0 $ACC_INVARIANT=acc(x.f) @@ -133,7 +133,7 @@ method unfoldingWithImpl(a: Int, x: Ref) @testAssertion("Implicit") res := unfolding implPred(a, x) in x.f }else{ - @irrelevant("Implicit") + @irrelevant("SourceCode") res := a } } diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/H-magicWands.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/H-magicWands.vpr index e9647b9e0..23fc2fa03 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/H-magicWands.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/H-magicWands.vpr @@ -32,10 +32,10 @@ method basicPackage(l: Ref) requires @dependency("Precondition")(list(l)) ensures list(l) { - @dependency("Implicit") + @dependency("Rewrite") unfold list(l) var tmp : Ref - @dependency("Implicit") + @dependency("SourceCode") tmp := l.next // $PrecisionTest: $READ_WRITE=l.val $ACC_INVARIANT=acc(l.val)&&acc(l.next, 1/2) @@ -65,11 +65,11 @@ method advancedPackage(a: Int, b: Int) @dependency("Rewrite") package (greater0(a) && greater0(b)) --* greater0(a + b) { - @dependency("Implicit") + @dependency("Rewrite") unfold greater0(a) - @dependency("Implicit") + @dependency("Rewrite") unfold greater0(b) - @dependency("Implicit") + @dependency("Rewrite") fold greater0(a + b) } @@ -108,7 +108,7 @@ method packagePrecision(l: Ref) @dependency("Rewrite") unfold list(l) var tmp : Ref - @dependency("Implicit") + @dependency("SourceCode") tmp := l.next // $PrecisionTest: $READ_WRITE=l.val $ACC_INVARIANT=acc(l.val) @@ -128,7 +128,7 @@ method packageExhale(x: Ref) requires @dependency("Precondition")(acc(x.f)) { - @dependency("Implicit") + @dependency("Rewrite") package acc(x.f, 1/2) --* acc(x.f) // $PrecisionTest: $READ_ONLY=x.f $ACC_INVARIANT=acc(x.f,1/4) diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/I-branches.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/I-branches.vpr index 997949032..0706a9ecf 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/I-branches.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/I-branches.vpr @@ -4,10 +4,10 @@ method branch1(){ var x: Int, y: Int if(@dependency("PathCondition")(x > 0)){ - @dependency("Implicit") + @dependency("SourceCode") y := x + 1 }else{ - @dependency("Implicit") + @dependency("SourceCode") y := -x + 1 // $PrecisionTest: $READ_WRITE=x $READ_ONLY=y $INVARIANT=y>-1 @@ -40,10 +40,10 @@ method branch3(){ // $PrecisionTest: $READ_WRITE=y $READ_ONLY=x if(@dependency("PathCondition")(x > 10)){ - @dependency("Implicit") + @dependency("SourceCode") y := x + 1 }else{ - @irrelevant("Implicit") + @irrelevant("SourceCode") y := 10 @irrelevant("Explicit") assume y > 0 @@ -57,11 +57,11 @@ method branch4(){ var x: Int, y: Int if(@dependency("PathCondition")(x > 0)){ - @irrelevant("Implicit") + @irrelevant("SourceCode") y := x + 1 // $PrecisionTest: $READ_WRITE=y $READ_ONLY=x $INVARIANT=x>0 }else{ - @dependency("Implicit") + @dependency("SourceCode") y := 10 - x @testAssertion("Explicit") assert y > 0 @@ -76,7 +76,7 @@ method branch5(){ // $PrecisionTest: $READ_ONLY=x,y $INVARIANT=y>0 if(@irrelevant("PathCondition")(x > 0)){ - @irrelevant("Implicit") + @irrelevant("SourceCode") y := x + 1 }else{ @testAssertion("Explicit") diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/J-loops.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/J-loops.vpr index 24097a3e6..d91f941b6 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/J-loops.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/J-loops.vpr @@ -3,18 +3,18 @@ field f:Int method loop1(){ var i: Int var res: Int - @dependency("Implicit") + @dependency("SourceCode") res := 0 - // @irrelevant("Implicit") + // @irrelevant("SourceCode") i := 10 while(@dependency("PathCondition")(i > 0)) invariant @irrelevant("LoopInvariant")(i <= 10) invariant @irrelevant("LoopInvariant")(i >= 0) invariant @dependency("LoopInvariant")(res >= 0) { - @dependency("Implicit") + @dependency("SourceCode") res := res + i - // @irrelevant("Implicit") + // @irrelevant("SourceCode") i := i - 1 } @@ -27,19 +27,19 @@ method loop1(){ method loop2(){ var i: Int var res: Int - @irrelevant("Implicit") + @irrelevant("SourceCode") res := 0 - @irrelevant("Implicit") + @irrelevant("SourceCode") i := 10 while(@dependency("PathCondition")(i > 0)) invariant @irrelevant("LoopInvariant")(i <= 10) invariant @irrelevant("LoopInvariant")(i >= 0) invariant @irrelevant("LoopInvariant")(res >= 0) { - @irrelevant("Implicit") + @irrelevant("SourceCode") res := res + i // $PrecisionTest: $READ_ONLY=res,i $INVARIANT=i<20 - @dependency("Implicit") + @dependency("SourceCode") i := i - 1 @testAssertion("Explicit") assert i >= 0 @@ -49,19 +49,19 @@ method loop2(){ method loop3(){ var i: Int var res: Int - @dependency("Implicit") + @dependency("SourceCode") res := 0 // $PrecisionTest: $READ_ONLY=res $READ_WRITE=i $INVARIANT=res<10 - @irrelevant("Implicit") + @irrelevant("SourceCode") i := 10 while(@dependency("PathCondition")(i > 0)) invariant @irrelevant("LoopInvariant")(i <= 10) invariant @irrelevant("LoopInvariant")(i >= 0) invariant @dependency("LoopInvariant")(res >= 0) { - @dependency("Implicit") + @dependency("SourceCode") res := res + i - @irrelevant("Implicit") + @irrelevant("SourceCode") i := i - 1 @testAssertion("Explicit") assert res > 0 @@ -74,18 +74,18 @@ method loop4(){ // $PrecisionTest: $READ_WRITE=i,res - @dependency("Implicit") + @dependency("SourceCode") res := 0 - @irrelevant("Implicit") + @irrelevant("SourceCode") i := 10 while(@irrelevant("PathCondition")(i > 0)) invariant @irrelevant("LoopInvariant")(i <= 10) invariant @irrelevant("LoopInvariant")(i >= 0) invariant @dependency("LoopInvariant")(res >= 0) { - @irrelevant("Implicit") + @irrelevant("SourceCode") res := res - 1 - @irrelevant("Implicit") + @irrelevant("SourceCode") i := i - 1 @dependency("Explicit") assume res > 0 diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/K-domain.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/K-domain.vpr index 20c8c9c76..a3aaa9cd8 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/K-domain.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/K-domain.vpr @@ -58,7 +58,7 @@ method domain3() inhale access(a) var i: Int - @dependency("Implicit") + @dependency("SourceCode") i := 0 while (@dependency("PathCondition")(i < len(a))) invariant @dependency("LoopInvariant")(access(a)) @@ -68,7 +68,7 @@ method domain3() // $PrecisionTest: $READ_ONLY=i $READ_WRITE=slot(a;i).val $INVARIANT=-1 Date: Tue, 27 Jan 2026 15:16:22 +0100 Subject: [PATCH 326/474] dependency node equality workaround --- .../dependencyAnalysis/DependencyAnalysisNode.scala | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala index ffc77c088..318496e18 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala @@ -52,8 +52,15 @@ trait DependencyAnalysisNode extends AbstractDependencyAnalysisNode { def getNodeString: String def getNodeType: String + // TODO ake: remove workaround override def hashCode(): Int = - toString.hashCode + id.hashCode() + + // TODO ake: remove workaround + override def equals(obj: Any): Boolean = obj match { + case node: DependencyAnalysisNode => node.id == this.id + case _ => false + } } trait GeneralAssumptionNode extends DependencyAnalysisNode { From 1cef81abecf6bc922fe415bf555f87f16cbf9b62 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 27 Jan 2026 15:37:29 +0100 Subject: [PATCH 327/474] fix assumption types --- src/main/scala/decider/Decider.scala | 8 ++++---- .../DependencyAnalysisNode.scala | 3 +-- .../DependencyAnalyzer.scala | 20 +++++++++---------- src/main/scala/rules/Brancher.scala | 8 ++++---- src/main/scala/rules/ChunkSupporter.scala | 10 +++++----- src/main/scala/rules/Consumer.scala | 12 +++++------ src/main/scala/rules/Evaluator.scala | 17 ++++++++-------- src/main/scala/rules/Executor.scala | 5 +---- src/main/scala/rules/HeapSupporter.scala | 7 ++++--- src/main/scala/rules/Joiner.scala | 4 ++-- .../rules/MoreCompleteExhaleSupporter.scala | 4 ++-- src/main/scala/rules/Producer.scala | 8 ++++---- .../scala/rules/QuantifiedChunkSupport.scala | 10 +++++----- src/main/scala/rules/StateConsolidator.scala | 2 +- .../supporters/functions/FunctionData.scala | 2 +- 15 files changed, 57 insertions(+), 63 deletions(-) diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 1c892ac85..7df128532 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -75,7 +75,7 @@ trait Decider { def assumeLabel(term: Term, assumptionLabel: String): Unit def check(t: Term, timeout: Int, assumptionType: AssumptionType=AssumptionType.Implicit): Boolean - def checkAndGetInfeasibilityNode(t: Term, timeout: Int, assumptionType: AssumptionType=AssumptionType.Implicit): (Boolean, Option[Int]) + def checkAndGetInfeasibilityNode(t: Term, timeout: Int, assumptionType: AssumptionType): (Boolean, Option[Int]) /* TODO: Consider changing assert such that * 1. It passes State and Operations to the continuation @@ -485,7 +485,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => }else if(result){ checkNode foreach dependencyAnalyzer.addNode dependencyAnalyzer.processUnsatCoreAndAddDependencies(prover.getLastUnsatCore, label) - val infeasibleNodeId = dependencyAnalyzer.addInfeasibilityNode(!isAssert, analysisSourceInfoStack.getFullSourceInfo) + val infeasibleNodeId = dependencyAnalyzer.addInfeasibilityNode(!isAssert, analysisSourceInfoStack.getFullSourceInfo, assumptionType) dependencyAnalyzer.addDependency(checkNode.map(_.id), infeasibleNodeId) pcs.setCurrentInfeasibilityNode(checkNode.map(_.id)) } @@ -496,14 +496,14 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => deciderAssert(t, assumptionType, Some(timeout), isCheck=true)._1 } - def checkAndGetInfeasibilityNode(t: Term, timeout: Int, assumptionType: AssumptionType=AssumptionType.Implicit): (Boolean, Option[Int]) = { + def checkAndGetInfeasibilityNode(t: Term, timeout: Int, assumptionType: AssumptionType): (Boolean, Option[Int]) = { var infeasibilityNodeId: Option[Int] = pcs.getCurrentInfeasibilityNode if(infeasibilityNodeId.isDefined){ return (true, infeasibilityNodeId) } val (success, checkNode) = deciderAssert(t, assumptionType, Some(timeout), isCheck=true) if(success){ - infeasibilityNodeId = dependencyAnalyzer.addInfeasibilityNode(isCheck = true, analysisSourceInfoStack.getFullSourceInfo) + infeasibilityNodeId = dependencyAnalyzer.addInfeasibilityNode(isCheck = true, analysisSourceInfoStack.getFullSourceInfo, assumptionType) dependencyAnalyzer.addDependency(checkNode.map(_.id), infeasibilityNodeId) } (success, infeasibilityNodeId) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala index 318496e18..dc330b97f 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala @@ -132,9 +132,8 @@ case class LabelNode(term: Var) extends GeneralAssumptionNode { * Infeasibility nodes allow to distinguish between dependencies coming from the fact that the assertion is not reachable * on a given path and dependencies used to prove the assertion on feasible paths. */ -case class InfeasibilityNode(sourceInfo: AnalysisSourceInfo) extends GeneralAssumptionNode { +case class InfeasibilityNode(sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType) extends GeneralAssumptionNode { val term: Term = False - val assumptionType: AssumptionType = AssumptionType.Implicit // TODO ake: assumption type? val isClosed: Boolean = true val description: String = "False" diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index af226c1d1..04e41915a 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -38,7 +38,7 @@ trait DependencyAnalyzer { def createAssertOrCheckNode(term: Term, assumptionType: AssumptionType, analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] def addAssertFalseNode(isCheck: Boolean, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo): Option[Int] - def addInfeasibilityNode(isCheck: Boolean, sourceInfo: AnalysisSourceInfo): Option[Int] + def addInfeasibilityNode(isCheck: Boolean, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] def addDependency(source: Option[Int], dest: Option[Int]): Unit def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit @@ -59,7 +59,7 @@ trait DependencyAnalyzer { /** * Adds an assertion and assumption node with the given analysis source info and dependencies to the current infeasibility node. */ - def addInfeasibilityDepToStmt(infeasNodeId: Option[Int], analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Unit = {} + def addInfeasibilityDepToStmt(infeasNodeId: Option[Int], analysisSourceInfo: AnalysisSourceInfo, dependencyType: DependencyType): Unit = {} /** * @return the final dependency graph representing all direct and transitive dependencies @@ -119,9 +119,7 @@ object DependencyAnalyzer { } def extractAssumptionTypeFromInfo(info: ast.Info): Option[AssumptionType] = { - val annotation = extractAnnotationFromInfo(info, assumptionTypeAnnotationKey) - if(annotation.isDefined && annotation.get.nonEmpty) AssumptionType.fromString(annotation.get.head) - else None + extractDependencyTypeFromInfo(info).map(_.assumptionType) } def extractDependencyTypeFromInfo(info: ast.Info): Option[DependencyType] = { @@ -333,8 +331,8 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { node.map(_.id) } - override def addInfeasibilityNode(isCheck: Boolean, sourceInfo: AnalysisSourceInfo): Option[Int] = { - val node = InfeasibilityNode(sourceInfo) + override def addInfeasibilityNode(isCheck: Boolean, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { + val node = InfeasibilityNode(sourceInfo, assumptionType) addNode(node) Some(node.id) } @@ -463,10 +461,10 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { * The resulting assumption node is required to ensure that unreachable statements/expressions are represented in the graph and * thus taken into account by graph queries, e.g. when determining uncovered statements or computing coverage. */ - override def addInfeasibilityDepToStmt(infeasNodeId: Option[Int], analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Unit = { - val newAssertionNodeId = addAssertNode(False, assumptionType, analysisSourceInfo) + override def addInfeasibilityDepToStmt(infeasNodeId: Option[Int], analysisSourceInfo: AnalysisSourceInfo, dependencyType: DependencyType): Unit = { + val newAssertionNodeId = addAssertNode(False, dependencyType.assertionType, analysisSourceInfo) addDependency(infeasNodeId, newAssertionNodeId) - val newAssumptionNodeId = addAssumption(False, analysisSourceInfo, assumptionType) + val newAssumptionNodeId = addAssumption(False, analysisSourceInfo, dependencyType.assumptionType) addDependency(infeasNodeId, newAssumptionNodeId) } } @@ -489,7 +487,7 @@ class NoDependencyAnalyzer extends DependencyAnalyzer { override def createAssertOrCheckNode(term: Term, assumptionType: AssumptionType, analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] = None override def addAssertFalseNode(isCheck: Boolean, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo): Option[Int] = None - override def addInfeasibilityNode(isCheck: Boolean, sourceInfo: AnalysisSourceInfo): Option[Int] = None + override def addInfeasibilityNode(isCheck: Boolean, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None override def addDependency(source: Option[Int], dest: Option[Int]): Unit = {} override def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit = {} diff --git a/src/main/scala/rules/Brancher.scala b/src/main/scala/rules/Brancher.scala index fdf7f4133..93f8186c1 100644 --- a/src/main/scala/rules/Brancher.scala +++ b/src/main/scala/rules/Brancher.scala @@ -66,10 +66,10 @@ object brancher extends BranchingRules { /* True if the then-branch is to be explored */ val executeThenBranch = ( skipPathFeasibilityCheck - || !v.decider.check(negatedCondition, Verifier.config.checkTimeout())) + || !v.decider.check(negatedCondition, Verifier.config.checkTimeout(), assumptionType)) val thenInfeasibilityNode: Option[Int] = if(Verifier.config.enableDependencyAnalysis() && !executeThenBranch) { - val (_, node) = v.decider.checkAndGetInfeasibilityNode(negatedCondition, Verifier.config.checkTimeout()) + val (_, node) = v.decider.checkAndGetInfeasibilityNode(negatedCondition, Verifier.config.checkTimeout(), assumptionType) node } else None @@ -77,10 +77,10 @@ object brancher extends BranchingRules { val executeElseBranch = ( !executeThenBranch /* Assumes that ast least one branch is feasible */ || skipPathFeasibilityCheck - || !v.decider.check(condition, Verifier.config.checkTimeout())) + || !v.decider.check(condition, Verifier.config.checkTimeout(), assumptionType)) val elseInfeasibilityNode: Option[Int] = if(Verifier.config.enableDependencyAnalysis() && !executeElseBranch) { - val (_, node) = v.decider.checkAndGetInfeasibilityNode(condition, Verifier.config.checkTimeout()) + val (_, node) = v.decider.checkAndGetInfeasibilityNode(condition, Verifier.config.checkTimeout(), assumptionType) node } else None v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) diff --git a/src/main/scala/rules/ChunkSupporter.scala b/src/main/scala/rules/ChunkSupporter.scala index bacd235e3..90d7df35d 100644 --- a/src/main/scala/rules/ChunkSupporter.scala +++ b/src/main/scala/rules/ChunkSupporter.scala @@ -35,7 +35,7 @@ trait ChunkSupportRules extends SymbolicExecutionRules { ve: VerificationError, v: Verifier, description: String, - dependencyType: DependencyType=DependencyType.Implicit) + dependencyType: DependencyType) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult @@ -50,7 +50,7 @@ trait ChunkSupportRules extends SymbolicExecutionRules { argsExp: Option[Seq[ast.Exp]], ve: VerificationError, v: Verifier, - assumptionType: AssumptionType=AssumptionType.Implicit) + assumptionType: AssumptionType) (Q: (State, Heap, Term, Verifier) => VerificationResult) : VerificationResult @@ -80,7 +80,7 @@ object chunkSupporter extends ChunkSupportRules { ve: VerificationError, v: Verifier, description: String, - dependencyType: DependencyType=DependencyType.Implicit) + dependencyType: DependencyType) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -238,7 +238,7 @@ object chunkSupporter extends ChunkSupportRules { argsExp: Option[Seq[ast.Exp]], ve: VerificationError, v: Verifier, - assumptionType: AssumptionType=AssumptionType.Implicit) + assumptionType: AssumptionType) (Q: (State, Heap, Term, Verifier) => VerificationResult) : VerificationResult = { @@ -258,7 +258,7 @@ object chunkSupporter extends ChunkSupportRules { argsExp: Option[Seq[ast.Exp]], ve: VerificationError, v: Verifier, - assumptionType: AssumptionType=AssumptionType.Implicit) + assumptionType: AssumptionType) (Q: (State, Term, Verifier) => VerificationResult) : VerificationResult = { diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index 908a127fe..2e2190329 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -37,7 +37,7 @@ trait ConsumptionRules extends SymbolicExecutionRules { * consumed partial heap. * @return The result of the continuation. */ - def consume(s: State, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, dependencyType: DependencyType=DependencyType.Implicit) + def consume(s: State, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, dependencyType: DependencyType) (Q: (State, Option[Term], Verifier) => VerificationResult) : VerificationResult @@ -62,7 +62,7 @@ trait ConsumptionRules extends SymbolicExecutionRules { returnSnap: Boolean, pvef: ast.Exp => PartialVerificationError, v: Verifier, - dependencyType: DependencyType=DependencyType.Implicit) + dependencyType: DependencyType) (Q: (State, Option[Term], Verifier) => VerificationResult) : VerificationResult } @@ -76,7 +76,7 @@ object consumer extends ConsumptionRules { */ /** @inheritdoc */ - def consume(s: State, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, dependencyType: DependencyType=DependencyType.Implicit) + def consume(s: State, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, dependencyType: DependencyType) (Q: (State, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -92,7 +92,7 @@ object consumer extends ConsumptionRules { returnSnap: Boolean, pvef: ast.Exp => PartialVerificationError, v: Verifier, - dependencyType: DependencyType=DependencyType.Implicit) + dependencyType: DependencyType) (Q: (State, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -145,7 +145,7 @@ object consumer extends ConsumptionRules { } } - private def consumeR(s: State, h: Heap, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, dependencyType: DependencyType=DependencyType.Implicit) + private def consumeR(s: State, h: Heap, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, dependencyType: DependencyType) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -201,7 +201,7 @@ object consumer extends ConsumptionRules { */ if(v.decider.isPathInfeasible()){ - v.decider.dependencyAnalyzer.addInfeasibilityDepToStmt(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, dependencyType.assertionType) + v.decider.dependencyAnalyzer.addInfeasibilityDepToStmt(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, dependencyType) return Q(s, h, Option.when(returnSnap)(Unit), v) } diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 8998233a7..2eff53d20 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -576,8 +576,7 @@ object evaluator extends EvaluationRules { case fapp @ ast.FuncApp(funcName, eArgs) => val func = s.program.findFunction(funcName) - val funcAssumptionType = AssumptionType.MethodCall - val assumptionType = DependencyAnalyzer.extractAssumptionTypeFromInfo(fapp.info).getOrElse(funcAssumptionType) + val dependencyType = DependencyAnalyzer.extractDependencyTypeFromInfo(fapp.info).getOrElse(DependencyType.MethodCall) evals2(s, eArgs, Nil, _ => pve, v)((s1, tArgs, eArgsNew, v1) => { // bookkeeper.functionApplications += 1 val joinFunctionArgs = tArgs //++ c2a.quantifiedVariables.filterNot(tArgs.contains) @@ -593,7 +592,7 @@ object evaluator extends EvaluationRules { * Hence, the joinedFApp will take two arguments, namely, i*i and i, * although the latter is not necessary. */ - joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s1a, v1, assumptionType=assumptionType)((s2, v2, QB) => { + joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s1a, v1, assumptionType=dependencyType.assumptionType)((s2, v2, QB) => { val pres = func.pres.map(_.transform { /* [Malte 2018-08-20] Two examples of the test suite, one of which is the regression * for Carbon issue #210, fail if the subsequent code that strips out triggers from @@ -651,13 +650,13 @@ object evaluator extends EvaluationRules { moreJoins = JoinMode.Off, assertReadAccessOnly = if (Verifier.config.respectFunctionPrePermAmounts()) s2.assertReadAccessOnly /* should currently always be false */ else true) - consumes(s3, pres, true, _ => pvePre, v2)((s4, snap, v3) => { + consumes(s3, pres, true, _ => pvePre, v2, DependencyType.Implicit)((s4, snap, v3) => { // TODO ake: why does DependencyType.MethodCall not work? val snap1 = snap.get.convert(sorts.Snap) val preFApp = App(functionSupporter.preconditionVersion(v3.symbolConverter.toFunction(func)), snap1 :: tArgs) val preExp = Option.when(withExp)({ DebugExp.createInstance(Some(s"precondition of ${func.name}(${eArgsNew.get.mkString(", ")}) holds"), None, None, InsertionOrderedSet.empty) }) - v3.decider.assume(preFApp, preExp, assumptionType=assumptionType) + v3.decider.assume(preFApp, preExp, assumptionType=dependencyType.assumptionType) val funcAnn = func.info.getUniqueInfo[AnnotationInfo] val tFApp = funcAnn match { case Some(a) if a.values.contains("opaque") => @@ -713,7 +712,7 @@ object evaluator extends EvaluationRules { // val c4 = c3.decCycleCounter(predicate) // eval(σ1, eIn, pve, c4)((tIn, c5) => // QB(tIn, c5))}) - consume(s4, acc, true, pve, v3)((s5, snap, v4) => { + consume(s4, acc, true, pve, v3, DependencyAnalyzer.extractDependencyTypeFromInfo(acc.info).getOrElse(DependencyType.Internal))((s5, snap, v4) => { val fr6 = s5.functionRecorder.recordSnapshot(pa, v4.decider.pcs.branchConditions, snap.get) .changeDepthBy(+1) @@ -750,7 +749,7 @@ object evaluator extends EvaluationRules { eval(s10, eIn, pve, v5)((s9, t9, e9, v9) => QB(s9, (t9, e9), v9)) }) } else { - produce(s7a, toSf(snap.get), body, pve, v4)((s8, v5) => { + produce(s7a, toSf(snap.get), body, pve, v4, DependencyAnalyzer.extractAssumptionTypeFromInfo(e.info).getOrElse(AssumptionType.Internal))((s8, v5) => { val s9 = s8.copy(g = s7.g, functionRecorder = s8.functionRecorder.changeDepthBy(-1), recordVisited = s3.recordVisited, @@ -785,7 +784,7 @@ object evaluator extends EvaluationRules { => Q(s4, r4._1, r4._2, v4)) case ast.Asserting(eAss, eIn) => - consume(s, eAss, false, pve, v)((s2, _, v2) => { + consume(s, eAss, false, pve, v, DependencyAnalyzer.extractDependencyTypeFromInfo(eAss.info).getOrElse(DependencyType.ExplicitAssertion))((s2, _, v2) => { val s3 = s2.copy(g = s.g, h = s.h) eval(s3, eIn, pve, v2)(Q) }) @@ -1423,7 +1422,7 @@ object evaluator extends EvaluationRules { var sJoined = entries.tail.foldLeft(entries.head.s)((sAcc, entry) => sAcc.merge(entry.s)) sJoined = sJoined.copy(functionRecorder = sJoined.functionRecorder.recordPathSymbol(joinSymbol)) - joinDefEqs foreach { case (t, exp, expNew) => v.decider.assume(t, exp, expNew, AssumptionType.Implicit)} + joinDefEqs foreach { case (t, exp, expNew) => v.decider.assume(t, exp, expNew, AssumptionType.Internal)} (sJoined, (joinTerm, joinExp)) } diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 5a141e8e9..7762c7960 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -542,9 +542,6 @@ object executor extends ExecutionRules { val pveCall = CallFailed(call) val pveCallTransformed = pveCall.withReasonNodeTransformed(reasonTransformer) - // TODO: make sure the join can still be made! - val finalAssumptionType = getAssumptionType(AssumptionType.MethodCall) - val mcLog = new MethodCallRecord(call, s, v.decider.pcs) val sepIdentifier = v.symbExLog.openScope(mcLog) val paramLog = new CommentRecord("Parameters", s, v.decider.pcs) @@ -571,7 +568,7 @@ object executor extends ExecutionRules { val outs = meth.formalReturns.map(_.localVar) val gOuts = Store(outs.map(x => (x, v2.decider.fresh(x))).toMap) val s4 = s3.copy(g = s3.g + gOuts, oldHeaps = s3.oldHeaps + (Verifier.PRE_STATE_LABEL -> magicWandSupporter.getEvalHeap(s1))) - produces(s4, freshSnap, meth.posts, _ => pveCallTransformed, v2, AssumptionType.MethodCall)((s5, v3) => { // TODO ake: assumptionType + produces(s4, freshSnap, meth.posts, _ => pveCallTransformed, v2, getAssumptionType(AssumptionType.MethodCall))((s5, v3) => { // TODO ake: make sure the join is always made! v3.symbExLog.closeScope(postCondId) v3.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) val gLhs = Store(lhs.zip(outs) diff --git a/src/main/scala/rules/HeapSupporter.scala b/src/main/scala/rules/HeapSupporter.scala index 524d75715..5d9035721 100644 --- a/src/main/scala/rules/HeapSupporter.scala +++ b/src/main/scala/rules/HeapSupporter.scala @@ -10,7 +10,7 @@ import viper.silicon import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType -import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyType, TransitivityAnalysisSourceInfo} +import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyAnalyzer, DependencyType, TransitivityAnalysisSourceInfo} import viper.silicon.interfaces.VerificationResult import viper.silicon.interfaces.state.{ChunkIdentifer, NonQuantifiedChunk, QuantifiedChunk} import viper.silicon.resources.{FieldID, PredicateID} @@ -210,7 +210,8 @@ class DefaultHeapSupportRules extends HeapSupportRules { FullPerm, Option.when(withExp)(ast.FullPerm()()), chunkOrderHeuristics, - v + v, + dependencyType ) v.decider.analysisSourceInfoStack.removeForcedSource() result match { @@ -416,7 +417,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { } } else { val resource = fa.res(s.program) - chunkSupporter.lookup(s, s.h, resource, Seq(tRcvr), Option.when(withExp)(Seq(eRcvr.get)), ve, v)((s2, h2, tSnap, v2) => { + chunkSupporter.lookup(s, s.h, resource, Seq(tRcvr), Option.when(withExp)(Seq(eRcvr.get)), ve, v, DependencyAnalyzer.extractDependencyTypeFromInfo(fa.info).map(_.assertionType).getOrElse(AssumptionType.Implicit))((s2, h2, tSnap, v2) => { val fr = s2.functionRecorder.recordSnapshot(fa, v2.decider.pcs.branchConditions, tSnap) val s3 = s2.copy(h = h2, functionRecorder = fr) Q(s3, tSnap, v) diff --git a/src/main/scala/rules/Joiner.scala b/src/main/scala/rules/Joiner.scala index 5492f349e..533cbd6e9 100644 --- a/src/main/scala/rules/Joiner.scala +++ b/src/main/scala/rules/Joiner.scala @@ -25,12 +25,12 @@ case class JoinDataEntry[D](s: State, data: D, pathConditions: RecordedPathCondi // we can directly merge JoinDataEntries to obtain new States, // and the join data entries themselves provide information about the path conditions to State.merge. def pathConditionAwareMerge(other: JoinDataEntry[D], v: Verifier): State = { - val res = State.merge(this.s, this.pathConditions, other.s, other.pathConditions, AnalysisInfo(v.decider, v.decider.dependencyAnalyzer, StringAnalysisSourceInfo("merge", NoPosition), AssumptionType.Implicit)) + val res = State.merge(this.s, this.pathConditions, other.s, other.pathConditions, AnalysisInfo(v.decider, v.decider.dependencyAnalyzer, StringAnalysisSourceInfo("merge", NoPosition), AssumptionType.Internal)) v.stateConsolidator(s).consolidate(res, v) } def pathConditionAwareMergeWithoutConsolidation(other: JoinDataEntry[D], v: Verifier): State = { - State.merge(this.s, this.pathConditions, other.s, other.pathConditions, AnalysisInfo(v.decider, v.decider.dependencyAnalyzer, StringAnalysisSourceInfo("merge", NoPosition), AssumptionType.Implicit)) + State.merge(this.s, this.pathConditions, other.s, other.pathConditions, AnalysisInfo(v.decider, v.decider.dependencyAnalyzer, StringAnalysisSourceInfo("merge", NoPosition), AssumptionType.Internal)) } } diff --git a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala index 49d76dc65..dc891967f 100644 --- a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala +++ b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala @@ -199,7 +199,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { argsExp: Option[Seq[ast.Exp]], ve: VerificationError, v: Verifier, - assumptionType: AssumptionType=AssumptionType.Implicit) + assumptionType: AssumptionType) (Q: (State, Term, Verifier) => VerificationResult) : VerificationResult = { @@ -238,7 +238,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { returnSnap: Boolean, ve: VerificationError, v: Verifier, - dependencyType: DependencyType=DependencyType.Implicit) + dependencyType: DependencyType) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index c80f5487a..a81539b4f 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -41,7 +41,7 @@ trait ProductionRules extends SymbolicExecutionRules { a: ast.Exp, pve: PartialVerificationError, v: Verifier, - assumptionType: AssumptionType = AssumptionType.Implicit) + assumptionType: AssumptionType) (Q: (State, Verifier) => VerificationResult) : VerificationResult @@ -66,7 +66,7 @@ trait ProductionRules extends SymbolicExecutionRules { as: Seq[ast.Exp], pvef: ast.Exp => PartialVerificationError, v: Verifier, - assumptionType: AssumptionType = AssumptionType.Implicit) + assumptionType: AssumptionType) (Q: (State, Verifier) => VerificationResult) : VerificationResult } @@ -106,7 +106,7 @@ object producer extends ProductionRules { a: ast.Exp, pve: PartialVerificationError, v: Verifier, - assumptionType: AssumptionType = AssumptionType.Implicit) + assumptionType: AssumptionType) (Q: (State, Verifier) => VerificationResult) : VerificationResult = @@ -118,7 +118,7 @@ object producer extends ProductionRules { as: Seq[ast.Exp], pvef: ast.Exp => PartialVerificationError, v: Verifier, - assumptionType: AssumptionType = AssumptionType.Implicit) + assumptionType: AssumptionType) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 78cc5daea..858d6d8b9 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -1147,7 +1147,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { notInjectiveReason: => ErrorReason, insufficientPermissionReason: => ErrorReason, v: Verifier, - dependencyType: DependencyType=DependencyType.Implicit) + dependencyType: DependencyType) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -1412,7 +1412,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optChunkOrderHeuristic: Option[Seq[QuantifiedBasicChunk] => Seq[QuantifiedBasicChunk]], pve: PartialVerificationError, v: Verifier, - dependencyType: DependencyType=DependencyType.Implicit) + dependencyType: DependencyType) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -1538,7 +1538,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { perms: Term, permsExp: Option[ast.Exp], v: Verifier, - assumptionType: AssumptionType=AssumptionType.Implicit) + assertionType: AssumptionType=AssumptionType.Implicit) : ConsumptionResult = { var permsAvailable: Term = NoPerm @@ -1555,7 +1555,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { // final check val result = - if (v.decider.check(tookEnoughCheck, Verifier.config.assertTimeout.getOrElse(0), assumptionType) /* This check is a must-check, i.e. an assert */ ) + if (v.decider.check(tookEnoughCheck, Verifier.config.assertTimeout.getOrElse(0), assertionType) /* This check is a must-check, i.e. an assert */ ) Complete() else Incomplete(PermMinus(permsAvailable, perms), permsAvailableExp.map(pa => ast.PermSub(pa, permsExp.get)())) @@ -1579,7 +1579,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { permsExp: Option[ast.Exp], // p(rs) chunkOrderHeuristic: Seq[QuantifiedBasicChunk] => Seq[QuantifiedBasicChunk], v: Verifier, - dependencyType: DependencyType=DependencyType.Implicit) + dependencyType: DependencyType) : (ConsumptionResult, State, Seq[QuantifiedBasicChunk]) = { val rmPermRecord = new CommentRecord("removePermissions", s, v.decider.pcs) diff --git a/src/main/scala/rules/StateConsolidator.scala b/src/main/scala/rules/StateConsolidator.scala index ba0d0e146..c99522715 100644 --- a/src/main/scala/rules/StateConsolidator.scala +++ b/src/main/scala/rules/StateConsolidator.scala @@ -7,9 +7,9 @@ package viper.silicon.rules import viper.silicon.Config -import viper.silicon.dependencyAnalysis.AssumptionType import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp +import viper.silicon.dependencyAnalysis.AssumptionType import viper.silicon.interfaces.state._ import viper.silicon.logger.records.data.{CommentRecord, SingleMergeRecord} import viper.silicon.resources.{NonQuantifiedPropertyInterpreter, Resources} diff --git a/src/main/scala/supporters/functions/FunctionData.scala b/src/main/scala/supporters/functions/FunctionData.scala index 842500918..c6500281c 100644 --- a/src/main/scala/supporters/functions/FunctionData.scala +++ b/src/main/scala/supporters/functions/FunctionData.scala @@ -321,7 +321,7 @@ class FunctionData(val programFunction: ast.Function, val bodyPreconditions = if (programFunction.body.isDefined) optBody.map(translatedBody => { val body = Implies(pre, FunctionPreconditionTransformer.transform(translatedBody, program)) (Forall(arguments, body, Seq(Trigger(functionApplication))), - Option.when(isAnalysisEnabled)((StringAnalysisSourceInfo("bodyPreconditionPropagationAxiom", programFunction.pos), AssumptionType.SourceCode))) + Option.when(isAnalysisEnabled)((ExpAnalysisSourceInfo(programFunction.body.get, programFunction.body.get.pos), AssumptionType.SourceCode))) }) else None bodyPreconditions.toSeq } From 12d4ff09410902da0f97889b68045e696785f4d6 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 28 Jan 2026 08:53:23 +0100 Subject: [PATCH 328/474] treat assertions as verification annotations --- .../DependencyGraphInterpreter.scala | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 44aad81a0..38d21fce5 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -229,19 +229,21 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen val relevantDependencies = relevantDependenciesPerAssertion.flatMap(_._2).filter(_.assumptionTypes.nonEmpty).toSet + val explicitAssertions = toUserLevelNodes(getExplicitAssertionNodes).getSourceSet() + // covered val coveredExplicitSources = UserLevelDependencyAnalysisNode.extractExplicitAssumptionNodes(relevantDependencies).getSourceSet() val coveredVerificationAnnotations = UserLevelDependencyAnalysisNode.extractVerificationAnnotationNodes(relevantDependencies).getSourceSet().diff(coveredExplicitSources) - val coveredSourceCodeStmts = relevantDependencies.getSourceSet().diff(coveredExplicitSources).diff(coveredVerificationAnnotations) + val coveredSourceCodeStmts = relevantDependencies.getSourceSet().diff(coveredExplicitSources).diff(coveredVerificationAnnotations).diff(explicitAssertions) // uncovered val uncoveredNodes = toUserLevelNodes(getNonInternalAssumptionNodes).diffBySource(relevantDependencies) val uncoveredExplicitSources = UserLevelDependencyAnalysisNode.extractExplicitAssumptionNodes(uncoveredNodes).getSourceSet() - val uncoveredVerificationAnnotations = UserLevelDependencyAnalysisNode.extractVerificationAnnotationNodes(uncoveredNodes).getSourceSet().diff(uncoveredExplicitSources) + val uncoveredVerificationAnnotations = UserLevelDependencyAnalysisNode.extractVerificationAnnotationNodes(uncoveredNodes).getSourceSet().diff(uncoveredExplicitSources) ++ explicitAssertions val uncoveredSourceCodeStmts = uncoveredNodes.getSourceSet().diff(uncoveredExplicitSources).diff(uncoveredVerificationAnnotations) // assertions - val relevantAssertions = relevantDependenciesPerAssertion // TODO ake: maybe .filter(_._1.lowLevelAssertionNodes.exists(n => !n.isInstanceOf[SimpleCheckNode])) + val relevantAssertions = relevantDependenciesPerAssertion val assertionsWithFailures = relevantAssertions.keySet.filter(_.hasFailures).getSourceSet() // should be empty to get a reasonable progress metric val assertionsWithExplicitDeps = relevantAssertions.filter(deps => deps._2.exists(d => AssumptionType.explicitAssumptionTypes.intersect(d.assumptionTypes).nonEmpty)).keySet.getSourceSet().diff(assertionsWithFailures) val fullyVerifiedAssertions = relevantAssertions.keySet.getSourceSet().diff(assertionsWithFailures).diff(assertionsWithExplicitDeps) @@ -264,7 +266,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen def getString(nodes: Set[AnalysisSourceInfo]): String = { - nodes.toList.sortBy(_.getLineNumber).mkString(("\n\t\t")) + nodes.toList.sortBy(_.getLineNumber).mkString("\n\t\t") } val info = { From 50821f39f4adff79fb3abd05624a2b155a11c8ee Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 29 Jan 2026 11:33:30 +0100 Subject: [PATCH 329/474] fix function axioms by removing analysis labels --- src/main/scala/supporters/functions/FunctionData.scala | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/scala/supporters/functions/FunctionData.scala b/src/main/scala/supporters/functions/FunctionData.scala index c6500281c..75e6b6c50 100644 --- a/src/main/scala/supporters/functions/FunctionData.scala +++ b/src/main/scala/supporters/functions/FunctionData.scala @@ -173,13 +173,18 @@ class FunctionData(val programFunction: ast.Function, private def generateNestedDefinitionalAxioms: InsertionOrderedSet[Term] = { val freshSymbols: Set[Identifier] = freshSymbolsAcrossAllPhases.map(_.id) - val nested = ( + val nestedTmp = ( freshFieldInvs.flatMap(_.definitionalAxioms) ++ freshFvfsAndDomains.flatMap (fvfDef => fvfDef.domainDefinitions ++ fvfDef.valueDefinitions) ++ freshPermMaps.flatMap (pmDef => pmDef.valueDefinitions) ++ freshConstrainedVars.map(_._2) ++ freshConstraints) + val nested = if(!Verifier.config.enableDependencyAnalysis()) nestedTmp + else nestedTmp.map(_.transform{ + case Var(name, _, _) if name.name.startsWith(DependencyAnalyzer.analysisLabelName) => True // replace dependency analysis labels by True to avoid errors + }()) + // Filter out nested definitions that contain free variables. // This should not happen, but currently can, due to bugs in the function axiomatisation code. // Fixing these bugs with the current way functions are axiomatised will be very difficult, From 73d930fec407a53d5c671fcc3710a0592fdfc6b1 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 29 Jan 2026 11:58:40 +0100 Subject: [PATCH 330/474] comment in warnings about function axioms --- .../supporters/functions/FunctionData.scala | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/main/scala/supporters/functions/FunctionData.scala b/src/main/scala/supporters/functions/FunctionData.scala index 75e6b6c50..3e032a9e2 100644 --- a/src/main/scala/supporters/functions/FunctionData.scala +++ b/src/main/scala/supporters/functions/FunctionData.scala @@ -8,7 +8,7 @@ package viper.silicon.supporters.functions import com.typesafe.scalalogging.LazyLogging import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType -import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, DependencyAnalyzer, AssumptionType, ExpAnalysisSourceInfo, StringAnalysisSourceInfo} +import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyAnalyzer, ExpAnalysisSourceInfo, StringAnalysisSourceInfo} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.interfaces.FatalResult import viper.silicon.rules.{InverseFunctions, PermMapDefinition, SnapshotMapDefinition, functionSupporter} @@ -23,7 +23,7 @@ import viper.silver.ast import viper.silver.ast.LocalVarWithVersion import viper.silver.ast.utility.Functions import viper.silver.parser.PUnknown -import viper.silver.reporter.Reporter +import viper.silver.reporter.{InternalWarningMessage, Reporter} import scala.annotation.unused @@ -189,20 +189,20 @@ class FunctionData(val programFunction: ast.Function, // This should not happen, but currently can, due to bugs in the function axiomatisation code. // Fixing these bugs with the current way functions are axiomatised will be very difficult, // but they should be resolved with Mauro's current work on heap snapshots. - // Once his changes are merged in, the commented warnings below should be turned into errors. + // Once his changes are merged in, the warnings below should be turned into errors. nested.filter(term => { val freeVars = term.freeVariables -- arguments val unknownVars = freeVars.filterNot(v => freshSymbols.contains(v.id)) - //if (unknownVars.nonEmpty) { - // val messageText = ( - // s"Found unexpected free variables $unknownVars " - // + s"in term $term during axiomatisation of function " - // + s"${programFunction.name}") - // - // reporter report InternalWarningMessage(messageText) - // logger warn messageText - //} + if (unknownVars.nonEmpty) { + val messageText = ( + s"Found unexpected free variables $unknownVars " + + s"in term $term during axiomatisation of function " + + s"${programFunction.name}") + + reporter report InternalWarningMessage(messageText) + logger warn messageText + } unknownVars.isEmpty }) From 5077d6837ec97fce226ca5d5185c1328a42c0491 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 29 Jan 2026 16:59:52 +0100 Subject: [PATCH 331/474] progress metric: remove special treatment of verification failures --- .../DependencyGraphInterpreter.scala | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 38d21fce5..32f41129e 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -222,7 +222,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen // TODO ake: this is suuuper slow. Can we reuse previously computed results? Caching? val relevantDependenciesPerAssertion = allAssertions .map(ass => (ass, toUserLevelNodes(getAllNonInternalDependencies(getNodesWithIdenticalSource(ass.lowerLevelNodes).map(_.id))).diffBySource(Set(ass)))).toMap - .filter{case (assertion, assumptions) => assumptions.nonEmpty || assertion.hasFailures} // filter out trivial assertions like `assert true` + .filter{case (_, assumptions) => assumptions.nonEmpty} // filter out trivial assertions like `assert true` // val endTime = System.nanoTime() // println(s"Runtime of computing dependencies per assertion: ${(endTime-startTime)/1e6}ms") @@ -244,9 +244,8 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen // assertions val relevantAssertions = relevantDependenciesPerAssertion - val assertionsWithFailures = relevantAssertions.keySet.filter(_.hasFailures).getSourceSet() // should be empty to get a reasonable progress metric - val assertionsWithExplicitDeps = relevantAssertions.filter(deps => deps._2.exists(d => AssumptionType.explicitAssumptionTypes.intersect(d.assumptionTypes).nonEmpty)).keySet.getSourceSet().diff(assertionsWithFailures) - val fullyVerifiedAssertions = relevantAssertions.keySet.getSourceSet().diff(assertionsWithFailures).diff(assertionsWithExplicitDeps) + val assertionsWithExplicitDeps = relevantAssertions.filter(deps => deps._2.exists(d => AssumptionType.explicitAssumptionTypes.intersect(d.assumptionTypes).nonEmpty)).keySet.getSourceSet() + val fullyVerifiedAssertions = relevantAssertions.keySet.getSourceSet().diff(assertionsWithExplicitDeps) val numRelevantAssertions = relevantAssertions.keySet.size.toDouble @@ -256,9 +255,8 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen val verificationProgressPeter = specQuality * proofQualityPeter // Lea's metric - val proofQualityPerAssertion = relevantAssertions.map { case (assertion, assumptions) => - if(assertion.hasFailures) 0.0 - else UserLevelDependencyAnalysisNode.extractNonExplicitAssumptionNodes(assumptions).size.toDouble / assumptions.size.toDouble + val proofQualityPerAssertion = relevantAssertions.map { case (_, assumptions) => + UserLevelDependencyAnalysisNode.extractNonExplicitAssumptionNodes(assumptions).size.toDouble / assumptions.size.toDouble } val proofQualityLea = if(numRelevantAssertions > 0) proofQualityPerAssertion.sum / numRelevantAssertions else 1.0 @@ -282,7 +280,6 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen "\n" + s"Fully verified assertions:\n\t\t${getString(fullyVerifiedAssertions)}" + "\n\n" + s"Assertions depending on explicit assumptions:\n\t\t${getString(assertionsWithExplicitDeps)}" + "\n\n" + - s"Failing assertions:\n\t\t${getString(assertionsWithFailures)}\n\n" + "\n" + s"#Verification Errors: ${errors.size}" + "\n\n" + "\n" + From e0d695db1822e1ec042ab854bccd6fb53fdef470 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 2 Feb 2026 09:44:44 +0100 Subject: [PATCH 332/474] optimize progress computation --- .../dependencyAnalysis/AnalysisInfo.scala | 3 +- .../DependencyAnalysisUserTool.scala | 28 ++++++- .../DependencyAnalyzer.scala | 6 +- .../dependencyAnalysis/DependencyGraph.scala | 43 +++++++++-- .../DependencyGraphInterpreter.scala | 76 ++++++++++++++++++- .../UserLevelDependencyAnalysisNode.scala | 2 +- 6 files changed, 142 insertions(+), 16 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala index f14ad30d6..ed02e809d 100644 --- a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala +++ b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala @@ -8,12 +8,13 @@ object AssumptionType extends Enumeration { def fromString(s: String): Option[Value] = values.find(_.toString == s) def explicitAssumptionTypes: Set[AssumptionType] = Set(Explicit, ExplicitPostcondition, DomainAxiom) - def postconditionTypes: Set[AssumptionType] = Set(ImplicitPostcondition, ExplicitPostcondition, MethodCall) // used to join graphs via postconditions + def postconditionTypes: Set[AssumptionType] = Set(ImplicitPostcondition, ExplicitPostcondition) // used to join graphs via postconditions def explicitAssertionTypes: Set[AssumptionType] = Set(Explicit, ImplicitPostcondition, ExplicitPostcondition) def internalTypes: Set[AssumptionType] = Set(Internal, Trigger) // will always be hidden from user def joinConditionTypes: Set[AssumptionType] = postconditionTypes ++ Set(FunctionBody) def verificationAnnotationTypes: Set[AssumptionType] = Set(LoopInvariant, Rewrite, ExplicitPostcondition, ImplicitPostcondition, Precondition, Explicit) def sourceCodeTypes: Set[AssumptionType] = Set(SourceCode, PathCondition, MethodCall, FunctionBody, Implicit) + def methodCallTypes: Set[AssumptionType] = Set(MethodCall) // used to join graphs via postconditions } import viper.silicon.dependencyAnalysis.AssumptionType._ diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala index 34da78f2d..7531ede5a 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala @@ -60,6 +60,8 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete handleProofCoverageLineQuery(inputParts.tail) }else if (inputParts.head.equalsIgnoreCase("progress") || inputParts.head.equalsIgnoreCase("prog")) { handleVerificationProgressQuery() + }else if (inputParts.head.equalsIgnoreCase("progressDebug")) { + handleVerificationProgressDEBUGQuery() }else if (inputParts.head.equalsIgnoreCase("guidance") || inputParts.head.equalsIgnoreCase("guide")) { handleVerificationGuidanceQuery() }else if(inputParts.head.equalsIgnoreCase("prune")) { @@ -141,10 +143,30 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete private def handleVerificationProgressQuery(): Unit = { if(verificationErrors.nonEmpty) println(s"Fix verification failures first!") - val ((progress, _, info), time) = measureTime(fullGraphInterpreter.computeVerificationProgress()) + val ((optProgressPeter, optProgressLea, optInfo), optTime) = measureTime(fullGraphInterpreter.computeVerificationProgressOptimized()) + // println(s"Overall verification progress: $progress") + println(s"$optInfo") + println(s"Peter: $optProgressPeter; Lea: $optProgressLea") + println(s"Finished in ${optTime}ms") + } + + private def handleVerificationProgressDEBUGQuery(): Unit = { + if(verificationErrors.nonEmpty) println(s"Fix verification failures first!") + + println("\nNaive implementation") + val ((naiveProgressPeter, naiveProgressLea, naiveInfo), naiveTime) = measureTime(fullGraphInterpreter.computeVerificationProgressNaive()) // println(s"Overall verification progress: $progress") - println(s"$info") - println(s"Finished in ${time}ms") + println(s"$naiveInfo") + println(s"Peter: $naiveProgressPeter; Lea: $naiveProgressLea") + println(s"Finished in ${naiveTime}ms") + + println("\nOptimized implementation") + val ((optProgressPeter, optProgressLea, optInfo), optTime) = measureTime(fullGraphInterpreter.computeVerificationProgressOptimized()) + // println(s"Overall verification progress: $progress") + println(s"$optInfo") + println(s"Peter: $optProgressPeter; Lea: $optProgressLea") + println(s"Finished in ${optTime}ms") + if(Math.abs(naiveProgressPeter - optProgressPeter) > 1e4 || Math.abs(naiveProgressLea - naiveProgressLea) > 1e4) println("Progress is not equal!") } protected def getSourceInfoString(nodes: Set[DependencyAnalysisNode]): String = { diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index 04e41915a..d68efc275 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -197,7 +197,7 @@ object DependencyAnalyzer { .groupBy(n => n.sourceInfo) .map{case (sourceInfo, axiomNodes) => (axiomNodes.map(_.id), axiomAssertionNodes.getOrElse(sourceInfo.getTopLevelSource, Seq.empty))} .foreach{case (axiomNodeIds, assertionNodeIds) => - newGraph.addEdges(assertionNodeIds, axiomNodeIds) // TODO ake: maybe we could merge the axiom nodes here since they represent the same axiom? + newGraph.addEdgesConnectingMethods(assertionNodeIds, axiomNodeIds) // TODO ake: maybe we could merge the axiom nodes here since they represent the same axiom? } stopTimeMeasurementAndAddToTotal(startTime, timeForFunctionJoin) @@ -206,13 +206,13 @@ object DependencyAnalyzer { // postconditions of methods assumed by every method call should depend on the assertions that justify them // hence, we add edges from assertions of method postconditions to assumptions of the same postcondition (at method calls) val relevantAssumptionNodes = joinCandidateNodes - .filter(node => node.isInstanceOf[GeneralAssumptionNode] && AssumptionType.postconditionTypes.contains(node.assumptionType)) + .filter(node => node.isInstanceOf[GeneralAssumptionNode] && AssumptionType.methodCallTypes.contains(node.assumptionType)) .groupBy(_.sourceInfo.getFineGrainedSource) .view.mapValues(_.map(_.id)) .toMap joinCandidateNodes.filter(node => AssumptionType.postconditionTypes.contains(node.assumptionType)) .map(node => (node.id, relevantAssumptionNodes.getOrElse(node.sourceInfo.getTopLevelSource, Seq.empty))) - .foreach { case (src, targets) => newGraph.addEdges(src, targets)} + .foreach { case (src, targets) => newGraph.addEdgesConnectingMethods(src, targets)} stopTimeMeasurementAndAddToTotal(startTime, timeForMethodJoin) diff --git a/src/main/scala/dependencyAnalysis/DependencyGraph.scala b/src/main/scala/dependencyAnalysis/DependencyGraph.scala index db0aa646a..f40d94b13 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraph.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraph.scala @@ -20,10 +20,11 @@ trait ReadOnlyDependencyGraph extends AbstractReadOnlyDependencyGraph { def getNodes: Seq[DependencyAnalysisNode] def getDirectEdges: Map[Int, Set[Int]] // target -> direct dependencies def getTransitiveEdges: Map[Int, Set[Int]] // target -> direct dependencies + def getEdgesConnectingMethods: Map[Int, Set[Int]] def getAllEdges: Map[Int, Set[Int]] // target -> direct dependencies def existsAnyDependency(sources: Set[Int], targets: Set[Int], includeInfeasibilityNodes: Boolean): Boolean - def getAllDependencies(sources: Set[Int], includeInfeasibilityNodes: Boolean): Set[Int] + def getAllDependencies(sources: Set[Int], includeInfeasibilityNodes: Boolean, includeIntraMethodEdges: Boolean=true): Set[Int] def getAllDependents(sources: Set[Int], includeInfeasibilityNodes: Boolean): Set[Int] def exportGraph(dirName: String): Unit @@ -33,17 +34,30 @@ class DependencyGraph extends ReadOnlyDependencyGraph { var nodes: mutable.Seq[DependencyAnalysisNode] = mutable.Seq() private val edges: mutable.Map[Int, Set[Int]] = mutable.Map.empty private val transitiveEdges: mutable.Map[Int, Set[Int]] = mutable.Map.empty // TODO ake: can be merged into edges? + private val edgesConnectingMethods: mutable.Map[Int, Set[Int]] = mutable.Map.empty // keep this, it's relevant for computing verification progress def getNodes: Seq[DependencyAnalysisNode] = nodes.toSeq def getDirectEdges: Map[Int, Set[Int]] = edges.toMap def getTransitiveEdges: Map[Int, Set[Int]] = transitiveEdges.toMap - def getAllEdges: Map[Int, Set[Int]] = { + def getEdgesConnectingMethods: Map[Int, Set[Int]] = edgesConnectingMethods.toMap + + def getIntraMethodEdges: Map[Int, Set[Int]] = { val keys = edges.keySet ++ transitiveEdges.keySet - val res = mutable.Map[Int, Set[Int]]() + val intraMethodEdges = mutable.Map[Int, Set[Int]]() + keys foreach {key => + intraMethodEdges.update(key, edges.getOrElse(key, Set()) ++ transitiveEdges.getOrElse(key, Set())) + } + intraMethodEdges.toMap + } + + def getAllEdges: Map[Int, Set[Int]] = { + val intraMethodEdges = getIntraMethodEdges + val keys = intraMethodEdges.keySet ++ edgesConnectingMethods.keySet + val allEdges = mutable.Map[Int, Set[Int]]() keys foreach {key => - res.update(key, edges.getOrElse(key, Set()) ++ transitiveEdges.getOrElse(key, Set())) + allEdges.update(key, intraMethodEdges.getOrElse(key, Set()) ++ edgesConnectingMethods.getOrElse(key, Set())) } - res.toMap + allEdges.toMap } // TODO ake: remove @@ -74,6 +88,21 @@ class DependencyGraph extends ReadOnlyDependencyGraph { targets foreach (addEdges(sources, _)) } + def addEdgesConnectingMethods(sources: Iterable[Int], target: Int): Unit = { + val oldSources = edgesConnectingMethods.getOrElse(target, Set.empty) + val newSources = sources.filter(_ != target) + if(newSources.nonEmpty) + edgesConnectingMethods.update(target, oldSources ++ newSources) + } + + def addEdgesConnectingMethods(sources: Iterable[Int], targets: Iterable[Int]): Unit = { + targets foreach (addEdgesConnectingMethods(sources, _)) + } + + def addEdgesConnectingMethods(source: Int, targets: Iterable[Int]): Unit = { + addEdgesConnectingMethods(Set(source), targets) + } + def existsAnyDependency(sources: Set[Int], targets: Set[Int], includeInfeasibilityNodes: Boolean): Boolean = { val infeasibilityNodeIds: Set[Int] = if(includeInfeasibilityNodes) Set.empty else (getNodes filter (_.isInstanceOf[InfeasibilityNode]) map (_.id)).toSet var visited: Set[Int] = Set.empty @@ -90,11 +119,11 @@ class DependencyGraph extends ReadOnlyDependencyGraph { false } - def getAllDependencies(targets: Set[Int], includeInfeasibilityNodes: Boolean): Set[Int] = { + def getAllDependencies(targets: Set[Int], includeInfeasibilityNodes: Boolean, includeEdgesConnectingMethods: Boolean=true): Set[Int] = { val infeasibilityNodeIds: Set[Int] = if(includeInfeasibilityNodes) Set.empty else (getNodes filter (_.isInstanceOf[InfeasibilityNode]) map (_.id)).toSet var visited: Set[Int] = Set.empty var queue: List[Int] = targets.toList - val allEdges = getAllEdges + val allEdges = if(includeEdgesConnectingMethods) getAllEdges else getIntraMethodEdges while(queue.nonEmpty){ val curr = queue.head val newVisits = allEdges.getOrElse(curr, Set()).diff(infeasibilityNodeIds) diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 32f41129e..817933f3b 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -11,6 +11,7 @@ import viper.silver.dependencyAnalysis.AbstractDependencyGraphInterpreter import java.io.PrintWriter import java.nio.file.Paths +import scala.collection.mutable class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependencyGraph, errors: List[Failure], member: Option[ast.Member]=None) extends AbstractDependencyGraphInterpreter{ @@ -23,7 +24,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen def getJoinCandidateNodes: Iterable[DependencyAnalysisNode] = joinCandidateNodes - protected lazy val joinCandidateNodes: Seq[DependencyAnalysisNode] = dependencyGraph.getNodes.filter(node => node.isInstanceOf[AxiomAssumptionNode] || AssumptionType.joinConditionTypes.contains(node.assumptionType)) + protected lazy val joinCandidateNodes: Seq[DependencyAnalysisNode] = dependencyGraph.getNodes.filter(node => node.isInstanceOf[AxiomAssumptionNode] || AssumptionType.joinConditionTypes.contains(node.assumptionType) || AssumptionType.methodCallTypes.contains(node.assumptionType)) private def toUserLevelNodes(nodes: Iterable[DependencyAnalysisNode]): Set[UserLevelDependencyAnalysisNode] = UserLevelDependencyAnalysisNode.from(nodes) @@ -214,6 +215,71 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen } def computeVerificationProgress(): (Double, Double, String) = { + computeVerificationProgressNaive() + } + + private def computeSpecQuality(): Double = { + val assertionNodeIds = getNonInternalAssertionNodes map (_.id) + val coveredNodes = toUserLevelNodes(getAllNonInternalDependencies(assertionNodeIds)).getSourceSet() + + val nonSourceCodeAssumptionTypes = AssumptionType.explicitAssumptionTypes ++ AssumptionType.verificationAnnotationTypes + val allSourceCodeNodes = toUserLevelNodes(getNonInternalAssumptionNodes).filter(n => nonSourceCodeAssumptionTypes.intersect(n.assumptionTypes).isEmpty).getSourceSet() + + val coveredSourceCodeNodes = coveredNodes.intersect(allSourceCodeNodes) + println(s"Covered:\n\t${coveredSourceCodeNodes.toList.sortBy(_.getLineNumber).mkString("\n\t")}") + println(s"Uncovered:\n\t${allSourceCodeNodes.diff(coveredSourceCodeNodes).toList.sortBy(_.getLineNumber).mkString("\n\t")}") + println(s"Spec Quality = ${coveredSourceCodeNodes.size} / ${allSourceCodeNodes.size}") + coveredSourceCodeNodes.size.toDouble / allSourceCodeNodes.size.toDouble + } + + val deps: DAMemo[AnalysisSourceInfo, Set[UserLevelDependencyAnalysisNode]] = DAMemo {assertionNode => + val allNonInternalAssertions = getNodes.filter(_.sourceInfo.getTopLevelSource.equals(assertionNode)) + val intraMethodDependencyIds = dependencyGraph.getAllDependencies(allNonInternalAssertions.map(_.id), includeInfeasibilityNodes=true, includeIntraMethodEdges=false) + + val intraMethodDependencies = toUserLevelNodes(getNonInternalAssumptionNodes.filter(node => intraMethodDependencyIds.contains(node.id) && !node.sourceInfo.getTopLevelSource.equals(assertionNode))) + + val postconditionNodeIds = intraMethodDependencyIds.flatMap(n => dependencyGraph.getEdgesConnectingMethods.getOrElse(n, Set.empty)) + val postconditionNodes = getNodes.filter(n => postconditionNodeIds.contains(n.id)) + + val transDeps = postconditionNodes.map(_.sourceInfo.getTopLevelSource).diff(Set(assertionNode)) flatMap deps + intraMethodDependencies ++ transDeps ++ toUserLevelNodes(postconditionNodes) + } + + private def computeAssertionQuality(assertionNode: AnalysisSourceInfo): Double = { + val allDependencies = deps(assertionNode) + val explicitDeps = allDependencies.filter(_.assumptionTypes.intersect(AssumptionType.explicitAssumptionTypes).nonEmpty).getSourceSet() + val allDeps = allDependencies.getSourceSet() + (allDeps.size - explicitDeps.size).toDouble / allDeps.size.toDouble + } + + def computeVerificationProgressOptimized(): (Double, Double, String) = { + val specQuality = computeSpecQuality() + + val allAssertions = getNonInternalAssertionNodes.map(_.sourceInfo.getTopLevelSource) + val assertionQualitiesTmp = allAssertions map (assertion => (computeAssertionQuality(assertion), assertion)) + val assertionQualities = assertionQualitiesTmp filterNot (_._1.isNaN) + val numAssertions = assertionQualities.size + val fullyVerifiedAssertions = assertionQualities.filter(_._1 == 1.0) + val numFullyVerifiedAssertions = fullyVerifiedAssertions.size + + val proofQualityPeter = numFullyVerifiedAssertions.toDouble / numAssertions.toDouble + + val assertionQualitiesSum = assertionQualities.map(_._1).sum + val proofQualityLea = assertionQualitiesSum / numAssertions.toDouble + + val info = { + s"Assertions with dependencies on explicit assumptions: ${assertionQualities.diff(fullyVerifiedAssertions).map(_._2).toList.sortBy(_.getLineNumber).mkString("\n\t")}" + + s"Assertions with perfect proof quality: ${fullyVerifiedAssertions.map(_._2).toList.sortBy(_.getLineNumber).mkString("\n\t")}" + + s"specQuality = $specQuality\n" + + s"proof quality (Peter): $numFullyVerifiedAssertions / $numAssertions = $proofQualityPeter\n" + + s"proof quality (Lea): $assertionQualitiesSum / $numAssertions = $proofQualityLea\n" + } + + (specQuality * proofQualityPeter, specQuality * proofQualityLea, info) + } + + + def computeVerificationProgressNaive(): (Double, Double, String) = { val allAssertions = toUserLevelNodes(getNonInternalAssertionNodes) // println(s"#assertions: ${allAssertions.size}") @@ -303,3 +369,11 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen allSourceCodeStmts.diff(coveredSourceCodeStmts).size } } + +case class DAMemo[A,B](f: A => B) extends (A => B) { + private val cache = mutable.Map.empty[A, B] + def apply(x: A) = cache getOrElseUpdate (x, f(x)) +} + + + diff --git a/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala b/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala index 2a0a21383..9e270dd08 100644 --- a/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala +++ b/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala @@ -55,7 +55,7 @@ object UserLevelDependencyAnalysisNode { } def getSourceSet(): Set[AnalysisSourceInfo] = { - left.map(_.source) + left.map(_.source.getTopLevelSource) } } } From 54733c2fad5540b9d6deb7687d35563e8be01af3 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 2 Feb 2026 10:31:51 +0100 Subject: [PATCH 333/474] optimize node storage (assumptions vs assertions) --- src/main/scala/decider/Decider.scala | 8 +-- .../DependencyAnalysisUserTool.scala | 2 +- .../DependencyAnalyzer.scala | 70 ++++++++++--------- .../dependencyAnalysis/DependencyGraph.scala | 43 +++++++++--- .../DependencyGraphInterpreter.scala | 6 +- 5 files changed, 79 insertions(+), 50 deletions(-) diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 7df128532..175579e62 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -480,10 +480,10 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => val result = isPathInfeasible() || prover.check(timeout, label) == Unsat if(isPathInfeasible()){ - checkNode foreach dependencyAnalyzer.addNode + checkNode foreach dependencyAnalyzer.addAssertionNode dependencyAnalyzer.addDependency(pcs.getCurrentInfeasibilityNode, checkNode.map(_.id)) }else if(result){ - checkNode foreach dependencyAnalyzer.addNode + checkNode foreach dependencyAnalyzer.addAssertionNode dependencyAnalyzer.processUnsatCoreAndAddDependencies(prover.getLastUnsatCore, label) val infeasibleNodeId = dependencyAnalyzer.addInfeasibilityNode(!isAssert, analysisSourceInfoStack.getFullSourceInfo, assumptionType) dependencyAnalyzer.addDependency(checkNode.map(_.id), infeasibleNodeId) @@ -535,9 +535,9 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => val result = asserted || proverAssert(t, timeout, DependencyAnalyzer.createAssertionLabel(assertNode map (_.id))) if(result){ - assertNode foreach dependencyAnalyzer.addNode + assertNode foreach dependencyAnalyzer.addAssertionNode }else if(!isCheck){ // TODO ake: only for asserts? - assertNode foreach {node => dependencyAnalyzer.addNode(node.getAssertFailedNode())} + assertNode foreach {node => dependencyAnalyzer.addAssertionNode(node.getAssertFailedNode())} } symbExLog.closeScope(sepIdentifier) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala index 7531ede5a..9be0dae7d 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala @@ -89,7 +89,7 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete val allAssumptions = interpreter.getNonInternalAssumptionNodes.filter(n => !n.isInstanceOf[AxiomAssumptionNode]) val assumptions = UserLevelDependencyAnalysisNode.from(allAssumptions) val assertions = UserLevelDependencyAnalysisNode.from(interpreter.getNonInternalAssertionNodes) - val nodes = UserLevelDependencyAnalysisNode.from(interpreter.getNonInternalAssertionNodes.union(allAssumptions)) + val nodes = UserLevelDependencyAnalysisNode.from(interpreter.getNonInternalAssertionNodes.asInstanceOf[Set[DependencyAnalysisNode]].union(allAssumptions.asInstanceOf[Set[DependencyAnalysisNode]])) println(s"#Assumptions = ${assumptions.size}") println(s"#Assertions = ${assertions.size}") println(s"#Nodes = ${nodes.size}") diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index d68efc275..4e5b69cbb 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -29,7 +29,8 @@ trait DependencyAnalyzer { def getChunkInhaleNode(chunk: Chunk): Option[PermissionInhaleNode] def addNodes(nodes: Iterable[DependencyAnalysisNode]): Unit - def addNode(node: DependencyAnalysisNode): Unit + def addAssertionNode(node: GeneralAssertionNode): Unit + def addAssumptionNode(node: GeneralAssumptionNode): Unit def addAssumption(assumption: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, description: Option[String] = None): Option[Int] def addAxiom(assumption: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, description: Option[String] = None): Option[Int] def registerInhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, labelNode: Option[LabelNode], analysisInfo: AnalysisInfo, isExhale: Boolean): CH = buildChunk(perm) @@ -175,7 +176,8 @@ object DependencyAnalyzer { var startTime = startTimeMeasurement() val newGraph = new DependencyGraph - newGraph.addNodes(dependencyGraphInterpreters.flatMap (_.getGraph.getNodes)) + newGraph.addAssumptionNodes(dependencyGraphInterpreters.flatMap (_.getGraph.getAssumptionNodes)) + newGraph.addAssertionNodes(dependencyGraphInterpreters.flatMap (_.getGraph.getAssertionNodes)) stopTimeMeasurementAndAddToTotal(startTime, timeToAddNodes) startTime = startTimeMeasurement() dependencyGraphInterpreters foreach (interpreter => interpreter.getGraph.getAllEdges foreach {case (t, deps) => newGraph.addEdges(deps, t)}) @@ -226,10 +228,10 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { override def getMember: Option[ast.Member] = Some(member) - override def getNodes: Iterable[DependencyAnalysisNode] = dependencyGraph.nodes + override def getNodes: Iterable[DependencyAnalysisNode] = dependencyGraph.getNodes override def getChunkInhaleNode(chunk: Chunk): Option[PermissionInhaleNode] = { - val inhaleNode = dependencyGraph.nodes + val inhaleNode = dependencyGraph.getAssumptionNodes .filter(c => c.isInstanceOf[PermissionInhaleNode] && chunk.equals(c.asInstanceOf[ChunkAnalysisInfo].getChunk)) .map(_.asInstanceOf[PermissionInhaleNode]) assert(inhaleNode.size == 1) @@ -237,36 +239,36 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { } private def getChunkNodeIds(oldChunks: Set[Chunk]): Set[Int] = { - dependencyGraph.nodes + dependencyGraph.getAssumptionNodes .filter(c => c.isInstanceOf[PermissionInhaleNode] && oldChunks.contains(c.asInstanceOf[ChunkAnalysisInfo].getChunk)) .map(_.id).toSet } private def getNodeIdsByTerm(terms: Set[Term]): Set[Int] = { - dependencyGraph.nodes + dependencyGraph.getNodes .filter(t => terms.contains(t.getTerm)) .map(_.id).toSet } override def addNodes(nodes: Iterable[DependencyAnalysisNode]): Unit = { - dependencyGraph.addNodes(nodes) + nodes foreach dependencyGraph.addNode } - override def addNode(node: DependencyAnalysisNode): Unit = { - dependencyGraph.addNode(node) - } + override def addAssumptionNode(node: GeneralAssumptionNode): Unit = dependencyGraph.addAssumptionNode(node) + + override def addAssertionNode(node: GeneralAssertionNode): Unit = dependencyGraph.addAssertionNode(node) // adding assumption nodes override def addAssumption(assumption: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, description: Option[String]): Option[Int] = { val node = SimpleAssumptionNode(assumption, description, analysisSourceInfo, assumptionType, isClosed_) - addNode(node) + addAssumptionNode(node) Some(node.id) } override def addAxiom(assumption: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, description: Option[String]): Option[Int] = { val node = AxiomAssumptionNode(assumption, description, analysisSourceInfo, assumptionType, isClosed_) - addNode(node) + addAssumptionNode(node) Some(node.id) } @@ -293,20 +295,20 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { private def addPermissionInhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, labelNode: LabelNode): Option[Int] = { val node = PermissionInhaleNode(chunk, permAmount, sourceInfo, assumptionType, isClosed_, labelNode) - addNode(node) + addAssumptionNode(node) Some(node.id) } private def addPermissionExhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { val node = PermissionExhaleNode(chunk, permAmount, sourceInfo, assumptionType, isClosed_) - addNode(node) + addAssertionNode(node) addPermissionDependencies(Set(chunk), Set(), Some(node.id)) Some(node.id) } override def createLabelNode(label: Var, sourceChunks: Iterable[Chunk], sourceTerms: Iterable[Term]): Option[LabelNode] = { val labelNode = LabelNode(label) - addNode(labelNode) + addAssumptionNode(labelNode) dependencyGraph.addEdges(getChunkNodeIds(sourceChunks.toSet) ++ getNodeIdsByTerm(sourceTerms.toSet), labelNode.id) Some(labelNode) } @@ -321,19 +323,19 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { def addAssertNode(term: Term, assumptionType: AssumptionType, analysisSourceInfo: AnalysisSourceInfo): Option[Int] = { val node = createAssertOrCheckNode(term, assumptionType, analysisSourceInfo, isCheck=false) - node foreach addNode + node foreach addAssertionNode node map (_.id) } override def addAssertFalseNode(isCheck: Boolean, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo): Option[Int] = { val node = createAssertOrCheckNode(False, assumptionType, sourceInfo, isCheck) - addNode(node.get) + addAssertionNode(node.get) node.map(_.id) } override def addInfeasibilityNode(isCheck: Boolean, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { val node = InfeasibilityNode(sourceInfo, assumptionType) - addNode(node) + addAssumptionNode(node) Some(node.id) } @@ -366,7 +368,7 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { override def addPermissionDependencies(sourceChunks: Set[Chunk], sourceTerms: Set[Term], newChunk: Chunk): Unit = { val startTime = startTimeMeasurement() - val newChunkId = dependencyGraph.nodes + val newChunkId = dependencyGraph.getAssumptionNodes .filter(c => c.isInstanceOf[PermissionInhaleNode] && c.isInstanceOf[ChunkAnalysisInfo] && newChunk.equals(c.asInstanceOf[ChunkAnalysisInfo].getChunk)) .map(_.id).toSet addPermissionDependencies(sourceChunks, sourceTerms, newChunkId.headOption) @@ -401,7 +403,7 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { } override def addFunctionAxiomEdges(): Unit = { - val axiomNodes = getNodes.filter(_.isInstanceOf[AxiomAssumptionNode]) + val axiomNodes = dependencyGraph.getAssumptionNodes.filter(_.isInstanceOf[AxiomAssumptionNode]) val postcondNodes = getNodes.filter(n => AssumptionType.postconditionTypes.contains(n.assumptionType)) axiomNodes foreach {aNode => val pNodes = postcondNodes filter (_.sourceInfo.toString.equals(aNode.sourceInfo.toString)) map (_.id) @@ -421,28 +423,30 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { val mergedGraph = new DependencyGraph val nodeMap = mutable.HashMap[Int, Int]() - dependencyGraph.getNodes.filter(keepNode).foreach { n => + + dependencyGraph.getAssumptionNodes.filter(keepNode).foreach { n => nodeMap.put(n.id, n.id) - mergedGraph.addNode(n) + mergedGraph.addAssumptionNode(n) } - - val nodesBySource = dependencyGraph.getNodes.filter(!keepNode(_)).groupBy(n => (n.sourceInfo, n.assumptionType)) - - nodesBySource foreach { case ((sourceInfo, assumptionType), nodes) => - val assumptionNodes = nodes.filter(_.isInstanceOf[GeneralAssumptionNode]) + val assumptionNodesBySource = dependencyGraph.getAssumptionNodes.filter(!keepNode(_)).groupBy(n => (n.sourceInfo, n.assumptionType)) + assumptionNodesBySource foreach { case ((sourceInfo, assumptionType), assumptionNodes) => if (assumptionNodes.nonEmpty) { val newNode = SimpleAssumptionNode(True, None, sourceInfo, assumptionType, isClosed = false) assumptionNodes foreach (n => nodeMap.put(n.id, newNode.id)) - mergedGraph.addNode(newNode) + mergedGraph.addAssumptionNode(newNode) } } - nodesBySource foreach { case ((sourceInfo, assumptionType), nodes) => - val assertionNodes = nodes.filter(_.isInstanceOf[GeneralAssertionNode]).map(_.asInstanceOf[GeneralAssertionNode]) + dependencyGraph.getAssertionNodes.filter(keepNode).foreach { n => + nodeMap.put(n.id, n.id) + mergedGraph.addAssertionNode(n) + } + val assertionNodesBySource = dependencyGraph.getAssertionNodes.filter(!keepNode(_)).groupBy(n => (n.sourceInfo, n.assumptionType)) + assertionNodesBySource foreach { case ((sourceInfo, assumptionType), assertionNodes) => if (assertionNodes.nonEmpty) { val newNode = SimpleAssertionNode(True, assumptionType, sourceInfo, isClosed = false, hasFailed=assertionNodes.exists(_.hasFailed)) assertionNodes foreach (n => nodeMap.put(n.id, newNode.id)) - mergedGraph.addNode(newNode) + mergedGraph.addAssertionNode(newNode) } } @@ -480,7 +484,8 @@ class NoDependencyAnalyzer extends DependencyAnalyzer { override def getChunkInhaleNode(chunk: Chunk): Option[PermissionInhaleNode] = None override def addNodes(nodes: Iterable[DependencyAnalysisNode]): Unit = {} - override def addNode(node: DependencyAnalysisNode): Unit = {} + override def addAssertionNode(node: GeneralAssertionNode): Unit = {} + override def addAssumptionNode(node: GeneralAssumptionNode): Unit = {} override def addAssumption(assumption: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, description: Option[String] = None): Option[Int] = None override def addAxiom(assumption: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, description: Option[String]): Option[Int] = None override def createLabelNode(labelTerm: Var, sourceChunks: Iterable[Chunk], sourceTerms: Iterable[Term]): Option[LabelNode] = None @@ -497,4 +502,5 @@ class NoDependencyAnalyzer extends DependencyAnalyzer { override def addFunctionAxiomEdges(): Unit = {} override def buildFinalGraph(): Option[DependencyGraph] = None + } \ No newline at end of file diff --git a/src/main/scala/dependencyAnalysis/DependencyGraph.scala b/src/main/scala/dependencyAnalysis/DependencyGraph.scala index f40d94b13..4ff6f068d 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraph.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraph.scala @@ -18,6 +18,8 @@ object DependencyGraphHelper { trait ReadOnlyDependencyGraph extends AbstractReadOnlyDependencyGraph { def getNodes: Seq[DependencyAnalysisNode] + def getAssumptionNodes: Seq[GeneralAssumptionNode] + def getAssertionNodes: Seq[GeneralAssertionNode] def getDirectEdges: Map[Int, Set[Int]] // target -> direct dependencies def getTransitiveEdges: Map[Int, Set[Int]] // target -> direct dependencies def getEdgesConnectingMethods: Map[Int, Set[Int]] @@ -31,12 +33,15 @@ trait ReadOnlyDependencyGraph extends AbstractReadOnlyDependencyGraph { } class DependencyGraph extends ReadOnlyDependencyGraph { - var nodes: mutable.Seq[DependencyAnalysisNode] = mutable.Seq() + private var assumptionNodes: mutable.Seq[GeneralAssumptionNode] = mutable.Seq() + private var assertionNodes: mutable.Seq[GeneralAssertionNode] = mutable.Seq() private val edges: mutable.Map[Int, Set[Int]] = mutable.Map.empty private val transitiveEdges: mutable.Map[Int, Set[Int]] = mutable.Map.empty // TODO ake: can be merged into edges? private val edgesConnectingMethods: mutable.Map[Int, Set[Int]] = mutable.Map.empty // keep this, it's relevant for computing verification progress - def getNodes: Seq[DependencyAnalysisNode] = nodes.toSeq + def getNodes: Seq[DependencyAnalysisNode] = getAssumptionNodes ++ getAssertionNodes + def getAssumptionNodes: Seq[GeneralAssumptionNode] = assumptionNodes.toSeq + def getAssertionNodes: Seq[GeneralAssertionNode] = assertionNodes.toSeq def getDirectEdges: Map[Int, Set[Int]] = edges.toMap def getTransitiveEdges: Map[Int, Set[Int]] = transitiveEdges.toMap def getEdgesConnectingMethods: Map[Int, Set[Int]] = edgesConnectingMethods.toMap @@ -65,12 +70,28 @@ class DependencyGraph extends ReadOnlyDependencyGraph { newEdges.foreach(e => edges.update(e._1, e._2)) } + def addAssumptionNode(node: GeneralAssumptionNode): Unit = { + assumptionNodes = assumptionNodes :+ node + } + + def addAssumptionNodes(newNodes: Iterable[GeneralAssumptionNode]): Unit = { + assumptionNodes = assumptionNodes ++ newNodes + } + def addNode(node: DependencyAnalysisNode): Unit = { - nodes = nodes :+ node + node match { + case node: GeneralAssertionNode => addAssertionNode(node) + case node: GeneralAssumptionNode => addAssumptionNode(node) + case _ => assert(false) + } + } + + def addAssertionNode(node: GeneralAssertionNode): Unit = { + assertionNodes = assertionNodes :+ node } - def addNodes(newNodes: Iterable[DependencyAnalysisNode]): Unit = { - nodes = nodes ++ newNodes + def addAssertionNodes(newNodes: Iterable[GeneralAssertionNode]): Unit = { + assertionNodes = assertionNodes ++ newNodes } def addEdges(source: Int, targets: Iterable[Int]): Unit = { @@ -104,7 +125,7 @@ class DependencyGraph extends ReadOnlyDependencyGraph { } def existsAnyDependency(sources: Set[Int], targets: Set[Int], includeInfeasibilityNodes: Boolean): Boolean = { - val infeasibilityNodeIds: Set[Int] = if(includeInfeasibilityNodes) Set.empty else (getNodes filter (_.isInstanceOf[InfeasibilityNode]) map (_.id)).toSet + val infeasibilityNodeIds: Set[Int] = if(includeInfeasibilityNodes) Set.empty else (getAssumptionNodes filter (_.isInstanceOf[InfeasibilityNode]) map (_.id)).toSet var visited: Set[Int] = Set.empty var queue: List[Int] = targets.toList val allEdges = getAllEdges @@ -120,7 +141,7 @@ class DependencyGraph extends ReadOnlyDependencyGraph { } def getAllDependencies(targets: Set[Int], includeInfeasibilityNodes: Boolean, includeEdgesConnectingMethods: Boolean=true): Set[Int] = { - val infeasibilityNodeIds: Set[Int] = if(includeInfeasibilityNodes) Set.empty else (getNodes filter (_.isInstanceOf[InfeasibilityNode]) map (_.id)).toSet + val infeasibilityNodeIds: Set[Int] = if(includeInfeasibilityNodes) Set.empty else (getAssumptionNodes filter (_.isInstanceOf[InfeasibilityNode]) map (_.id)).toSet var visited: Set[Int] = Set.empty var queue: List[Int] = targets.toList val allEdges = if(includeEdgesConnectingMethods) getAllEdges else getIntraMethodEdges @@ -134,7 +155,7 @@ class DependencyGraph extends ReadOnlyDependencyGraph { } def getAllDependents(sources: Set[Int], includeInfeasibilityNodes: Boolean): Set[Int] = { - val infeasibilityNodeIds: Set[Int] = if(includeInfeasibilityNodes) Set.empty else (getNodes filter (_.isInstanceOf[InfeasibilityNode]) map (_.id)).toSet + val infeasibilityNodeIds: Set[Int] = if(includeInfeasibilityNodes) Set.empty else (getAssumptionNodes filter (_.isInstanceOf[InfeasibilityNode]) map (_.id)).toSet var visited: Set[Int] = Set.empty var queue: Set[Int] = sources val allEdges = getAllEdges @@ -185,8 +206,8 @@ class DependencyGraph extends ReadOnlyDependencyGraph { // TODO ake: maybe move to DependencyAnalyzer? def removeLabelNodes(): Unit = { - nodes filter (_.isInstanceOf[LabelNode]) foreach removeAllEdgesForNode - nodes = nodes filter (!_.isInstanceOf[LabelNode]) + assumptionNodes filter (_.isInstanceOf[LabelNode]) foreach removeAllEdgesForNode + assumptionNodes = assumptionNodes filter (!_.isInstanceOf[LabelNode]) } def exportGraph(dirName: String): Unit = { @@ -213,7 +234,7 @@ class DependencyGraph extends ReadOnlyDependencyGraph { val writer = new PrintWriter(fileName) val headerParts = mutable.Seq("id", "node type", "assumption type", "node info", "source info", "position", "fine grained source") writer.println(headerParts.mkString(sep)) - nodes foreach (n => writer.println(getNodeExportString(n).replace("\n", " "))) + getNodes foreach (n => writer.println(getNodeExportString(n).replace("\n", " "))) writer.close() } } diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 817933f3b..3a7e1938f 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -20,6 +20,8 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen def getName: String = name def getMember: Option[ast.Member] = member def getNodes: Set[DependencyAnalysisNode] = dependencyGraph.getNodes.toSet + def getAssumptionNodes: Set[DependencyAnalysisNode] = dependencyGraph.getAssumptionNodes.toSet + def getAssertionNodes: Set[DependencyAnalysisNode] = dependencyGraph.getAssertionNodes.toSet def getErrors: List[Failure] = errors def getJoinCandidateNodes: Iterable[DependencyAnalysisNode] = joinCandidateNodes @@ -37,7 +39,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen def getDirectDependencies(nodeIdsToAnalyze: Set[Int]): Set[DependencyAnalysisNode] = { var queue = nodeIdsToAnalyze var result: Set[Int] = Set.empty - val internalNodeIds = getNodes.diff(getNonInternalAssumptionNodes).map(_.id) + val internalNodeIds = getAssumptionNodes.diff(getNonInternalAssumptionNodes).map(_.id) while(queue.nonEmpty){ val directDependencyIds = queue flatMap (id => dependencyGraph.getDirectEdges.getOrElse(id, Set.empty)) queue = internalNodeIds.intersect(directDependencyIds).diff(result) // internal assumptions are hidden -> add their direct dependencies instead @@ -233,7 +235,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen } val deps: DAMemo[AnalysisSourceInfo, Set[UserLevelDependencyAnalysisNode]] = DAMemo {assertionNode => - val allNonInternalAssertions = getNodes.filter(_.sourceInfo.getTopLevelSource.equals(assertionNode)) + val allNonInternalAssertions = getAssertionNodes.filter(_.sourceInfo.getTopLevelSource.equals(assertionNode)) val intraMethodDependencyIds = dependencyGraph.getAllDependencies(allNonInternalAssertions.map(_.id), includeInfeasibilityNodes=true, includeIntraMethodEdges=false) val intraMethodDependencies = toUserLevelNodes(getNonInternalAssumptionNodes.filter(node => intraMethodDependencyIds.contains(node.id) && !node.sourceInfo.getTopLevelSource.equals(assertionNode))) From a12e784e5ead727f68bd6e1c27a361137522bbfe Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 2 Feb 2026 10:33:30 +0100 Subject: [PATCH 334/474] minor fix --- .../scala/dependencyAnalysis/DependencyAnalysisUserTool.scala | 2 +- .../scala/dependencyAnalysis/DependencyGraphInterpreter.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala index 9be0dae7d..beff9ff35 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala @@ -143,7 +143,7 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete private def handleVerificationProgressQuery(): Unit = { if(verificationErrors.nonEmpty) println(s"Fix verification failures first!") - val ((optProgressPeter, optProgressLea, optInfo), optTime) = measureTime(fullGraphInterpreter.computeVerificationProgressOptimized()) + val ((optProgressPeter, optProgressLea, optInfo), optTime) = measureTime(fullGraphInterpreter.computeVerificationProgress()) // println(s"Overall verification progress: $progress") println(s"$optInfo") println(s"Peter: $optProgressPeter; Lea: $optProgressLea") diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 3a7e1938f..5cd68b084 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -217,7 +217,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen } def computeVerificationProgress(): (Double, Double, String) = { - computeVerificationProgressNaive() + computeVerificationProgressOptimized() } private def computeSpecQuality(): Double = { From 212a37ed340de72987d92d4ea8e32e223d1bf564 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 2 Feb 2026 11:14:26 +0100 Subject: [PATCH 335/474] combine spec and proof quality computation --- .../DependencyAnalysisUserTool.scala | 2 +- .../DependencyGraphInterpreter.scala | 28 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala index beff9ff35..9f1ce8b78 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala @@ -166,7 +166,7 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete println(s"$optInfo") println(s"Peter: $optProgressPeter; Lea: $optProgressLea") println(s"Finished in ${optTime}ms") - if(Math.abs(naiveProgressPeter - optProgressPeter) > 1e4 || Math.abs(naiveProgressLea - naiveProgressLea) > 1e4) println("Progress is not equal!") + if(Math.abs(naiveProgressPeter - optProgressPeter) > 1e-4 || Math.abs(naiveProgressLea - naiveProgressLea) > 1e-4) println("Progress is not equal!") } protected def getSourceInfoString(nodes: Set[DependencyAnalysisNode]): String = { diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 5cd68b084..0d4f161b3 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -10,6 +10,7 @@ import viper.silver.ast.{If, Stmt} import viper.silver.dependencyAnalysis.AbstractDependencyGraphInterpreter import java.io.PrintWriter +import java.lang.Double.isNaN import java.nio.file.Paths import scala.collection.mutable @@ -220,14 +221,12 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen computeVerificationProgressOptimized() } - private def computeSpecQuality(): Double = { - val assertionNodeIds = getNonInternalAssertionNodes map (_.id) - val coveredNodes = toUserLevelNodes(getAllNonInternalDependencies(assertionNodeIds)).getSourceSet() + private def computeSpecQuality(coveredNodes: Set[UserLevelDependencyAnalysisNode]): Double = { val nonSourceCodeAssumptionTypes = AssumptionType.explicitAssumptionTypes ++ AssumptionType.verificationAnnotationTypes val allSourceCodeNodes = toUserLevelNodes(getNonInternalAssumptionNodes).filter(n => nonSourceCodeAssumptionTypes.intersect(n.assumptionTypes).isEmpty).getSourceSet() - val coveredSourceCodeNodes = coveredNodes.intersect(allSourceCodeNodes) + val coveredSourceCodeNodes = coveredNodes.getSourceSet().intersect(allSourceCodeNodes) println(s"Covered:\n\t${coveredSourceCodeNodes.toList.sortBy(_.getLineNumber).mkString("\n\t")}") println(s"Uncovered:\n\t${allSourceCodeNodes.diff(coveredSourceCodeNodes).toList.sortBy(_.getLineNumber).mkString("\n\t")}") println(s"Spec Quality = ${coveredSourceCodeNodes.size} / ${allSourceCodeNodes.size}") @@ -247,31 +246,32 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen intraMethodDependencies ++ transDeps ++ toUserLevelNodes(postconditionNodes) } - private def computeAssertionQuality(assertionNode: AnalysisSourceInfo): Double = { - val allDependencies = deps(assertionNode) + private def computeAssertionQuality(allDependencies: Set[UserLevelDependencyAnalysisNode]): Double = { val explicitDeps = allDependencies.filter(_.assumptionTypes.intersect(AssumptionType.explicitAssumptionTypes).nonEmpty).getSourceSet() val allDeps = allDependencies.getSourceSet() (allDeps.size - explicitDeps.size).toDouble / allDeps.size.toDouble } def computeVerificationProgressOptimized(): (Double, Double, String) = { - val specQuality = computeSpecQuality() - val allAssertions = getNonInternalAssertionNodes.map(_.sourceInfo.getTopLevelSource) - val assertionQualitiesTmp = allAssertions map (assertion => (computeAssertionQuality(assertion), assertion)) - val assertionQualities = assertionQualitiesTmp filterNot (_._1.isNaN) + val allAssertions = getNonInternalAssertionNodes.map(_.sourceInfo.getTopLevelSource).toList + val assertionDeps = allAssertions map deps + + val specQuality = computeSpecQuality(assertionDeps.flatten.toSet) + + val assertionQualities = assertionDeps map computeAssertionQuality filterNot isNaN val numAssertions = assertionQualities.size - val fullyVerifiedAssertions = assertionQualities.filter(_._1 == 1.0) + val fullyVerifiedAssertions = assertionQualities.filter(_ == 1.0) val numFullyVerifiedAssertions = fullyVerifiedAssertions.size val proofQualityPeter = numFullyVerifiedAssertions.toDouble / numAssertions.toDouble - val assertionQualitiesSum = assertionQualities.map(_._1).sum + val assertionQualitiesSum = assertionQualities.sum val proofQualityLea = assertionQualitiesSum / numAssertions.toDouble val info = { - s"Assertions with dependencies on explicit assumptions: ${assertionQualities.diff(fullyVerifiedAssertions).map(_._2).toList.sortBy(_.getLineNumber).mkString("\n\t")}" + - s"Assertions with perfect proof quality: ${fullyVerifiedAssertions.map(_._2).toList.sortBy(_.getLineNumber).mkString("\n\t")}" + +// s"Assertions with dependencies on explicit assumptions: ${assertionQualities.diff(fullyVerifiedAssertions).map(_._3).toList.sortBy(_.getLineNumber).mkString("\n\t")}" + +// s"Assertions with perfect proof quality: ${fullyVerifiedAssertions.map(_._3).toList.sortBy(_.getLineNumber).mkString("\n\t")}" + s"specQuality = $specQuality\n" + s"proof quality (Peter): $numFullyVerifiedAssertions / $numAssertions = $proofQualityPeter\n" + s"proof quality (Lea): $assertionQualitiesSum / $numAssertions = $proofQualityLea\n" From cfd76f651aff099baecc31e10c62d80a462e6ea7 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 2 Feb 2026 16:17:02 +0100 Subject: [PATCH 336/474] fix assumption and assertion types --- src/main/scala/decider/Decider.scala | 27 +++++-- .../dependencyAnalysis/AnalysisInfo.scala | 22 +++++- .../DependencyAnalysisUserTool.scala | 2 +- .../DependencyAnalyzer.scala | 7 +- .../DependencyGraphInterpreter.scala | 35 ++++----- .../dependencyAnalysis/SourceInfoStack.scala | 71 ++++++++++++------- src/main/scala/rules/Brancher.scala | 11 ++- src/main/scala/rules/ChunkSupporter.scala | 4 +- src/main/scala/rules/Consumer.scala | 4 +- src/main/scala/rules/Evaluator.scala | 30 ++++---- src/main/scala/rules/Executor.scala | 12 ++-- src/main/scala/rules/HeapSupporter.scala | 8 +-- src/main/scala/rules/Joiner.scala | 4 +- .../rules/MoreCompleteExhaleSupporter.scala | 2 +- src/main/scala/rules/PredicateSupporter.scala | 2 +- src/main/scala/rules/Producer.scala | 6 +- .../scala/rules/QuantifiedChunkSupport.scala | 2 +- .../functions/FunctionVerificationUnit.scala | 9 ++- .../all/function-sum.vpr | 4 +- .../dependencyAnalysisTests/all/functions.vpr | 10 +-- .../unitTests/E-function-call.vpr | 8 +-- 21 files changed, 167 insertions(+), 113 deletions(-) diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 175579e62..bba73ad08 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -52,7 +52,9 @@ trait Decider { def pushScope(): Unit def popScope(): Unit - def checkSmoke(isAssert: Boolean = false, assumptionType: AssumptionType=AssumptionType.Implicit): Boolean + def checkSmoke(assumptionType: AssumptionType): Boolean + def checkSmoke(assumptionType: AssumptionType, isAssert: Boolean): Boolean + def checkSmoke(isAssert: Boolean = false): Boolean def setCurrentBranchCondition(t: Term, te: (ast.Exp, Option[ast.Exp]), assumptionType: AssumptionType): Unit def setPathConditionMark(): Mark @@ -74,14 +76,17 @@ trait Decider { def assume(terms: Iterable[Term], debugExp: Option[DebugExp], enforceAssumption: Boolean, assumptionType: AssumptionType): Unit def assumeLabel(term: Term, assumptionLabel: String): Unit - def check(t: Term, timeout: Int, assumptionType: AssumptionType=AssumptionType.Implicit): Boolean + def check(t: Term, timeout: Int): Boolean + def check(t: Term, timeout: Int, assumptionType: AssumptionType): Boolean def checkAndGetInfeasibilityNode(t: Term, timeout: Int, assumptionType: AssumptionType): (Boolean, Option[Int]) /* TODO: Consider changing assert such that * 1. It passes State and Operations to the continuation * 2. The implementation reacts to a failing assertion by e.g. a state consolidation */ - def assert(t: Term, assumptionType: AssumptionType=AssumptionType.Implicit, timeout: Option[Int] = None)(Q: Boolean => VerificationResult): VerificationResult + def assert(t: Term, timeout: Option[Int] = None)(Q: Boolean => VerificationResult): VerificationResult + def assert(t: Term, assumptionType: AssumptionType)(Q: Boolean => VerificationResult): VerificationResult + def assert(t: Term, assumptionType: AssumptionType, timeout: Option[Int])(Q: Boolean => VerificationResult): VerificationResult def fresh(id: String, sort: Sort, ptype: Option[PType]): Var def fresh(id: String, argSorts: Seq[Sort], resultSort: Sort): Function @@ -472,7 +477,11 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => /* Asserting facts */ - def checkSmoke(isAssert: Boolean = false, assumptionType: AssumptionType=AssumptionType.Implicit): Boolean = { + def checkSmoke(assumptionType: AssumptionType): Boolean = checkSmoke(assumptionType, isAssert = false) + + def checkSmoke(isAssert: Boolean=false): Boolean = checkSmoke(analysisSourceInfoStack.getAssertionType, isAssert) + + def checkSmoke(assumptionType: AssumptionType, isAssert: Boolean): Boolean = { val checkNode = dependencyAnalyzer.createAssertOrCheckNode(False, assumptionType, analysisSourceInfoStack.getFullSourceInfo, !isAssert) val label = DependencyAnalyzer.createAssertionLabel(checkNode.map(_.id)) @@ -492,7 +501,9 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => result } - def check(t: Term, timeout: Int, assumptionType: AssumptionType=AssumptionType.Implicit): Boolean = { + def check(t: Term, timeout: Int): Boolean = check(t, timeout, analysisSourceInfoStack.getAssertionType) + + def check(t: Term, timeout: Int, assumptionType: AssumptionType): Boolean = { deciderAssert(t, assumptionType, Some(timeout), isCheck=true)._1 } @@ -509,7 +520,11 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => (success, infeasibilityNodeId) } - def assert(t: Term, assumptionType: AssumptionType=AssumptionType.Implicit, timeout: Option[Int] = Verifier.config.assertTimeout.toOption)(Q: Boolean => VerificationResult): VerificationResult = { + def assert(t: Term, timeout: Option[Int] = Verifier.config.assertTimeout.toOption)(Q: Boolean => VerificationResult): VerificationResult = assert(t, analysisSourceInfoStack.getAssertionType, timeout)(Q) + + def assert(t: Term, assumptionType: AssumptionType)(Q: Boolean => VerificationResult): VerificationResult = assert(t, assumptionType, timeout=Verifier.config.assertTimeout.toOption)(Q) + + def assert(t: Term, assumptionType: AssumptionType, timeout: Option[Int])(Q: Boolean => VerificationResult): VerificationResult = { val (success, _) = deciderAssert(t, assumptionType, timeout) // If the SMT query was not successful, store it (possibly "overwriting" diff --git a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala index ed02e809d..08771da94 100644 --- a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala +++ b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala @@ -13,12 +13,13 @@ object AssumptionType extends Enumeration { def internalTypes: Set[AssumptionType] = Set(Internal, Trigger) // will always be hidden from user def joinConditionTypes: Set[AssumptionType] = postconditionTypes ++ Set(FunctionBody) def verificationAnnotationTypes: Set[AssumptionType] = Set(LoopInvariant, Rewrite, ExplicitPostcondition, ImplicitPostcondition, Precondition, Explicit) - def sourceCodeTypes: Set[AssumptionType] = Set(SourceCode, PathCondition, MethodCall, FunctionBody, Implicit) + def sourceCodeTypes: Set[AssumptionType] = Set(SourceCode, PathCondition, MethodCall, FunctionBody) def methodCallTypes: Set[AssumptionType] = Set(MethodCall) // used to join graphs via postconditions } import viper.silicon.dependencyAnalysis.AssumptionType._ import viper.silicon.decider.Decider +import viper.silver.ast object DependencyType { @@ -36,6 +37,25 @@ object DependencyType { def make(singleType: AssumptionType): DependencyType = DependencyType(singleType, singleType) + def get(stmt: ast.Stmt): DependencyType = { + val annotatedDependencyType = DependencyAnalyzer.extractDependencyTypeFromInfo(stmt.info) + if(annotatedDependencyType.isDefined) return annotatedDependencyType.get + + stmt match { + case _: ast.MethodCall => MethodCall + case _: ast.NewStmt | _: ast.AbstractAssign => SourceCode + case _: ast.Exhale | _: ast.Assert => ExplicitAssertion + case _: ast.Inhale | _: ast.Assume => ExplicitAssumption + case _: ast.Fold | _: ast.Unfold | _: ast.Package | _: ast.Apply => Rewrite + case _: ast.Quasihavoc | _: ast.Quasihavocall => Implicit + case _ => Implicit /* TODO: should not happen */ + } + } + + def get(exp: ast.Exp, dependencyType: DependencyType): DependencyType = DependencyAnalyzer.extractDependencyTypeFromInfo(exp.info).getOrElse(dependencyType) + + def get(exp: ast.Exp): DependencyType = get(exp, Implicit) + } case class DependencyType(assumptionType: AssumptionType, assertionType: AssumptionType) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala index 9f1ce8b78..7ada592c8 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala @@ -166,7 +166,7 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete println(s"$optInfo") println(s"Peter: $optProgressPeter; Lea: $optProgressLea") println(s"Finished in ${optTime}ms") - if(Math.abs(naiveProgressPeter - optProgressPeter) > 1e-4 || Math.abs(naiveProgressLea - naiveProgressLea) > 1e-4) println("Progress is not equal!") + if(Math.abs(naiveProgressPeter - optProgressPeter) > 0.001 || Math.abs(naiveProgressLea - optProgressLea) > 0.001) println("Progress is not equal!") } protected def getSourceInfoString(nodes: Set[DependencyAnalysisNode]): String = { diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index 4e5b69cbb..76d622283 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -376,10 +376,9 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { } override def addCustomTransitiveDependency(sourceSourceInfo: AnalysisSourceInfo, targetSourceInfo: AnalysisSourceInfo): Unit = { - // TODO ake: remove this since this is already done in buildFinalGraph()? -// val sourceNodes = assumptionGraph.nodes filter (n => n.isInstanceOf[GeneralAssertionNode] && n.sourceInfo.getSourceForTransitiveEdges.equals(sourceSourceInfo.getSourceForTransitiveEdges)) -// val targetNodes = assumptionGraph.nodes filter (n => n.isInstanceOf[GeneralAssumptionNode] && n.sourceInfo.getSourceForTransitiveEdges.equals(targetSourceInfo.getSourceForTransitiveEdges)) -// assumptionGraph.addEdges(sourceNodes map (_.id), targetNodes map (_.id)) + val sourceNodes = dependencyGraph.getAssertionNodes filter (n => n.sourceInfo.getSourceForTransitiveEdges.equals(sourceSourceInfo.getSourceForTransitiveEdges)) + val targetNodes = dependencyGraph.getAssumptionNodes filter (n => n.sourceInfo.getSourceForTransitiveEdges.equals(targetSourceInfo.getSourceForTransitiveEdges)) + dependencyGraph.addEdges(sourceNodes map (_.id), targetNodes map (_.id)) } override def addDependenciesForExplicitPostconditions(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp]): Unit = { diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 0d4f161b3..d1749d276 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -237,41 +237,41 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen val allNonInternalAssertions = getAssertionNodes.filter(_.sourceInfo.getTopLevelSource.equals(assertionNode)) val intraMethodDependencyIds = dependencyGraph.getAllDependencies(allNonInternalAssertions.map(_.id), includeInfeasibilityNodes=true, includeIntraMethodEdges=false) - val intraMethodDependencies = toUserLevelNodes(getNonInternalAssumptionNodes.filter(node => intraMethodDependencyIds.contains(node.id) && !node.sourceInfo.getTopLevelSource.equals(assertionNode))) + val intraMethodDependencies = getNonInternalAssumptionNodes.filter(node => intraMethodDependencyIds.contains(node.id) && !node.sourceInfo.getTopLevelSource.equals(assertionNode)) val postconditionNodeIds = intraMethodDependencyIds.flatMap(n => dependencyGraph.getEdgesConnectingMethods.getOrElse(n, Set.empty)) val postconditionNodes = getNodes.filter(n => postconditionNodeIds.contains(n.id)) val transDeps = postconditionNodes.map(_.sourceInfo.getTopLevelSource).diff(Set(assertionNode)) flatMap deps - intraMethodDependencies ++ transDeps ++ toUserLevelNodes(postconditionNodes) + toUserLevelNodes(transDeps.flatMap(_.lowerLevelNodes) ++ intraMethodDependencies ++ postconditionNodes) } private def computeAssertionQuality(allDependencies: Set[UserLevelDependencyAnalysisNode]): Double = { val explicitDeps = allDependencies.filter(_.assumptionTypes.intersect(AssumptionType.explicitAssumptionTypes).nonEmpty).getSourceSet() - val allDeps = allDependencies.getSourceSet() - (allDeps.size - explicitDeps.size).toDouble / allDeps.size.toDouble + val numDepsTotal = allDependencies.getSourceSet().size + (numDepsTotal - explicitDeps.size).toDouble / numDepsTotal.toDouble } def computeVerificationProgressOptimized(): (Double, Double, String) = { val allAssertions = getNonInternalAssertionNodes.map(_.sourceInfo.getTopLevelSource).toList - val assertionDeps = allAssertions map deps + val assertionDeps = allAssertions map (ass => (deps(ass), ass)) - val specQuality = computeSpecQuality(assertionDeps.flatten.toSet) + val specQuality = computeSpecQuality(assertionDeps.flatMap(_._1).toSet) - val assertionQualities = assertionDeps map computeAssertionQuality filterNot isNaN + val assertionQualities = assertionDeps map (ass => (computeAssertionQuality(ass._1), ass._2)) filterNot (n => isNaN(n._1)) val numAssertions = assertionQualities.size - val fullyVerifiedAssertions = assertionQualities.filter(_ == 1.0) + val fullyVerifiedAssertions = assertionQualities.filter(_._1 == 1.0) val numFullyVerifiedAssertions = fullyVerifiedAssertions.size val proofQualityPeter = numFullyVerifiedAssertions.toDouble / numAssertions.toDouble - val assertionQualitiesSum = assertionQualities.sum + val assertionQualitiesSum = assertionQualities.map(_._1).sum val proofQualityLea = assertionQualitiesSum / numAssertions.toDouble val info = { -// s"Assertions with dependencies on explicit assumptions: ${assertionQualities.diff(fullyVerifiedAssertions).map(_._3).toList.sortBy(_.getLineNumber).mkString("\n\t")}" + -// s"Assertions with perfect proof quality: ${fullyVerifiedAssertions.map(_._3).toList.sortBy(_.getLineNumber).mkString("\n\t")}" + + s"Assertions with dependencies on explicit assumptions:\n\t\t${assertionQualities.filterNot(_._1 == 1.0).sortBy(_._2.getLineNumber).mkString("\n\t\t")}" + "\n\n" + + s"Assertions with perfect proof quality:\n\t\t${fullyVerifiedAssertions.map(_._2).sortBy(_.getLineNumber).mkString("\n\t\t")}" + "\n\n" + s"specQuality = $specQuality\n" + s"proof quality (Peter): $numFullyVerifiedAssertions / $numAssertions = $proofQualityPeter\n" + s"proof quality (Lea): $assertionQualitiesSum / $numAssertions = $proofQualityLea\n" @@ -289,7 +289,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen // val startTime = System.nanoTime() // TODO ake: this is suuuper slow. Can we reuse previously computed results? Caching? val relevantDependenciesPerAssertion = allAssertions - .map(ass => (ass, toUserLevelNodes(getAllNonInternalDependencies(getNodesWithIdenticalSource(ass.lowerLevelNodes).map(_.id))).diffBySource(Set(ass)))).toMap + .map(ass => (ass, toUserLevelNodes(getAllNonInternalDependencies(ass.lowerLevelNodes.map(_.id))).diffBySource(Set(ass)))).toMap .filter{case (_, assumptions) => assumptions.nonEmpty} // filter out trivial assertions like `assert true` // val endTime = System.nanoTime() @@ -323,11 +323,12 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen val verificationProgressPeter = specQuality * proofQualityPeter // Lea's metric - val proofQualityPerAssertion = relevantAssertions.map { case (_, assumptions) => - UserLevelDependencyAnalysisNode.extractNonExplicitAssumptionNodes(assumptions).size.toDouble / assumptions.size.toDouble + val proofQualityPerAssertion = relevantAssertions.toList.map { case (assertion, assumptions) => + val nonExplicitDeps = UserLevelDependencyAnalysisNode.extractNonExplicitAssumptionNodes(assumptions) + (nonExplicitDeps.size.toDouble / assumptions.size.toDouble, assertion) } - val proofQualityLea = if(numRelevantAssertions > 0) proofQualityPerAssertion.sum / numRelevantAssertions else 1.0 + val proofQualityLea = if(numRelevantAssertions > 0) proofQualityPerAssertion.map(_._1).sum / numRelevantAssertions else 1.0 val verificationProgressLea = specQuality * proofQualityLea @@ -349,12 +350,12 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen s"Fully verified assertions:\n\t\t${getString(fullyVerifiedAssertions)}" + "\n\n" + s"Assertions depending on explicit assumptions:\n\t\t${getString(assertionsWithExplicitDeps)}" + "\n\n" + "\n" + - s"#Verification Errors: ${errors.size}" + "\n\n" + + s"Assertion Qualities:\n\t\t${proofQualityPerAssertion.filterNot(_._1 == 1.0).sortBy(_._2.source.getLineNumber).mkString("\n\t\t")}" + "\n\n" + "\n" + s"Verification Progress (Peter):\n\t${coveredSourceCodeStmts.size}/${coveredSourceCodeStmts.size + uncoveredSourceCodeStmts.size} * " + s"${fullyVerifiedAssertions.size}/${relevantAssertions.keySet.size} = ${"%.2f".format(verificationProgressPeter)}" + "\n" + s"Verification Progress (Lea):\n\t${coveredSourceCodeStmts.size}/${coveredSourceCodeStmts.size + uncoveredSourceCodeStmts.size} * " + - f"${"%.2f".format(proofQualityPerAssertion.sum)}/${relevantAssertions.keys.size} = ${"%.2f".format(verificationProgressLea)}" + "\n" + f"${"%.2f".format(proofQualityPerAssertion.map(_._1).sum)}/${relevantAssertions.keys.size} = ${"%.2f".format(verificationProgressLea)}" + "\n" } (verificationProgressPeter, verificationProgressLea, info) } diff --git a/src/main/scala/dependencyAnalysis/SourceInfoStack.scala b/src/main/scala/dependencyAnalysis/SourceInfoStack.scala index 118a72d87..0d9b53048 100644 --- a/src/main/scala/dependencyAnalysis/SourceInfoStack.scala +++ b/src/main/scala/dependencyAnalysis/SourceInfoStack.scala @@ -1,5 +1,6 @@ package viper.silicon.dependencyAnalysis +import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType import viper.silicon.verifier.Verifier import viper.silver.ast.NoPosition @@ -15,52 +16,58 @@ object SourceInfoStackID { trait SourceInfoStack { - def getAnalysisSourceInfos: List[AnalysisSourceInfo] + def getAnalysisSourceInfos: List[(AnalysisSourceInfo, DependencyType)] def getFullSourceInfo: AnalysisSourceInfo - def addAnalysisSourceInfo(analysisSourceInfo: AnalysisSourceInfo): Unit + def getAssumptionType: AssumptionType - def setAnalysisSourceInfo(analysisSourceInfo: List[AnalysisSourceInfo]): Unit + def getAssertionType: AssumptionType + + def getDependencyType: DependencyType + + def addAnalysisSourceInfo(analysisSourceInfo: AnalysisSourceInfo, dependencyType: DependencyType): Unit + + def setAnalysisSourceInfo(analysisSourceInfo: List[(AnalysisSourceInfo, DependencyType)]): Unit def popAnalysisSourceInfo(analysisSourceInfo: AnalysisSourceInfo): Unit - def getForcedSource: Option[AnalysisSourceInfo] + def getForcedSource: Option[(AnalysisSourceInfo, DependencyType)] - def setUniqueForcedSource(description: String): Unit + def setUniqueForcedSource(description: String, dependencyType: DependencyType = DependencyType.Internal): Unit - def setForcedSource(description: String): Unit + def setForcedSource(description: String, dependencyType: DependencyType = DependencyType.Internal): Unit - def setForcedSource(source: AnalysisSourceInfo): Unit + def setForcedSource(source: AnalysisSourceInfo, dependencyType: DependencyType): Unit - def setForcedSource(sourceOpt: Option[AnalysisSourceInfo]): Unit + def setForcedSource(sourceOpt: Option[(AnalysisSourceInfo, DependencyType)]): Unit def removeForcedSource(): Unit } case class AnalysisSourceInfoStack() extends SourceInfoStack { - private var sourceInfos: List[AnalysisSourceInfo] = List.empty - private var forcedMainSource: Option[AnalysisSourceInfo] = None + private var sourceInfos: List[(AnalysisSourceInfo, DependencyType)] = List.empty + private var forcedMainSource: Option[(AnalysisSourceInfo, DependencyType)] = None - override def getAnalysisSourceInfos: List[AnalysisSourceInfo] = sourceInfos + override def getAnalysisSourceInfos: List[(AnalysisSourceInfo, DependencyType)] = sourceInfos override def getFullSourceInfo: AnalysisSourceInfo = { if(!Verifier.config.enableDependencyAnalysis()) return NoAnalysisSourceInfo() if(forcedMainSource.isDefined) - return forcedMainSource.get + return forcedMainSource.get._1 if(sourceInfos.size <= 1){ - sourceInfos.headOption.getOrElse(NoAnalysisSourceInfo()) + sourceInfos.headOption.map(_._1).getOrElse(NoAnalysisSourceInfo()) } else{ - CompositeAnalysisSourceInfo(sourceInfos.last, sourceInfos.head) + CompositeAnalysisSourceInfo(sourceInfos.last._1, sourceInfos.head._1) } } - override def addAnalysisSourceInfo(analysisSourceInfo: AnalysisSourceInfo): Unit = { + override def addAnalysisSourceInfo(analysisSourceInfo: AnalysisSourceInfo, dependencyType: DependencyType): Unit = { if(!Verifier.config.enableDependencyAnalysis()) return - sourceInfos = analysisSourceInfo +: sourceInfos + sourceInfos = (analysisSourceInfo, dependencyType) +: sourceInfos } - override def setAnalysisSourceInfo(analysisSourceInfo: List[AnalysisSourceInfo]): Unit = { + override def setAnalysisSourceInfo(analysisSourceInfo: List[(AnalysisSourceInfo, DependencyType)]): Unit = { if(!Verifier.config.enableDependencyAnalysis()) return sourceInfos = analysisSourceInfo } @@ -70,33 +77,43 @@ case class AnalysisSourceInfoStack() extends SourceInfoStack { var currSourceInfo = sourceInfos // popping just one source info might not be enough since infeasible branches might return without popping the source info - while(currSourceInfo.nonEmpty && !currSourceInfo.head.equals(analysisSourceInfo)) { + while(currSourceInfo.nonEmpty && !currSourceInfo.head._1.equals(analysisSourceInfo)) { currSourceInfo = currSourceInfo.tail } - if(currSourceInfo.isEmpty || !currSourceInfo.head.equals(analysisSourceInfo)) + if(currSourceInfo.isEmpty || !currSourceInfo.head._1.equals(analysisSourceInfo)) throw new RuntimeException("unexpected source info") sourceInfos = currSourceInfo.tail } - override def getForcedSource: Option[AnalysisSourceInfo] = forcedMainSource + override def getForcedSource: Option[(AnalysisSourceInfo, DependencyType)] = forcedMainSource - override def setForcedSource(description: String): Unit = { - forcedMainSource = Some(StringAnalysisSourceInfo(description, NoPosition)) + override def setForcedSource(description: String, dependencyType: DependencyType = DependencyType.Internal): Unit = { + forcedMainSource = Some(StringAnalysisSourceInfo(description, NoPosition), dependencyType) } - override def setUniqueForcedSource(description: String): Unit = { - forcedMainSource = Some(StringAnalysisSourceInfo(description + SourceInfoStackID.nextId(), NoPosition)) + override def setUniqueForcedSource(description: String, dependencyType: DependencyType = DependencyType.Internal): Unit = { + forcedMainSource = Some(StringAnalysisSourceInfo(description + SourceInfoStackID.nextId(), NoPosition), dependencyType) } - override def setForcedSource(source: AnalysisSourceInfo): Unit = { - forcedMainSource = Some(source) + override def setForcedSource(source: AnalysisSourceInfo, dependencyType: DependencyType): Unit = { + forcedMainSource = Some(source, dependencyType) } - override def setForcedSource(sourceOpt: Option[AnalysisSourceInfo]): Unit = { + override def setForcedSource(sourceOpt: Option[(AnalysisSourceInfo, DependencyType)]): Unit = { forcedMainSource = sourceOpt } override def removeForcedSource(): Unit = { forcedMainSource = None } + + override def getAssumptionType: AssumptionType = getDependencyType.assumptionType + + override def getAssertionType: AssumptionType = getDependencyType.assertionType + + override def getDependencyType: DependencyType = forcedMainSource match { + case Some(value) => value._2 + case None => sourceInfos.lastOption.map(_._2).getOrElse(DependencyType.Implicit) + } + } \ No newline at end of file diff --git a/src/main/scala/rules/Brancher.scala b/src/main/scala/rules/Brancher.scala index 93f8186c1..580acfc2a 100644 --- a/src/main/scala/rules/Brancher.scala +++ b/src/main/scala/rules/Brancher.scala @@ -6,12 +6,10 @@ package viper.silicon.rules -import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, ExpAnalysisSourceInfo} - -import java.util.concurrent._ import viper.silicon.common.concurrency._ import viper.silicon.decider.PathConditionStack import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType +import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, DependencyType} import viper.silicon.interfaces.{Unreachable, VerificationResult} import viper.silicon.reporting.condenseToViperResult import viper.silicon.state.State @@ -21,6 +19,7 @@ import viper.silver.ast import viper.silver.reporter.BranchFailureMessage import viper.silver.verifier.Failure +import java.util.concurrent._ import scala.collection.immutable.HashSet trait BranchingRules extends SymbolicExecutionRules { @@ -62,7 +61,7 @@ object brancher extends BranchingRules { ) val sourceInfo = AnalysisSourceInfo.createAnalysisSourceInfo(conditionExp._1) - v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo) + v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo, DependencyType.get(conditionExp._1, DependencyType.PathCondition)) /* True if the then-branch is to be explored */ val executeThenBranch = ( skipPathFeasibilityCheck @@ -164,7 +163,7 @@ object brancher extends BranchingRules { executionFlowController.locally(s, v0)((s1, v1) => { v1.decider.prover.comment(s"[else-branch: $cnt | $negatedCondition]") val sourceInfo = AnalysisSourceInfo.createAnalysisSourceInfo(conditionExp._1) - v1.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo) + v1.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo, DependencyType.get(conditionExp._1, DependencyType.PathCondition)) v1.decider.pcs.setCurrentInfeasibilityNode(elseInfeasibilityNode) v1.decider.setCurrentBranchCondition(negatedCondition, (negatedConditionExp, negatedConditionExpNew), assumptionType) v1.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) @@ -219,7 +218,7 @@ object brancher extends BranchingRules { executionFlowController.locally(s, v)((s1, v1) => { v1.decider.prover.comment(s"[then-branch: $cnt | $condition]") val sourceInfo = AnalysisSourceInfo.createAnalysisSourceInfo(conditionExp._1) - v1.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo) + v1.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo, DependencyType.get(conditionExp._1, DependencyType.PathCondition)) v1.decider.pcs.setCurrentInfeasibilityNode(thenInfeasibilityNode) v1.decider.setCurrentBranchCondition(condition, conditionExp, assumptionType) v1.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) diff --git a/src/main/scala/rules/ChunkSupporter.scala b/src/main/scala/rules/ChunkSupporter.scala index 90d7df35d..34b43e793 100644 --- a/src/main/scala/rules/ChunkSupporter.scala +++ b/src/main/scala/rules/ChunkSupporter.scala @@ -145,7 +145,7 @@ object chunkSupporter extends ChunkSupportRules { case _ => None } QS(s2.copy(h = s.h), h2, snap, v1) - case (_, s2, h2, _) if v1.decider.checkSmoke(isAssert=true, dependencyType.assertionType) => + case (_, s2, h2, _) if v1.decider.checkSmoke(dependencyType.assertionType, isAssert = true) => if(Verifier.config.disableInfeasibilityChecks()) QS(s2.copy(h = s.h), h2, None, v1) else @@ -267,7 +267,7 @@ object chunkSupporter extends ChunkSupportRules { findRes match { case Some(ch) if v.decider.check(IsPositive(ch.perm), Verifier.config.assertTimeout.getOrElse(0), assumptionType) => Q(s, ch.snap, v) - case _ if v.decider.checkSmoke(isAssert=true, assumptionType) => + case _ if v.decider.checkSmoke(assumptionType, isAssert = true) => if (s.isInPackage || Verifier.config.disableInfeasibilityChecks()) { val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) Q(s, snap, v) diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index 2e2190329..12293c37a 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -180,7 +180,7 @@ object consumer extends ConsumptionRules { val sepIdentifier = v1.symbExLog.openScope(new ConsumeRecord(a, s1, v.decider.pcs)) val sourceInfo = AnalysisSourceInfo.createAnalysisSourceInfo(a) - v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo) + v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo, DependencyType.get(a, dependencyType)) consumeTlc(s1, h0, a, returnSnap, pve, v1, dependencyType)((s2, h2, snap2, v2) => { v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) @@ -356,7 +356,7 @@ object consumer extends ConsumptionRules { (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { eval(s, e0, pve, v)((s1, t0, e0New, v1) => - joiner.join[(Heap, Option[Term]), (Heap, Option[Term])](s1, v1, resetState = false)((s1, v1, QB) => { + joiner.join[(Heap, Option[Term]), (Heap, Option[Term])](s1, v1, dependencyType.assumptionType, resetState = false)((s1, v1, QB) => { branch(s1.copy(parallelizeBranches = false), t0, (e0, e0New), v1, dependencyType.assumptionType)( (s2, v2) => consumeR(s2.copy(parallelizeBranches = s1.parallelizeBranches), h, a1, returnSnap, pve, v2, dependencyType)((s3, h1, t1, v3) => { diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 2eff53d20..7bd463f55 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -89,7 +89,7 @@ object evaluator extends EvaluationRules { val sepIdentifier = v.symbExLog.openScope(new EvaluateRecord(e, s, v.decider.pcs)) val sourceInfo = AnalysisSourceInfo.createAnalysisSourceInfo(e) - v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo) + v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo, DependencyType.get(e)) eval3(s, e, pve, v)((s1, t, eNew, v1) => { v1.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) v1.symbExLog.closeScope(sepIdentifier) @@ -188,7 +188,7 @@ object evaluator extends EvaluationRules { case _: ast.WildcardPerm => val (tVar, tConstraints, eVar) = v.decider.freshARP() val constraintExp = Option.when(withExp)(DebugExp.createInstance(s"${eVar.get.toString} > none", true)) - v.decider.assumeDefinition(tConstraints, constraintExp, AssumptionType.Internal) + v.decider.assumeDefinition(tConstraints, constraintExp, v.decider.analysisSourceInfoStack.getAssumptionType) /* TODO: Only record wildcards in State.constrainableARPs that are used in exhale * position. Currently, wildcards used in inhale position (only) may not be removed * from State.constrainableARPs (potentially inefficient, but should be sound). @@ -258,7 +258,7 @@ object evaluator extends EvaluationRules { eval(s, e0, pve, v)((s1, t0, e0New, v1) => { val t = v1.decider.appliedFresh("letvar", v1.symbolConverter.toSort(x.typ), s1.relevantQuantifiedVariables.map(_._1)) val debugExp = Option.when(withExp)(DebugExp.createInstance("letvar assignment", InsertionOrderedSet(DebugExp.createInstance(ast.EqCmp(x.localVar, e0)(), ast.EqCmp(x.localVar, e0New.get)())))) - v1.decider.assumeDefinition(BuiltinEquals(t, t0), debugExp, AssumptionType.Internal) + v1.decider.assumeDefinition(BuiltinEquals(t, t0), debugExp, v1.decider.analysisSourceInfoStack.getAssumptionType) val newFuncRec = s1.functionRecorder.recordFreshSnapshot(t.applicable.asInstanceOf[Function]).enterLet(l) val possibleTriggersBefore = if (s1.recordPossibleTriggers) s1.possibleTriggers else Map.empty eval(s1.copy(g = s1.g + (x.localVar, (t0, e0New)), functionRecorder = newFuncRec), e1, pve, v1)((s2, t2, e1New, v2) => { @@ -309,7 +309,7 @@ object evaluator extends EvaluationRules { val condExpRecord = new CondExpRecord(condExp, s, v.decider.pcs, "CondExp") val uidCondExp = v.symbExLog.openScope(condExpRecord) eval(s, e0, pve, v)((s1, t0, e0New, v1) => - joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s1, v1)((s2, v2, QB) => + joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s1, v1, v1.decider.analysisSourceInfoStack.getAssumptionType)((s2, v2, QB) => brancher.branch(s2.copy(parallelizeBranches = false), t0, (e0, e0New), v2, AssumptionType.Internal)( (s3, v3) => eval(s3.copy(parallelizeBranches = s2.parallelizeBranches), e1, pve, v3)((s4, t4, e4, v4) => QB(s4, (t4, e4), v4)), (s3, v3) => eval(s3.copy(parallelizeBranches = s2.parallelizeBranches), e2, pve, v3)((s4, t4, e4, v4) => QB(s4, (t4, e4), v4))) @@ -558,7 +558,7 @@ object evaluator extends EvaluationRules { val exp = ast.Forall(eQuant.variables, eTriggers, body)(sourceQuant.pos, sourceQuant.info, sourceQuant.errT) DebugExp.createInstance(exp, expNew) }) - v1.decider.assume(Quantification(Forall, tVars, FunctionPreconditionTransformer.transform(tBody, s1.program), tTriggers, name, quantWeight), debugExp, AssumptionType.Internal) + v1.decider.assume(Quantification(Forall, tVars, FunctionPreconditionTransformer.transform(tBody, s1.program), tTriggers, name, quantWeight), debugExp, v1.decider.analysisSourceInfoStack.getAssumptionType) } val tQuant = Quantification(qantOp, tVars, tBody, tTriggers, name, quantWeight) @@ -567,7 +567,7 @@ object evaluator extends EvaluationRules { Q(s2, tQuant, eQuantNew, v1) case (s1, _, _, _, _, None, v1) => // This should not happen unless the current path is dead. - if (v1.decider.checkSmoke(true)) { + if (v1.decider.checkSmoke(isAssert = true)) { Unreachable() } else { createFailure(pve.dueTo(InternalReason(sourceQuant, "Quantifier evaluation failed.")), v1, s1, "quantifier could be evaluated") @@ -576,7 +576,7 @@ object evaluator extends EvaluationRules { case fapp @ ast.FuncApp(funcName, eArgs) => val func = s.program.findFunction(funcName) - val dependencyType = DependencyAnalyzer.extractDependencyTypeFromInfo(fapp.info).getOrElse(DependencyType.MethodCall) + val dependencyType = v.decider.analysisSourceInfoStack.getDependencyType evals2(s, eArgs, Nil, _ => pve, v)((s1, tArgs, eArgsNew, v1) => { // bookkeeper.functionApplications += 1 val joinFunctionArgs = tArgs //++ c2a.quantifiedVariables.filterNot(tArgs.contains) @@ -650,7 +650,7 @@ object evaluator extends EvaluationRules { moreJoins = JoinMode.Off, assertReadAccessOnly = if (Verifier.config.respectFunctionPrePermAmounts()) s2.assertReadAccessOnly /* should currently always be false */ else true) - consumes(s3, pres, true, _ => pvePre, v2, DependencyType.Implicit)((s4, snap, v3) => { // TODO ake: why does DependencyType.MethodCall not work? + consumes(s3, pres, true, _ => pvePre, v2, v2.decider.analysisSourceInfoStack.getDependencyType)((s4, snap, v3) => { val snap1 = snap.get.convert(sorts.Snap) val preFApp = App(functionSupporter.preconditionVersion(v3.symbolConverter.toFunction(func)), snap1 :: tArgs) val preExp = Option.when(withExp)({ @@ -700,7 +700,7 @@ object evaluator extends EvaluationRules { eval(s1, ePerm.getOrElse(ast.FullPerm()()), pve, v1)((s2, tPerm, ePermNew, v2) => v2.decider.assert(IsPositive(tPerm)) { // TODO: Replace with permissionSupporter.assertNotNegative case true => - joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s2, v2)((s3, v3, QB) => { + joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s2, v2, v2.decider.analysisSourceInfoStack.getAssumptionType)((s3, v3, QB) => { val s4 = s3.incCycleCounter(predicate) .copy(recordVisited = true) /* [2014-12-10 Malte] The commented code should replace the code following @@ -749,7 +749,7 @@ object evaluator extends EvaluationRules { eval(s10, eIn, pve, v5)((s9, t9, e9, v9) => QB(s9, (t9, e9), v9)) }) } else { - produce(s7a, toSf(snap.get), body, pve, v4, DependencyAnalyzer.extractAssumptionTypeFromInfo(e.info).getOrElse(AssumptionType.Internal))((s8, v5) => { + produce(s7a, toSf(snap.get), body, pve, v4, v4.decider.analysisSourceInfoStack.getAssumptionType)((s8, v5) => { val s9 = s8.copy(g = s7.g, functionRecorder = s8.functionRecorder.changeDepthBy(-1), recordVisited = s3.recordVisited, @@ -776,7 +776,7 @@ object evaluator extends EvaluationRules { } case ast.Applying(wand, eIn) => - joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s, v)((s1, v1, QB) => + joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s, v, v.decider.analysisSourceInfoStack.getAssumptionType)((s1, v1, QB) => magicWandSupporter.applyWand(s1, wand, pve, v1)((s2, v2) => { eval(s2, eIn, pve, v2)((s3, t, eInNew, v3) => QB(s3, (t, eInNew), v3)) }))(join(eIn.typ, "joined_applying", s.relevantQuantifiedVariables.map(_._1), @@ -895,7 +895,7 @@ object evaluator extends EvaluationRules { val exp = ast.EqCmp(ast.SeqLength(seq)(), ast.IntLit(es.size)())(seq.pos, seq.info, seq.errT) DebugExp.createInstance(exp, expNew) }) - v1.decider.assume(SeqLength(tSeq) === IntLiteral(es.size), debugExp, AssumptionType.Internal) + v1.decider.assume(SeqLength(tSeq) === IntLiteral(es.size), debugExp, v1.decider.analysisSourceInfoStack.getAssumptionType) Q(s1, tSeq, esNew.map(en => ast.ExplicitSeq(en)(e.pos, e.info, e.errT)), v1)}) /* Sets and multisets */ @@ -1079,7 +1079,7 @@ object evaluator extends EvaluationRules { // check succeeds, then the continuation for evals(es2) is never invoked). This caused issue #842. // In this case, we return None. val expPair = (viper.silicon.utils.ast.BigAnd(es1), es1New.map(viper.silicon.utils.ast.BigAnd(_))) - v2.decider.setCurrentBranchCondition(bc, expPair, AssumptionType.Internal) + v2.decider.setCurrentBranchCondition(bc, expPair, v2.decider.analysisSourceInfoStack.getAssumptionType) var es2AndTriggerTerms: Option[(Seq[Term], Option[Seq[ast.Exp]], Seq[Trigger], (Seq[Term], Seq[Quantification]), Option[(InsertionOrderedSet[DebugExp], InsertionOrderedSet[DebugExp])], Map[ast.Exp, Term])] = None var finalState = s3 val es2AndTriggerResult = evals(s3, es2, _ => pve, v2)((s4, ts2, es2New, v3) => { @@ -1116,7 +1116,7 @@ object evaluator extends EvaluationRules { (Q: (State, Term, Option[ast.Exp], Verifier) => VerificationResult) : VerificationResult = { - joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s, v)((s1, v1, QB) => + joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s, v, v.decider.analysisSourceInfoStack.getAssumptionType)((s1, v1, QB) => brancher.branch(s1.copy(parallelizeBranches = false), tLhs, eLhs, v1, AssumptionType.Internal, fromShortCircuitingAnd = fromShortCircuitingAnd)( (s2, v2) => eval(s2.copy(parallelizeBranches = s1.parallelizeBranches), eRhs, pve, v2)((s2, tRhs, eRhsNew, v2) => QB(s2, (tRhs, eRhsNew), v2)), (s2, v2) => QB(s2.copy(parallelizeBranches = s1.parallelizeBranches), (True, Option.when(withExp)(ast.TrueLit()())), v2)) @@ -1514,7 +1514,7 @@ object evaluator extends EvaluationRules { case `stop` => Q(s1, t0, e0New, v1) // Done, if last expression was true/false for or/and (optimisation) case _ => val expPair = if (constructor == Or) (exps.head, e0New) else (ast.Not(exps.head)(), e0New.map(ast.Not(_)())) - joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s1, v1)((s2, v2, QB) => + joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s1, v1, v1.decider.analysisSourceInfoStack.getAssumptionType)((s2, v2, QB) => brancher.branch(s2.copy(parallelizeBranches = false), if (constructor == Or) t0 else Not(t0), expPair, v2, AssumptionType.Internal, fromShortCircuitingAnd = true)( (s3, v3) => QB(s3.copy(parallelizeBranches = s2.parallelizeBranches), (t0, e0New), v3), (s3, v3) => evalSeqShortCircuit(constructor, s3.copy(parallelizeBranches = s2.parallelizeBranches), exps.tail, pve, v3)((s2, t2, e2, v2) => QB(s2, (t2, e2), v2))) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 7762c7960..cb05923d8 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -152,12 +152,12 @@ object executor extends ExecutionRules { case _ => false }) - val dependencyType = DependencyAnalyzer.extractDependencyTypeFromInfo(cedge1.condition.info) + val dependencyType = DependencyAnalyzer.extractDependencyTypeFromInfo(cedge1.condition.info).getOrElse(DependencyType.PathCondition) eval(s, cedge1.condition, pvef(cedge1.condition), v)((s1, t0, condNew, v1) => // The type arguments here are Null because there is no need to pass any join data. - joiner.join[scala.Null, scala.Null](s1, v1, resetState = false)((s2, v2, QB) => { - brancher.branch(s2, t0, (cedge1.condition, condNew), v2, dependencyType.map(_.assumptionType).getOrElse(AssumptionType.PathCondition))( + joiner.join[scala.Null, scala.Null](s1, v1, dependencyType.assumptionType, resetState = false)((s2, v2, QB) => { + brancher.branch(s2, t0, (cedge1.condition, condNew), v2, dependencyType.assumptionType)( // Follow only until join point. (s3, v3) => follow(s3, edge1, v3, Some(newJoinPoint))((s, v) => QB(s, null, v)), (s3, v3) => follow(s3, edge2, v3, Some(newJoinPoint))((s, v) => QB(s, null, v)) @@ -293,7 +293,7 @@ object executor extends ExecutionRules { v2.decider.declareAndRecordAsFreshMacros(fm1.filter(!v2.decider.freshMacros.contains(_))) /* [BRANCH-PARALLELISATION] */ v2.decider.assume(pcs.assumptions map (t => v.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t))), Some(pcs.assumptionExps), "Loop invariant", enforceAssumption=false, assumptionType=AssumptionType.Internal) v2.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) - if (v2.decider.checkSmoke() && !Verifier.config.disableInfeasibilityChecks()) + if (!Verifier.config.disableInfeasibilityChecks() && v2.decider.checkSmoke()) Success() else { execs(s3, stmts, v2)((s4, v3) => { @@ -344,7 +344,7 @@ object executor extends ExecutionRules { : VerificationResult = { val sepIdentifier = v.symbExLog.openScope(new ExecuteRecord(stmt, s, v.decider.pcs)) val sourceInfo = AnalysisSourceInfo.createAnalysisSourceInfo(stmt) - v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo) + v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo, DependencyType.get(stmt)) exec2(s, stmt, v)((s1, v1) => { v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) v1.symbExLog.closeScope(sepIdentifier) @@ -469,7 +469,7 @@ object executor extends ExecutionRules { case assert @ ast.Assert(a: ast.FalseLit) if !s.isInPackage => /* "assert false" triggers a smoke check. If successful, we backtrack. */ executionFlowController.tryOrFail0(s.copy(h = magicWandSupporter.getEvalHeap(s)), v)((s1, v1, QS) => { - if (v1.decider.checkSmoke(isAssert=true, getAssertionType(AssumptionType.Explicit))) + if (v1.decider.checkSmoke(getAssertionType(AssumptionType.Explicit), isAssert = true)) QS(s1.copy(h = s.h), v1) else createFailure(AssertFailed(assert) dueTo AssertionFalse(a), v1, s1, False, true, Option.when(withExp)(a)) diff --git a/src/main/scala/rules/HeapSupporter.scala b/src/main/scala/rules/HeapSupporter.scala index 5d9035721..2be975178 100644 --- a/src/main/scala/rules/HeapSupporter.scala +++ b/src/main/scala/rules/HeapSupporter.scala @@ -197,7 +197,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { val s2 = triggerResourceIfNeeded(s, ass.lhs, Seq(tRcvr), eRcvrNew.map(Seq(_)), v) v.decider.clearModel() val lhsSourceInfo = TransitivityAnalysisSourceInfo(v.decider.analysisSourceInfoStack.getFullSourceInfo, AnalysisSourceInfo.createAnalysisSourceInfo(ass.lhs)) - v.decider.analysisSourceInfoStack.setForcedSource(lhsSourceInfo) // splitting lhs and rhs to make permission flow analysis more precise + v.decider.analysisSourceInfoStack.setForcedSource(lhsSourceInfo,v.decider.analysisSourceInfoStack.getDependencyType) // splitting lhs and rhs to make permission flow analysis more precise val result = quantifiedChunkSupporter.removePermissions( s2, relevantChunks, @@ -221,7 +221,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { v.decider.prover.comment("Definitional axioms for singleton-FVF's value") val debugExp = Option.when(withExp)(DebugExp.createInstance("Definitional axioms for singleton-FVF's value", isInternal_ = true)) v.decider.assumeDefinition(smValueDef, debugExp, dependencyType.assumptionType) - v.decider.analysisSourceInfoStack.setForcedSource(lhsSourceInfo) // splitting lhs and rhs to make permission flow analysis more precise + v.decider.analysisSourceInfoStack.setForcedSource(lhsSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType) // splitting lhs and rhs to make permission flow analysis more precise val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(Seq(`?r`), Option.when(withExp)(Seq(ast.LocalVarDecl("r", ast.Ref)(ass.pos, ass.info, ass.errT))), field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), sm, s.program, v, AssumptionType.Internal, isExhale=false) if (s3.heapDependentTriggers.contains(field)) { @@ -240,7 +240,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { } else { val description = s"consume ${ass.pos}: $ass" val lhsSourceInfo = TransitivityAnalysisSourceInfo(v.decider.analysisSourceInfoStack.getFullSourceInfo, AnalysisSourceInfo.createAnalysisSourceInfo(ass.lhs)) - v.decider.analysisSourceInfoStack.setForcedSource(lhsSourceInfo) // splitting lhs and rhs to make permission flow analysis more precise + v.decider.analysisSourceInfoStack.setForcedSource(lhsSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType) // splitting lhs and rhs to make permission flow analysis more precise chunkSupporter.consume(s, s.h, field, Seq(tRcvr), eRcvrNew.map(Seq(_)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), false, ve, v, description, dependencyType)((s3, h3, _, v3) => { val id = BasicChunkIdentifier(field.name) val newChunk = BasicChunk.apply(FieldID, id, Seq(tRcvr), eRcvrNew.map(Seq(_)), tRhs, eRhsNew, FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), v3.decider.getAnalysisInfo(AssumptionType.Internal)) @@ -417,7 +417,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { } } else { val resource = fa.res(s.program) - chunkSupporter.lookup(s, s.h, resource, Seq(tRcvr), Option.when(withExp)(Seq(eRcvr.get)), ve, v, DependencyAnalyzer.extractDependencyTypeFromInfo(fa.info).map(_.assertionType).getOrElse(AssumptionType.Implicit))((s2, h2, tSnap, v2) => { + chunkSupporter.lookup(s, s.h, resource, Seq(tRcvr), Option.when(withExp)(Seq(eRcvr.get)), ve, v, v.decider.analysisSourceInfoStack.getAssumptionType)((s2, h2, tSnap, v2) => { val fr = s2.functionRecorder.recordSnapshot(fa, v2.decider.pcs.branchConditions, tSnap) val s3 = s2.copy(h = h2, functionRecorder = fr) Q(s3, tSnap, v) diff --git a/src/main/scala/rules/Joiner.scala b/src/main/scala/rules/Joiner.scala index 533cbd6e9..9bdb78708 100644 --- a/src/main/scala/rules/Joiner.scala +++ b/src/main/scala/rules/Joiner.scala @@ -36,7 +36,7 @@ case class JoinDataEntry[D](s: State, data: D, pathConditions: RecordedPathCondi trait JoiningRules extends SymbolicExecutionRules { - def join[D, JD](s: State, v: Verifier, resetState: Boolean = true, assumptionType: AssumptionType = AssumptionType.Implicit) + def join[D, JD](s: State, v: Verifier, assumptionType: AssumptionType, resetState: Boolean = true) (block: (State, Verifier, (State, D, Verifier) => VerificationResult) => VerificationResult) (merge: Seq[JoinDataEntry[D]] => (State, JD)) (Q: (State, JD, Verifier) => VerificationResult) @@ -44,7 +44,7 @@ trait JoiningRules extends SymbolicExecutionRules { } object joiner extends JoiningRules { - def join[D, JD](s: State, v: Verifier, resetState: Boolean = true, assumptionType: AssumptionType = AssumptionType.Implicit) + def join[D, JD](s: State, v: Verifier, assumptionType: AssumptionType, resetState: Boolean = true) (block: (State, Verifier, (State, D, Verifier) => VerificationResult) => VerificationResult) (merge: Seq[JoinDataEntry[D]] => (State, JD)) (Q: (State, JD, Verifier) => VerificationResult) diff --git a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala index dc891967f..409e4d477 100644 --- a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala +++ b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala @@ -207,7 +207,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { val relevantChunks = findChunksWithID[NonQuantifiedChunk](h.values, id).toSeq if (relevantChunks.isEmpty) { - if (v.decider.checkSmoke(true)) { + if (v.decider.checkSmoke(isAssert = true)) { if (s.isInPackage || Verifier.config.disableInfeasibilityChecks()) { val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) Q(s, snap, v) diff --git a/src/main/scala/rules/PredicateSupporter.scala b/src/main/scala/rules/PredicateSupporter.scala index b0aebb1c6..62d440f73 100644 --- a/src/main/scala/rules/PredicateSupporter.scala +++ b/src/main/scala/rules/PredicateSupporter.scala @@ -149,7 +149,7 @@ object predicateSupporter extends PredicateSupportRules { val substCond = cond.replace(toReplace) if (!isUnfolding && s.moreJoins.id >= JoinMode.Impure.id) { - joiner.join[scala.Null, scala.Null](s, v, resetState = false)((s1, v1, QB) => { + joiner.join[scala.Null, scala.Null](s, v, dependencyType.assumptionType, resetState = false)((s1, v1, QB) => { brancher.branch(s1, substCond, condExp, v1, dependencyType.assumptionType)( (s2, v2) => { producePredicateContents(s2, left, toReplace, v2, dependencyType, isUnfolding)((s3, v3) => QB(s3, null, v3)) diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index a81539b4f..63f3b5096 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -152,7 +152,7 @@ object producer extends ProductionRules { val pve = pves.head val sourceInfo = AnalysisSourceInfo.createAnalysisSourceInfo(a) - v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo) + v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo, DependencyType.get(a, DependencyType.make(assumptionType))) if (as.tail.isEmpty) wrappedProduceTlc(s, sf, a, pve, v, assumptionType)((s1, v1) => { v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) @@ -239,7 +239,7 @@ object producer extends ProductionRules { eval(s, e0, pve, v)((s1, t0, e0New, v1) => // The type arguments here are Null because there is no need to pass any join data. - joiner.join[scala.Null, scala.Null](s1, v1, resetState = false)((s1, v1, QB) => + joiner.join[scala.Null, scala.Null](s1, v1, assumptionType, resetState = false)((s1, v1, QB) => branch(s1.copy(parallelizeBranches = false), t0, (e0, e0New), v1, assumptionType)( (s2, v2) => produceR(s2.copy(parallelizeBranches = s1.parallelizeBranches), sf, a0, pve, v2, assumptionType)((s3, v3) => { v3.symbExLog.closeScope(uidImplies) @@ -292,7 +292,7 @@ object producer extends ProductionRules { eval(s, e0, pve, v)((s1, t0, e0New, v1) => // The type arguments here are Null because there is no need to pass any join data. - joiner.join[scala.Null, scala.Null](s1, v1, resetState = false)((s1, v1, QB) => + joiner.join[scala.Null, scala.Null](s1, v1, assumptionType, resetState = false)((s1, v1, QB) => branch(s1.copy(parallelizeBranches = false), t0, (e0, e0New), v1, assumptionType)( (s2, v2) => produceR(s2.copy(parallelizeBranches = s1.parallelizeBranches), sf, a1, pve, v2, assumptionType)((s3, v3) => { v3.symbExLog.closeScope(uidCondExp) diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 858d6d8b9..bdf419729 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -1538,7 +1538,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { perms: Term, permsExp: Option[ast.Exp], v: Verifier, - assertionType: AssumptionType=AssumptionType.Implicit) + assertionType: AssumptionType) : ConsumptionResult = { var permsAvailable: Term = NoPerm diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index e113d54a7..338c14c5e 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -7,7 +7,7 @@ package viper.silicon.supporters.functions import com.typesafe.scalalogging.Logger -import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType +import viper.silicon.dependencyAnalysis.AssumptionType.{AssumptionType, ExplicitPostcondition, ImplicitPostcondition} import viper.silicon.dependencyAnalysis._ import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp @@ -245,6 +245,9 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver var phase1Data: Seq[Phase1Data] = Vector.empty var recorders: Seq[FunctionRecorder] = Vector.empty + val postconditionType = DependencyAnalyzer.extractAssumptionTypeFromInfo(function.info) + .getOrElse(if(function.body.isDefined) ImplicitPostcondition else ExplicitPostcondition) + val result = executionFlowController.locally(s, v)((s0, _) => { val preMark = decider.setPathConditionMark() produces(s0, toSf(`?s`), pres, ContractNotWellformed, v, AssumptionType.Precondition)((s1, _) => { @@ -254,7 +257,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver // The postcondition must be produced with a fresh snapshot (different from `?s`) because // the postcondition's snapshot structure is most likely different than that of the // precondition - produces(s1, freshSnap, posts, ContractNotWellformed, v, AssumptionType.ExplicitPostcondition)((s2, _) => { + produces(s1, freshSnap, posts, ContractNotWellformed, v, postconditionType)((s2, _) => { recorders :+= s2.functionRecorder Success()})})}) @@ -296,7 +299,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver val eNew = ast.EqCmp(ast.Result(function.typ)(), bodyNew.get)(function.pos, function.info, function.errT) Some(DebugExp.createInstance(e, eNew)) } else { None } - decider.analysisSourceInfoStack.setForcedSource(AnalysisSourceInfo.createAnalysisSourceInfo(body)) + decider.analysisSourceInfoStack.setForcedSource(AnalysisSourceInfo.createAnalysisSourceInfo(body), DependencyType.get(body, DependencyType.make(AssumptionType.FunctionBody))) decider.assume(BuiltinEquals(data.formalResult, tBody), debugExp, AssumptionType.FunctionBody) decider.analysisSourceInfoStack.removeForcedSource() consumes(s2, posts, false, postconditionViolated, v, DependencyType.make(postConditionType))((s3, _, _) => { diff --git a/src/test/resources/dependencyAnalysisTests/all/function-sum.vpr b/src/test/resources/dependencyAnalysisTests/all/function-sum.vpr index e5783be54..37c950f23 100644 --- a/src/test/resources/dependencyAnalysisTests/all/function-sum.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/function-sum.vpr @@ -20,7 +20,7 @@ method call2(){ @dependency("Explicit") assume y > 0 - @dependency("MethodCall") + @dependency("SourceCode") z := sum(x, y) // $PrecisionTest: $READ_ONLY=x,y,z $INVARIANT=x>0 @@ -36,7 +36,7 @@ method call2Unv(){ @dependency("Explicit") assume y > 0 - @dependency("MethodCall") + @dependency("SourceCode") z := sumUnverified(x, y) // $PrecisionTest: $READ_ONLY=x,y,z $INVARIANT=x>0 diff --git a/src/test/resources/dependencyAnalysisTests/all/functions.vpr b/src/test/resources/dependencyAnalysisTests/all/functions.vpr index efd65fb2a..f318ba261 100644 --- a/src/test/resources/dependencyAnalysisTests/all/functions.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/functions.vpr @@ -37,7 +37,7 @@ method predicateClient(x: Ref) x.f := 10 @dependency("Rewrite") fold greater0(x) - @dependency("MethodCall") + @dependency("SourceCode") a := foo(x) @testAssertion("Explicit") @@ -52,7 +52,7 @@ method predicateClientPostcond(x: Ref) x.f := 10 @dependency("Rewrite") fold greater0(x) - @dependency("MethodCall") + @dependency("SourceCode") a := fooPostcond(x) @testAssertion("Explicit") @@ -64,7 +64,7 @@ method callDiv(){ var x: Int @dependency("Explicit") assume x > 10 - @dependency("MethodCall") + @dependency("SourceCode") x := div100(x) @testAssertion("Explicit") @@ -75,7 +75,7 @@ method callDivPostcond(){ var x: Int @dependency("Explicit") assume x > 10 - @dependency("MethodCall") + @dependency("SourceCode") x := div100Postcond(x) @testAssertion("Explicit") @@ -90,7 +90,7 @@ function fooInput(a: Int): Int method client(a: Int) { var res: Int - @dependency("MethodCall") + @dependency("SourceCode") res := fooInput(a) @testAssertion("Explicit") diff --git a/src/test/resources/dependencyAnalysisTests/unitTests/E-function-call.vpr b/src/test/resources/dependencyAnalysisTests/unitTests/E-function-call.vpr index 6488774c1..89f6d22e8 100644 --- a/src/test/resources/dependencyAnalysisTests/unitTests/E-function-call.vpr +++ b/src/test/resources/dependencyAnalysisTests/unitTests/E-function-call.vpr @@ -32,7 +32,7 @@ method call2(){ @dependency("Explicit") assume y > 0 - @dependency("MethodCall") + @dependency("SourceCode") z := sum(x, y) // $PrecisionTest: $READ_ONLY=x,y,z $INVARIANT=x>0 @@ -49,7 +49,7 @@ method call3(x: Int, y: Int) assume y > 0 var n: Int, n2: Int - @dependency("MethodCall") + @dependency("SourceCode") n := sum(x, y) // $PrecisionTest: $READ_WRITE=n2 $READ_ONLY=x,y,n $INVARIANT=x>0 @@ -75,12 +75,12 @@ method call4(x: Int, y: Int, z: Int) assume z > 0 var n: Int, n2: Int - @dependency("MethodCall") + @dependency("SourceCode") n := sumUnverified(x, y) // $PrecisionTest: $READ_WRITE=n2 $READ_ONLY=n,z,x,y $INVARIANT=x>0 - @irrelevant("MethodCall") + @irrelevant("SourceCode") n2 := sumUnverified(x, z) @testAssertion("Explicit") From 07cc81400226db937c3efcb34fd2a5a77b955b34 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 4 Feb 2026 17:30:10 +0100 Subject: [PATCH 337/474] progress computation optimizations (removing internal nodes, node maps) --- .../DependencyAnalysisUserTool.scala | 11 +- .../DependencyAnalyzer.scala | 1 + .../dependencyAnalysis/DependencyGraph.scala | 28 +++-- .../DependencyGraphInterpreter.scala | 113 ++++++++++++++---- .../UserLevelDependencyAnalysisNode.scala | 4 + 5 files changed, 118 insertions(+), 39 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala index 7ada592c8..eaaa3cab3 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala @@ -86,13 +86,18 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete } private def handleGraphSizeQuery(interpreter: DependencyGraphInterpreter): Unit = { - val allAssumptions = interpreter.getNonInternalAssumptionNodes.filter(n => !n.isInstanceOf[AxiomAssumptionNode]) + val allAssumptions = interpreter.getNonInternalAssumptionNodes val assumptions = UserLevelDependencyAnalysisNode.from(allAssumptions) - val assertions = UserLevelDependencyAnalysisNode.from(interpreter.getNonInternalAssertionNodes) - val nodes = UserLevelDependencyAnalysisNode.from(interpreter.getNonInternalAssertionNodes.asInstanceOf[Set[DependencyAnalysisNode]].union(allAssumptions.asInstanceOf[Set[DependencyAnalysisNode]])) + val allAssertions = interpreter.getNonInternalAssertionNodes + val assertions = UserLevelDependencyAnalysisNode.from(allAssertions) + val nodes = UserLevelDependencyAnalysisNode.from(allAssertions.union(allAssumptions)) println(s"#Assumptions = ${assumptions.size}") println(s"#Assertions = ${assertions.size}") println(s"#Nodes = ${nodes.size}") + println(s"#low-level Assumptions (non-internal) = ${allAssumptions.size}") + println(s"#low-level Assumptions (all) = ${interpreter.getAssumptionNodes.size}") + println(s"#low-level Assertions (non-internal) = ${allAssertions.size}") + println(s"#low-level Assertions (all) = ${interpreter.getAssertionNodes.size}") println("Done.") } diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index 76d622283..e7ee4eaa7 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -398,6 +398,7 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { dependencyGraph.removeLabelNodes() val mergedGraph = if(Verifier.config.enableDependencyAnalysisDebugging()) dependencyGraph else buildAndGetMergedGraph() mergedGraph.addTransitiveEdges() + if(!Verifier.config.enableDependencyAnalysisDebugging()) mergedGraph.removeInternalNodes() Some(mergedGraph) } diff --git a/src/main/scala/dependencyAnalysis/DependencyGraph.scala b/src/main/scala/dependencyAnalysis/DependencyGraph.scala index 4ff6f068d..bb5e7e58c 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraph.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraph.scala @@ -21,7 +21,6 @@ trait ReadOnlyDependencyGraph extends AbstractReadOnlyDependencyGraph { def getAssumptionNodes: Seq[GeneralAssumptionNode] def getAssertionNodes: Seq[GeneralAssertionNode] def getDirectEdges: Map[Int, Set[Int]] // target -> direct dependencies - def getTransitiveEdges: Map[Int, Set[Int]] // target -> direct dependencies def getEdgesConnectingMethods: Map[Int, Set[Int]] def getAllEdges: Map[Int, Set[Int]] // target -> direct dependencies @@ -36,21 +35,19 @@ class DependencyGraph extends ReadOnlyDependencyGraph { private var assumptionNodes: mutable.Seq[GeneralAssumptionNode] = mutable.Seq() private var assertionNodes: mutable.Seq[GeneralAssertionNode] = mutable.Seq() private val edges: mutable.Map[Int, Set[Int]] = mutable.Map.empty - private val transitiveEdges: mutable.Map[Int, Set[Int]] = mutable.Map.empty // TODO ake: can be merged into edges? private val edgesConnectingMethods: mutable.Map[Int, Set[Int]] = mutable.Map.empty // keep this, it's relevant for computing verification progress def getNodes: Seq[DependencyAnalysisNode] = getAssumptionNodes ++ getAssertionNodes def getAssumptionNodes: Seq[GeneralAssumptionNode] = assumptionNodes.toSeq def getAssertionNodes: Seq[GeneralAssertionNode] = assertionNodes.toSeq def getDirectEdges: Map[Int, Set[Int]] = edges.toMap - def getTransitiveEdges: Map[Int, Set[Int]] = transitiveEdges.toMap def getEdgesConnectingMethods: Map[Int, Set[Int]] = edgesConnectingMethods.toMap def getIntraMethodEdges: Map[Int, Set[Int]] = { - val keys = edges.keySet ++ transitiveEdges.keySet + val keys = edges.keySet val intraMethodEdges = mutable.Map[Int, Set[Int]]() keys foreach {key => - intraMethodEdges.update(key, edges.getOrElse(key, Set()) ++ transitiveEdges.getOrElse(key, Set())) + intraMethodEdges.update(key, edges.getOrElse(key, Set())) } intraMethodEdges.toMap } @@ -168,9 +165,9 @@ class DependencyGraph extends ReadOnlyDependencyGraph { } private def addTransitiveEdges(sources: Iterable[DependencyAnalysisNode], target: DependencyAnalysisNode): Unit = { - val oldSources = transitiveEdges.getOrElse(target.id, Set.empty) + val oldSources = edges.getOrElse(target.id, Set.empty) val newSources = sources map(_.id) // filter(_ > target.id) does not work due to loop invariants - if(newSources.nonEmpty) transitiveEdges.update(target.id, oldSources ++ newSources) + if(newSources.nonEmpty) edges.update(target.id, oldSources ++ newSources) } private def addTransitiveEdges(sources: Iterable[DependencyAnalysisNode], targets: Iterable[DependencyAnalysisNode]): Unit = { @@ -200,14 +197,22 @@ class DependencyGraph extends ReadOnlyDependencyGraph { val predecessors = (edges filter { case (_, t) => t.contains(id) }).keys val successors = edges.getOrElse(id, Set.empty) edges.remove(id) - predecessors foreach (pid => edges.update(pid, edges.getOrElse(pid, Set.empty).filter(_ != id))) - addEdges(successors, predecessors) + predecessors foreach (pid => edges.update(pid, edges.getOrElse(pid, Set.empty).filter(_ != id) ++ successors)) } // TODO ake: maybe move to DependencyAnalyzer? def removeLabelNodes(): Unit = { - assumptionNodes filter (_.isInstanceOf[LabelNode]) foreach removeAllEdgesForNode - assumptionNodes = assumptionNodes filter (!_.isInstanceOf[LabelNode]) + def filterCriteria(n: DependencyAnalysisNode) = n.isInstanceOf[LabelNode] + + assumptionNodes filter filterCriteria foreach removeAllEdgesForNode + assumptionNodes = assumptionNodes filterNot filterCriteria + } + + def removeInternalNodes(): Unit = { + def filterCriteria(n: DependencyAnalysisNode) = AssumptionType.internalTypes.contains(n.assumptionType) + + assumptionNodes filter filterCriteria foreach removeAllEdgesForNode + assumptionNodes = assumptionNodes filterNot filterCriteria } def exportGraph(dirName: String): Unit = { @@ -221,7 +226,6 @@ class DependencyGraph extends ReadOnlyDependencyGraph { val writer = new PrintWriter(fileName) writer.println("source,target,label") edges foreach (e => e._2 foreach (s => writer.println(s.toString + "," + e._1.toString + ",direct"))) - transitiveEdges foreach (e => e._2 foreach (s => writer.println(s.toString + "," + e._1.toString + ",transitive"))) writer.close() } diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index d1749d276..09575851c 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -1,6 +1,6 @@ package viper.silicon.dependencyAnalysis -import dependencyAnalysis.UserLevelDependencyAnalysisNode +import dependencyAnalysis.{CompactUserLevelDependencyAnalysisNode, UserLevelDependencyAnalysisNode} import viper.silicon.interfaces.Failure import viper.silicon.verifier.Verifier import viper.silver.ast @@ -20,6 +20,10 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen def getGraph: ReadOnlyDependencyGraph = dependencyGraph def getName: String = name def getMember: Option[ast.Member] = member + + lazy val nodesMap: Map[Int, DependencyAnalysisNode] = getNodes.map(node => (node.id, node)).toMap + lazy val nonInternalAssumptionNodesMap: Map[Int, DependencyAnalysisNode] = getNonInternalAssumptionNodes(getNodes).map(node => (node.id, node)).toMap + lazy val assertionNodesMap: Map[Int, DependencyAnalysisNode] = getAssertionNodes.map(node => (node.id, node)).toMap def getNodes: Set[DependencyAnalysisNode] = dependencyGraph.getNodes.toSet def getAssumptionNodes: Set[DependencyAnalysisNode] = dependencyGraph.getAssumptionNodes.toSet def getAssertionNodes: Set[DependencyAnalysisNode] = dependencyGraph.getAssertionNodes.toSet @@ -52,7 +56,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen def getAllNonInternalDependencies(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[DependencyAnalysisNode] = { val allDependencies = dependencyGraph.getAllDependencies(nodeIdsToAnalyze, includeInfeasibilityNodes).diff(nodeIdsToAnalyze) - getNonInternalAssumptionNodes.filter(node => allDependencies.contains(node.id)) + allDependencies flatMap nonInternalAssumptionNodesMap.get } def getAllExplicitDependencies(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[DependencyAnalysisNode] = { @@ -70,9 +74,11 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen getExplicitAssertionNodes.filter(node => allDependents.contains(node.id)) } - def getNonInternalAssumptionNodes: Set[DependencyAnalysisNode] = getNodes filter (node => - (node.isInstanceOf[GeneralAssumptionNode] && !AssumptionType.internalTypes.contains(node.assumptionType)) - || AssumptionType.postconditionTypes.contains(node.assumptionType) // postconditions act as assumptions for callers + def getNonInternalAssumptionNodes: Set[DependencyAnalysisNode] = nonInternalAssumptionNodesMap.values.toSet + + def getNonInternalAssumptionNodes(nodes: Set[DependencyAnalysisNode]): Set[DependencyAnalysisNode] = nodes filter (node => + (node.isInstanceOf[GeneralAssumptionNode] && !AssumptionType.internalTypes.contains(node.assumptionType)) + || AssumptionType.postconditionTypes.contains(node.assumptionType) // postconditions act as assumptions for callers ) def getExplicitAssumptionNodes: Set[DependencyAnalysisNode] = getNonInternalAssumptionNodes filter (node => @@ -84,9 +90,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen .exists(node => dependencyGraph.existsAnyDependency(Set(node.id), nodesToAnalyze map (_.id) filter (_ != node.id), includeInfeasibilityNodes)) - def getNonInternalAssertionNodes: Set[DependencyAnalysisNode] = getNodes filter (node => - node.isInstanceOf[GeneralAssertionNode] && !AssumptionType.internalTypes.contains(node.assumptionType) - ) + def getNonInternalAssertionNodes: Set[DependencyAnalysisNode] = getAssertionNodes filter (node => !AssumptionType.internalTypes.contains(node.assumptionType)) def getExplicitAssertionNodes: Set[DependencyAnalysisNode] = getNonInternalAssertionNodes.filter(node => AssumptionType.explicitAssertionTypes.contains(node.assumptionType)) @@ -221,40 +225,96 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen computeVerificationProgressOptimized() } - private def computeSpecQuality(coveredNodes: Set[UserLevelDependencyAnalysisNode]): Double = { + private def computeSpecQuality(coveredNodes: Set[CompactUserLevelDependencyAnalysisNode]): Double = { val nonSourceCodeAssumptionTypes = AssumptionType.explicitAssumptionTypes ++ AssumptionType.verificationAnnotationTypes - val allSourceCodeNodes = toUserLevelNodes(getNonInternalAssumptionNodes).filter(n => nonSourceCodeAssumptionTypes.intersect(n.assumptionTypes).isEmpty).getSourceSet() + val allSourceCodeNodes = toCompactUserLevelNodes(getNonInternalAssumptionNodes).filter(n => nonSourceCodeAssumptionTypes.intersect(n.assumptionTypes).isEmpty).map(_.source.getTopLevelSource) - val coveredSourceCodeNodes = coveredNodes.getSourceSet().intersect(allSourceCodeNodes) - println(s"Covered:\n\t${coveredSourceCodeNodes.toList.sortBy(_.getLineNumber).mkString("\n\t")}") - println(s"Uncovered:\n\t${allSourceCodeNodes.diff(coveredSourceCodeNodes).toList.sortBy(_.getLineNumber).mkString("\n\t")}") + val coveredSourceCodeNodes = coveredNodes.map(_.source.getTopLevelSource).intersect(allSourceCodeNodes) +// println(s"Covered:\n\t${coveredSourceCodeNodes.toList.sortBy(_.getLineNumber).mkString("\n\t")}") +// println(s"Uncovered:\n\t${allSourceCodeNodes.diff(coveredSourceCodeNodes).toList.sortBy(_.getLineNumber).mkString("\n\t")}") println(s"Spec Quality = ${coveredSourceCodeNodes.size} / ${allSourceCodeNodes.size}") coveredSourceCodeNodes.size.toDouble / allSourceCodeNodes.size.toDouble } - val deps: DAMemo[AnalysisSourceInfo, Set[UserLevelDependencyAnalysisNode]] = DAMemo {assertionNode => - val allNonInternalAssertions = getAssertionNodes.filter(_.sourceInfo.getTopLevelSource.equals(assertionNode)) + var perMethodDependencyRuntime: Long = 0L + var depsToPostcondRuntime: Long = 0L + var aggregationOfSummaryNodesRuntime: Long = 0L + var filteringNodesRuntime: Long = 0L + + private lazy val sourceToAssertionNodes: Map[AnalysisSourceInfo, Set[DependencyAnalysisNode]] = getNonInternalAssertionNodes.groupBy(_.sourceInfo.getTopLevelSource) + + val deps: DAMemo[AnalysisSourceInfo, Set[CompactUserLevelDependencyAnalysisNode]] = DAMemo { assertionNode => + val startFilteringNodes0 = System.nanoTime() + val allNonInternalAssertions = sourceToAssertionNodes.getOrElse(assertionNode, Set.empty) + filteringNodesRuntime = filteringNodesRuntime + (System.nanoTime() - startFilteringNodes0) + val startPerMethodDeps = System.nanoTime() val intraMethodDependencyIds = dependencyGraph.getAllDependencies(allNonInternalAssertions.map(_.id), includeInfeasibilityNodes=true, includeIntraMethodEdges=false) + perMethodDependencyRuntime = perMethodDependencyRuntime + (System.nanoTime() - startPerMethodDeps) - val intraMethodDependencies = getNonInternalAssumptionNodes.filter(node => intraMethodDependencyIds.contains(node.id) && !node.sourceInfo.getTopLevelSource.equals(assertionNode)) + val startFilteringNodes1 = System.nanoTime() + val intraMethodDependencies = intraMethodDependencyIds.flatMap(nonInternalAssumptionNodesMap.get).filter(!_.sourceInfo.getTopLevelSource.equals(assertionNode)) + filteringNodesRuntime = filteringNodesRuntime + (System.nanoTime() - startFilteringNodes1) + val startDepsToPostcond = System.nanoTime() val postconditionNodeIds = intraMethodDependencyIds.flatMap(n => dependencyGraph.getEdgesConnectingMethods.getOrElse(n, Set.empty)) - val postconditionNodes = getNodes.filter(n => postconditionNodeIds.contains(n.id)) + depsToPostcondRuntime = depsToPostcondRuntime + (System.nanoTime() - startDepsToPostcond) + val startFilteringNodes2 = System.nanoTime() + val postconditionNodes = postconditionNodeIds flatMap nodesMap.get + filteringNodesRuntime = filteringNodesRuntime + (System.nanoTime() - startFilteringNodes2) + val transDeps = postconditionNodes.map(_.sourceInfo.getTopLevelSource).diff(Set(assertionNode)) flatMap deps - toUserLevelNodes(transDeps.flatMap(_.lowerLevelNodes) ++ intraMethodDependencies ++ postconditionNodes) + + val startAggregation = System.nanoTime() + val res = reduceCompactUserLevelNodes(toCompactUserLevelNodes(intraMethodDependencies ++ postconditionNodes) ++ transDeps) + aggregationOfSummaryNodesRuntime = aggregationOfSummaryNodesRuntime + (System.nanoTime() - startAggregation) + res + } + + private def reduceCompactUserLevelNodes(inputNodes: Set[CompactUserLevelDependencyAnalysisNode]): Set[CompactUserLevelDependencyAnalysisNode] = { + + val resultMap: mutable.Map[AnalysisSourceInfo, CompactUserLevelDependencyAnalysisNode] = mutable.Map() + + for (node <- inputNodes) { + val existingNode = resultMap.get(node.source) + + val newNode = existingNode match { + case Some(existing) => + CompactUserLevelDependencyAnalysisNode( + source = node.source, + assumptionTypes = existing.assumptionTypes ++ node.assumptionTypes, + assertionTypes = existing.assertionTypes ++ node.assertionTypes, + hasFailures = existing.hasFailures || node.hasFailures + ) + case None => node + } + + resultMap.update(node.source, newNode) + } + + resultMap.values.toSet + } + + private def toCompactUserLevelNodes(lowLevelNodes: Set[DependencyAnalysisNode]): Set[CompactUserLevelDependencyAnalysisNode] = { + lowLevelNodes.groupBy(_.sourceInfo.getTopLevelSource).map{case (source, nodes) => + val assertionNodes = nodes.filter(_.isInstanceOf[GeneralAssertionNode]) + CompactUserLevelDependencyAnalysisNode(source, + nodes.filter(_.isInstanceOf[GeneralAssumptionNode]).map(_.assumptionType), + assertionNodes.map(_.assumptionType), + assertionNodes.exists(_.asInstanceOf[GeneralAssertionNode].hasFailed) + )}.toSet } - private def computeAssertionQuality(allDependencies: Set[UserLevelDependencyAnalysisNode]): Double = { - val explicitDeps = allDependencies.filter(_.assumptionTypes.intersect(AssumptionType.explicitAssumptionTypes).nonEmpty).getSourceSet() - val numDepsTotal = allDependencies.getSourceSet().size + private def computeAssertionQuality(allDependencies: Set[CompactUserLevelDependencyAnalysisNode]): Double = { + val explicitDeps = allDependencies.filter(_.assumptionTypes.intersect(AssumptionType.explicitAssumptionTypes).nonEmpty).map(_.source) + val numDepsTotal = allDependencies.map(_.source).size (numDepsTotal - explicitDeps.size).toDouble / numDepsTotal.toDouble } def computeVerificationProgressOptimized(): (Double, Double, String) = { - val allAssertions = getNonInternalAssertionNodes.map(_.sourceInfo.getTopLevelSource).toList + val allAssertions = sourceToAssertionNodes.keySet.toList val assertionDeps = allAssertions map (ass => (deps(ass), ass)) val specQuality = computeSpecQuality(assertionDeps.flatMap(_._1).toSet) @@ -270,13 +330,18 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen val proofQualityLea = assertionQualitiesSum / numAssertions.toDouble val info = { - s"Assertions with dependencies on explicit assumptions:\n\t\t${assertionQualities.filterNot(_._1 == 1.0).sortBy(_._2.getLineNumber).mkString("\n\t\t")}" + "\n\n" + - s"Assertions with perfect proof quality:\n\t\t${fullyVerifiedAssertions.map(_._2).sortBy(_.getLineNumber).mkString("\n\t\t")}" + "\n\n" + +// s"Assertions with dependencies on explicit assumptions:\n\t\t${assertionQualities.filterNot(_._1 == 1.0).sortBy(_._2.getLineNumber).mkString("\n\t\t")}" + "\n\n" + +// s"Assertions with perfect proof quality:\n\t\t${fullyVerifiedAssertions.map(_._2).sortBy(_.getLineNumber).mkString("\n\t\t")}" + "\n\n" + s"specQuality = $specQuality\n" + s"proof quality (Peter): $numFullyVerifiedAssertions / $numAssertions = $proofQualityPeter\n" + s"proof quality (Lea): $assertionQualitiesSum / $numAssertions = $proofQualityLea\n" } + println(s"Runtimes:\n\tperMethodDependencyRuntime: ${perMethodDependencyRuntime/1e6}ms\n\t" + + s"depsToPostcondRuntime: ${depsToPostcondRuntime/1e6}ms\n\t" + + s"aggregationOfSummaryNodesRuntime: ${aggregationOfSummaryNodesRuntime/1e6}ms\n\t" + + s"filteringNodesRuntime: ${filteringNodesRuntime/1e6}ms\n\t") + (specQuality * proofQualityPeter, specQuality * proofQualityLea, info) } diff --git a/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala b/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala index 9e270dd08..725495bcb 100644 --- a/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala +++ b/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala @@ -82,3 +82,7 @@ case class UserLevelDependencyAnalysisNode(source: AnalysisSourceInfo, lowerLeve } + +case class CompactUserLevelDependencyAnalysisNode(source: AnalysisSourceInfo, assumptionTypes: Set[AssumptionType], assertionTypes: Set[AssumptionType], hasFailures: Boolean) { + def position: Position = source.getPosition +} From b482ccd675fdaac5732f884899a99f755f32c965 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 9 Feb 2026 15:01:29 +0100 Subject: [PATCH 338/474] infeasible paths: optimizations --- .../DependencyAnalyzer.scala | 12 ++---- src/main/scala/rules/Brancher.scala | 10 +++++ src/main/scala/rules/ChunkSupporter.scala | 8 ++++ src/main/scala/rules/Consumer.scala | 2 +- src/main/scala/rules/Evaluator.scala | 14 +++++-- src/main/scala/rules/Executor.scala | 41 ++++++++++--------- src/main/scala/rules/HeapSupporter.scala | 33 ++++++++++++++- src/main/scala/rules/Producer.scala | 10 +++++ src/main/scala/rules/StateConsolidator.scala | 4 ++ src/main/scala/state/Chunks.scala | 1 - src/test/scala/DependencyAnalysisTests.scala | 2 +- 11 files changed, 101 insertions(+), 36 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index e7ee4eaa7..55d3c4db3 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -60,7 +60,7 @@ trait DependencyAnalyzer { /** * Adds an assertion and assumption node with the given analysis source info and dependencies to the current infeasibility node. */ - def addInfeasibilityDepToStmt(infeasNodeId: Option[Int], analysisSourceInfo: AnalysisSourceInfo, dependencyType: DependencyType): Unit = {} + def addAssertionWithDepToInfeasNode(infeasNodeId: Option[Int], analysisSourceInfo: AnalysisSourceInfo, dependencyType: DependencyType): Unit = {} /** * @return the final dependency graph representing all direct and transitive dependencies @@ -459,18 +459,14 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { } /** - * Adds an assertion and assumption node with the given analysis source info and dependencies to the current infeasibility node. - * If the infeasibility node is not defined, this operation does nothing. + * Adds an assertion node with the given analysis source info and dependencies to the current infeasibility node. * The resulting assertion node is required to detect dependencies of the source statement/expression on infeasible paths. - * The resulting assumption node is required to ensure that unreachable statements/expressions are represented in the graph and - * thus taken into account by graph queries, e.g. when determining uncovered statements or computing coverage. */ - override def addInfeasibilityDepToStmt(infeasNodeId: Option[Int], analysisSourceInfo: AnalysisSourceInfo, dependencyType: DependencyType): Unit = { + override def addAssertionWithDepToInfeasNode(infeasNodeId: Option[Int], analysisSourceInfo: AnalysisSourceInfo, dependencyType: DependencyType): Unit = { val newAssertionNodeId = addAssertNode(False, dependencyType.assertionType, analysisSourceInfo) addDependency(infeasNodeId, newAssertionNodeId) - val newAssumptionNodeId = addAssumption(False, analysisSourceInfo, dependencyType.assumptionType) - addDependency(infeasNodeId, newAssumptionNodeId) } + } /** diff --git a/src/main/scala/rules/Brancher.scala b/src/main/scala/rules/Brancher.scala index 580acfc2a..6b2bdc7a1 100644 --- a/src/main/scala/rules/Brancher.scala +++ b/src/main/scala/rules/Brancher.scala @@ -16,6 +16,7 @@ import viper.silicon.state.State import viper.silicon.state.terms.{FunctionDecl, MacroDecl, Not, Term} import viper.silicon.verifier.Verifier import viper.silver.ast +import viper.silver.ast.utility.Expressions import viper.silver.reporter.BranchFailureMessage import viper.silver.verifier.Failure @@ -45,6 +46,15 @@ object brancher extends BranchingRules { fElse: (State, Verifier) => VerificationResult) : VerificationResult = { + if(v.decider.isPathInfeasible()){ + if(!Expressions.isKnownWellDefined(conditionExp._1, Some(s.program))){ + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType) + } + v.decider.dependencyAnalyzer.addAssumption(condition, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getAssumptionType) + + return fThen(s, v).combine(fElse(s, v)) + } + val negatedCondition = Not(condition) val negatedConditionExp = ast.Not(conditionExp._1)(pos = conditionExp._1.pos, info = conditionExp._1.info, ast.NoTrafos) val negatedConditionExpNew = conditionExp._2.map(ce => ast.Not(ce)(pos = ce.pos, info = ce.info, ast.NoTrafos)) diff --git a/src/main/scala/rules/ChunkSupporter.scala b/src/main/scala/rules/ChunkSupporter.scala index 34b43e793..a4d62a0f7 100644 --- a/src/main/scala/rules/ChunkSupporter.scala +++ b/src/main/scala/rules/ChunkSupporter.scala @@ -83,6 +83,10 @@ object chunkSupporter extends ChunkSupportRules { dependencyType: DependencyType) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { + if(v.decider.isPathInfeasible()){ + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType) + return Q(s, h, Option.when(returnSnap)(Unit), v) + } consume2(s, h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, dependencyType)((s2, h2, optSnap, v2) => optSnap match { @@ -241,6 +245,10 @@ object chunkSupporter extends ChunkSupportRules { assumptionType: AssumptionType) (Q: (State, Heap, Term, Verifier) => VerificationResult) : VerificationResult = { + if(v.decider.isPathInfeasible()){ + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType) + return Q(s, h, Unit, v) + } executionFlowController.tryOrFail2[Heap, Term](s.copy(h = h), v)((s1, v1, QS) => { val lookupFunction = diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index 12293c37a..6a3a15bf6 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -201,7 +201,7 @@ object consumer extends ConsumptionRules { */ if(v.decider.isPathInfeasible()){ - v.decider.dependencyAnalyzer.addInfeasibilityDepToStmt(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, dependencyType) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, dependencyType) return Q(s, h, Option.when(returnSnap)(Unit), v) } diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 7bd463f55..c446e4ec7 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -7,11 +7,10 @@ package viper.silicon.rules import viper.silicon -import viper.silicon.debugger.DebugExp import viper.silicon.Config.JoinMode -import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyAnalyzer, DependencyType, ExpAnalysisSourceInfo} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp +import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyAnalyzer, DependencyType} import viper.silicon.interfaces._ import viper.silicon.interfaces.state.ChunkIdentifer import viper.silicon.logger.records.data.{CondExpRecord, EvaluateRecord, ImpliesRecord} @@ -24,7 +23,7 @@ import viper.silicon.utils.toSf import viper.silicon.verifier.Verifier import viper.silicon.{Map, TriggerSets} import viper.silver.ast -import viper.silver.ast.{AnnotationInfo, LocalVarWithVersion, TrueLit, WeightedQuantifier} +import viper.silver.ast.utility.Expressions import viper.silver.ast.{AnnotationInfo, LocalVarWithVersion, WeightedQuantifier} import viper.silver.reporter.{AnnotationWarning, WarningsDuringVerification} import viper.silver.utility.Common.Rational @@ -100,6 +99,15 @@ object evaluator extends EvaluationRules { (Q: (State, Term, Option[ast.Exp], Verifier) => VerificationResult) : VerificationResult = { + if(v.decider.isPathInfeasible()){ + if(!Expressions.isKnownWellDefined(e, Some(s.program))){ + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType) + } + val sort = v.symbolConverter.toSort(e.typ) + val newVar = v.decider.fresh(sort, None) // just make sure the returned term typechecks + return Q(s, newVar, None, v) + } + /* For debugging only */ e match { diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index cb05923d8..1a26c6561 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -7,19 +7,11 @@ package viper.silicon.rules import viper.silicon.Config.JoinMode - -import scala.annotation.unused -import viper.silver.cfg.silver.SilverCfg -import viper.silver.cfg.silver.SilverCfg.{SilverBlock, SilverEdge} -import viper.silver.verifier.{CounterexampleTransformer, NullPartialVerificationError, PartialVerificationError} -import viper.silver.verifier.errors._ -import viper.silver.verifier.reasons._ -import viper.silver.{ast, cfg} -import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType -import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyAnalyzer, DependencyType, ExpAnalysisSourceInfo, StmtAnalysisSourceInfo, TransitivityAnalysisSourceInfo} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.decider.RecordedPathConditions +import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType +import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyAnalyzer, DependencyType} import viper.silicon.interfaces._ import viper.silicon.interfaces.state.{NonQuantifiedChunk, QuantifiedChunk} import viper.silicon.logger.records.data.{CommentRecord, ConditionalEdgeRecord, ExecuteRecord, MethodCallRecord} @@ -28,12 +20,13 @@ import viper.silicon.state.terms._ import viper.silicon.utils.ast.{BigAnd, extractPTypeFromExp, simplifyVariableName} import viper.silicon.utils.freshSnap import viper.silicon.verifier.Verifier +import viper.silver.ast.utility.Statements import viper.silver.cfg.silver.SilverCfg import viper.silver.cfg.silver.SilverCfg.{SilverBlock, SilverEdge} import viper.silver.cfg.{ConditionalEdge, StatementBlock} import viper.silver.verifier.errors._ import viper.silver.verifier.reasons._ -import viper.silver.verifier.{CounterexampleTransformer, PartialVerificationError} +import viper.silver.verifier.{CounterexampleTransformer, NullPartialVerificationError, PartialVerificationError} import viper.silver.{ast, cfg} import scala.annotation.unused @@ -100,7 +93,7 @@ object executor extends ExecutionRules { def handleOutEdge(s: State, edge: SilverEdge, v: Verifier): State = { edge.kind match { - case cfg.Kind.Out => + case cfg.Kind.Out if !v.decider.isPathInfeasible() => val (fr1, h1) = v.stateConsolidator(s).merge(s.functionRecorder, s, s.h, s.invariantContexts.head, v) val s1 = s.copy(functionRecorder = fr1, h = h1, invariantContexts = s.invariantContexts.tail) @@ -130,7 +123,7 @@ object executor extends ExecutionRules { case (Seq(), _) => Q(s, v) case (Seq(edge), _) => follow(s, edge, v, joinPoint)(Q) case (Seq(edge1, edge2), Some(newJoinPoint)) if - s.moreJoins.id >= JoinMode.All.id && + (s.moreJoins.id >= JoinMode.All.id && // Can't directly match type because of type erasure ... edge1.isInstanceOf[ConditionalEdge[ast.Stmt, ast.Exp]] && edge2.isInstanceOf[ConditionalEdge[ast.Stmt, ast.Exp]] && @@ -138,8 +131,10 @@ object executor extends ExecutionRules { // this is the case if the source is a statement block, // as opposed to a loop head block. edge1.source.isInstanceOf[StatementBlock[ast.Stmt, ast.Exp]] && - edge2.source.isInstanceOf[StatementBlock[ast.Stmt, ast.Exp]] => + edge2.source.isInstanceOf[StatementBlock[ast.Stmt, ast.Exp]]) || + v.decider.isPathInfeasible() => + val isPathInfeasibleBefore = v.decider.isPathInfeasible() assert(edge1.source == edge2.source) val cedge1 = edge1.asInstanceOf[ConditionalEdge[ast.Stmt, ast.Exp]] @@ -166,6 +161,8 @@ object executor extends ExecutionRules { val s2 = entries match { case Seq(entry) => // One branch is dead entry.s + case Seq(entry1, _) if isPathInfeasibleBefore => // no need to merge since path is dead anyway + entry1.s case Seq(entry1, entry2) => // Both branches are alive entry1.pathConditionAwareMerge(entry2, v1) case _ => @@ -333,12 +330,6 @@ object executor extends ExecutionRules { else Q(s, v) - // TODO ake: skipping some statements (even in infeasible paths) resulted in variable not found errors - private def alwaysExecute(stmt: ast.Stmt): Boolean = stmt match { - case ast.NewStmt(_, _) | ast.LocalVarDeclStmt(_) | ast.Label(_, _) => true - case _ => false - } - def exec(s: State, stmt: ast.Stmt, v: Verifier) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -356,6 +347,16 @@ object executor extends ExecutionRules { (continuation: (State, Verifier) => VerificationResult) : VerificationResult = { + if(v.decider.isPathInfeasible()){ + if(Statements.hasProofObligations(stmt, state.program)){ + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType) + } + if(Statements.introducesSmtAssumptions(stmt)){ + v.decider.dependencyAnalyzer.addAssumption(True, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType) + } + return continuation(state, v) + } + val s = state.copy(h = magicWandSupporter.getExecutionHeap(state)) val Q: (State, Verifier) => VerificationResult = (s, v) => { continuation(magicWandSupporter.moveToReserveHeap(s, v), v)} diff --git a/src/main/scala/rules/HeapSupporter.scala b/src/main/scala/rules/HeapSupporter.scala index 2be975178..db08f48da 100644 --- a/src/main/scala/rules/HeapSupporter.scala +++ b/src/main/scala/rules/HeapSupporter.scala @@ -10,16 +10,16 @@ import viper.silicon import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType -import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyAnalyzer, DependencyType, TransitivityAnalysisSourceInfo} +import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyType, TransitivityAnalysisSourceInfo} import viper.silicon.interfaces.VerificationResult import viper.silicon.interfaces.state.{ChunkIdentifer, NonQuantifiedChunk, QuantifiedChunk} import viper.silicon.resources.{FieldID, PredicateID} import viper.silicon.rules.havocSupporter.{HavocHelperData, HavocOneData, HavocallData} import viper.silicon.rules.quantifiedChunkSupporter.freshSnapshotMap -import viper.silicon.state.{BasicChunk, BasicChunkIdentifier, ChunkIdentifier, Heap, MagicWandChunk, MagicWandIdentifier, QuantifiedBasicChunk, QuantifiedFieldChunk, QuantifiedMagicWandChunk, QuantifiedPredicateChunk, State, Store} import viper.silicon.state.terms._ import viper.silicon.state.terms.perms.IsPositive import viper.silicon.state.terms.predef.{`?r`, `?s`} +import viper.silicon.state._ import viper.silicon.supporters.functions.NoopFunctionRecorder import viper.silicon.utils.ast.{BigAnd, replaceVarsInExp} import viper.silicon.utils.freshSnap @@ -187,6 +187,12 @@ class DefaultHeapSupportRules extends HeapSupportRules { dependencyType: DependencyType) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { + if(v.decider.isPathInfeasible()){ + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType) + v.decider.dependencyAnalyzer.addAssumption(False, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getAssumptionType) + return Q(s, v) + } + val field = ass.lhs.field val ve = pve dueTo InsufficientPermission(ass.lhs) if (s.qpFields.contains(field)) { @@ -265,6 +271,11 @@ class DefaultHeapSupportRules extends HeapSupportRules { v: Verifier) (Q: (State, Term, Verifier) => VerificationResult): VerificationResult = { + if(v.decider.isPathInfeasible()){ + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType) + return Q(s, NoPerm, v) + } + val res = resAcc.res(s.program) /* It is assumed that, for a given field/predicate/wand identifier (res) * either only quantified or only non-quantified chunks are used. @@ -327,6 +338,14 @@ class DefaultHeapSupportRules extends HeapSupportRules { v: Verifier) (Q: (State, Term, Verifier) => VerificationResult) : VerificationResult = { + if(v.decider.isPathInfeasible()){ + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType) + + val sort = v.symbolConverter.toSort(fa.field.typ) + val newVar = v.decider.fresh(sort, None) // just make sure the returned term typechecks + return Q(s, newVar, v) + } + if (s.qpFields.contains(fa.field)) { val (relevantChunks, _) = quantifiedChunkSupporter.splitHeap[QuantifiedFieldChunk](s.h, BasicChunkIdentifier(fa.field.name)) @@ -518,6 +537,11 @@ class DefaultHeapSupportRules extends HeapSupportRules { v: Verifier, dependencyType: DependencyType) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult): VerificationResult = { + if(v.decider.isPathInfeasible()){ + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType) + return Q(s, h, Some(Unit), v) + } + val resource = resAcc.res(s.program) val useQPs = s.isQuantifiedResource(resource) if (useQPs) { @@ -629,6 +653,11 @@ class DefaultHeapSupportRules extends HeapSupportRules { v: Verifier, dependencyType: DependencyType) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult): VerificationResult = { + if(v.decider.isPathInfeasible()){ + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType) + return Q(s, h, Some(Unit), v) + } + quantifiedChunkSupporter.consume( s, h, diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index 63f3b5096..2a2fb39c2 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -16,6 +16,7 @@ import viper.silicon.state._ import viper.silicon.state.terms._ import viper.silicon.verifier.Verifier import viper.silver.ast +import viper.silver.ast.utility.Expressions import viper.silver.ast.utility.QuantifiedPermissions.QuantifiedPermissionAssertion import viper.silver.verifier.PartialVerificationError import viper.silver.verifier.reasons.{NegativePermission, QPAssertionNotInjective} @@ -211,6 +212,15 @@ object producer extends ProductionRules { (Q: (State, Verifier) => VerificationResult) : VerificationResult = { + if(v.decider.isPathInfeasible()){ + if(!Expressions.isKnownWellDefined(a, Some(s.program))){ + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType) + } + v.decider.dependencyAnalyzer.addAssumption(True, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getAssumptionType) + + return Q(s, v) + } + val sepIdentifier = v.symbExLog.openScope(new ProduceRecord(a, s, v.decider.pcs)) produceTlc(s, sf, a, pve, v, assumptionType)((s1, v1) => { v1.symbExLog.closeScope(sepIdentifier) diff --git a/src/main/scala/rules/StateConsolidator.scala b/src/main/scala/rules/StateConsolidator.scala index c99522715..a6fcb1c94 100644 --- a/src/main/scala/rules/StateConsolidator.scala +++ b/src/main/scala/rules/StateConsolidator.scala @@ -58,6 +58,8 @@ class MinimalStateConsolidator extends StateConsolidationRules { */ class DefaultStateConsolidator(protected val config: Config) extends StateConsolidationRules { def consolidate(s: State, v: Verifier): State = { + if(v.decider.isPathInfeasible()) return s + val comLog = new CommentRecord("state consolidation", s, v.decider.pcs) val sepIdentifier = v.symbExLog.openScope(comLog) val prevForcedSource = v.decider.analysisSourceInfoStack.getForcedSource @@ -135,6 +137,8 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol } def merge(fr1: FunctionRecorder, s: State, h: Heap, newH: Heap, v: Verifier): (FunctionRecorder, Heap) = { + if(v.decider.isPathInfeasible()) return (fr1, h) + val mergeLog = new CommentRecord("Merge", null, v.decider.pcs) val sepIdentifier = v.symbExLog.openScope(mergeLog) val (fr2, mergedChunks, newlyAddedChunks, snapEqs) = singleMerge(fr1, h.values.toSeq, newH.values.toSeq, s.functionRecorderQuantifiedVariables().map(_._1), v) diff --git a/src/main/scala/state/Chunks.scala b/src/main/scala/state/Chunks.scala index 33b9b3ab3..9adf4634b 100644 --- a/src/main/scala/state/Chunks.scala +++ b/src/main/scala/state/Chunks.scala @@ -7,7 +7,6 @@ package viper.silicon.state import viper.silicon -import viper.silver.ast import viper.silicon.dependencyAnalysis.AnalysisInfo import viper.silicon.interfaces.state._ import viper.silicon.resources._ diff --git a/src/test/scala/DependencyAnalysisTests.scala b/src/test/scala/DependencyAnalysisTests.scala index b734a46a9..5b63f2034 100644 --- a/src/test/scala/DependencyAnalysisTests.scala +++ b/src/test/scala/DependencyAnalysisTests.scala @@ -60,7 +60,7 @@ class DependencyAnalysisTests extends AnyFunSuite with DependencyAnalysisTestFra resetFrontend() executeTest(dirName + "/", fileName, frontend) }catch{ - case t: Throwable => fail(t.toString) + case t: Throwable => fail(t) } } } From 81b25c0f63f69a52dda7cb724215f5e88e974fab Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 9 Feb 2026 15:02:53 +0100 Subject: [PATCH 339/474] update submodules --- silver | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/silver b/silver index ba44390e2..f0f273c6c 160000 --- a/silver +++ b/silver @@ -1 +1 @@ -Subproject commit ba44390e2f98e0809e81ff2d6535f8d1c26cf718 +Subproject commit f0f273c6c92a00ec96be35ee9b224d16bebe2ac2 From 0a2bd2bf5fc017bdd15b6ed526fbe53e57128b28 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 10 Feb 2026 11:38:07 +0100 Subject: [PATCH 340/474] fix infeasible path incompleteness bug --- src/main/scala/decider/Decider.scala | 3 +++ src/main/scala/rules/Brancher.scala | 4 ++-- src/main/scala/rules/Executor.scala | 3 ++- .../all/infeasiblePath-completeness.vpr | 14 ++++++++++++++ 4 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 src/test/resources/dependencyAnalysisTests/all/infeasiblePath-completeness.vpr diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index bba73ad08..3b69e3742 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -290,6 +290,8 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } def setCurrentBranchCondition(t: Term, te: (ast.Exp, Option[ast.Exp]), assumptionType: AssumptionType): Unit = { + if(isPathInfeasible()) return + pathConditions.setCurrentBranchCondition(t, te) assume(t, Option.when(te._2.isDefined)(te._1), te._2, assumptionType) } @@ -495,6 +497,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => checkNode foreach dependencyAnalyzer.addAssertionNode dependencyAnalyzer.processUnsatCoreAndAddDependencies(prover.getLastUnsatCore, label) val infeasibleNodeId = dependencyAnalyzer.addInfeasibilityNode(!isAssert, analysisSourceInfoStack.getFullSourceInfo, assumptionType) + assumeWithoutSmokeChecks(InsertionOrderedSet((False, DependencyAnalyzer.createAssumptionLabel(infeasibleNodeId)))) dependencyAnalyzer.addDependency(checkNode.map(_.id), infeasibleNodeId) pcs.setCurrentInfeasibilityNode(checkNode.map(_.id)) } diff --git a/src/main/scala/rules/Brancher.scala b/src/main/scala/rules/Brancher.scala index 6b2bdc7a1..47f124674 100644 --- a/src/main/scala/rules/Brancher.scala +++ b/src/main/scala/rules/Brancher.scala @@ -174,8 +174,8 @@ object brancher extends BranchingRules { v1.decider.prover.comment(s"[else-branch: $cnt | $negatedCondition]") val sourceInfo = AnalysisSourceInfo.createAnalysisSourceInfo(conditionExp._1) v1.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo, DependencyType.get(conditionExp._1, DependencyType.PathCondition)) - v1.decider.pcs.setCurrentInfeasibilityNode(elseInfeasibilityNode) v1.decider.setCurrentBranchCondition(negatedCondition, (negatedConditionExp, negatedConditionExpNew), assumptionType) + v1.decider.pcs.setCurrentInfeasibilityNode(elseInfeasibilityNode) v1.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) var functionsOfElseBranchdDeciderBefore: Set[FunctionDecl] = null @@ -229,8 +229,8 @@ object brancher extends BranchingRules { v1.decider.prover.comment(s"[then-branch: $cnt | $condition]") val sourceInfo = AnalysisSourceInfo.createAnalysisSourceInfo(conditionExp._1) v1.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo, DependencyType.get(conditionExp._1, DependencyType.PathCondition)) - v1.decider.pcs.setCurrentInfeasibilityNode(thenInfeasibilityNode) v1.decider.setCurrentBranchCondition(condition, conditionExp, assumptionType) + v1.decider.pcs.setCurrentInfeasibilityNode(thenInfeasibilityNode) v1.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) fThen(v1.stateConsolidator(s1).consolidateOptionally(s1, v1), v1) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 1a26c6561..f10f379e0 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -288,6 +288,7 @@ object executor extends ExecutionRules { intermediateResult combine executionFlowController.locally(s2, v1)((s3, v2) => { v2.decider.declareAndRecordAsFreshFunctions(ff1 -- v2.decider.freshFunctions) /* [BRANCH-PARALLELISATION] */ v2.decider.declareAndRecordAsFreshMacros(fm1.filter(!v2.decider.freshMacros.contains(_))) /* [BRANCH-PARALLELISATION] */ + v2.decider.pcs.setCurrentInfeasibilityNode(pcs.infeasibilityNodeId) v2.decider.assume(pcs.assumptions map (t => v.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t))), Some(pcs.assumptionExps), "Loop invariant", enforceAssumption=false, assumptionType=AssumptionType.Internal) v2.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) if (!Verifier.config.disableInfeasibilityChecks() && v2.decider.checkSmoke()) @@ -352,7 +353,7 @@ object executor extends ExecutionRules { v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType) } if(Statements.introducesSmtAssumptions(stmt)){ - v.decider.dependencyAnalyzer.addAssumption(True, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType) + v.decider.dependencyAnalyzer.addAssumption(True, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getAssumptionType) } return continuation(state, v) } diff --git a/src/test/resources/dependencyAnalysisTests/all/infeasiblePath-completeness.vpr b/src/test/resources/dependencyAnalysisTests/all/infeasiblePath-completeness.vpr new file mode 100644 index 000000000..9d8718e15 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/all/infeasiblePath-completeness.vpr @@ -0,0 +1,14 @@ +field f: Int + + +method client(b: Bool, x: Ref) + requires b ==> acc(x.f) +{ + + while(b) + invariant b ==> acc(x.f) + { + exhale acc(x.f) + inhale acc(x.f) + } +} \ No newline at end of file From f195487a1aa8948be4ff7f6e26aac2a89b9fb0c5 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 10 Feb 2026 17:20:53 +0100 Subject: [PATCH 341/474] fix unsoundness bug --- src/main/scala/decider/Decider.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 3b69e3742..48b7c6bf8 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -497,7 +497,8 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => checkNode foreach dependencyAnalyzer.addAssertionNode dependencyAnalyzer.processUnsatCoreAndAddDependencies(prover.getLastUnsatCore, label) val infeasibleNodeId = dependencyAnalyzer.addInfeasibilityNode(!isAssert, analysisSourceInfoStack.getFullSourceInfo, assumptionType) - assumeWithoutSmokeChecks(InsertionOrderedSet((False, DependencyAnalyzer.createAssumptionLabel(infeasibleNodeId)))) +// THIS IS UNSOUND! Unsoundness is introduced when infeasibility is introduced while executing a package statements and pontentially in other cases as well. +// assumeWithoutSmokeChecks(InsertionOrderedSet((False, DependencyAnalyzer.createAssumptionLabel(infeasibleNodeId)))) dependencyAnalyzer.addDependency(checkNode.map(_.id), infeasibleNodeId) pcs.setCurrentInfeasibilityNode(checkNode.map(_.id)) } From 722f36cf534dfe6f795ff2338f7cdda0bad54c5f Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 10 Feb 2026 17:50:17 +0100 Subject: [PATCH 342/474] TMP: run pipeline with DA enabled --- src/main/scala/Config.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/scala/Config.scala b/src/main/scala/Config.scala index d54fa674e..f9ebd24bf 100644 --- a/src/main/scala/Config.scala +++ b/src/main/scala/Config.scala @@ -438,7 +438,7 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { private val rawProverArgs: ScallopOption[String] = opt[String]("proverArgs", descr = ( "Command-line arguments which should be forwarded to the prover. " + "The expected format is \" ... \", excluding the quotation marks."), - default = None, + default = Some("proof=true unsat-core=true"), noshort = true ) @@ -699,7 +699,7 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { val enableDependencyAnalysis: ScallopOption[Boolean] = opt[Boolean]("enableDependencyAnalysis", descr = "Enable dependency analysis mode", - default = Some(false), + default = Some(true), noshort = true ) @@ -711,7 +711,7 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { val disableInfeasibilityChecks: ScallopOption[Boolean] = opt[Boolean]("disableInfeasibilityChecks", descr = "Disable infeasibility checks. As a consequence all paths will be explored to the end. (Potentially) huge performance overhead!", - default = Some(false), + default = Some(true), noshort = true ) From c2bf37e73cfeecfe15e1c46de373a70b0085ad0d Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 10 Feb 2026 17:50:29 +0100 Subject: [PATCH 343/474] Revert "TMP: run pipeline with DA enabled" This reverts commit 722f36cf534dfe6f795ff2338f7cdda0bad54c5f. --- src/main/scala/Config.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/scala/Config.scala b/src/main/scala/Config.scala index f9ebd24bf..d54fa674e 100644 --- a/src/main/scala/Config.scala +++ b/src/main/scala/Config.scala @@ -438,7 +438,7 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { private val rawProverArgs: ScallopOption[String] = opt[String]("proverArgs", descr = ( "Command-line arguments which should be forwarded to the prover. " + "The expected format is \" ... \", excluding the quotation marks."), - default = Some("proof=true unsat-core=true"), + default = None, noshort = true ) @@ -699,7 +699,7 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { val enableDependencyAnalysis: ScallopOption[Boolean] = opt[Boolean]("enableDependencyAnalysis", descr = "Enable dependency analysis mode", - default = Some(true), + default = Some(false), noshort = true ) @@ -711,7 +711,7 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { val disableInfeasibilityChecks: ScallopOption[Boolean] = opt[Boolean]("disableInfeasibilityChecks", descr = "Disable infeasibility checks. As a consequence all paths will be explored to the end. (Potentially) huge performance overhead!", - default = Some(true), + default = Some(false), noshort = true ) From 35ede491d5261129e663e5a84b3b09e3ca9c31d5 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 11 Feb 2026 16:21:47 +0100 Subject: [PATCH 344/474] make DA more precise for magic wands --- src/main/scala/rules/MagicWandSupporter.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 89b2ebe87..68cd37542 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -11,7 +11,7 @@ import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.decider.RecordedPathConditions import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType -import viper.silicon.dependencyAnalysis.{AssumptionType, DependencyType} +import viper.silicon.dependencyAnalysis.{AssumptionType, DependencyType, ExpAnalysisSourceInfo} import viper.silicon.interfaces._ import viper.silicon.interfaces.state._ import viper.silicon.state._ @@ -373,7 +373,10 @@ object magicWandSupporter extends SymbolicExecutionRules { val freshSnapRoot = freshSnap(sorts.Snap, v1) // Produce the wand's LHS. + val prevForcedSource = v.decider.analysisSourceInfoStack.getForcedSource + v.decider.analysisSourceInfoStack.setForcedSource(ExpAnalysisSourceInfo(wand.left, wand.left.pos), dependencyType) produce(s1.copy(conservingSnapshotGeneration = true), toSf(freshSnapRoot), wand.left, pve, v1, dependencyType.assumptionType)((sLhs, v2) => { + v.decider.analysisSourceInfoStack.setForcedSource(prevForcedSource) val proofScriptCfg = proofScript.toCfg() val emptyHeap = v2.heapSupporter.getEmptyHeap(sLhs.program) From 662685b3c9ce28e09763793fecf13864e55552e9 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 11 Feb 2026 17:33:38 +0100 Subject: [PATCH 345/474] fix graph export (edges) --- src/main/scala/dependencyAnalysis/DependencyGraph.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyGraph.scala b/src/main/scala/dependencyAnalysis/DependencyGraph.scala index bb5e7e58c..74ace7fd5 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraph.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraph.scala @@ -225,7 +225,7 @@ class DependencyGraph extends ReadOnlyDependencyGraph { private def exportEdges(fileName: String): Unit = { val writer = new PrintWriter(fileName) writer.println("source,target,label") - edges foreach (e => e._2 foreach (s => writer.println(s.toString + "," + e._1.toString + ",direct"))) + getAllEdges foreach (e => e._2 foreach (s => writer.println(s.toString + "," + e._1.toString + ",direct"))) writer.close() } From 5b678f74d4aec66fb0566cdb8bea459350bf9f44 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 11 Feb 2026 17:35:35 +0100 Subject: [PATCH 346/474] (WIP) fix chunk nodes --- src/main/scala/decider/Decider.scala | 15 +-- .../DependencyAnalysisNode.scala | 7 +- .../DependencyAnalyzer.scala | 92 ++++++++++--------- src/main/scala/interfaces/state/Chunks.scala | 19 +++- src/main/scala/rules/PredicateSupporter.scala | 2 +- src/main/scala/rules/StateConsolidator.scala | 2 +- src/main/scala/state/Chunks.scala | 20 ++-- .../classification-infeasible-path.vpr | 0 8 files changed, 89 insertions(+), 68 deletions(-) create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgress/classification-infeasible-path.vpr diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 48b7c6bf8..54333cac7 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -320,11 +320,12 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => if(!Verifier.config.enableDependencyAnalysis()) return buildChunk(perm) + val labelNodeOpt = getOrCreateAnalysisLabelNode() // if(createLabel) getOrCreateAnalysisLabelNode() else getOrCreateAnalysisLabelNode(sourceChunks) + if(isExhale) - dependencyAnalyzer.registerExhaleChunk(sourceChunks, buildChunk, perm, analysisInfo) + dependencyAnalyzer.registerExhaleChunk(sourceChunks, buildChunk, perm, labelNodeOpt, analysisInfo) else { - val labelNodeOpt = if(createLabel) getOrCreateAnalysisLabelNode() else getOrCreateAnalysisLabelNode(sourceChunks) - dependencyAnalyzer.registerInhaleChunk(sourceChunks, buildChunk, perm, labelNodeOpt, analysisInfo, createLabel) + dependencyAnalyzer.registerInhaleChunk(sourceChunks, buildChunk, perm, labelNodeOpt, analysisInfo) } } @@ -332,10 +333,10 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => if(!Verifier.config.enableDependencyAnalysis()) return None - if(sourceChunks.size == 1 && sourceTerms.isEmpty){ - val chunkInhaleNode = dependencyAnalyzer.getChunkInhaleNode(sourceChunks.head) - return chunkInhaleNode.map(_.labelNode) - } +// if(sourceChunks.size == 1 && sourceTerms.isEmpty){ +// val chunkInhaleNode = dependencyAnalyzer.getChunkNode(sourceChunks.head) +// return chunkInhaleNode.map(_.labelNode) +// } val (label, _) = fresh(ast.LocalVar(DependencyAnalyzer.analysisLabelName, ast.Bool)()) val labelNode = dependencyAnalyzer.createLabelNode(label, sourceChunks, sourceTerms) val smtLabel = DependencyAnalyzer.createAssumptionLabel(labelNode.map(_.id)) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala index dc330b97f..30659badf 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala @@ -74,9 +74,10 @@ trait GeneralAssertionNode extends DependencyAnalysisNode { def getAssertFailedNode(): GeneralAssertionNode } +// this is not strictly needed anymore but storing the chunk and label node is useful for debugging purposes trait ChunkAnalysisInfo { val chunk: Chunk - def getChunk: Chunk = chunk + val labelNode: LabelNode } case class SimpleAssumptionNode(term: Term, description: Option[String], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean) extends GeneralAssumptionNode { @@ -105,11 +106,11 @@ case class PermissionInhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSo override def getNodeType: String = "Inhale" } -case class PermissionExhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean, hasFailed: Boolean = false) extends GeneralAssertionNode with ChunkAnalysisInfo { +case class PermissionExhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean, labelNode: LabelNode, hasFailed: Boolean = false) extends GeneralAssertionNode with ChunkAnalysisInfo { override def getNodeType: String = "Exhale" override def getNodeString: String = "exhale " + chunk.toString - override def getAssertFailedNode(): GeneralAssertionNode = PermissionExhaleNode(chunk, term, sourceInfo, assumptionType, isClosed, hasFailed=true) + override def getAssertFailedNode(): GeneralAssertionNode = PermissionExhaleNode(chunk, term, sourceInfo, assumptionType, isClosed, labelNode, hasFailed=true) } /** diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index 55d3c4db3..9a5411b56 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -26,15 +26,15 @@ trait DependencyAnalyzer { def getMember: Option[ast.Member] def getNodes: Iterable[DependencyAnalysisNode] - def getChunkInhaleNode(chunk: Chunk): Option[PermissionInhaleNode] +// def getChunkNode(chunk: Chunk): Option[ChunkAnalysisInfo] def addNodes(nodes: Iterable[DependencyAnalysisNode]): Unit def addAssertionNode(node: GeneralAssertionNode): Unit def addAssumptionNode(node: GeneralAssumptionNode): Unit def addAssumption(assumption: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, description: Option[String] = None): Option[Int] def addAxiom(assumption: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, description: Option[String] = None): Option[Int] - def registerInhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, labelNode: Option[LabelNode], analysisInfo: AnalysisInfo, isExhale: Boolean): CH = buildChunk(perm) - def registerExhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, analysisInfo: AnalysisInfo): CH = buildChunk(perm) + def registerInhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, labelNode: Option[LabelNode], analysisInfo: AnalysisInfo): CH = buildChunk(perm) + def registerExhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, labelNodeOpt: Option[LabelNode], analysisInfo: AnalysisInfo): CH = buildChunk(perm) def createLabelNode(label: Var, sourceChunks: Iterable[Chunk], sourceTerms: Iterable[Term]): Option[LabelNode] def createAssertOrCheckNode(term: Term, assumptionType: AssumptionType, analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] @@ -43,7 +43,7 @@ trait DependencyAnalyzer { def addDependency(source: Option[Int], dest: Option[Int]): Unit def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit - def addPermissionDependencies(sourceChunks: Set[Chunk], sourceTerms: Set[Term], targetChunk: Chunk): Unit +// def addPermissionDependencies(sourceChunks: Set[Chunk], sourceTerms: Set[Term], targetChunk: Chunk): Unit def addCustomTransitiveDependency(sourceSourceInfo: AnalysisSourceInfo, targetSourceInfo: AnalysisSourceInfo): Unit /** @@ -230,19 +230,21 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { override def getNodes: Iterable[DependencyAnalysisNode] = dependencyGraph.getNodes - override def getChunkInhaleNode(chunk: Chunk): Option[PermissionInhaleNode] = { - val inhaleNode = dependencyGraph.getAssumptionNodes - .filter(c => c.isInstanceOf[PermissionInhaleNode] && chunk.equals(c.asInstanceOf[ChunkAnalysisInfo].getChunk)) - .map(_.asInstanceOf[PermissionInhaleNode]) - assert(inhaleNode.size == 1) - inhaleNode.headOption - } - - private def getChunkNodeIds(oldChunks: Set[Chunk]): Set[Int] = { - dependencyGraph.getAssumptionNodes - .filter(c => c.isInstanceOf[PermissionInhaleNode] && oldChunks.contains(c.asInstanceOf[ChunkAnalysisInfo].getChunk)) - .map(_.id).toSet - } + // TODO ake: remove once we are sure this is not needed anymore +// override def getChunkNode(chunk: Chunk): Option[ChunkAnalysisInfo] = { +// val chunkNode = dependencyGraph.getNodes +// .filter(c => c.isInstanceOf[ChunkAnalysisInfo] && chunk.equals(c.asInstanceOf[ChunkAnalysisInfo].getChunk)) +// .map(_.asInstanceOf[ChunkAnalysisInfo]) +// assert(chunkNode.size == 1) +// chunkNode.headOption +// } +// +// private def getChunkNodeIds(oldChunks: Set[Chunk]): Set[Int] = { +// Set.empty +// dependencyGraph.getNodes +// .filter(c => c.isInstanceOf[ChunkAnalysisInfo] && oldChunks.contains(c.asInstanceOf[ChunkAnalysisInfo].getChunk)) +// .map(_.id).toSet +// } private def getNodeIdsByTerm(terms: Set[Term]): Set[Int] = { dependencyGraph.getNodes @@ -272,23 +274,26 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { Some(node.id) } - override def registerExhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, analysisInfo: AnalysisInfo): CH = { + override def registerExhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, labelNodeOpt: Option[LabelNode], analysisInfo: AnalysisInfo): CH = { val startTime = startTimeMeasurement() - val chunk = buildChunk(perm) - val chunkNode = addPermissionExhaleNode(chunk, chunk.perm, analysisInfo.sourceInfo, analysisInfo.assumptionType) - addPermissionDependencies(sourceChunks, Set(), chunkNode) + val labelNode = labelNodeOpt.get + val chunk = buildChunk(Ite(labelNode.term, perm, NoPerm)) + val chunkNode = addPermissionExhaleNode(chunk, chunk.perm, analysisInfo.sourceInfo, analysisInfo.assumptionType, labelNode) + if(chunkNode.isDefined) + addDependency(chunkNode, Some(labelNode.id)) +// addPermissionDependencies(sourceChunks, Set(), chunkNode) TODO ake: can be removed stopTimeMeasurementAndAddToTotal(startTime, runtimeOverheadPermissionNodes) chunk } - override def registerInhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, labelNodeOpt: Option[LabelNode], analysisInfo: AnalysisInfo, isExhale: Boolean): CH = { + override def registerInhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, labelNodeOpt: Option[LabelNode], analysisInfo: AnalysisInfo): CH = { val startTime = startTimeMeasurement() val labelNode = labelNodeOpt.get val chunk = buildChunk(Ite(labelNode.term, perm, NoPerm)) val chunkNode = addPermissionInhaleNode(chunk, chunk.perm, analysisInfo.sourceInfo, analysisInfo.assumptionType, labelNode) if(chunkNode.isDefined) addDependency(chunkNode, Some(labelNode.id)) - addPermissionDependencies(sourceChunks, Set(), chunkNode) +// addPermissionDependencies(sourceChunks, Set(), chunkNode) TODO ake: can be removed stopTimeMeasurementAndAddToTotal(startTime, runtimeOverheadPermissionNodes) chunk } @@ -299,17 +304,17 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { Some(node.id) } - private def addPermissionExhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { - val node = PermissionExhaleNode(chunk, permAmount, sourceInfo, assumptionType, isClosed_) + private def addPermissionExhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, labelNode: LabelNode): Option[Int] = { + val node = PermissionExhaleNode(chunk, permAmount, sourceInfo, assumptionType, isClosed_, labelNode) addAssertionNode(node) - addPermissionDependencies(Set(chunk), Set(), Some(node.id)) +// addPermissionDependencies(Set(chunk), Set(), Some(node.id)) TODO ake: can be removed Some(node.id) } override def createLabelNode(label: Var, sourceChunks: Iterable[Chunk], sourceTerms: Iterable[Term]): Option[LabelNode] = { val labelNode = LabelNode(label) addAssumptionNode(labelNode) - dependencyGraph.addEdges(getChunkNodeIds(sourceChunks.toSet) ++ getNodeIdsByTerm(sourceTerms.toSet), labelNode.id) + dependencyGraph.addEdges(/* getChunkNodeIds(sourceChunks.toSet) ++ TODO ake: can be removed */ getNodeIdsByTerm(sourceTerms.toSet), labelNode.id) Some(labelNode) } @@ -359,21 +364,22 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { stopTimeMeasurementAndAddToTotal(startTime, timeToProcessUnsatCore) } - private def addPermissionDependencies(sourceChunks: Set[Chunk], sourceTerms: Set[Term], newChunkNodeId: Option[Int]): Unit = { - if(newChunkNodeId.isEmpty) return - - val sourceNodeIds = getChunkNodeIds(sourceChunks).filter(id => id != newChunkNodeId.get) ++ getNodeIdsByTerm(sourceTerms) - dependencyGraph.addEdges(sourceNodeIds, newChunkNodeId.get) - } - - override def addPermissionDependencies(sourceChunks: Set[Chunk], sourceTerms: Set[Term], newChunk: Chunk): Unit = { - val startTime = startTimeMeasurement() - val newChunkId = dependencyGraph.getAssumptionNodes - .filter(c => c.isInstanceOf[PermissionInhaleNode] && c.isInstanceOf[ChunkAnalysisInfo] && newChunk.equals(c.asInstanceOf[ChunkAnalysisInfo].getChunk)) - .map(_.id).toSet - addPermissionDependencies(sourceChunks, sourceTerms, newChunkId.headOption) - stopTimeMeasurementAndAddToTotal(startTime, runtimeOverheadPermissionNodes) - } + // TODO ake: remove once we are sure this is not needed anymore +// private def addPermissionDependencies(sourceChunks: Set[Chunk], sourceTerms: Set[Term], newChunkNodeId: Option[Int]): Unit = { +// if(newChunkNodeId.isEmpty) return +// +// val sourceNodeIds = getChunkNodeIds(sourceChunks).filter(id => id != newChunkNodeId.get) ++ getNodeIdsByTerm(sourceTerms) +// dependencyGraph.addEdges(sourceNodeIds, newChunkNodeId.get) +// } +// +// override def addPermissionDependencies(sourceChunks: Set[Chunk], sourceTerms: Set[Term], newChunk: Chunk): Unit = { +// val startTime = startTimeMeasurement() +// val newChunkId = dependencyGraph.getNodes +// .filter(c => c.isInstanceOf[ChunkAnalysisInfo] && c.isInstanceOf[ChunkAnalysisInfo] && newChunk.equals(c.asInstanceOf[ChunkAnalysisInfo].getChunk)) +// .map(_.id).toSet +//// addPermissionDependencies(sourceChunks, sourceTerms, newChunkId.headOption) +// stopTimeMeasurementAndAddToTotal(startTime, runtimeOverheadPermissionNodes) +// } override def addCustomTransitiveDependency(sourceSourceInfo: AnalysisSourceInfo, targetSourceInfo: AnalysisSourceInfo): Unit = { val sourceNodes = dependencyGraph.getAssertionNodes filter (n => n.sourceInfo.getSourceForTransitiveEdges.equals(sourceSourceInfo.getSourceForTransitiveEdges)) @@ -477,7 +483,6 @@ class NoDependencyAnalyzer extends DependencyAnalyzer { override def getMember: Option[ast.Member] = None override def getNodes: Iterable[DependencyAnalysisNode] = Set.empty - override def getChunkInhaleNode(chunk: Chunk): Option[PermissionInhaleNode] = None override def addNodes(nodes: Iterable[DependencyAnalysisNode]): Unit = {} override def addAssertionNode(node: GeneralAssertionNode): Unit = {} @@ -492,7 +497,6 @@ class NoDependencyAnalyzer extends DependencyAnalyzer { override def addDependency(source: Option[Int], dest: Option[Int]): Unit = {} override def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit = {} - override def addPermissionDependencies(sourceChunks: Set[Chunk], sourceTerms: Set[Term], targetChunk: Chunk): Unit = {} override def addCustomTransitiveDependency(sourceSourceInfo: AnalysisSourceInfo, targetSourceInfo: AnalysisSourceInfo): Unit = {} override def addDependenciesForExplicitPostconditions(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp]): Unit = {} override def addFunctionAxiomEdges(): Unit = {} diff --git a/src/main/scala/interfaces/state/Chunks.scala b/src/main/scala/interfaces/state/Chunks.scala index 109c4487b..865c7ea03 100644 --- a/src/main/scala/interfaces/state/Chunks.scala +++ b/src/main/scala/interfaces/state/Chunks.scala @@ -18,7 +18,7 @@ trait Chunk { val perm: Term val permExp: Option[ast.Exp] - def substitute(terms: silicon.Map[Term, Term]): Chunk + protected def substitute(terms: silicon.Map[Term, Term]): Chunk } trait ChunkIdentifer @@ -32,7 +32,9 @@ trait GeneralChunk extends Chunk { protected def permPlus(perm: Term, permExp: Option[ast.Exp]): GeneralChunk protected def withPerm(newPerm: Term, newPermExp: Option[ast.Exp]): GeneralChunk - def permScale(perm: Term, permExp: Option[ast.Exp]): GeneralChunk + protected def permScale(perm: Term, permExp: Option[ast.Exp]): GeneralChunk + + protected def substitute(terms: silicon.Map[Term, Term]): GeneralChunk val permExp: Option[ast.Exp] } @@ -66,6 +68,19 @@ object GeneralChunk { chunk.withPerm(finalPerm, newPermExp)}, newPerm, analysisInfo, isExhale, createLabel=true) } + + def permScale(chunk: GeneralChunk, newPerm: Term, newPermExp: Option[ast.Exp], analysisInfo: AnalysisInfo, isExhale: Boolean=false): GeneralChunk = { + analysisInfo.decider.registerDerivedChunk[GeneralChunk](Set(chunk), {finalPerm => + chunk.permScale(finalPerm, newPermExp)}, + newPerm, analysisInfo, isExhale, createLabel=true) + } + + def substitute(chunk: GeneralChunk, terms: silicon.Map[Term, Term], analysisInfo: AnalysisInfo, isExhale: Boolean=false): GeneralChunk = { + val newChunk = chunk.substitute(terms) + analysisInfo.decider.registerDerivedChunk[GeneralChunk](Set(chunk), {finalPerm => + newChunk.withPerm(finalPerm, newChunk.permExp)}, + newChunk.perm, analysisInfo, isExhale, createLabel=true) + } } trait NonQuantifiedChunk extends GeneralChunk { diff --git a/src/main/scala/rules/PredicateSupporter.scala b/src/main/scala/rules/PredicateSupporter.scala index 62d440f73..e7d31266c 100644 --- a/src/main/scala/rules/PredicateSupporter.scala +++ b/src/main/scala/rules/PredicateSupporter.scala @@ -106,7 +106,7 @@ object predicateSupporter extends PredicateSupportRules { case PredicateLeafNode(h, assumptions) => val debugExp = Option.when(withExp)(DebugExp.createInstance("Assumption from unfolded predicate body")) assumptions.foreach(a => v.decider.assume(a.replace(toReplace), debugExp, dependencyType.assumptionType)) - val substChunks = h.values.map(_.substitute(toReplace).asInstanceOf[GeneralChunk].permScale(s.permissionScalingFactor, s.permissionScalingFactorExp)) // TODO ake + val substChunks = h.values.map(chunk => GeneralChunk.permScale(GeneralChunk.substitute(chunk.asInstanceOf[GeneralChunk], toReplace, v.decider.getAnalysisInfo(dependencyType.assumptionType)), s.permissionScalingFactor, s.permissionScalingFactorExp, v.decider.getAnalysisInfo(dependencyType.assumptionType))) val quantifiedResourceIdentifiers: Set[ChunkIdentifer] = s.qpPredicates.map(p => BasicChunkIdentifier(p.name)) ++ s.qpFields.map(f => BasicChunkIdentifier(f.name)) ++ s.qpMagicWands diff --git a/src/main/scala/rules/StateConsolidator.scala b/src/main/scala/rules/StateConsolidator.scala index a6fcb1c94..3fa83e6c4 100644 --- a/src/main/scala/rules/StateConsolidator.scala +++ b/src/main/scala/rules/StateConsolidator.scala @@ -215,7 +215,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol private def mergeChunks(fr1: FunctionRecorder, chunk1: Chunk, chunk2: Chunk, qvars: Seq[Var], v: Verifier): Option[(FunctionRecorder, Chunk, Term)] = { val result = mergeChunks1(fr1, chunk1, chunk2, qvars, v) result.map({case (fRec, ch, snapEq) => - v.decider.dependencyAnalyzer.addPermissionDependencies(Set(chunk1, chunk2), Set(), ch) +// v.decider.dependencyAnalyzer.addPermissionDependencies(Set(chunk1, chunk2), Set(), ch) (fRec, ch, v.decider.wrapWithDependencyAnalysisLabel(snapEq, Set(chunk1, chunk2)))}) } diff --git a/src/main/scala/state/Chunks.scala b/src/main/scala/state/Chunks.scala index 9adf4634b..bd003b7e0 100644 --- a/src/main/scala/state/Chunks.scala +++ b/src/main/scala/state/Chunks.scala @@ -67,7 +67,7 @@ case class BasicChunk private (resourceID: BaseID, override protected def permPlus(newPerm: Term, newPermExp: Option[ast.Exp]): BasicChunk = withPerm(PermPlus(perm, newPerm), newPermExp.map(npe => ast.PermAdd(permExp.get, npe)())) - override def permScale(newPerm: Term, newPermExp: Option[ast.Exp]) = + override protected def permScale(newPerm: Term, newPermExp: Option[ast.Exp]) = withPerm(PermTimes(perm, newPerm), permExp.map(pe => ast.PermMul(pe, newPermExp.get)())) override protected def withPerm(newPerm: Term, newPermExp: Option[ast.Exp]): BasicChunk = new BasicChunk(resourceID, id, args, argsExp, snap, snapExp, newPerm, newPermExp) override protected def withSnap(newSnap: Term, newSnapExp: Option[ast.Exp]): BasicChunk = new BasicChunk(resourceID, id, args, argsExp, newSnap, newSnapExp, perm, permExp) @@ -77,7 +77,7 @@ case class BasicChunk private (resourceID: BaseID, case PredicateID => s"$id($snap; ${args.mkString(",")}) # $perm" } - override def substitute(terms: silicon.Map[Term, Term]) = { + override protected def substitute(terms: silicon.Map[Term, Term]): BasicChunk = { copy(args = args.map(_.replace(terms)), snap = snap.replace(terms), perm = perm.replace(terms)) } } @@ -172,14 +172,14 @@ case class QuantifiedFieldChunk private(id: BasicChunkIdentifier, override protected def permPlus(newPerm: Term, newPermExp: Option[ast.Exp]) = withPerm(PermPlus(permValue, newPerm), newPermExp.map(npe => ast.PermAdd(permValueExp.get, npe)())) - override def permScale(newPerm: Term, newPermExp: Option[ast.Exp]) = + override protected def permScale(newPerm: Term, newPermExp: Option[ast.Exp]) = withPerm(PermTimes(perm, newPerm), permExp.map(pe => ast.PermMul(pe, newPermExp.get)())) override protected def withSnapshotMap(newFvf: Term) = new QuantifiedFieldChunk(id, newFvf, condition, conditionExp, permValue, permValueExp, invs, singletonRcvr, singletonRcvrExp, hints) override lazy val toString = s"${terms.Forall} ${`?r`} :: ${`?r`}.$id -> $fvf # $perm" - override def substitute(terms: silicon.Map[Term, Term]): Chunk = + override protected def substitute(terms: silicon.Map[Term, Term]): QuantifiedFieldChunk = copy(fvf = fvf.replace(terms), condition = condition.replace(terms), permValue = permValue.replace(terms), singletonRcvr = singletonRcvr.map(_.replace(terms)), hints = hints.map(_.replace(terms)), invs = invs.map(_.substitute(terms))) } @@ -243,14 +243,14 @@ case class QuantifiedPredicateChunk private(id: BasicChunkIdentifier, override protected def permPlus(newPerm: Term, newPermExp: Option[ast.Exp]) = withPerm(PermPlus(permValue, newPerm), newPermExp.map(npe => ast.PermAdd(permValueExp.get, npe)())) - override def permScale(newPerm: Term, newPermExp: Option[ast.Exp]) = + override protected def permScale(newPerm: Term, newPermExp: Option[ast.Exp]) = withPerm(PermTimes(perm, newPerm), permExp.map(pe => ast.PermMul(pe, newPermExp.get)())) override protected def withSnapshotMap(newPsf: Term) = new QuantifiedPredicateChunk(id, quantifiedVars, quantifiedVarExps, newPsf, condition, conditionExp, permValue, permValueExp, invs, singletonArgs, singletonArgExps, hints) override lazy val toString = s"${terms.Forall} ${quantifiedVars.mkString(",")} :: $id(${quantifiedVars.mkString(",")}) -> $psf # $perm" - override def substitute(terms: silicon.Map[Term, Term]): Chunk = + override protected def substitute(terms: silicon.Map[Term, Term]): QuantifiedPredicateChunk = copy(psf = psf.replace(terms), condition = condition.replace(terms), permValue = permValue.replace(terms), singletonArgs = singletonArgs.map(_.map(_.replace(terms))), hints = hints.map(_.replace(terms)), invs = invs.map(_.substitute(terms))) } @@ -305,7 +305,7 @@ case class QuantifiedMagicWandChunk private(id: MagicWandIdentifier, override protected def permPlus(newPerm: Term, newPermExp: Option[ast.Exp]) = withPerm(PermPlus(perm, newPerm), newPermExp.map(npe => ast.PermAdd(permExp.get, npe)())) - override def permScale(newPerm: Term, newPermExp: Option[ast.Exp]) = + override protected def permScale(newPerm: Term, newPermExp: Option[ast.Exp]) = withPerm(PermTimes(perm, newPerm), permExp.map(pe => ast.PermMul(pe, newPermExp.get)())) override protected def withPerm(newPerm: Term, newPermExp: Option[ast.Exp]) = new QuantifiedMagicWandChunk(id, quantifiedVars, quantifiedVarExps, wsf, newPerm, newPermExp, invs, singletonArgs, singletonArgExps, hints) @@ -314,7 +314,7 @@ case class QuantifiedMagicWandChunk private(id: MagicWandIdentifier, override lazy val toString = s"${terms.Forall} ${quantifiedVars.mkString(",")} :: $id(${quantifiedVars.mkString(",")}) -> $wsf # $perm" - override def substitute(terms: silicon.Map[Term, Term]): Chunk = + override protected def substitute(terms: silicon.Map[Term, Term]): QuantifiedMagicWandChunk = copy(wsf = wsf.replace(terms), perm = perm.replace(terms), singletonArgs = singletonArgs.map(_.map(_.replace(terms))), hints = hints.map(_.replace(terms)), invs = invs.map(_.substitute(terms))) } @@ -373,7 +373,7 @@ case class MagicWandChunk private(id: MagicWandIdentifier, override protected def permPlus(newPerm: Term, newPermExp: Option[ast.Exp]) = withPerm(PermPlus(perm, newPerm), newPermExp.map(npe => ast.PermAdd(permExp.get, npe)())) - override def permScale(newPerm: Term, newPermExp: Option[ast.Exp]) = + override protected def permScale(newPerm: Term, newPermExp: Option[ast.Exp]) = withPerm(PermTimes(perm, newPerm), permExp.map(pe => ast.PermMul(pe, newPermExp.get)())) override protected def withPerm(newPerm: Term, newPermExp: Option[ast.Exp]) = new MagicWandChunk(id, bindings, args, argsExp, snap, newPerm, newPermExp) @@ -393,7 +393,7 @@ case class MagicWandChunk private(id: MagicWandIdentifier, s"wand@$pos[$snap; ${args.mkString(", ")}]" } - override def substitute(terms: silicon.Map[Term, Term]) = { + override protected def substitute(terms: silicon.Map[Term, Term]): MagicWandChunk = { copy(args = args.map(_.replace(terms)), snap = snap.replace(terms).asInstanceOf[MagicWandSnapshot], perm = perm.replace(terms)) } } diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgress/classification-infeasible-path.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgress/classification-infeasible-path.vpr new file mode 100644 index 000000000..e69de29bb From 1874235c55bb097898287a3801f57dc20663a07f Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 12 Feb 2026 08:30:46 +0100 Subject: [PATCH 347/474] minor fix --- src/main/scala/rules/Executor.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index f10f379e0..a9def9bc5 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -288,7 +288,7 @@ object executor extends ExecutionRules { intermediateResult combine executionFlowController.locally(s2, v1)((s3, v2) => { v2.decider.declareAndRecordAsFreshFunctions(ff1 -- v2.decider.freshFunctions) /* [BRANCH-PARALLELISATION] */ v2.decider.declareAndRecordAsFreshMacros(fm1.filter(!v2.decider.freshMacros.contains(_))) /* [BRANCH-PARALLELISATION] */ - v2.decider.pcs.setCurrentInfeasibilityNode(pcs.infeasibilityNodeId) + if(v2.decider.pcs.getCurrentInfeasibilityNode.isEmpty) v2.decider.pcs.setCurrentInfeasibilityNode(pcs.infeasibilityNodeId) v2.decider.assume(pcs.assumptions map (t => v.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t))), Some(pcs.assumptionExps), "Loop invariant", enforceAssumption=false, assumptionType=AssumptionType.Internal) v2.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) if (!Verifier.config.disableInfeasibilityChecks() && v2.decider.checkSmoke()) From c4d6f44789843b9a462d1bcc02d69765c8833ca0 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 12 Feb 2026 10:21:41 +0100 Subject: [PATCH 348/474] add test case for infeasible paths --- .../classification-infeasible-path.vpr | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgress/classification-infeasible-path.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgress/classification-infeasible-path.vpr index e69de29bb..a533a482c 100644 --- a/src/test/resources/dependencyAnalysisTests/verificationProgress/classification-infeasible-path.vpr +++ b/src/test/resources/dependencyAnalysisTests/verificationProgress/classification-infeasible-path.vpr @@ -0,0 +1,42 @@ +field f: Int + +predicate p(x: Ref){ acc(x.f) } + +method foo(x: Ref, y: Ref, n: Int, b: Bool, s: Seq[Int]) + requires !b + requires acc(x.f) && x.f == 0 +{ + if(b){ + x.f := 1 + assert x.f > 0 + var i: Int + i := 0 + if(i > 0){ + i := 1 + } + + //s[0 := 1] + + i := s[0] + + assume |s| > 0 + + assume s[0] == 1 + + assume 2 in s + + while(i > 10) + invariant i > 10 + { + assert false + i := x.f + i := -10 + y.f := 3 + inhale i > 10 + fold p(y) + package true --* p(y) + apply true --* p(y) + unfold p(y) + } + } +} From 187238d6f6fc8c3d7c584bea49233f5ccff3e5a1 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 12 Feb 2026 15:19:01 +0100 Subject: [PATCH 349/474] fix missing dependency types --- .../scala/dependencyAnalysis/AnalysisInfo.scala | 14 ++++++-------- .../DependencyGraphInterpreter.scala | 6 +++--- .../dependencyAnalysis/SourceInfoStack.scala | 9 ++++++--- src/main/scala/rules/Brancher.scala | 6 ++++-- src/main/scala/rules/Evaluator.scala | 6 +++--- src/main/scala/rules/Executor.scala | 16 ++++++++-------- .../functions/FunctionVerificationUnit.scala | 2 +- 7 files changed, 31 insertions(+), 28 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala index 08771da94..f6a88bd1f 100644 --- a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala +++ b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala @@ -3,17 +3,17 @@ package viper.silicon.dependencyAnalysis object AssumptionType extends Enumeration { type AssumptionType = Value - val Explicit, LoopInvariant, PathCondition, Rewrite, SourceCode, DomainAxiom, Implicit, Internal, Trigger, ExplicitPostcondition, ImplicitPostcondition, MethodCall, FunctionBody, Precondition = Value + val Explicit, LoopInvariant, PathCondition, Rewrite, SourceCode, DomainAxiom, Implicit, Internal, Trigger, ExplicitPostcondition, ImplicitPostcondition, MethodCall, FunctionBody, Precondition, Unknown = Value def fromString(s: String): Option[Value] = values.find(_.toString == s) - def explicitAssumptionTypes: Set[AssumptionType] = Set(Explicit, ExplicitPostcondition, DomainAxiom) + def explicitAssumptionTypes: Set[AssumptionType] = Set(Explicit, ExplicitPostcondition) def postconditionTypes: Set[AssumptionType] = Set(ImplicitPostcondition, ExplicitPostcondition) // used to join graphs via postconditions def explicitAssertionTypes: Set[AssumptionType] = Set(Explicit, ImplicitPostcondition, ExplicitPostcondition) def internalTypes: Set[AssumptionType] = Set(Internal, Trigger) // will always be hidden from user def joinConditionTypes: Set[AssumptionType] = postconditionTypes ++ Set(FunctionBody) - def verificationAnnotationTypes: Set[AssumptionType] = Set(LoopInvariant, Rewrite, ExplicitPostcondition, ImplicitPostcondition, Precondition, Explicit) - def sourceCodeTypes: Set[AssumptionType] = Set(SourceCode, PathCondition, MethodCall, FunctionBody) + def verificationAnnotationTypes: Set[AssumptionType] = Set(LoopInvariant, Rewrite, ExplicitPostcondition, ImplicitPostcondition, Precondition, Explicit, DomainAxiom) + def sourceCodeTypes: Set[AssumptionType] = AssumptionType.values.diff(explicitAssumptionTypes ++ explicitAssertionTypes ++ verificationAnnotationTypes ++ internalTypes) def methodCallTypes: Set[AssumptionType] = Set(MethodCall) // used to join graphs via postconditions } @@ -47,15 +47,13 @@ object DependencyType { case _: ast.Exhale | _: ast.Assert => ExplicitAssertion case _: ast.Inhale | _: ast.Assume => ExplicitAssumption case _: ast.Fold | _: ast.Unfold | _: ast.Package | _: ast.Apply => Rewrite - case _: ast.Quasihavoc | _: ast.Quasihavocall => Implicit - case _ => Implicit /* TODO: should not happen */ + case _: ast.Quasihavoc | _: ast.Quasihavocall => DependencyType.Implicit + case _ => DependencyType.make(Unknown) /* TODO: should not happen */ } } def get(exp: ast.Exp, dependencyType: DependencyType): DependencyType = DependencyAnalyzer.extractDependencyTypeFromInfo(exp.info).getOrElse(dependencyType) - def get(exp: ast.Exp): DependencyType = get(exp, Implicit) - } case class DependencyType(assumptionType: AssumptionType, assertionType: AssumptionType) diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 09575851c..3cc693c38 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -231,8 +231,8 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen val allSourceCodeNodes = toCompactUserLevelNodes(getNonInternalAssumptionNodes).filter(n => nonSourceCodeAssumptionTypes.intersect(n.assumptionTypes).isEmpty).map(_.source.getTopLevelSource) val coveredSourceCodeNodes = coveredNodes.map(_.source.getTopLevelSource).intersect(allSourceCodeNodes) -// println(s"Covered:\n\t${coveredSourceCodeNodes.toList.sortBy(_.getLineNumber).mkString("\n\t")}") -// println(s"Uncovered:\n\t${allSourceCodeNodes.diff(coveredSourceCodeNodes).toList.sortBy(_.getLineNumber).mkString("\n\t")}") +// println(s"Covered Source Code:\n\t${coveredSourceCodeNodes.toList.sortBy(_.getLineNumber).mkString("\n\t")}") +// println(s"Uncovered Source Code:\n\t${allSourceCodeNodes.diff(coveredSourceCodeNodes).toList.sortBy(_.getLineNumber).mkString("\n\t")}") println(s"Spec Quality = ${coveredSourceCodeNodes.size} / ${allSourceCodeNodes.size}") coveredSourceCodeNodes.size.toDouble / allSourceCodeNodes.size.toDouble } @@ -319,7 +319,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen val specQuality = computeSpecQuality(assertionDeps.flatMap(_._1).toSet) - val assertionQualities = assertionDeps map (ass => (computeAssertionQuality(ass._1), ass._2)) filterNot (n => isNaN(n._1)) + val assertionQualities = assertionDeps filterNot (_._1.isEmpty) map (ass => (computeAssertionQuality(ass._1), ass._2)) filterNot (n => isNaN(n._1)) val numAssertions = assertionQualities.size val fullyVerifiedAssertions = assertionQualities.filter(_._1 == 1.0) val numFullyVerifiedAssertions = fullyVerifiedAssertions.size diff --git a/src/main/scala/dependencyAnalysis/SourceInfoStack.scala b/src/main/scala/dependencyAnalysis/SourceInfoStack.scala index 0d9b53048..591b6670c 100644 --- a/src/main/scala/dependencyAnalysis/SourceInfoStack.scala +++ b/src/main/scala/dependencyAnalysis/SourceInfoStack.scala @@ -111,9 +111,12 @@ case class AnalysisSourceInfoStack() extends SourceInfoStack { override def getAssertionType: AssumptionType = getDependencyType.assertionType - override def getDependencyType: DependencyType = forcedMainSource match { - case Some(value) => value._2 - case None => sourceInfos.lastOption.map(_._2).getOrElse(DependencyType.Implicit) + override def getDependencyType: DependencyType = { + if(!Verifier.config.enableDependencyAnalysis()) return DependencyType.make(AssumptionType.Unknown) + forcedMainSource match { + case Some(value) => value._2 + case None => sourceInfos.lastOption.map(_._2).getOrElse(DependencyType.make(AssumptionType.Unknown)) + } } } \ No newline at end of file diff --git a/src/main/scala/rules/Brancher.scala b/src/main/scala/rules/Brancher.scala index 47f124674..ae8bed5af 100644 --- a/src/main/scala/rules/Brancher.scala +++ b/src/main/scala/rules/Brancher.scala @@ -9,7 +9,7 @@ package viper.silicon.rules import viper.silicon.common.concurrency._ import viper.silicon.decider.PathConditionStack import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType -import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, DependencyType} +import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, DependencyAnalyzer, DependencyType} import viper.silicon.interfaces.{Unreachable, VerificationResult} import viper.silicon.reporting.condenseToViperResult import viper.silicon.state.State @@ -47,11 +47,13 @@ object brancher extends BranchingRules { : VerificationResult = { if(v.decider.isPathInfeasible()){ + val analysisSourceInfo = AnalysisSourceInfo.createAnalysisSourceInfo(conditionExp._1) + v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(analysisSourceInfo, DependencyAnalyzer.extractDependencyTypeFromInfo(conditionExp._1.info).getOrElse(DependencyType.PathCondition)) if(!Expressions.isKnownWellDefined(conditionExp._1, Some(s.program))){ v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType) } v.decider.dependencyAnalyzer.addAssumption(condition, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getAssumptionType) - + v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(analysisSourceInfo) return fThen(s, v).combine(fElse(s, v)) } diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index c446e4ec7..6b46b5c3d 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -42,7 +42,7 @@ trait EvaluationRules extends SymbolicExecutionRules { (Q: (State, List[Term], Option[List[ast.Exp]], Verifier) => VerificationResult) : VerificationResult - def eval(s: State, e: ast.Exp, pve: PartialVerificationError, v: Verifier) + def eval(s: State, e: ast.Exp, pve: PartialVerificationError, v: Verifier, dependencyType: Option[DependencyType]=None) (Q: (State, Term, Option[ast.Exp], Verifier) => VerificationResult) : VerificationResult @@ -82,13 +82,13 @@ object evaluator extends EvaluationRules { /** Wrapper Method for eval, for logging. See Executor.scala for explanation of analogue. **/ @inline - def eval(s: State, e: ast.Exp, pve: PartialVerificationError, v: Verifier) + def eval(s: State, e: ast.Exp, pve: PartialVerificationError, v: Verifier, dependencyType: Option[DependencyType]=None) (Q: (State, Term, Option[ast.Exp], Verifier) => VerificationResult) : VerificationResult = { val sepIdentifier = v.symbExLog.openScope(new EvaluateRecord(e, s, v.decider.pcs)) val sourceInfo = AnalysisSourceInfo.createAnalysisSourceInfo(e) - v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo, DependencyType.get(e)) + v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo, dependencyType.getOrElse(v.decider.analysisSourceInfoStack.getDependencyType)) eval3(s, e, pve, v)((s1, t, eNew, v1) => { v1.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) v1.symbExLog.closeScope(sepIdentifier) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index a9def9bc5..427abb325 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -68,12 +68,12 @@ object executor extends ExecutionRules { val condEdgeRecord = new ConditionalEdgeRecord(ce.condition, s, v.decider.pcs) val sepIdentifier = v.symbExLog.openScope(condEdgeRecord) val s1 = handleOutEdge(s, edge, v) - val dependencyType = DependencyAnalyzer.extractDependencyTypeFromInfo(ce.condition.info) - eval(s1, ce.condition, IfFailed(ce.condition), v)((s2, tCond, condNew, v1) => + val dependencyType = DependencyAnalyzer.extractDependencyTypeFromInfo(ce.condition.info).getOrElse(DependencyType.PathCondition) + eval(s1, ce.condition, IfFailed(ce.condition), v, Some(dependencyType))((s2, tCond, condNew, v1) => /* Using branch(...) here ensures that the edge condition is recorded * as a branch condition on the pathcondition stack. */ - brancher.branch(s2.copy(parallelizeBranches = false), tCond, (ce.condition, condNew), v1, dependencyType.map(_.assumptionType).getOrElse(AssumptionType.PathCondition))( + brancher.branch(s2.copy(parallelizeBranches = false), tCond, (ce.condition, condNew), v1, dependencyType.assumptionType)( (s3, v3) => exec(s3.copy(parallelizeBranches = s2.parallelizeBranches), ce.target, ce.kind, v3, joinPoint)((s4, v4) => { v4.symbExLog.closeScope(sepIdentifier) @@ -149,7 +149,7 @@ object executor extends ExecutionRules { val dependencyType = DependencyAnalyzer.extractDependencyTypeFromInfo(cedge1.condition.info).getOrElse(DependencyType.PathCondition) - eval(s, cedge1.condition, pvef(cedge1.condition), v)((s1, t0, condNew, v1) => + eval(s, cedge1.condition, pvef(cedge1.condition), v, Some(dependencyType))((s1, t0, condNew, v1) => // The type arguments here are Null because there is no need to pass any join data. joiner.join[scala.Null, scala.Null](s1, v1, dependencyType.assumptionType, resetState = false)((s2, v2, QB) => { brancher.branch(s2, t0, (cedge1.condition, condNew), v2, dependencyType.assumptionType)( @@ -183,9 +183,9 @@ object executor extends ExecutionRules { if Verifier.config.parallelizeBranches() && cond2 == ast.Not(cond1)() => val condEdgeRecord = new ConditionalEdgeRecord(thenEdge.condition, s, v.decider.pcs) val sepIdentifier = v.symbExLog.openScope(condEdgeRecord) - val dependencyType = DependencyAnalyzer.extractDependencyTypeFromInfo(thenEdge.condition.info) - val res = eval(s, thenEdge.condition, IfFailed(thenEdge.condition), v)((s2, tCond, eCondNew, v1) => - brancher.branch(s2, tCond, (thenEdge.condition, eCondNew), v1, dependencyType.map(_.assumptionType).getOrElse(AssumptionType.PathCondition))( + val dependencyType = DependencyAnalyzer.extractDependencyTypeFromInfo(thenEdge.condition.info).getOrElse(DependencyType.PathCondition) + val res = eval(s, thenEdge.condition, IfFailed(thenEdge.condition), v, Some(dependencyType))((s2, tCond, eCondNew, v1) => + brancher.branch(s2, tCond, (thenEdge.condition, eCondNew), v1, dependencyType.assumptionType)( (s3, v3) => { follow(s3, thenEdge, v3, joinPoint)(Q) }, @@ -301,7 +301,7 @@ object executor extends ExecutionRules { case (result, _) if !result.continueVerification => result case (intermediateResult, eCond) => intermediateResult combine executionFlowController.locally(s4, v3)((s5, v4) => { - eval(s5, eCond, WhileFailed(eCond), v4)((_, _, _, _) => + eval(s5, eCond, WhileFailed(eCond), v4, Some(DependencyAnalyzer.extractDependencyTypeFromInfo(eCond.info).getOrElse(DependencyType.PathCondition)))((_, _, _, _) => Success()) }) } diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index 338c14c5e..ebe6a718d 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -293,7 +293,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver val labelledPcsPre = pcsPre map (t => v.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t))) decider.assume(labelledPcsPre, pcsPreExp, s"precondition of ${function.name}", enforceAssumption=false, assumptionType=annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Precondition)) v.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) - eval(s1, body, FunctionNotWellformed(function), v)((s2, tBody, bodyNew, _) => { + eval(s1, body, FunctionNotWellformed(function), v, Some(DependencyType.make(AssumptionType.FunctionBody)))((s2, tBody, bodyNew, _) => { val debugExp = if (wExp) { val e = ast.EqCmp(ast.Result(function.typ)(), body)(function.pos, function.info, function.errT) val eNew = ast.EqCmp(ast.Result(function.typ)(), bodyNew.get)(function.pos, function.info, function.errT) From 3b2a0ea5e34d8fe0572e7460dcc2139e72436a64 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 14 Feb 2026 09:46:16 +0100 Subject: [PATCH 350/474] fix graph join for frontends --- src/main/scala/decider/Decider.scala | 6 ++--- .../dependencyAnalysis/AnalysisInfo.scala | 2 +- .../DependencyAnalysisNode.scala | 10 +++++--- .../DependencyAnalyzer.scala | 24 +++++++++---------- .../DependencyGraphInterpreter.scala | 3 ++- .../dependencyAnalysis/SourceInfoStack.scala | 1 + src/main/scala/rules/Brancher.scala | 2 +- src/main/scala/rules/Consumer.scala | 2 +- src/main/scala/rules/Executor.scala | 4 +++- src/main/scala/rules/HeapSupporter.scala | 2 +- src/main/scala/rules/Joiner.scala | 4 ++-- src/main/scala/rules/Producer.scala | 4 ++-- 12 files changed, 36 insertions(+), 28 deletions(-) diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 54333cac7..7d24195e9 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -168,7 +168,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => analysisSourceInfoStack = AnalysisSourceInfoStack() } - def getAnalysisInfo(assumptionType: AssumptionType): AnalysisInfo = AnalysisInfo(this, dependencyAnalyzer, analysisSourceInfoStack.getFullSourceInfo, assumptionType) + def getAnalysisInfo(assumptionType: AssumptionType): AnalysisInfo = AnalysisInfo(this, dependencyAnalyzer, analysisSourceInfoStack.getFullSourceInfo, assumptionType, analysisSourceInfoStack.isJoinRelevantAssumption) def functionDecls: Set[FunctionDecl] = _declaredFreshFunctions def macroDecls: Vector[MacroDecl] = _declaredFreshMacros @@ -387,7 +387,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } val filteredAssumptionsWithLabels = filteredAssumptions map{case (t, _) => - val assumptionId: Option[Int] = dependencyAnalyzer.addAssumption(t, analysisSourceInfo, assumptionType) + val assumptionId: Option[Int] = dependencyAnalyzer.addAssumption(t, analysisSourceInfo, assumptionType, analysisSourceInfoStack.isJoinRelevantAssumption) (t, DependencyAnalyzer.createAssumptionLabel(assumptionId)) } @@ -423,7 +423,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => private def addAssumptionLabels(filteredTerms: Iterable[Term], assumptionType: AssumptionType) = { filteredTerms map (t => { - val assumptionIds = dependencyAnalyzer.addAssumption(t, analysisSourceInfoStack.getFullSourceInfo, assumptionType) + val assumptionIds = dependencyAnalyzer.addAssumption(t, analysisSourceInfoStack.getFullSourceInfo, assumptionType, analysisSourceInfoStack.isJoinRelevantAssumption) (t, DependencyAnalyzer.createAssumptionLabel(assumptionIds)) }) } diff --git a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala index f6a88bd1f..4eef89a01 100644 --- a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala +++ b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala @@ -59,7 +59,7 @@ object DependencyType { case class DependencyType(assumptionType: AssumptionType, assertionType: AssumptionType) case class AnalysisInfo(decider: Decider, dependencyAnalyzer: DependencyAnalyzer, sourceInfo: AnalysisSourceInfo, - assumptionType: AssumptionType) { + assumptionType: AssumptionType, isJoinNode: Boolean) { def withAssumptionType(newAssumptionType: AssumptionType): AnalysisInfo = { copy(assumptionType=newAssumptionType) } diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala index 30659badf..87f00a999 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala @@ -65,6 +65,8 @@ trait DependencyAnalysisNode extends AbstractDependencyAnalysisNode { trait GeneralAssumptionNode extends DependencyAnalysisNode { override def getNodeType: String = "Assumption" + + val isJoinNode: Boolean } trait GeneralAssertionNode extends DependencyAnalysisNode { override def getNodeType: String = "Assertion" @@ -80,11 +82,11 @@ trait ChunkAnalysisInfo { val labelNode: LabelNode } -case class SimpleAssumptionNode(term: Term, description: Option[String], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean) extends GeneralAssumptionNode { +case class SimpleAssumptionNode(term: Term, description: Option[String], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean, isJoinNode: Boolean) extends GeneralAssumptionNode { override def getNodeString: String = "assume " + term.toString + description.map(" (" + _ + ")").getOrElse("") } -case class AxiomAssumptionNode(term: Term, description: Option[String], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean) extends GeneralAssumptionNode { +case class AxiomAssumptionNode(term: Term, description: Option[String], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean, isJoinNode: Boolean = false) extends GeneralAssumptionNode { override def getNodeString: String = "assume axiom " + term.toString + description.map(" (" + _ + ")").getOrElse("") } @@ -101,7 +103,7 @@ case class SimpleCheckNode(term: Term, assumptionType: AssumptionType, sourceInf override def getAssertFailedNode(): GeneralAssertionNode = SimpleCheckNode(term, assumptionType, sourceInfo, isClosed, hasFailed=true) } -case class PermissionInhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean, labelNode: LabelNode) extends GeneralAssumptionNode with ChunkAnalysisInfo { +case class PermissionInhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean, labelNode: LabelNode, isJoinNode: Boolean) extends GeneralAssumptionNode with ChunkAnalysisInfo { override def getNodeString: String = "inhale " + chunk.toString override def getNodeType: String = "Inhale" } @@ -122,6 +124,7 @@ case class LabelNode(term: Var) extends GeneralAssumptionNode { val assumptionType: AssumptionType = AssumptionType.Internal val isClosed: Boolean = true val description: String = term.toString + val isJoinNode: Boolean = false override def getNodeType: String = "Label" override def getNodeString: String = "assume " + description } @@ -137,6 +140,7 @@ case class InfeasibilityNode(sourceInfo: AnalysisSourceInfo, assumptionType: Ass val term: Term = False val isClosed: Boolean = true val description: String = "False" + val isJoinNode: Boolean = false override def getNodeType: String = "Infeasible" override def getNodeString: String = "infeasible" diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index 9a5411b56..4b45c13d8 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -31,7 +31,7 @@ trait DependencyAnalyzer { def addNodes(nodes: Iterable[DependencyAnalysisNode]): Unit def addAssertionNode(node: GeneralAssertionNode): Unit def addAssumptionNode(node: GeneralAssumptionNode): Unit - def addAssumption(assumption: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, description: Option[String] = None): Option[Int] + def addAssumption(assumption: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isJoinNode: Boolean, description: Option[String] = None): Option[Int] def addAxiom(assumption: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, description: Option[String] = None): Option[Int] def registerInhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, labelNode: Option[LabelNode], analysisInfo: AnalysisInfo): CH = buildChunk(perm) def registerExhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, labelNodeOpt: Option[LabelNode], analysisInfo: AnalysisInfo): CH = buildChunk(perm) @@ -208,7 +208,7 @@ object DependencyAnalyzer { // postconditions of methods assumed by every method call should depend on the assertions that justify them // hence, we add edges from assertions of method postconditions to assumptions of the same postcondition (at method calls) val relevantAssumptionNodes = joinCandidateNodes - .filter(node => node.isInstanceOf[GeneralAssumptionNode] && AssumptionType.methodCallTypes.contains(node.assumptionType)) + .filter(node => node.isInstanceOf[GeneralAssumptionNode] && node.asInstanceOf[GeneralAssumptionNode].isJoinNode) .groupBy(_.sourceInfo.getFineGrainedSource) .view.mapValues(_.map(_.id)) .toMap @@ -262,8 +262,8 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { override def addAssertionNode(node: GeneralAssertionNode): Unit = dependencyGraph.addAssertionNode(node) // adding assumption nodes - override def addAssumption(assumption: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, description: Option[String]): Option[Int] = { - val node = SimpleAssumptionNode(assumption, description, analysisSourceInfo, assumptionType, isClosed_) + override def addAssumption(assumption: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isJoinNode: Boolean, description: Option[String]): Option[Int] = { + val node = SimpleAssumptionNode(assumption, description, analysisSourceInfo, assumptionType, isClosed_, isJoinNode) addAssumptionNode(node) Some(node.id) } @@ -290,7 +290,7 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { val startTime = startTimeMeasurement() val labelNode = labelNodeOpt.get val chunk = buildChunk(Ite(labelNode.term, perm, NoPerm)) - val chunkNode = addPermissionInhaleNode(chunk, chunk.perm, analysisInfo.sourceInfo, analysisInfo.assumptionType, labelNode) + val chunkNode = addPermissionInhaleNode(chunk, chunk.perm, analysisInfo.sourceInfo, analysisInfo.assumptionType, labelNode, isJoinNode=analysisInfo.isJoinNode) if(chunkNode.isDefined) addDependency(chunkNode, Some(labelNode.id)) // addPermissionDependencies(sourceChunks, Set(), chunkNode) TODO ake: can be removed @@ -298,8 +298,8 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { chunk } - private def addPermissionInhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, labelNode: LabelNode): Option[Int] = { - val node = PermissionInhaleNode(chunk, permAmount, sourceInfo, assumptionType, isClosed_, labelNode) + private def addPermissionInhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, labelNode: LabelNode, isJoinNode: Boolean): Option[Int] = { + val node = PermissionInhaleNode(chunk, permAmount, sourceInfo, assumptionType, isClosed_, labelNode, isJoinNode) addAssumptionNode(node) Some(node.id) } @@ -388,7 +388,7 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { } override def addDependenciesForExplicitPostconditions(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp]): Unit = { - val sourceNodeIds = sourceExps.flatMap(e => addAssumption(True, AnalysisSourceInfo.createAnalysisSourceInfo(e), AssumptionType.Precondition, None)) + val sourceNodeIds = sourceExps.flatMap(e => addAssumption(True, AnalysisSourceInfo.createAnalysisSourceInfo(e), AssumptionType.Precondition, isJoinNode=false, None)) val targetNodes = targetExps.flatMap(e => addAssertNode(True, AssumptionType.ExplicitPostcondition, AnalysisSourceInfo.createAnalysisSourceInfo(e))) dependencyGraph.addEdges(sourceNodeIds, targetNodes) } @@ -434,10 +434,10 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { nodeMap.put(n.id, n.id) mergedGraph.addAssumptionNode(n) } - val assumptionNodesBySource = dependencyGraph.getAssumptionNodes.filter(!keepNode(_)).groupBy(n => (n.sourceInfo, n.assumptionType)) - assumptionNodesBySource foreach { case ((sourceInfo, assumptionType), assumptionNodes) => + val assumptionNodesBySource = dependencyGraph.getAssumptionNodes.filter(!keepNode(_)).groupBy(n => (n.sourceInfo, n.assumptionType, n.isJoinNode)) + assumptionNodesBySource foreach { case ((sourceInfo, assumptionType, isJoinNode), assumptionNodes) => if (assumptionNodes.nonEmpty) { - val newNode = SimpleAssumptionNode(True, None, sourceInfo, assumptionType, isClosed = false) + val newNode = SimpleAssumptionNode(True, None, sourceInfo, assumptionType, isClosed = false, isJoinNode=isJoinNode) assumptionNodes foreach (n => nodeMap.put(n.id, newNode.id)) mergedGraph.addAssumptionNode(newNode) } @@ -487,7 +487,7 @@ class NoDependencyAnalyzer extends DependencyAnalyzer { override def addNodes(nodes: Iterable[DependencyAnalysisNode]): Unit = {} override def addAssertionNode(node: GeneralAssertionNode): Unit = {} override def addAssumptionNode(node: GeneralAssumptionNode): Unit = {} - override def addAssumption(assumption: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, description: Option[String] = None): Option[Int] = None + override def addAssumption(assumption: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isJoinNode: Boolean, description: Option[String] = None): Option[Int] = None override def addAxiom(assumption: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, description: Option[String]): Option[Int] = None override def createLabelNode(labelTerm: Var, sourceChunks: Iterable[Chunk], sourceTerms: Iterable[Term]): Option[LabelNode] = None diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 3cc693c38..66eeebfe2 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -31,7 +31,8 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen def getJoinCandidateNodes: Iterable[DependencyAnalysisNode] = joinCandidateNodes - protected lazy val joinCandidateNodes: Seq[DependencyAnalysisNode] = dependencyGraph.getNodes.filter(node => node.isInstanceOf[AxiomAssumptionNode] || AssumptionType.joinConditionTypes.contains(node.assumptionType) || AssumptionType.methodCallTypes.contains(node.assumptionType)) + protected lazy val joinCandidateNodes: Seq[DependencyAnalysisNode] = dependencyGraph.getAssertionNodes.filter(node => AssumptionType.joinConditionTypes.contains(node.assumptionType)) ++ + dependencyGraph.getAssumptionNodes.filter(node => node.isJoinNode || node.isInstanceOf[AxiomAssumptionNode] || AssumptionType.joinConditionTypes.contains(node.assumptionType)) private def toUserLevelNodes(nodes: Iterable[DependencyAnalysisNode]): Set[UserLevelDependencyAnalysisNode] = UserLevelDependencyAnalysisNode.from(nodes) diff --git a/src/main/scala/dependencyAnalysis/SourceInfoStack.scala b/src/main/scala/dependencyAnalysis/SourceInfoStack.scala index 591b6670c..dbe89526b 100644 --- a/src/main/scala/dependencyAnalysis/SourceInfoStack.scala +++ b/src/main/scala/dependencyAnalysis/SourceInfoStack.scala @@ -48,6 +48,7 @@ trait SourceInfoStack { case class AnalysisSourceInfoStack() extends SourceInfoStack { private var sourceInfos: List[(AnalysisSourceInfo, DependencyType)] = List.empty private var forcedMainSource: Option[(AnalysisSourceInfo, DependencyType)] = None + var isJoinRelevantAssumption: Boolean = false override def getAnalysisSourceInfos: List[(AnalysisSourceInfo, DependencyType)] = sourceInfos diff --git a/src/main/scala/rules/Brancher.scala b/src/main/scala/rules/Brancher.scala index ae8bed5af..41c48048d 100644 --- a/src/main/scala/rules/Brancher.scala +++ b/src/main/scala/rules/Brancher.scala @@ -52,7 +52,7 @@ object brancher extends BranchingRules { if(!Expressions.isKnownWellDefined(conditionExp._1, Some(s.program))){ v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType) } - v.decider.dependencyAnalyzer.addAssumption(condition, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getAssumptionType) + v.decider.dependencyAnalyzer.addAssumption(condition, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getAssumptionType, isJoinNode=false) v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(analysisSourceInfo) return fThen(s, v).combine(fElse(s, v)) } diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index 6a3a15bf6..5496d115f 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -382,7 +382,7 @@ object consumer extends ConsumptionRules { State.mergeHeap( entry1.data._1, And(entry1.pathConditions.branchConditions), Option.when(withExp)(BigAnd(entry1.pathConditions.branchConditionExps.map(_._2.get))), entry2.data._1, And(entry2.pathConditions.branchConditions), Option.when(withExp)(BigAnd(entry2.pathConditions.branchConditionExps.map(_._2.get))), - AnalysisInfo(v.decider, v.decider.dependencyAnalyzer, StringAnalysisSourceInfo("conditional join", e0.pos), dependencyType.assumptionType) + AnalysisInfo(v.decider, v.decider.dependencyAnalyzer, StringAnalysisSourceInfo("conditional join", e0.pos), dependencyType.assumptionType, v.decider.analysisSourceInfoStack.isJoinRelevantAssumption) ), // Assume that entry1.pcs is inverse of entry2.pcs (entry1.data._2, entry2.data._2) match { diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 427abb325..ed9b52715 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -353,7 +353,7 @@ object executor extends ExecutionRules { v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType) } if(Statements.introducesSmtAssumptions(stmt)){ - v.decider.dependencyAnalyzer.addAssumption(True, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getAssumptionType) + v.decider.dependencyAnalyzer.addAssumption(True, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getAssumptionType, isJoinNode=false) } return continuation(state, v) } @@ -570,7 +570,9 @@ object executor extends ExecutionRules { val outs = meth.formalReturns.map(_.localVar) val gOuts = Store(outs.map(x => (x, v2.decider.fresh(x))).toMap) val s4 = s3.copy(g = s3.g + gOuts, oldHeaps = s3.oldHeaps + (Verifier.PRE_STATE_LABEL -> magicWandSupporter.getEvalHeap(s1))) + v2.decider.analysisSourceInfoStack.isJoinRelevantAssumption = true produces(s4, freshSnap, meth.posts, _ => pveCallTransformed, v2, getAssumptionType(AssumptionType.MethodCall))((s5, v3) => { // TODO ake: make sure the join is always made! + v3.decider.analysisSourceInfoStack.isJoinRelevantAssumption = false v3.symbExLog.closeScope(postCondId) v3.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) val gLhs = Store(lhs.zip(outs) diff --git a/src/main/scala/rules/HeapSupporter.scala b/src/main/scala/rules/HeapSupporter.scala index db08f48da..a83bb012a 100644 --- a/src/main/scala/rules/HeapSupporter.scala +++ b/src/main/scala/rules/HeapSupporter.scala @@ -189,7 +189,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { : VerificationResult = { if(v.decider.isPathInfeasible()){ v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType) - v.decider.dependencyAnalyzer.addAssumption(False, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getAssumptionType) + v.decider.dependencyAnalyzer.addAssumption(False, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getAssumptionType, isJoinNode=false) return Q(s, v) } diff --git a/src/main/scala/rules/Joiner.scala b/src/main/scala/rules/Joiner.scala index 9bdb78708..f507f780a 100644 --- a/src/main/scala/rules/Joiner.scala +++ b/src/main/scala/rules/Joiner.scala @@ -25,12 +25,12 @@ case class JoinDataEntry[D](s: State, data: D, pathConditions: RecordedPathCondi // we can directly merge JoinDataEntries to obtain new States, // and the join data entries themselves provide information about the path conditions to State.merge. def pathConditionAwareMerge(other: JoinDataEntry[D], v: Verifier): State = { - val res = State.merge(this.s, this.pathConditions, other.s, other.pathConditions, AnalysisInfo(v.decider, v.decider.dependencyAnalyzer, StringAnalysisSourceInfo("merge", NoPosition), AssumptionType.Internal)) + val res = State.merge(this.s, this.pathConditions, other.s, other.pathConditions, AnalysisInfo(v.decider, v.decider.dependencyAnalyzer, StringAnalysisSourceInfo("merge", NoPosition), AssumptionType.Internal, isJoinNode=false)) v.stateConsolidator(s).consolidate(res, v) } def pathConditionAwareMergeWithoutConsolidation(other: JoinDataEntry[D], v: Verifier): State = { - State.merge(this.s, this.pathConditions, other.s, other.pathConditions, AnalysisInfo(v.decider, v.decider.dependencyAnalyzer, StringAnalysisSourceInfo("merge", NoPosition), AssumptionType.Internal)) + State.merge(this.s, this.pathConditions, other.s, other.pathConditions, AnalysisInfo(v.decider, v.decider.dependencyAnalyzer, StringAnalysisSourceInfo("merge", NoPosition), AssumptionType.Internal, isJoinNode=false)) } } diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index 2a2fb39c2..189daf6cd 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -216,7 +216,7 @@ object producer extends ProductionRules { if(!Expressions.isKnownWellDefined(a, Some(s.program))){ v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType) } - v.decider.dependencyAnalyzer.addAssumption(True, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getAssumptionType) + v.decider.dependencyAnalyzer.addAssumption(True, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getAssumptionType, isJoinNode=false) return Q(s, v) } @@ -235,7 +235,7 @@ object producer extends ProductionRules { _assumptionType: AssumptionType) (continuation: (State, Verifier) => VerificationResult) : VerificationResult = { - val assumptionType = DependencyAnalyzer.extractAssumptionTypeFromInfo(a.info).getOrElse(_assumptionType) + val assumptionType = _assumptionType v.logger.debug(s"\nPRODUCE ${viper.silicon.utils.ast.sourceLineColumn(a)}: $a") v.logger.debug(v.stateFormatter.format(s, v.decider.pcs)) From eb695ee3237eadca285aff1499099f070d7f34f2 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 24 Feb 2026 10:25:50 +0100 Subject: [PATCH 351/474] minor fixes --- src/main/scala/decider/Decider.scala | 11 +++++++---- src/main/scala/dependencyAnalysis/AnalysisInfo.scala | 10 +++++----- .../scala/dependencyAnalysis/DependencyAnalyzer.scala | 4 ++-- src/main/scala/rules/Brancher.scala | 4 ++-- src/main/scala/rules/Executor.scala | 4 ++-- src/main/scala/verifier/DefaultMainVerifier.scala | 4 +++- 6 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 7d24195e9..95229e5a2 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -120,6 +120,7 @@ trait Decider { def initDependencyAnalyzer(member: Member, preambleNodes: Iterable[DependencyAnalysisNode]): Unit def removeDependencyAnalyzer(): Unit def getAnalysisInfo(assumptionType: AssumptionType): AnalysisInfo + def isDependencyAnalysisEnabled: Boolean } /* @@ -152,6 +153,8 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => var dependencyAnalyzer: DependencyAnalyzer = new NoDependencyAnalyzer() var analysisSourceInfoStack: AnalysisSourceInfoStack = AnalysisSourceInfoStack() + def isDependencyAnalysisEnabled: Boolean = Verifier.config.enableDependencyAnalysis() && !dependencyAnalyzer.isInstanceOf[NoDependencyAnalyzer] + override def initDependencyAnalyzer(member: Member, preambleNodes: Iterable[DependencyAnalysisNode]): Unit = { val isAnalysisEnabled = DependencyAnalyzer.extractEnableAnalysisFromInfo(member.info).getOrElse(Verifier.config.enableDependencyAnalysis()) if(isAnalysisEnabled) { @@ -317,7 +320,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } def registerDerivedChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, analysisInfo: AnalysisInfo, isExhale: Boolean, createLabel: Boolean=true): CH = { - if(!Verifier.config.enableDependencyAnalysis()) + if(!isDependencyAnalysisEnabled) return buildChunk(perm) val labelNodeOpt = getOrCreateAnalysisLabelNode() // if(createLabel) getOrCreateAnalysisLabelNode() else getOrCreateAnalysisLabelNode(sourceChunks) @@ -330,7 +333,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } private def getOrCreateAnalysisLabelNode(sourceChunks: Iterable[Chunk] = Set.empty, sourceTerms: Iterable[Term] = Set.empty): Option[LabelNode] = { - if(!Verifier.config.enableDependencyAnalysis()) + if(!isDependencyAnalysisEnabled) return None // if(sourceChunks.size == 1 && sourceTerms.isEmpty){ @@ -345,7 +348,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } def wrapWithDependencyAnalysisLabel(term: Term, sourceChunks: Iterable[Chunk] = Set.empty, sourceTerms: Iterable[Term] = Set.empty): Term = { - if(!Verifier.config.enableDependencyAnalysis() || term.equals(True) || sourceChunks.size + sourceTerms.size == 0) + if(!isDependencyAnalysisEnabled || term.equals(True) || sourceChunks.size + sourceTerms.size == 0) return term val labelNode = getOrCreateAnalysisLabelNode(sourceChunks, sourceTerms) @@ -548,7 +551,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => val assertRecord = new DeciderAssertRecord(t, timeout) val sepIdentifier = symbExLog.openScope(assertRecord) - val asserted = if(Verifier.config.enableDependencyAnalysis()) t.equals(True) else isKnownToBeTrue(t) + val asserted = if(isDependencyAnalysisEnabled) t.equals(True) else isKnownToBeTrue(t) val assertNode = if(!asserted) dependencyAnalyzer.createAssertOrCheckNode(t, assumptionType, decider.analysisSourceInfoStack.getFullSourceInfo, isCheck) else None diff --git a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala index 4eef89a01..0dae723cd 100644 --- a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala +++ b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala @@ -3,18 +3,17 @@ package viper.silicon.dependencyAnalysis object AssumptionType extends Enumeration { type AssumptionType = Value - val Explicit, LoopInvariant, PathCondition, Rewrite, SourceCode, DomainAxiom, Implicit, Internal, Trigger, ExplicitPostcondition, ImplicitPostcondition, MethodCall, FunctionBody, Precondition, Unknown = Value + val Explicit, LoopInvariant, PathCondition, Rewrite, SourceCode, DomainAxiom, Implicit, Internal, Trigger, ExplicitPostcondition, ImplicitPostcondition, MethodCall, FunctionBody, Precondition, Ghost, Unknown = Value def fromString(s: String): Option[Value] = values.find(_.toString == s) def explicitAssumptionTypes: Set[AssumptionType] = Set(Explicit, ExplicitPostcondition) - def postconditionTypes: Set[AssumptionType] = Set(ImplicitPostcondition, ExplicitPostcondition) // used to join graphs via postconditions + def postconditionTypes: Set[AssumptionType] = Set(ImplicitPostcondition, ExplicitPostcondition) def explicitAssertionTypes: Set[AssumptionType] = Set(Explicit, ImplicitPostcondition, ExplicitPostcondition) def internalTypes: Set[AssumptionType] = Set(Internal, Trigger) // will always be hidden from user def joinConditionTypes: Set[AssumptionType] = postconditionTypes ++ Set(FunctionBody) - def verificationAnnotationTypes: Set[AssumptionType] = Set(LoopInvariant, Rewrite, ExplicitPostcondition, ImplicitPostcondition, Precondition, Explicit, DomainAxiom) + def verificationAnnotationTypes: Set[AssumptionType] = Set(LoopInvariant, Rewrite, ExplicitPostcondition, ImplicitPostcondition, Precondition, Explicit, DomainAxiom, Ghost) def sourceCodeTypes: Set[AssumptionType] = AssumptionType.values.diff(explicitAssumptionTypes ++ explicitAssertionTypes ++ verificationAnnotationTypes ++ internalTypes) - def methodCallTypes: Set[AssumptionType] = Set(MethodCall) // used to join graphs via postconditions } import viper.silicon.dependencyAnalysis.AssumptionType._ @@ -26,7 +25,7 @@ object DependencyType { val Implicit: DependencyType = DependencyType(AssumptionType.Implicit, AssumptionType.Implicit) val SourceCode: DependencyType = DependencyType(AssumptionType.SourceCode, AssumptionType.SourceCode) val Explicit: DependencyType = DependencyType(AssumptionType.Explicit, AssumptionType.Explicit) - val ExplicitAssertion: DependencyType = DependencyType(AssumptionType.Internal, AssumptionType.Explicit) + val ExplicitAssertion: DependencyType = DependencyType(AssumptionType.Ghost, AssumptionType.Explicit) val ExplicitAssumption: DependencyType = DependencyType(AssumptionType.Explicit, AssumptionType.Implicit) val PathCondition: DependencyType = DependencyType(AssumptionType.PathCondition, AssumptionType.Implicit) val Invariant: DependencyType = DependencyType(AssumptionType.LoopInvariant, AssumptionType.LoopInvariant) @@ -34,6 +33,7 @@ object DependencyType { val Internal: DependencyType = DependencyType(AssumptionType.Internal, AssumptionType.Internal) val Trigger: DependencyType = DependencyType(AssumptionType.Trigger, AssumptionType.Trigger) val MethodCall: DependencyType = DependencyType(AssumptionType.MethodCall, AssumptionType.MethodCall) + val Ghost: DependencyType = DependencyType.make(AssumptionType.Ghost) def make(singleType: AssumptionType): DependencyType = DependencyType(singleType, singleType) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index 4b45c13d8..a49c37f0d 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -124,9 +124,9 @@ object DependencyAnalyzer { } def extractDependencyTypeFromInfo(info: ast.Info): Option[DependencyType] = { - val annotation = extractAnnotationFromInfo(info, assumptionTypeAnnotationKey) + val annotation = Some(List.empty) // TODO ake extractAnnotationFromInfo(info, assumptionTypeAnnotationKey) val dependencyAnalysisInfo = info.getUniqueInfo[FrontendDependencyAnalysisInfo] - if(annotation.isDefined && annotation.get.nonEmpty) AssumptionType.fromString(annotation.get.head).map(DependencyType.make) + if(annotation.isDefined && annotation.get.nonEmpty) AssumptionType.fromString(annotation.get.head).map(DependencyType.make) // TODO ake: assumption and assertion type might not be the same! else if(dependencyAnalysisInfo.isDefined) dependencyAnalysisInfo.get.dependencyType else None } diff --git a/src/main/scala/rules/Brancher.scala b/src/main/scala/rules/Brancher.scala index 41c48048d..28804a798 100644 --- a/src/main/scala/rules/Brancher.scala +++ b/src/main/scala/rules/Brancher.scala @@ -79,7 +79,7 @@ object brancher extends BranchingRules { skipPathFeasibilityCheck || !v.decider.check(negatedCondition, Verifier.config.checkTimeout(), assumptionType)) - val thenInfeasibilityNode: Option[Int] = if(Verifier.config.enableDependencyAnalysis() && !executeThenBranch) { + val thenInfeasibilityNode: Option[Int] = if(v.decider.isDependencyAnalysisEnabled && !executeThenBranch) { val (_, node) = v.decider.checkAndGetInfeasibilityNode(negatedCondition, Verifier.config.checkTimeout(), assumptionType) node } else None @@ -90,7 +90,7 @@ object brancher extends BranchingRules { || skipPathFeasibilityCheck || !v.decider.check(condition, Verifier.config.checkTimeout(), assumptionType)) - val elseInfeasibilityNode: Option[Int] = if(Verifier.config.enableDependencyAnalysis() && !executeElseBranch) { + val elseInfeasibilityNode: Option[Int] = if(v.decider.isDependencyAnalysisEnabled && !executeElseBranch) { val (_, node) = v.decider.checkAndGetInfeasibilityNode(condition, Verifier.config.checkTimeout(), assumptionType) node } else None diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index ed9b52715..a1ab509fb 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -456,7 +456,7 @@ object executor extends ExecutionRules { case _ => produce(s, freshSnap, a, InhaleFailed(inhale), v, getAssumptionType(AssumptionType.Explicit))((s1, v1) => { v1.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterInhale) - if(Verifier.config.enableDependencyAnalysis() && a.isInstanceOf[ast.FalseLit]) { + if(v1.decider.isDependencyAnalysisEnabled && a.isInstanceOf[ast.FalseLit]) { val (_, node) = v1.decider.checkAndGetInfeasibilityNode(False, Verifier.config.checkTimeout(), getAssumptionType(AssumptionType.Explicit)) v1.decider.pcs.setCurrentInfeasibilityNode(node) } @@ -692,7 +692,7 @@ object executor extends ExecutionRules { private def ssaifyRhs(rhs: Term, rhsExp: ast.Exp, rhsExpNew: Option[ast.Exp], name: String, typ: ast.Type, v: Verifier, s : State, assumptionType: AssumptionType): (Term, Option[ast.Exp]) = { rhs match { - case _: Var | _: Literal if !Verifier.config.enableDependencyAnalysis() => + case _: Var | _: Literal if !v.decider.isDependencyAnalysisEnabled => (rhs, rhsExpNew) case _ => diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index 1e9aa33d0..d89a72a0b 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -645,6 +645,8 @@ class DefaultMainVerifier(config: Config, result.getFullDependencyGraphInterpreter.exportGraph() } + DependencyAnalyzer.stopTimeMeasurementAndAddToTotal(postProcessingStartTime, DependencyAnalyzer.timeOfPostprocessing) + if (Verifier.config.startDependencyAnalysisTool()) { val commandLineTool = new DependencyAnalysisUserTool(result.getFullDependencyGraphInterpreter, dependencyGraphInterpreters, program, verificationErrors) commandLineTool.run() @@ -657,6 +659,6 @@ class DefaultMainVerifier(config: Config, case _ => } - DependencyAnalyzer.stopTimeMeasurementAndAddToTotal(postProcessingStartTime, DependencyAnalyzer.timeOfPostprocessing) + } } From 9afa0cd4e88796eca1871eeb652fd7f4683f2744 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 24 Feb 2026 10:27:00 +0100 Subject: [PATCH 352/474] add more profiling instrumentation --- .../dependencyAnalysis/DependencyAnalyzer.scala | 16 +++++++++++++++- .../DependencyGraphInterpreter.scala | 8 ++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index a49c37f0d..fc2e48f98 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -1,7 +1,7 @@ package viper.silicon.dependencyAnalysis import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType -import viper.silicon.dependencyAnalysis.DependencyAnalyzer.{runtimeOverheadPermissionNodes, startTimeMeasurement, stopTimeMeasurementAndAddToTotal, timeToProcessUnsatCore} +import viper.silicon.dependencyAnalysis.DependencyAnalyzer.{runtimeOverheadPermissionNodes, startTimeMeasurement, stopTimeMeasurementAndAddToTotal, timeToAddTransitiveEdges, timeToMergeNodes, timeToProcessUnsatCore, timeToRemoveInternalNodes} import viper.silicon.interfaces.state.{Chunk, GeneralChunk} import viper.silicon.state.terms._ import viper.silicon.verifier.Verifier @@ -83,6 +83,9 @@ object DependencyAnalyzer { val runtimeOverheadPermissionNodes: AtomicLong = new AtomicLong(0) val timeToExtractUnsatCore: AtomicLong = new AtomicLong(0) val timeToProcessUnsatCore: AtomicLong = new AtomicLong(0) + val timeToMergeNodes: AtomicLong = new AtomicLong(0) + val timeToRemoveInternalNodes: AtomicLong = new AtomicLong(0) + val timeToAddTransitiveEdges: AtomicLong = new AtomicLong(0) def startTimeMeasurement(): Long = { if(!Verifier.config.enableDependencyAnalysisProfiling()) return 0 @@ -105,6 +108,9 @@ object DependencyAnalyzer { println(s" Time spent on adding explicit permission nodes: ${runtimeOverheadPermissionNodes.get() / 1e6}ms") println(s" Time spent on extracting the unsat core: ${timeToExtractUnsatCore.get() / 1e6}ms") println(s" Time spent on processing the unsat core: ${timeToProcessUnsatCore.get() / 1e6}ms") + println(s" Time spent on merging lower-level nodes: ${timeToMergeNodes.get() / 1e6}ms") + println(s" Time spent on removing internal nodes: ${timeToRemoveInternalNodes.get() / 1e6}ms") + println(s" Time spent on adding transitive edges in lower-level graph: ${timeToAddTransitiveEdges.get() / 1e6}ms") println(s" Postprocessing: ${timeOfPostprocessing.get() / 1e6}ms") println(s" Time spent on adding nodes: ${timeToAddNodes.get() / 1e6}ms") println(s" Time spent on adding edges: ${timeToAddEdges.get() / 1e6}ms") @@ -401,10 +407,18 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { * Further, this operation removes unnecessary details from the graph by, for example, removing label nodes and merging identical nodes. */ override def buildFinalGraph(): Option[DependencyGraph] = { + val removingNodesStart = DependencyAnalyzer.startTimeMeasurement() dependencyGraph.removeLabelNodes() + DependencyAnalyzer.stopTimeMeasurementAndAddToTotal(removingNodesStart, timeToRemoveInternalNodes) + val mergingNodesStart = DependencyAnalyzer.startTimeMeasurement() val mergedGraph = if(Verifier.config.enableDependencyAnalysisDebugging()) dependencyGraph else buildAndGetMergedGraph() + DependencyAnalyzer.stopTimeMeasurementAndAddToTotal(mergingNodesStart, timeToMergeNodes) + val addingTransitiveEdgesStart = DependencyAnalyzer.startTimeMeasurement() mergedGraph.addTransitiveEdges() + DependencyAnalyzer.stopTimeMeasurementAndAddToTotal(addingTransitiveEdgesStart, timeToAddTransitiveEdges) + val removingNodesStart2 = DependencyAnalyzer.startTimeMeasurement() if(!Verifier.config.enableDependencyAnalysisDebugging()) mergedGraph.removeInternalNodes() + DependencyAnalyzer.stopTimeMeasurementAndAddToTotal(removingNodesStart2, timeToRemoveInternalNodes) Some(mergedGraph) } diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 66eeebfe2..ec1ec44a7 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -232,8 +232,8 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen val allSourceCodeNodes = toCompactUserLevelNodes(getNonInternalAssumptionNodes).filter(n => nonSourceCodeAssumptionTypes.intersect(n.assumptionTypes).isEmpty).map(_.source.getTopLevelSource) val coveredSourceCodeNodes = coveredNodes.map(_.source.getTopLevelSource).intersect(allSourceCodeNodes) -// println(s"Covered Source Code:\n\t${coveredSourceCodeNodes.toList.sortBy(_.getLineNumber).mkString("\n\t")}") -// println(s"Uncovered Source Code:\n\t${allSourceCodeNodes.diff(coveredSourceCodeNodes).toList.sortBy(_.getLineNumber).mkString("\n\t")}") + println(s"Covered Source Code:\n\t${coveredSourceCodeNodes.toList.sortBy(_.getLineNumber).mkString("\n\t")}") + println(s"Uncovered Source Code:\n\t${allSourceCodeNodes.diff(coveredSourceCodeNodes).toList.sortBy(_.getLineNumber).mkString("\n\t")}") println(s"Spec Quality = ${coveredSourceCodeNodes.size} / ${allSourceCodeNodes.size}") coveredSourceCodeNodes.size.toDouble / allSourceCodeNodes.size.toDouble } @@ -331,8 +331,8 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen val proofQualityLea = assertionQualitiesSum / numAssertions.toDouble val info = { -// s"Assertions with dependencies on explicit assumptions:\n\t\t${assertionQualities.filterNot(_._1 == 1.0).sortBy(_._2.getLineNumber).mkString("\n\t\t")}" + "\n\n" + -// s"Assertions with perfect proof quality:\n\t\t${fullyVerifiedAssertions.map(_._2).sortBy(_.getLineNumber).mkString("\n\t\t")}" + "\n\n" + + s"Assertions with dependencies on explicit assumptions:\n\t\t${assertionQualities.filterNot(_._1 == 1.0).sortBy(_._2.getLineNumber).mkString("\n\t\t")}" + "\n\n" + + s"Assertions with perfect proof quality:\n\t\t${fullyVerifiedAssertions.map(_._2).sortBy(_.getLineNumber).mkString("\n\t\t")}" + "\n\n" + s"specQuality = $specQuality\n" + s"proof quality (Peter): $numFullyVerifiedAssertions / $numAssertions = $proofQualityPeter\n" + s"proof quality (Lea): $assertionQualitiesSum / $numAssertions = $proofQualityLea\n" From 23c3c3175ad68c04453938532a5ca3ce2b60056f Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 27 Feb 2026 13:13:09 +0100 Subject: [PATCH 353/474] add enableUnsatCore config option --- src/main/scala/Config.scala | 6 ++++++ src/main/scala/decider/ProverStdIO.scala | 8 ++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main/scala/Config.scala b/src/main/scala/Config.scala index d54fa674e..769d6bc54 100644 --- a/src/main/scala/Config.scala +++ b/src/main/scala/Config.scala @@ -727,6 +727,12 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { noshort = true ) + val enableUnsatCores: ScallopOption[Boolean] = opt[Boolean]("enableUnsatCores", + descr = "Enables UNSAT cores", + default = Some(false), + noshort = true + ) + val dependencyAnalysisPostProcessingMode: ScallopOption[Int] = opt[Int]("dependencyAnalysisPostProcessingMode", descr = "Postprocessing mode: 0=default, 1=disable memory footprint optimizations, 2=disable joining of graphs and all of 1 (does not compute dependencies between methods), 3=disable transitive edges and all of 2 (UNSOUND)", default = Some(0), diff --git a/src/main/scala/decider/ProverStdIO.scala b/src/main/scala/decider/ProverStdIO.scala index 90b46fbce..3354c39ae 100644 --- a/src/main/scala/decider/ProverStdIO.scala +++ b/src/main/scala/decider/ProverStdIO.scala @@ -252,8 +252,8 @@ abstract class ProverStdIO(uniqueId: String, def assume(term: String, label: String): Unit = { // bookkeeper.assumptionCounter += 1 - if(Verifier.config.enableDependencyAnalysis() && label.nonEmpty){ - writeLine("(assert (! " + term + " :named " + label + "))") + if((Verifier.config.enableDependencyAnalysis() && label.nonEmpty) || Verifier.config.enableUnsatCores()){ + writeLine("(assert (! " + term + " :named " + (if(label.nonEmpty) label else nextProverLabel()) + "))") }else{ writeLine("(assert " + term + ")") } @@ -282,8 +282,8 @@ abstract class ProverStdIO(uniqueId: String, push() setTimeout(timeout) - if(Verifier.config.enableDependencyAnalysis() && label.nonEmpty){ - writeLine("(assert (! (not " + goal + ") :named " + label + "))") + if((Verifier.config.enableDependencyAnalysis() && label.nonEmpty) || Verifier.config.enableUnsatCores()){ + writeLine("(assert (! (not " + goal + ") :named " + (if(label.nonEmpty) label else nextProverLabel()) + "))") }else{ writeLine("(assert (not " + goal + "))") } From 5b9d5d2c0febd09db768e8332aed42bf57014ae5 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 27 Feb 2026 13:16:41 +0100 Subject: [PATCH 354/474] fix for gobra interfaces --- .../scala/dependencyAnalysis/AnalysisInfo.scala | 4 ++-- .../dependencyAnalysis/AnalysisSourceInfo.scala | 17 +++++++++++++++++ .../DependencyAnalysisNode.scala | 10 ++++++---- .../DependencyAnalysisUserTool.scala | 2 +- .../dependencyAnalysis/DependencyAnalyzer.scala | 2 +- .../dependencyAnalysis/DependencyGraph.scala | 2 +- .../DependencyGraphInterpreter.scala | 3 +-- src/main/scala/rules/Evaluator.scala | 12 +++++++++++- src/main/scala/rules/Executor.scala | 4 ++-- src/main/scala/supporters/MethodSupporter.scala | 14 ++++++++++++-- 10 files changed, 54 insertions(+), 16 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala index 0dae723cd..44999ec8a 100644 --- a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala +++ b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala @@ -3,14 +3,14 @@ package viper.silicon.dependencyAnalysis object AssumptionType extends Enumeration { type AssumptionType = Value - val Explicit, LoopInvariant, PathCondition, Rewrite, SourceCode, DomainAxiom, Implicit, Internal, Trigger, ExplicitPostcondition, ImplicitPostcondition, MethodCall, FunctionBody, Precondition, Ghost, Unknown = Value + val Explicit, LoopInvariant, PathCondition, Rewrite, SourceCode, DomainAxiom, Implicit, Internal, Trigger, ExplicitPostcondition, ImplicitPostcondition, MethodCall, FunctionBody, Precondition, Ghost, CustomInternal, Unknown = Value def fromString(s: String): Option[Value] = values.find(_.toString == s) def explicitAssumptionTypes: Set[AssumptionType] = Set(Explicit, ExplicitPostcondition) def postconditionTypes: Set[AssumptionType] = Set(ImplicitPostcondition, ExplicitPostcondition) def explicitAssertionTypes: Set[AssumptionType] = Set(Explicit, ImplicitPostcondition, ExplicitPostcondition) - def internalTypes: Set[AssumptionType] = Set(Internal, Trigger) // will always be hidden from user + def internalTypes: Set[AssumptionType] = Set(Internal, Trigger, CustomInternal) // will always be hidden from user def joinConditionTypes: Set[AssumptionType] = postconditionTypes ++ Set(FunctionBody) def verificationAnnotationTypes: Set[AssumptionType] = Set(LoopInvariant, Rewrite, ExplicitPostcondition, ImplicitPostcondition, Precondition, Explicit, DomainAxiom, Ghost) def sourceCodeTypes: Set[AssumptionType] = AssumptionType.values.diff(explicitAssumptionTypes ++ explicitAssertionTypes ++ verificationAnnotationTypes ++ internalTypes) diff --git a/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala b/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala index df500c65d..56d015bca 100644 --- a/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala +++ b/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala @@ -1,5 +1,6 @@ package viper.silicon.dependencyAnalysis +import viper.silicon.state.terms.True import viper.silver.ast import viper.silver.ast._ @@ -124,3 +125,19 @@ case class CompositeAnalysisSourceInfo(coarseGrainedSource: AnalysisSourceInfo, override def getDescription: String = coarseGrainedSource.getDescription } + + +case class DependencyAnalysisJoinNodeInfo(sourceInfo: AnalysisSourceInfo) extends Info { + def getAssertionNode: GeneralAssertionNode = + SimpleAssertionNode(True, AssumptionType.CustomInternal, sourceInfo, isClosed=false, isJoinNode=true) + + def getAssertionNode(outerSourceInfo: AnalysisSourceInfo): GeneralAssertionNode = + SimpleAssertionNode(True, AssumptionType.CustomInternal, CompositeAnalysisSourceInfo(outerSourceInfo, sourceInfo), isClosed=false, isJoinNode=true) + + def getAssumptionNode(outerSourceInfo: AnalysisSourceInfo): GeneralAssumptionNode = + SimpleAssumptionNode(True, None, CompositeAnalysisSourceInfo(outerSourceInfo, sourceInfo), AssumptionType.CustomInternal, isClosed=false, isJoinNode=true) + + override def comment: Seq[String] = Nil + + override def isCached: Boolean = false +} \ No newline at end of file diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala index 87f00a999..571e65505 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala @@ -34,6 +34,9 @@ trait DependencyAnalysisNode extends AbstractDependencyAnalysisNode { */ val isClosed: Boolean + + val isJoinNode: Boolean + /** * The assumes or asserted Silicon term. Currently, only used for debugging purposes. */ @@ -66,7 +69,6 @@ trait DependencyAnalysisNode extends AbstractDependencyAnalysisNode { trait GeneralAssumptionNode extends DependencyAnalysisNode { override def getNodeType: String = "Assumption" - val isJoinNode: Boolean } trait GeneralAssertionNode extends DependencyAnalysisNode { override def getNodeType: String = "Assertion" @@ -90,13 +92,13 @@ case class AxiomAssumptionNode(term: Term, description: Option[String], sourceIn override def getNodeString: String = "assume axiom " + term.toString + description.map(" (" + _ + ")").getOrElse("") } -case class SimpleAssertionNode(term: Term, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo, isClosed: Boolean, hasFailed: Boolean = false) extends GeneralAssertionNode { +case class SimpleAssertionNode(term: Term, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo, isClosed: Boolean, hasFailed: Boolean = false, isJoinNode: Boolean = false) extends GeneralAssertionNode { override def getNodeString: String = "assert " + term.toString override def getAssertFailedNode(): GeneralAssertionNode = SimpleAssertionNode(term, assumptionType, sourceInfo, isClosed, hasFailed=true) } -case class SimpleCheckNode(term: Term, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo, isClosed: Boolean, hasFailed: Boolean = false) extends GeneralAssertionNode { +case class SimpleCheckNode(term: Term, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo, isClosed: Boolean, hasFailed: Boolean = false, isJoinNode: Boolean = false) extends GeneralAssertionNode { override def getNodeString: String = "check " + term override def getNodeType: String = "Check" @@ -108,7 +110,7 @@ case class PermissionInhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSo override def getNodeType: String = "Inhale" } -case class PermissionExhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean, labelNode: LabelNode, hasFailed: Boolean = false) extends GeneralAssertionNode with ChunkAnalysisInfo { +case class PermissionExhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean, labelNode: LabelNode, hasFailed: Boolean = false, isJoinNode: Boolean = false) extends GeneralAssertionNode with ChunkAnalysisInfo { override def getNodeType: String = "Exhale" override def getNodeString: String = "exhale " + chunk.toString diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala index eaaa3cab3..27321eb6d 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala @@ -41,7 +41,7 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete println(infoString) } }catch { - case e: Exception => println(e.getMessage) + case e: Exception => println("Error:\n" + e.getMessage) } runInternal() } diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index fc2e48f98..51c0c8233 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -218,7 +218,7 @@ object DependencyAnalyzer { .groupBy(_.sourceInfo.getFineGrainedSource) .view.mapValues(_.map(_.id)) .toMap - joinCandidateNodes.filter(node => AssumptionType.postconditionTypes.contains(node.assumptionType)) + joinCandidateNodes.filter(node => AssumptionType.postconditionTypes.contains(node.assumptionType) || node.isJoinNode) .map(node => (node.id, relevantAssumptionNodes.getOrElse(node.sourceInfo.getTopLevelSource, Seq.empty))) .foreach { case (src, targets) => newGraph.addEdgesConnectingMethods(src, targets)} diff --git a/src/main/scala/dependencyAnalysis/DependencyGraph.scala b/src/main/scala/dependencyAnalysis/DependencyGraph.scala index 74ace7fd5..f29860d5c 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraph.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraph.scala @@ -209,7 +209,7 @@ class DependencyGraph extends ReadOnlyDependencyGraph { } def removeInternalNodes(): Unit = { - def filterCriteria(n: DependencyAnalysisNode) = AssumptionType.internalTypes.contains(n.assumptionType) + def filterCriteria(n: DependencyAnalysisNode) = AssumptionType.internalTypes.contains(n.assumptionType) && !AssumptionType.CustomInternal.equals(n.assumptionType) assumptionNodes filter filterCriteria foreach removeAllEdgesForNode assumptionNodes = assumptionNodes filterNot filterCriteria diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index ec1ec44a7..f8109e1f3 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -31,8 +31,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen def getJoinCandidateNodes: Iterable[DependencyAnalysisNode] = joinCandidateNodes - protected lazy val joinCandidateNodes: Seq[DependencyAnalysisNode] = dependencyGraph.getAssertionNodes.filter(node => AssumptionType.joinConditionTypes.contains(node.assumptionType)) ++ - dependencyGraph.getAssumptionNodes.filter(node => node.isJoinNode || node.isInstanceOf[AxiomAssumptionNode] || AssumptionType.joinConditionTypes.contains(node.assumptionType)) + protected lazy val joinCandidateNodes: Seq[DependencyAnalysisNode] = dependencyGraph.getNodes.filter(node => node.isJoinNode || node.isInstanceOf[AxiomAssumptionNode] || AssumptionType.joinConditionTypes.contains(node.assumptionType)) private def toUserLevelNodes(nodes: Iterable[DependencyAnalysisNode]): Set[UserLevelDependencyAnalysisNode] = UserLevelDependencyAnalysisNode.from(nodes) diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 6b46b5c3d..c755c34ee 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -10,7 +10,7 @@ import viper.silicon import viper.silicon.Config.JoinMode import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp -import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyAnalyzer, DependencyType} +import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyAnalysisJoinNodeInfo, DependencyAnalyzer, DependencyType} import viper.silicon.interfaces._ import viper.silicon.interfaces.state.ChunkIdentifer import viper.silicon.logger.records.data.{CondExpRecord, EvaluateRecord, ImpliesRecord} @@ -89,6 +89,16 @@ object evaluator extends EvaluationRules { val sepIdentifier = v.symbExLog.openScope(new EvaluateRecord(e, s, v.decider.pcs)) val sourceInfo = AnalysisSourceInfo.createAnalysisSourceInfo(e) v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo, dependencyType.getOrElse(v.decider.analysisSourceInfoStack.getDependencyType)) + if(e.info.getUniqueInfo[DependencyAnalysisJoinNodeInfo].isDefined){ + val joinNodeInfo = e.info.getUniqueInfo[DependencyAnalysisJoinNodeInfo].get + val currentTopLevelSource = v.decider.analysisSourceInfoStack.getFullSourceInfo.getTopLevelSource + val assertionNode = joinNodeInfo.getAssertionNode(currentTopLevelSource) + val assumptionNode = joinNodeInfo.getAssumptionNode(currentTopLevelSource) + v.decider.dependencyAnalyzer.addAssertionNode(assertionNode) + v.decider.dependencyAnalyzer.addAssumptionNode(assumptionNode) + v.decider.dependencyAnalyzer.addDependency(Some(assumptionNode.id), Some(assertionNode.id)) + } + eval3(s, e, pve, v)((s1, t, eNew, v1) => { v1.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) v1.symbExLog.closeScope(sepIdentifier) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index a1ab509fb..40d7e51da 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -421,7 +421,7 @@ object executor extends ExecutionRules { val debugExp = Option.when(withExp)(ast.NeCmp(x, ast.NullLit()())()) val debugExpSubst = Option.when(withExp)(ast.NeCmp(eRcvrNew.get, ast.NullLit()())()) val (debugHeapName, debugLabel) = v.getDebugOldLabel(s, stmt.pos) - v.decider.assume(tRcvr !== Null, debugExp, debugExpSubst, AssumptionType.SourceCode) + v.decider.assume(tRcvr !== Null, debugExp, debugExpSubst, getAssumptionType(AssumptionType.SourceCode)) val eRcvr = Option.when(withExp)(Seq(x)) val p = FullPerm @@ -571,7 +571,7 @@ object executor extends ExecutionRules { val gOuts = Store(outs.map(x => (x, v2.decider.fresh(x))).toMap) val s4 = s3.copy(g = s3.g + gOuts, oldHeaps = s3.oldHeaps + (Verifier.PRE_STATE_LABEL -> magicWandSupporter.getEvalHeap(s1))) v2.decider.analysisSourceInfoStack.isJoinRelevantAssumption = true - produces(s4, freshSnap, meth.posts, _ => pveCallTransformed, v2, getAssumptionType(AssumptionType.MethodCall))((s5, v3) => { // TODO ake: make sure the join is always made! + produces(s4, freshSnap, meth.posts, _ => pveCallTransformed, v2, getAssumptionType(AssumptionType.MethodCall))((s5, v3) => { v3.decider.analysisSourceInfoStack.isJoinRelevantAssumption = false v3.symbExLog.closeScope(postCondId) v3.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index bb9c961c8..c05155088 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -9,7 +9,7 @@ package viper.silicon.supporters import com.typesafe.scalalogging.Logger import viper.silicon.Map import viper.silicon.decider.Decider -import viper.silicon.dependencyAnalysis.{AssumptionType, DependencyAnalyzer, DependencyGraphInterpreter, DependencyType} +import viper.silicon.dependencyAnalysis.{AssumptionType, DependencyAnalysisJoinNodeInfo, DependencyAnalyzer, DependencyGraphInterpreter, DependencyType} import viper.silicon.interfaces._ import viper.silicon.logger.records.data.WellformednessCheckRecord import viper.silicon.rules.{consumer, executionFlowController, executor, producer} @@ -78,7 +78,15 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif } val annotatedAssumptionTypeOpt = DependencyAnalyzer.extractAssumptionTypeFromInfo(method.info) - val postConditionType = annotatedAssumptionTypeOpt.getOrElse(if(method.body.isDefined) AssumptionType.ImplicitPostcondition else AssumptionType.ExplicitPostcondition) + var postConditionType = annotatedAssumptionTypeOpt.getOrElse(if(method.body.isDefined) AssumptionType.ImplicitPostcondition else AssumptionType.ExplicitPostcondition) + + val daJoinNodeInfoOpt = method.info.getUniqueInfo[DependencyAnalysisJoinNodeInfo] + if(daJoinNodeInfoOpt.isDefined){ + val infodaJoinNodeInfo = daJoinNodeInfoOpt.get + v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(infodaJoinNodeInfo.sourceInfo, DependencyType.make(AssumptionType.CustomInternal)) + postConditionType = AssumptionType.CustomInternal + v.decider.dependencyAnalyzer.addAssertionNode(infodaJoinNodeInfo.getAssertionNode) + } errorsReportedSoFar.set(0) val result = @@ -113,6 +121,8 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif result.dependencyGraphInterpreter = v.decider.dependencyAnalyzer.buildFinalGraph().map(new DependencyGraphInterpreter(method.name, _, allErrors, Some(method))) + if(daJoinNodeInfoOpt.isDefined) v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(daJoinNodeInfoOpt.get.sourceInfo) + v.decider.resetProverOptions() symbExLog.closeMemberScope() From 62e7dcfb34d778b6b0edf38ac3c92f27bc70149c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pereira?= Date: Fri, 27 Feb 2026 14:22:03 +0100 Subject: [PATCH 355/474] cherry-pick silicon PR #961 --- .../scala/verifier/DefaultMainVerifier.scala | 58 ++++++++++++++----- 1 file changed, 43 insertions(+), 15 deletions(-) diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index d89a72a0b..1086c0154 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -233,10 +233,17 @@ class DefaultMainVerifier(config: Config, */ val functionVerificationResults = functionsSupporter.units.toList flatMap (function => { val startTime = System.currentTimeMillis() - decider.initDependencyAnalyzer(function, allProvers.getPreambleAnalysisNodes ++ decider.prover.getPreambleAnalysisNodes) - val results = functionsSupporter.verify(createInitialState(function, program, functionData, predicateData), function) - .flatMap(extractAllVerificationResults) - decider.removeDependencyAnalyzer() + var results: Seq[VerificationResult] = null + try { + decider.initDependencyAnalyzer(function, allProvers.getPreambleAnalysisNodes ++ decider.prover.getPreambleAnalysisNodes) + results = functionsSupporter.verify(createInitialState(function, program, functionData, predicateData), function) + .flatMap(extractAllVerificationResults) + decider.removeDependencyAnalyzer() + } catch { + case e : Throwable => + logger error s"An exception was thrown while verifying function `${function.name}`." + throw e + } val elapsed = System.currentTimeMillis() - startTime reporter report VerificationResultMessage(s"silicon", function, elapsed, condenseToViperResult(results)) logger debug s"Silicon finished verification of function `${function.name}` in ${viper.silver.reporter.format.formatMillisReadably(elapsed)} seconds with the following result: ${condenseToViperResult(results).toString}" @@ -245,10 +252,17 @@ class DefaultMainVerifier(config: Config, val predicateVerificationResults = predicateSupporter.units.toList flatMap (predicate => { val startTime = System.currentTimeMillis() - decider.initDependencyAnalyzer(predicate, allProvers.getPreambleAnalysisNodes ++ decider.prover.getPreambleAnalysisNodes) - val results = predicateSupporter.verify(createInitialState(predicate, program, functionData, predicateData), predicate) - .flatMap(extractAllVerificationResults) - decider.removeDependencyAnalyzer() + var results: Seq[VerificationResult] = null + try { + decider.initDependencyAnalyzer(predicate, allProvers.getPreambleAnalysisNodes ++ decider.prover.getPreambleAnalysisNodes) + results = predicateSupporter.verify(createInitialState(predicate, program, functionData, predicateData), predicate) + .flatMap(extractAllVerificationResults) + decider.removeDependencyAnalyzer() + } catch { + case e: Throwable => + logger error s"An exception was thrown while verifying predicate `${predicate.name}`." + throw e + } val elapsed = System.currentTimeMillis() - startTime reporter report VerificationResultMessage(s"silicon", predicate, elapsed, condenseToViperResult(results)) logger debug s"Silicon finished verification of predicate `${predicate.name}` in ${viper.silver.reporter.format.formatMillisReadably(elapsed)} seconds with the following result: ${condenseToViperResult(results).toString}" @@ -270,16 +284,22 @@ class DefaultMainVerifier(config: Config, val verificationTaskFutures: Seq[Future[Seq[VerificationResult]]] = program.methods.filterNot(excludeMethod).map(method => { - val s = createInitialState(method, program, functionData, predicateData).copy(parallelizeBranches = Verifier.config.parallelizeBranches()) /* [BRANCH-PARALLELISATION] */ _verificationPoolManager.queueVerificationTask(v => { val startTime = System.currentTimeMillis() - v.decider.initDependencyAnalyzer(method, allProvers.getPreambleAnalysisNodes ++ v.decider.prover.getPreambleAnalysisNodes) - val results = v.methodSupporter.verify(s, method) - .flatMap(extractAllVerificationResults) - v.decider.removeDependencyAnalyzer() + var results: Seq[VerificationResult] = null + try { + v.decider.initDependencyAnalyzer(method, allProvers.getPreambleAnalysisNodes ++ v.decider.prover.getPreambleAnalysisNodes) + results = v.methodSupporter.verify(s, method) + .flatMap(extractAllVerificationResults) + v.decider.removeDependencyAnalyzer() + } catch { + case e: Throwable => + logger error s"An exception was thrown while verifying method `${method.name}`." + throw e + } val elapsed = System.currentTimeMillis() - startTime reporter report VerificationResultMessage(s"silicon", method, elapsed, condenseToViperResult(results)) @@ -292,8 +312,16 @@ class DefaultMainVerifier(config: Config, _verificationPoolManager.queueVerificationTask(v => { val startTime = System.currentTimeMillis() - val results = v.cfgSupporter.verify(s, cfg) - .flatMap(extractAllVerificationResults) + + var results: Seq[VerificationResult] = null + try { + results = v.cfgSupporter.verify(s, cfg) + .flatMap(extractAllVerificationResults) + } catch { + case e: Throwable => + logger error s"An exception was thrown while verifying a cfg." + throw e + } val elapsed = System.currentTimeMillis() - startTime reporter report VerificationResultMessage(s"silicon"/*, cfg*/, elapsed, condenseToViperResult(results)) From ae05d01f2d7ba86b120d6fe9986f5b3cb2ed9a64 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 27 Feb 2026 17:51:59 +0100 Subject: [PATCH 356/474] implement graph importer as a stand-alone program --- .../AnalysisSourceInfo.scala | 1 + .../DependencyAnalysisNode.scala | 23 +-- .../dependencyAnalysis/DependencyGraph.scala | 7 +- .../DependencyGraphImporter.scala | 147 ++++++++++++++++++ .../DependencyGraphInterpreter.scala | 12 +- .../scala/verifier/DefaultMainVerifier.scala | 5 +- 6 files changed, 178 insertions(+), 17 deletions(-) create mode 100644 src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala diff --git a/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala b/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala index 56d015bca..a419f541a 100644 --- a/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala +++ b/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala @@ -9,6 +9,7 @@ object AnalysisSourceInfo { p match { case NoPosition => "???" case filePos: AbstractSourcePosition => filePos.file.getFileName.toString + " @ line " + filePos.line + case filePos: FilePosition => filePos.file.getFileName.toString + " @ line " + filePos.line case lineColumn: HasLineColumn => "line " + lineColumn.line.toString case VirtualPosition(identifier) => "label " + identifier } diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala index 571e65505..5bf564ec6 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala @@ -12,7 +12,9 @@ trait DependencyAnalysisNode extends AbstractDependencyAnalysisNode { /** * The unique node id, which is also given to the SMT solver such that unsat cores can be mapped back to dependency nodes. */ - val id: Int = DependencyGraphHelper.nextId() + val _id: Option[Int] + val id: Int = if(_id.isEmpty) DependencyGraphHelper.nextId() else _id.get + /** * Stores information about which source code statement / expression created this node. @@ -84,44 +86,45 @@ trait ChunkAnalysisInfo { val labelNode: LabelNode } -case class SimpleAssumptionNode(term: Term, description: Option[String], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean, isJoinNode: Boolean) extends GeneralAssumptionNode { +case class SimpleAssumptionNode(term: Term, description: Option[String], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean, isJoinNode: Boolean, _id: Option[Int]=None) extends GeneralAssumptionNode { override def getNodeString: String = "assume " + term.toString + description.map(" (" + _ + ")").getOrElse("") } -case class AxiomAssumptionNode(term: Term, description: Option[String], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean, isJoinNode: Boolean = false) extends GeneralAssumptionNode { +case class AxiomAssumptionNode(term: Term, description: Option[String], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean, isJoinNode: Boolean = false, _id: Option[Int]=None) extends GeneralAssumptionNode { override def getNodeString: String = "assume axiom " + term.toString + description.map(" (" + _ + ")").getOrElse("") + override def getNodeType: String = "Axiom" } -case class SimpleAssertionNode(term: Term, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo, isClosed: Boolean, hasFailed: Boolean = false, isJoinNode: Boolean = false) extends GeneralAssertionNode { +case class SimpleAssertionNode(term: Term, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo, isClosed: Boolean, hasFailed: Boolean = false, isJoinNode: Boolean = false, _id: Option[Int]=None) extends GeneralAssertionNode { override def getNodeString: String = "assert " + term.toString override def getAssertFailedNode(): GeneralAssertionNode = SimpleAssertionNode(term, assumptionType, sourceInfo, isClosed, hasFailed=true) } -case class SimpleCheckNode(term: Term, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo, isClosed: Boolean, hasFailed: Boolean = false, isJoinNode: Boolean = false) extends GeneralAssertionNode { +case class SimpleCheckNode(term: Term, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo, isClosed: Boolean, hasFailed: Boolean = false, isJoinNode: Boolean = false, _id: Option[Int]=None) extends GeneralAssertionNode { override def getNodeString: String = "check " + term override def getNodeType: String = "Check" override def getAssertFailedNode(): GeneralAssertionNode = SimpleCheckNode(term, assumptionType, sourceInfo, isClosed, hasFailed=true) } -case class PermissionInhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean, labelNode: LabelNode, isJoinNode: Boolean) extends GeneralAssumptionNode with ChunkAnalysisInfo { +case class PermissionInhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean, labelNode: LabelNode, isJoinNode: Boolean, _id: Option[Int]=None) extends GeneralAssumptionNode with ChunkAnalysisInfo { override def getNodeString: String = "inhale " + chunk.toString override def getNodeType: String = "Inhale" } -case class PermissionExhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean, labelNode: LabelNode, hasFailed: Boolean = false, isJoinNode: Boolean = false) extends GeneralAssertionNode with ChunkAnalysisInfo { +case class PermissionExhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean, labelNode: LabelNode, hasFailed: Boolean = false, isJoinNode: Boolean = false, _id: Option[Int]=None) extends GeneralAssertionNode with ChunkAnalysisInfo { override def getNodeType: String = "Exhale" override def getNodeString: String = "exhale " + chunk.toString - override def getAssertFailedNode(): GeneralAssertionNode = PermissionExhaleNode(chunk, term, sourceInfo, assumptionType, isClosed, labelNode, hasFailed=true) + override def getAssertFailedNode(): GeneralAssertionNode = PermissionExhaleNode(chunk, term, sourceInfo, assumptionType, isClosed, labelNode, hasFailed=true, _id=_id) } /** * Label nodes are internally used nodes, mostly used to improve precision of the dependency analysis. * They are completely hidden from users. */ -case class LabelNode(term: Var) extends GeneralAssumptionNode { +case class LabelNode(term: Var, _id: Option[Int]=None) extends GeneralAssumptionNode { val sourceInfo: AnalysisSourceInfo = NoAnalysisSourceInfo() val assumptionType: AssumptionType = AssumptionType.Internal val isClosed: Boolean = true @@ -138,7 +141,7 @@ case class LabelNode(term: Var) extends GeneralAssumptionNode { * Infeasibility nodes allow to distinguish between dependencies coming from the fact that the assertion is not reachable * on a given path and dependencies used to prove the assertion on feasible paths. */ -case class InfeasibilityNode(sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType) extends GeneralAssumptionNode { +case class InfeasibilityNode(sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, _id: Option[Int]=None) extends GeneralAssumptionNode { val term: Term = False val isClosed: Boolean = true val description: String = "False" diff --git a/src/main/scala/dependencyAnalysis/DependencyGraph.scala b/src/main/scala/dependencyAnalysis/DependencyGraph.scala index f29860d5c..bb01fe723 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraph.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraph.scala @@ -225,18 +225,19 @@ class DependencyGraph extends ReadOnlyDependencyGraph { private def exportEdges(fileName: String): Unit = { val writer = new PrintWriter(fileName) writer.println("source,target,label") - getAllEdges foreach (e => e._2 foreach (s => writer.println(s.toString + "," + e._1.toString + ",direct"))) + getDirectEdges foreach (e => e._2 foreach (s => writer.println(s.toString + "," + e._1.toString + ",direct"))) + getEdgesConnectingMethods foreach (e => e._2 foreach (s => writer.println(s.toString + "," + e._1.toString + ",interprocedural"))) writer.close() } private def exportNodes(fileName: String): Unit = { val sep = "#" def getNodeExportString(node: DependencyAnalysisNode): String = { - val parts = mutable.Seq(node.id.toString, node.getNodeType, node.assumptionType.toString, node.getNodeString, node.sourceInfo.toString, node.sourceInfo.getPositionString, node.sourceInfo.getFineGrainedSource.toString) + val parts = mutable.Seq(node.id.toString, node.getNodeType, node.assumptionType.toString, node.getNodeString, node.sourceInfo.toString, node.sourceInfo.getPositionString, node.sourceInfo.getFineGrainedSource.toString, node.sourceInfo.getDescription) parts.map(_.replace("#", "@")).mkString(sep) } val writer = new PrintWriter(fileName) - val headerParts = mutable.Seq("id", "node type", "assumption type", "node info", "source info", "position", "fine grained source") + val headerParts = mutable.Seq("id", "node type", "assumption type", "node info", "source info", "position", "fine grained source", "description") writer.println(headerParts.mkString(sep)) getNodes foreach (n => writer.println(getNodeExportString(n).replace("\n", " "))) writer.close() diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala new file mode 100644 index 000000000..08cb10594 --- /dev/null +++ b/src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala @@ -0,0 +1,147 @@ +package viper.silicon.dependencyAnalysis + +import viper.silicon +import viper.silicon.SiliconFrontend +import viper.silicon.interfaces.state.Chunk +import viper.silicon.state.SimpleIdentifier +import viper.silicon.state.terms.sorts.Bool +import viper.silicon.state.terms.{NoPerm, Term, True, Var} +import viper.silver.ast +import viper.silver.ast._ +import viper.silver.frontend.SilFrontend + +import java.nio.file.Paths +import scala.io.Source +import scala.io.StdIn.readLine + +object DependencyGraphImporter { + + lazy val dummyLabelNode = LabelNode(dummyVar) + lazy val dummyVar = Var.actualCreate((SimpleIdentifier("a"), Bool, false)) + lazy val frontend: SiliconFrontend = createFrontend(Seq.empty) + + def main(args: Array[String]): Unit = { + print("Path to graph folder: ") + val userInput = readLine() + val graph = importGraphFromCsv(userInput) + + // TODO ake: doesn't fully work yet, because the exported program has a different line numbering than the program used for the analysis + val program = importProgram(userInput) + + val interpreter = new DependencyGraphInterpreter("test", graph, List.empty, None) + val userTool = new DependencyAnalysisUserTool(interpreter, Seq.empty, program, List.empty) + userTool.run() + } + + + def importGraphFromCsv(csvFilePath: String): ReadOnlyDependencyGraph = { + val graph = new DependencyGraph() + createNodesFromCsv(graph, csvFilePath) + createEdgesFromCsv(graph, csvFilePath) + graph + } + + def createNodesFromCsv(graph: DependencyGraph, csvFilePath: String): Unit = { + + val bufferedSource = Source.fromFile(csvFilePath + "/nodes.csv") + for (line <- bufferedSource.getLines().drop(1)) { + val fields = line.split("#").map(_.trim) + val nodeIdStr = fields(0) + val nodeType = fields(1) + val assumptionType = AssumptionType.fromString(fields(2)).get + val position = parsePositionString(fields(5)) + val sourceInfo = StringAnalysisSourceInfo(fields(7), position) + + // The following node properties are only relevant for graph construction, thus we can use dummy values while querying the graph. + val term: Term = True + val chunk: Chunk = DummyChunk() + val description: Option[String] = None + val isClosed: Boolean = false + val labelNode: LabelNode = dummyLabelNode + val isJoinNode: Boolean = false + + val nodeId = Some(nodeIdStr.toInt) + // Create node based on type + val node = nodeType match { + case "Assumption" => SimpleAssumptionNode(term, description, sourceInfo, assumptionType, isClosed, isJoinNode, _id=nodeId) + case "Axiom" => AxiomAssumptionNode(term, description, sourceInfo, assumptionType, isClosed, isJoinNode, _id=nodeId) + case "Assertion" => SimpleAssertionNode(term, assumptionType, sourceInfo, isClosed, hasFailed = false, isJoinNode, _id=nodeId) + case "Check" => SimpleCheckNode(term, assumptionType, sourceInfo, isClosed, hasFailed = false, isJoinNode, _id=nodeId) + case "Inhale" => PermissionInhaleNode(chunk, term, sourceInfo, assumptionType, isClosed, labelNode, isJoinNode, _id=nodeId) + case "Exhale" => PermissionExhaleNode(chunk, term, sourceInfo, assumptionType, isClosed, labelNode, hasFailed = false, isJoinNode, _id=nodeId) + case "Label" => LabelNode(dummyVar, _id=nodeId) + case "Infeasible" => InfeasibilityNode(sourceInfo, assumptionType, _id=nodeId) + case _ => throw new IllegalArgumentException(s"Unknown node type: $nodeType") + } + + graph.addNode(node) + } + bufferedSource.close() + } + + def createEdgesFromCsv(graph: DependencyGraph, csvFilePath: String): Unit = { + + val bufferedSource = Source.fromFile(csvFilePath + "/edges.csv") + for (line <- bufferedSource.getLines().drop(1)) { + val Array(sourceId, targetId, tag) = line.split(",").map(_.trim) + + tag match { + case "direct" => graph.addEdges(List(sourceId.toInt), targetId.toInt) + case "interprocedural" => graph.addEdgesConnectingMethods(List(sourceId.toInt), targetId.toInt) + case _ => throw new IllegalArgumentException(s"Unknown tag: $tag") + } + + } + bufferedSource.close() + } + + def importProgram(userInput: String): Program = { + loadProgram(userInput +"\\", "program.vpr", frontend) + } + + def createFrontend(commandLineArgs: Seq[String]): SiliconFrontend = { + val reporter = DependencyAnalysisReporter() + val fe = new SiliconFrontend(reporter) + val backend = fe.createVerifier("") + backend.parseCommandLine(commandLineArgs ++ List("--ignoreFile", "dummy.sil")) + fe.init(backend) + fe.setVerifier(backend) + backend.start() + fe + } + + def loadProgram(filePrefix: String, fileName: String, frontend: SilFrontend): Program = { + val testFile = Paths.get(filePrefix + fileName) + + frontend.reset(testFile) + frontend.runTo(frontend.Translation) + + frontend.translationResult + } + + def parsePositionString(positionString: String): Position = positionString match { + case "???" => NoPosition + case str if str.startsWith("label ") => + val identifier = str.stripPrefix("label ") + VirtualPosition(identifier) + case str if str.contains(" @ line ") => + val parts = str.split(" @ line ") + val fileName = parts(0) + val line = parts(1).toInt + FilePosition(Paths.get(fileName), line, 0) + case str if str.startsWith("line ") => + val line = str.stripPrefix("line ").toInt + LineColumnPosition(line, 0) + case _ => + throw new IllegalArgumentException(s"Cannot parse position from string: $positionString") + } + + +} + +private case class DummyChunk() extends Chunk { + val perm: Term = NoPerm + val permExp: Option[ast.Exp] = None + + override protected def substitute(terms: silicon.Map[Term, Term]): Chunk = this +} \ No newline at end of file diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index f8109e1f3..943dc4307 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -6,7 +6,7 @@ import viper.silicon.verifier.Verifier import viper.silver.ast import viper.silver.ast.utility.ViperStrategy import viper.silver.ast.utility.rewriter.Traverse -import viper.silver.ast.{If, Stmt} +import viper.silver.ast.{If, Program, Stmt} import viper.silver.dependencyAnalysis.AbstractDependencyGraphInterpreter import java.io.PrintWriter @@ -98,11 +98,19 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen def getAssertionNodesWithFailures: Set[GeneralAssertionNode] = getNonInternalAssertionNodes.filter(_.isInstanceOf[GeneralAssertionNode]).map(_.asInstanceOf[GeneralAssertionNode]).filter(_.hasFailed) - def exportGraph(): Unit = { + def exportGraph(program: ast.Program): Unit = { if(Verifier.config.dependencyAnalysisExportPath.isEmpty) return val directory = Paths.get(Verifier.config.dependencyAnalysisExportPath()).toFile directory.mkdir() dependencyGraph.exportGraph(Verifier.config.dependencyAnalysisExportPath() + "/" + name) + exportProgram(program, Verifier.config.dependencyAnalysisExportPath() + "/" + name) + } + + private def exportProgram(program: Program, path: String) = { + // TODO ake: we should copy the original source file in order to keep the line numbering! + val writer = new PrintWriter(path + "/program.vpr") + writer.println(program.toString()) + writer.close() } private def getNodesWithIdenticalSource(nodes: Set[DependencyAnalysisNode]): Set[DependencyAnalysisNode] = { diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index d89a72a0b..543afc555 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -637,12 +637,13 @@ class DefaultMainVerifier(config: Config, val dependencyGraphInterpreters = verificationResults.filter(_.dependencyGraphInterpreter.isDefined).map(_.dependencyGraphInterpreter.get) val verificationErrors: List[Failure] = (verificationResults filter (_.isInstanceOf[Failure])) map (_.asInstanceOf[Failure]) + // TODO ake: make sure we can access the name of frontend programs (instead of naming it "joined") val result = DependencyAnalysisResult(inputFile.map(_.replaceAll("\\\\", "_").replaceAll("/", "_").replaceAll(".vpr", "")).getOrElse("joined"), program, dependencyGraphInterpreters) dependencyAnalysisResult = Some(result) if (Verifier.config.dependencyAnalysisExportPath.isDefined) { - result.dependencyGraphInterpreters foreach (_.exportGraph()) - result.getFullDependencyGraphInterpreter.exportGraph() + result.dependencyGraphInterpreters foreach (_.exportGraph(program)) + result.getFullDependencyGraphInterpreter.exportGraph(program) } DependencyAnalyzer.stopTimeMeasurementAndAddToTotal(postProcessingStartTime, DependencyAnalyzer.timeOfPostprocessing) From 6fe9365ae9cf37be8cf019e8eae5efdc4e767df1 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 28 Feb 2026 12:08:42 +0100 Subject: [PATCH 357/474] add command line arguments to graph importer --- .../DependencyAnalysisUserTool.scala | 4 ++ .../DependencyGraphImporter.scala | 50 ++++++++++++++++--- src/main/scala/dependencyAnalysis/README.md | 11 ++++ 3 files changed, 57 insertions(+), 8 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala index 27321eb6d..740b5b8d3 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala @@ -28,6 +28,10 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete runInternal() } + def run(commandStr: String): Unit = { + handleUserInput(commandStr) + } + @tailrec private def runInternal(): Unit = { try { diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala index 08cb10594..6ec0815b0 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala @@ -12,25 +12,59 @@ import viper.silver.frontend.SilFrontend import java.nio.file.Paths import scala.io.Source -import scala.io.StdIn.readLine object DependencyGraphImporter { - lazy val dummyLabelNode = LabelNode(dummyVar) - lazy val dummyVar = Var.actualCreate((SimpleIdentifier("a"), Bool, false)) + lazy val dummyLabelNode: LabelNode = LabelNode(dummyVar) + lazy val dummyVar: Var = Var.actualCreate((SimpleIdentifier("a"), Bool, false)) lazy val frontend: SiliconFrontend = createFrontend(Seq.empty) + /** + * This method processes command line arguments to import a dependency graph and execute queries on it. + * + * Expected command line arguments: + * - `--graphFolder "[PATH_TO_GRAPH]"`: (Required) Specifies the path to the folder containing the dependency graph export files. + * - `--cmds "[SEMICOLON_SEPARATED_LIST_OF_QUERIES]"`: (Optional) Specifies a series of commands separated by semicolons. + * The supported commands correspond to the ones of the DependencyAnalysisUserTool. + * If this argument is not provided, the interactive mode of the DependencyAnalysisUserTool will start instead. + * + * @throws IllegalArgumentException if the `--graphFolder` argument is not provided. + */ + def main(args: Array[String]): Unit = { - print("Path to graph folder: ") - val userInput = readLine() - val graph = importGraphFromCsv(userInput) + val graphFolder = extractGraphFolderFromArgs(args) + val graph = importGraphFromCsv(graphFolder) // TODO ake: doesn't fully work yet, because the exported program has a different line numbering than the program used for the analysis - val program = importProgram(userInput) + val program = importProgram(graphFolder) val interpreter = new DependencyGraphInterpreter("test", graph, List.empty, None) val userTool = new DependencyAnalysisUserTool(interpreter, Seq.empty, program, List.empty) - userTool.run() + + runUserTool(args, userTool) + } + + private def extractGraphFolderFromArgs(args: Array[String]): String = { + val idx = args.indexOf("--graphFolder") + if(0 <= idx && idx < args.length - 1) + args(idx + 1) + else + throw new IllegalArgumentException("Error: --graphFolder argument is required but not found.") + } + + def runUserTool(args: Array[String], userTool: DependencyAnalysisUserTool): Unit = { + val cmdsIndex = args.indexOf("--cmds") + + val cmds = if (0 <= cmdsIndex && cmdsIndex < args.length - 1) args(cmdsIndex + 1).split(";").map(_.trim) + else Array.empty + + if(cmds.isEmpty) + userTool.run() + else + cmds foreach {c => + println(s"\n--------\nProcessing command \"$c\"...") + userTool.run(c) + } } diff --git a/src/main/scala/dependencyAnalysis/README.md b/src/main/scala/dependencyAnalysis/README.md index dda8f5c8b..b03c23dc4 100644 --- a/src/main/scala/dependencyAnalysis/README.md +++ b/src/main/scala/dependencyAnalysis/README.md @@ -74,3 +74,14 @@ Importing dependency graphs to Neo4j: 1. For a quick start, open the Explore tool and search for `label_NonInternal-(any)-label_NonInternal`. The tool presents the graph described in (ii). 1. Some query templates, which can be imported to Neo4j, can be found in `neo4j_query_saved_cypher_2025-9-17.csv`. +# Graph Importer and Stand-Alone Interpreter + +Once a graph has been exported, it can also be loaded and analyzed using a stand-along importer and interpreter, namely `viper.silicon.dependencyAnalysis.DependencyGraphImporter`. + +Example arguments: +- `--graphFolder "C:\Users\andre\dev\viper\gobra\viperserver\silicon\graphExports\src_test_resources_dependencyAnalysisTests_viperTest" --cmds "dep 16;downDep 14;progress"` + - executes each of the semicolon-separated commands. +- `--graphFolder "C:\Users\andre\dev\viper\gobra\viperserver\silicon\graphExports\src_test_resources_dependencyAnalysisTests_viperTest"` + - starts the interactive command line tool. + +Note that currently the pruning is not supported. From 18b0939ffa55e4e37caaf925e98b19c7ce88e532 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 28 Feb 2026 18:48:10 +0100 Subject: [PATCH 358/474] cleanup smoke checks --- src/main/scala/decider/Decider.scala | 29 +++++++++---------- .../DependencyAnalysisUserTool.scala | 12 ++++++++ src/main/scala/rules/Brancher.scala | 19 ++++-------- src/main/scala/rules/Executor.scala | 5 +--- 4 files changed, 32 insertions(+), 33 deletions(-) diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 95229e5a2..b4778c325 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -78,7 +78,7 @@ trait Decider { def check(t: Term, timeout: Int): Boolean def check(t: Term, timeout: Int, assumptionType: AssumptionType): Boolean - def checkAndGetInfeasibilityNode(t: Term, timeout: Int, assumptionType: AssumptionType): (Boolean, Option[Int]) + def checkSmokeAndSetInfeasibilityNode(): Unit /* TODO: Consider changing assert such that * 1. It passes State and Operations to the continuation @@ -501,7 +501,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => checkNode foreach dependencyAnalyzer.addAssertionNode dependencyAnalyzer.processUnsatCoreAndAddDependencies(prover.getLastUnsatCore, label) val infeasibleNodeId = dependencyAnalyzer.addInfeasibilityNode(!isAssert, analysisSourceInfoStack.getFullSourceInfo, assumptionType) -// THIS IS UNSOUND! Unsoundness is introduced when infeasibility is introduced while executing a package statements and pontentially in other cases as well. +// THIS WOULD BE UNSOUND! Unsoundness is introduced when infeasibility is introduced while executing a package statements and pontentially in other cases as well. // assumeWithoutSmokeChecks(InsertionOrderedSet((False, DependencyAnalyzer.createAssumptionLabel(infeasibleNodeId)))) dependencyAnalyzer.addDependency(checkNode.map(_.id), infeasibleNodeId) pcs.setCurrentInfeasibilityNode(checkNode.map(_.id)) @@ -509,23 +509,22 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => result } - def check(t: Term, timeout: Int): Boolean = check(t, timeout, analysisSourceInfoStack.getAssertionType) - - def check(t: Term, timeout: Int, assumptionType: AssumptionType): Boolean = { - deciderAssert(t, assumptionType, Some(timeout), isCheck=true)._1 - } - - def checkAndGetInfeasibilityNode(t: Term, timeout: Int, assumptionType: AssumptionType): (Boolean, Option[Int]) = { + def checkSmokeAndSetInfeasibilityNode(): Unit = { var infeasibilityNodeId: Option[Int] = pcs.getCurrentInfeasibilityNode - if(infeasibilityNodeId.isDefined){ - return (true, infeasibilityNodeId) - } - val (success, checkNode) = deciderAssert(t, assumptionType, Some(timeout), isCheck=true) + if(infeasibilityNodeId.isDefined) return + + val (success, checkNode) = deciderAssert(False, AssumptionType.Internal, Some(Verifier.config.checkTimeout()), isCheck=true) if(success){ - infeasibilityNodeId = dependencyAnalyzer.addInfeasibilityNode(isCheck = true, analysisSourceInfoStack.getFullSourceInfo, assumptionType) + infeasibilityNodeId = dependencyAnalyzer.addInfeasibilityNode(isCheck = true, analysisSourceInfoStack.getFullSourceInfo, AssumptionType.CustomInternal) dependencyAnalyzer.addDependency(checkNode.map(_.id), infeasibilityNodeId) + pcs.setCurrentInfeasibilityNode(infeasibilityNodeId) } - (success, infeasibilityNodeId) + } + + def check(t: Term, timeout: Int): Boolean = check(t, timeout, analysisSourceInfoStack.getAssertionType) + + def check(t: Term, timeout: Int, assumptionType: AssumptionType): Boolean = { + deciderAssert(t, assumptionType, Some(timeout), isCheck=true)._1 } def assert(t: Term, timeout: Option[Int] = Verifier.config.assertTimeout.toOption)(Q: Boolean => VerificationResult): VerificationResult = assert(t, analysisSourceInfoStack.getAssertionType, timeout)(Q) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala index 740b5b8d3..73c09b82d 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala @@ -66,6 +66,8 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete handleVerificationProgressQuery() }else if (inputParts.head.equalsIgnoreCase("progressDebug")) { handleVerificationProgressDEBUGQuery() + }else if (inputParts.head.equalsIgnoreCase("progressNaive")) { + handleVerificationProgressNaiveQuery() }else if (inputParts.head.equalsIgnoreCase("guidance") || inputParts.head.equalsIgnoreCase("guide")) { handleVerificationGuidanceQuery() }else if(inputParts.head.equalsIgnoreCase("prune")) { @@ -178,6 +180,16 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete if(Math.abs(naiveProgressPeter - optProgressPeter) > 0.001 || Math.abs(naiveProgressLea - optProgressLea) > 0.001) println("Progress is not equal!") } + private def handleVerificationProgressNaiveQuery(): Unit = { + if(verificationErrors.nonEmpty) println(s"Fix verification failures first!") + + val ((naiveProgressPeter, naiveProgressLea, naiveInfo), naiveTime) = measureTime(fullGraphInterpreter.computeVerificationProgressNaive()) + // println(s"Overall verification progress: $progress") + println(s"$naiveInfo") + println(s"Peter: $naiveProgressPeter; Lea: $naiveProgressLea") + println(s"Finished in ${naiveTime}ms") + } + protected def getSourceInfoString(nodes: Set[DependencyAnalysisNode]): String = { UserLevelDependencyAnalysisNode.mkUserLevelString(nodes, "\n\t") } diff --git a/src/main/scala/rules/Brancher.scala b/src/main/scala/rules/Brancher.scala index 28804a798..398bcab89 100644 --- a/src/main/scala/rules/Brancher.scala +++ b/src/main/scala/rules/Brancher.scala @@ -9,7 +9,7 @@ package viper.silicon.rules import viper.silicon.common.concurrency._ import viper.silicon.decider.PathConditionStack import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType -import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, DependencyAnalyzer, DependencyType} +import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyAnalyzer, DependencyType} import viper.silicon.interfaces.{Unreachable, VerificationResult} import viper.silicon.reporting.condenseToViperResult import viper.silicon.state.State @@ -77,23 +77,14 @@ object brancher extends BranchingRules { /* True if the then-branch is to be explored */ val executeThenBranch = ( skipPathFeasibilityCheck - || !v.decider.check(negatedCondition, Verifier.config.checkTimeout(), assumptionType)) - - val thenInfeasibilityNode: Option[Int] = if(v.decider.isDependencyAnalysisEnabled && !executeThenBranch) { - val (_, node) = v.decider.checkAndGetInfeasibilityNode(negatedCondition, Verifier.config.checkTimeout(), assumptionType) - node - } else None + || !v.decider.check(negatedCondition, Verifier.config.checkTimeout(), AssumptionType.Internal)) /* False if the then-branch is to be explored */ val executeElseBranch = ( !executeThenBranch /* Assumes that ast least one branch is feasible */ || skipPathFeasibilityCheck - || !v.decider.check(condition, Verifier.config.checkTimeout(), assumptionType)) + || !v.decider.check(condition, Verifier.config.checkTimeout(), AssumptionType.Internal)) - val elseInfeasibilityNode: Option[Int] = if(v.decider.isDependencyAnalysisEnabled && !executeElseBranch) { - val (_, node) = v.decider.checkAndGetInfeasibilityNode(condition, Verifier.config.checkTimeout(), assumptionType) - node - } else None v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) val parallelizeElseBranch = s.parallelizeBranches && executeThenBranch && executeElseBranch @@ -177,7 +168,7 @@ object brancher extends BranchingRules { val sourceInfo = AnalysisSourceInfo.createAnalysisSourceInfo(conditionExp._1) v1.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo, DependencyType.get(conditionExp._1, DependencyType.PathCondition)) v1.decider.setCurrentBranchCondition(negatedCondition, (negatedConditionExp, negatedConditionExpNew), assumptionType) - v1.decider.pcs.setCurrentInfeasibilityNode(elseInfeasibilityNode) + if(v.decider.isDependencyAnalysisEnabled && !executeElseBranch) v.decider.checkSmokeAndSetInfeasibilityNode() v1.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) var functionsOfElseBranchdDeciderBefore: Set[FunctionDecl] = null @@ -232,7 +223,7 @@ object brancher extends BranchingRules { val sourceInfo = AnalysisSourceInfo.createAnalysisSourceInfo(conditionExp._1) v1.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo, DependencyType.get(conditionExp._1, DependencyType.PathCondition)) v1.decider.setCurrentBranchCondition(condition, conditionExp, assumptionType) - v1.decider.pcs.setCurrentInfeasibilityNode(thenInfeasibilityNode) + if(v.decider.isDependencyAnalysisEnabled && !executeThenBranch) v.decider.checkSmokeAndSetInfeasibilityNode() v1.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) fThen(v1.stateConsolidator(s1).consolidateOptionally(s1, v1), v1) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 40d7e51da..9a2bd4ed2 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -456,10 +456,7 @@ object executor extends ExecutionRules { case _ => produce(s, freshSnap, a, InhaleFailed(inhale), v, getAssumptionType(AssumptionType.Explicit))((s1, v1) => { v1.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterInhale) - if(v1.decider.isDependencyAnalysisEnabled && a.isInstanceOf[ast.FalseLit]) { - val (_, node) = v1.decider.checkAndGetInfeasibilityNode(False, Verifier.config.checkTimeout(), getAssumptionType(AssumptionType.Explicit)) - v1.decider.pcs.setCurrentInfeasibilityNode(node) - } + if(v1.decider.isDependencyAnalysisEnabled && a.isInstanceOf[ast.FalseLit]) v1.decider.checkSmokeAndSetInfeasibilityNode() Q(s1, v1)}) } From 2a5fa17e157b2e611772ec172de53645f1734587 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 2 Mar 2026 10:49:45 +0100 Subject: [PATCH 359/474] cleanup fix for Gobra interfaces (custom join nodes) --- src/main/scala/decider/Decider.scala | 30 ++++++++++++++ .../AnalysisSourceInfo.scala | 20 +--------- .../FrontendDependencyAnalysisInfo.scala | 39 +++++++++++++++++-- src/main/scala/rules/Brancher.scala | 12 ++---- src/main/scala/rules/Consumer.scala | 3 +- src/main/scala/rules/Evaluator.scala | 12 +----- src/main/scala/rules/Executor.scala | 3 +- src/main/scala/rules/Producer.scala | 3 +- 8 files changed, 76 insertions(+), 46 deletions(-) diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index b4778c325..db7d6ce8b 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -66,6 +66,8 @@ trait Decider { def registerChunk[CH <: GeneralChunk](buildChunk: Term => CH, perm: Term, analysisInfo: AnalysisInfo, isExhale: Boolean): CH def registerDerivedChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, analysisInfo: AnalysisInfo, isExhale: Boolean, createLabel: Boolean=true): CH def wrapWithDependencyAnalysisLabel(term: Term, sourceChunks: Iterable[Chunk] = Set.empty, sourceTerms: Iterable[Term] = Set.empty): Term + def pushAndGetAnalysisSourceInfo(e: ast.Exp, dependencyType: Option[DependencyType]): AnalysisSourceInfo + def pushAndGetAnalysisSourceInfo(stmt: ast.Stmt, dependencyType: Option[DependencyType]): AnalysisSourceInfo def isPathInfeasible(): Boolean def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp], assumptionType: AssumptionType): Unit @@ -363,6 +365,34 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } } + def pushAndGetAnalysisSourceInfo(e: ast.Exp, dependencyType: Option[DependencyType]): AnalysisSourceInfo = { + val sourceInfo = AnalysisSourceInfo.createAnalysisSourceInfo(e) + pushAnalysisSourceInfo(sourceInfo, e.info, dependencyType) + sourceInfo + } + + def pushAndGetAnalysisSourceInfo(stmt: ast.Stmt, dependencyType: Option[DependencyType]): AnalysisSourceInfo = { + val sourceInfo = AnalysisSourceInfo.createAnalysisSourceInfo(stmt) + pushAnalysisSourceInfo(sourceInfo, stmt.info, dependencyType) + sourceInfo + } + + private def pushAnalysisSourceInfo(sourceInfo: AnalysisSourceInfo, info: ast.Info, dependencyType: Option[DependencyType]): Unit = { + analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo, dependencyType.getOrElse(analysisSourceInfoStack.getDependencyType)) + if (info.getUniqueInfo[DependencyAnalysisJoinNodeInfo].isDefined) { + // add assertions and assumptions nodes that can be used for a graph join + val joinNodeInfo = info.getUniqueInfo[DependencyAnalysisJoinNodeInfo].get + val currentTopLevelSource = analysisSourceInfoStack.getFullSourceInfo.getTopLevelSource + val assertionNode = joinNodeInfo.getAssertionNode(currentTopLevelSource) + val assumptionNode = joinNodeInfo.getAssumptionNode(currentTopLevelSource) + dependencyAnalyzer.addAssertionNode(assertionNode) + dependencyAnalyzer.addAssumptionNode(assumptionNode) + dependencyAnalyzer.addDependency(Some(assumptionNode.id), Some(assertionNode.id)) + } + } + + + def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp], assumptionType: AssumptionType): Unit = { if (finalExp.isDefined) { assume(assumptions=InsertionOrderedSet((t, Some(DebugExp.createInstance(e.get, finalExp.get)))), analysisSourceInfoStack.getFullSourceInfo, enforceAssumption = false, isDefinition = false, assumptionType) diff --git a/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala b/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala index a419f541a..f2dcc450c 100644 --- a/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala +++ b/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala @@ -17,13 +17,13 @@ object AnalysisSourceInfo { def createAnalysisSourceInfo(exp: ast.Exp): AnalysisSourceInfo = { val depInfo = exp.info.getUniqueInfo[FrontendDependencyAnalysisInfo] - if(depInfo.isDefined) depInfo.get.createAnalysisSourceInfo() + if(depInfo.isDefined) depInfo.get.getAnalysisSourceInfo else ExpAnalysisSourceInfo(exp, exp.pos) } def createAnalysisSourceInfo(stmt: ast.Stmt): AnalysisSourceInfo = { val depInfo = stmt.info.getUniqueInfo[FrontendDependencyAnalysisInfo] - if(depInfo.isDefined) depInfo.get.createAnalysisSourceInfo() + if(depInfo.isDefined) depInfo.get.getAnalysisSourceInfo else StmtAnalysisSourceInfo(stmt, stmt.pos) } @@ -126,19 +126,3 @@ case class CompositeAnalysisSourceInfo(coarseGrainedSource: AnalysisSourceInfo, override def getDescription: String = coarseGrainedSource.getDescription } - - -case class DependencyAnalysisJoinNodeInfo(sourceInfo: AnalysisSourceInfo) extends Info { - def getAssertionNode: GeneralAssertionNode = - SimpleAssertionNode(True, AssumptionType.CustomInternal, sourceInfo, isClosed=false, isJoinNode=true) - - def getAssertionNode(outerSourceInfo: AnalysisSourceInfo): GeneralAssertionNode = - SimpleAssertionNode(True, AssumptionType.CustomInternal, CompositeAnalysisSourceInfo(outerSourceInfo, sourceInfo), isClosed=false, isJoinNode=true) - - def getAssumptionNode(outerSourceInfo: AnalysisSourceInfo): GeneralAssumptionNode = - SimpleAssumptionNode(True, None, CompositeAnalysisSourceInfo(outerSourceInfo, sourceInfo), AssumptionType.CustomInternal, isClosed=false, isJoinNode=true) - - override def comment: Seq[String] = Nil - - override def isCached: Boolean = false -} \ No newline at end of file diff --git a/src/main/scala/dependencyAnalysis/FrontendDependencyAnalysisInfo.scala b/src/main/scala/dependencyAnalysis/FrontendDependencyAnalysisInfo.scala index f1ef6f296..d97dd16c2 100644 --- a/src/main/scala/dependencyAnalysis/FrontendDependencyAnalysisInfo.scala +++ b/src/main/scala/dependencyAnalysis/FrontendDependencyAnalysisInfo.scala @@ -1,14 +1,47 @@ package viper.silicon.dependencyAnalysis -import viper.silver.ast.{AbstractSourcePosition, Info} +import viper.silicon.state.terms.True +import viper.silver.ast.{Info, Position} abstract class FrontendDependencyAnalysisInfo extends Info { override val comment = Nil override val isCached = false val info: String - val pos: AbstractSourcePosition + val pos: Position val dependencyType: Option[DependencyType]=None - def createAnalysisSourceInfo(): AnalysisSourceInfo + def getAnalysisSourceInfo: AnalysisSourceInfo } + +case class SimpleFrontendDependencyAnalysisInfo(sourceInfo: AnalysisSourceInfo, _dependencyType: DependencyType) extends FrontendDependencyAnalysisInfo { + + override val info: String = sourceInfo.toString + override val pos: Position = sourceInfo.getPosition + override val dependencyType: Option[DependencyType] = Some(_dependencyType) + + override def getAnalysisSourceInfo: AnalysisSourceInfo = sourceInfo +} + +/* + Viper statements / expressions with a DependencyAnalysisJoinNodeInfo will produce an assertion and assumption node with the given source info and dependency type, + and with isJoinNode set to true. + The idea is that these nodes can be used to join graphs without the need for an explicit method call. + We use this, for example, for Gobra interfaces and implementation proofs. + */ +case class DependencyAnalysisJoinNodeInfo(sourceInfo: AnalysisSourceInfo, _dependencyType: DependencyType = DependencyType.make(AssumptionType.CustomInternal)) extends FrontendDependencyAnalysisInfo { + def getAssertionNode: GeneralAssertionNode = + SimpleAssertionNode(True, AssumptionType.CustomInternal, sourceInfo, isClosed=false, isJoinNode=true) + + def getAssertionNode(outerSourceInfo: AnalysisSourceInfo): GeneralAssertionNode = + SimpleAssertionNode(True, AssumptionType.CustomInternal, CompositeAnalysisSourceInfo(outerSourceInfo, sourceInfo), isClosed=false, isJoinNode=true) + + def getAssumptionNode(outerSourceInfo: AnalysisSourceInfo): GeneralAssumptionNode = + SimpleAssumptionNode(True, None, CompositeAnalysisSourceInfo(outerSourceInfo, sourceInfo), AssumptionType.CustomInternal, isClosed=false, isJoinNode=true) + + override val info: String = sourceInfo.toString + override val pos: Position = sourceInfo.getPosition + override val dependencyType: Option[DependencyType] = Some(_dependencyType) + + override def getAnalysisSourceInfo: AnalysisSourceInfo = sourceInfo +} \ No newline at end of file diff --git a/src/main/scala/rules/Brancher.scala b/src/main/scala/rules/Brancher.scala index 398bcab89..f2321c235 100644 --- a/src/main/scala/rules/Brancher.scala +++ b/src/main/scala/rules/Brancher.scala @@ -47,8 +47,7 @@ object brancher extends BranchingRules { : VerificationResult = { if(v.decider.isPathInfeasible()){ - val analysisSourceInfo = AnalysisSourceInfo.createAnalysisSourceInfo(conditionExp._1) - v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(analysisSourceInfo, DependencyAnalyzer.extractDependencyTypeFromInfo(conditionExp._1.info).getOrElse(DependencyType.PathCondition)) + val analysisSourceInfo = v.decider.pushAndGetAnalysisSourceInfo(conditionExp._1, Some(DependencyAnalyzer.extractDependencyTypeFromInfo(conditionExp._1.info).getOrElse(DependencyType.PathCondition))) if(!Expressions.isKnownWellDefined(conditionExp._1, Some(s.program))){ v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType) } @@ -72,8 +71,7 @@ object brancher extends BranchingRules { && s.quantifiedVariables.map(_._1).exists(condition.freeVariables.contains)) ) - val sourceInfo = AnalysisSourceInfo.createAnalysisSourceInfo(conditionExp._1) - v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo, DependencyType.get(conditionExp._1, DependencyType.PathCondition)) + val sourceInfo = v.decider.pushAndGetAnalysisSourceInfo(conditionExp._1, Some(DependencyType.get(conditionExp._1, DependencyType.PathCondition))) /* True if the then-branch is to be explored */ val executeThenBranch = ( skipPathFeasibilityCheck @@ -165,8 +163,7 @@ object brancher extends BranchingRules { v0.decider.analysisSourceInfoStack.setAnalysisSourceInfo(currentAnalysisSourceInfos) executionFlowController.locally(s, v0)((s1, v1) => { v1.decider.prover.comment(s"[else-branch: $cnt | $negatedCondition]") - val sourceInfo = AnalysisSourceInfo.createAnalysisSourceInfo(conditionExp._1) - v1.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo, DependencyType.get(conditionExp._1, DependencyType.PathCondition)) + val sourceInfo = v.decider.pushAndGetAnalysisSourceInfo(conditionExp._1, Some(DependencyType.get(conditionExp._1, DependencyType.PathCondition))) v1.decider.setCurrentBranchCondition(negatedCondition, (negatedConditionExp, negatedConditionExpNew), assumptionType) if(v.decider.isDependencyAnalysisEnabled && !executeElseBranch) v.decider.checkSmokeAndSetInfeasibilityNode() v1.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) @@ -220,8 +217,7 @@ object brancher extends BranchingRules { v.decider.analysisSourceInfoStack.setAnalysisSourceInfo(currentAnalysisSourceInfos) executionFlowController.locally(s, v)((s1, v1) => { v1.decider.prover.comment(s"[then-branch: $cnt | $condition]") - val sourceInfo = AnalysisSourceInfo.createAnalysisSourceInfo(conditionExp._1) - v1.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo, DependencyType.get(conditionExp._1, DependencyType.PathCondition)) + val sourceInfo = v.decider.pushAndGetAnalysisSourceInfo(conditionExp._1, Some(DependencyType.get(conditionExp._1, DependencyType.PathCondition))) v1.decider.setCurrentBranchCondition(condition, conditionExp, assumptionType) if(v.decider.isDependencyAnalysisEnabled && !executeThenBranch) v.decider.checkSmokeAndSetInfeasibilityNode() v1.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index 5496d115f..7e21316e3 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -179,8 +179,7 @@ object consumer extends ConsumptionRules { val s1 = s0.copy(h = s.h) /* s1 is s, but the retrying flag might be set */ val sepIdentifier = v1.symbExLog.openScope(new ConsumeRecord(a, s1, v.decider.pcs)) - val sourceInfo = AnalysisSourceInfo.createAnalysisSourceInfo(a) - v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo, DependencyType.get(a, dependencyType)) + val sourceInfo = v1.decider.pushAndGetAnalysisSourceInfo(a, Some(DependencyType.get(a, dependencyType))) consumeTlc(s1, h0, a, returnSnap, pve, v1, dependencyType)((s2, h2, snap2, v2) => { v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index c755c34ee..fb5ca58d2 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -87,17 +87,7 @@ object evaluator extends EvaluationRules { : VerificationResult = { val sepIdentifier = v.symbExLog.openScope(new EvaluateRecord(e, s, v.decider.pcs)) - val sourceInfo = AnalysisSourceInfo.createAnalysisSourceInfo(e) - v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo, dependencyType.getOrElse(v.decider.analysisSourceInfoStack.getDependencyType)) - if(e.info.getUniqueInfo[DependencyAnalysisJoinNodeInfo].isDefined){ - val joinNodeInfo = e.info.getUniqueInfo[DependencyAnalysisJoinNodeInfo].get - val currentTopLevelSource = v.decider.analysisSourceInfoStack.getFullSourceInfo.getTopLevelSource - val assertionNode = joinNodeInfo.getAssertionNode(currentTopLevelSource) - val assumptionNode = joinNodeInfo.getAssumptionNode(currentTopLevelSource) - v.decider.dependencyAnalyzer.addAssertionNode(assertionNode) - v.decider.dependencyAnalyzer.addAssumptionNode(assumptionNode) - v.decider.dependencyAnalyzer.addDependency(Some(assumptionNode.id), Some(assertionNode.id)) - } + val sourceInfo = v.decider.pushAndGetAnalysisSourceInfo(e, dependencyType) eval3(s, e, pve, v)((s1, t, eNew, v1) => { v1.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 9a2bd4ed2..7a577d273 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -335,8 +335,7 @@ object executor extends ExecutionRules { (Q: (State, Verifier) => VerificationResult) : VerificationResult = { val sepIdentifier = v.symbExLog.openScope(new ExecuteRecord(stmt, s, v.decider.pcs)) - val sourceInfo = AnalysisSourceInfo.createAnalysisSourceInfo(stmt) - v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo, DependencyType.get(stmt)) + val sourceInfo = v.decider.pushAndGetAnalysisSourceInfo(stmt, Some(DependencyType.get(stmt))) exec2(s, stmt, v)((s1, v1) => { v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) v1.symbExLog.closeScope(sepIdentifier) diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index 189daf6cd..4218ec2d6 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -152,8 +152,7 @@ object producer extends ProductionRules { val a = as.head.whenInhaling val pve = pves.head - val sourceInfo = AnalysisSourceInfo.createAnalysisSourceInfo(a) - v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo, DependencyType.get(a, DependencyType.make(assumptionType))) + val sourceInfo = v.decider.pushAndGetAnalysisSourceInfo(a, Some(DependencyType.get(a, DependencyType.make(assumptionType)))) if (as.tail.isEmpty) wrappedProduceTlc(s, sf, a, pve, v, assumptionType)((s1, v1) => { v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) From 19f6dadefb487bd5a21d80269f5d4b37b0303f37 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 2 Mar 2026 13:33:08 +0100 Subject: [PATCH 360/474] handle join nodes in function verification unit --- .../functions/FunctionVerificationUnit.scala | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index ebe6a718d..b4317759c 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -281,9 +281,17 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver var recorders: Seq[FunctionRecorder] = Vector.empty val wExp = evaluator.withExp val annotatedAssumptionTypeOpt = DependencyAnalyzer.extractAssumptionTypeFromInfo(function.info) - val postConditionType = annotatedAssumptionTypeOpt.getOrElse(if(function.body.isDefined) AssumptionType.ImplicitPostcondition else AssumptionType.ExplicitPostcondition) + var postConditionType = annotatedAssumptionTypeOpt.getOrElse(if(function.body.isDefined) AssumptionType.ImplicitPostcondition else AssumptionType.ExplicitPostcondition) decider.dependencyAnalyzer.addNodes(v.decider.prover.getPreambleAnalysisNodes) + val daJoinNodeInfoOpt = function.info.getUniqueInfo[DependencyAnalysisJoinNodeInfo] + if(daJoinNodeInfoOpt.isDefined){ + val infodaJoinNodeInfo = daJoinNodeInfoOpt.get + v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(infodaJoinNodeInfo.sourceInfo, DependencyType.make(AssumptionType.CustomInternal)) + postConditionType = AssumptionType.CustomInternal + v.decider.dependencyAnalyzer.addAssertionNode(infodaJoinNodeInfo.getAssertionNode) + } + val result = phase1data.foldLeft(Success(): VerificationResult) { case (fatalResult: FatalResult, _) => fatalResult case (intermediateResult, Phase1Data(sPre, bcsPre, bcsPreExp, pcsPre, pcsPreExp)) => @@ -306,6 +314,8 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver recorders :+= s3.functionRecorder Success()})})})} + if(daJoinNodeInfoOpt.isDefined) v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(daJoinNodeInfoOpt.get.sourceInfo) + data.advancePhase(recorders) result From 977c980b878a7a53239502a6cb855e7e49aa452c Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 2 Mar 2026 13:51:39 +0100 Subject: [PATCH 361/474] optimize graph export --- .../scala/dependencyAnalysis/DependencyGraph.scala | 14 ++++++++++---- .../DependencyGraphImporter.scala | 5 ++--- src/main/scala/verifier/DefaultMainVerifier.scala | 2 +- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyGraph.scala b/src/main/scala/dependencyAnalysis/DependencyGraph.scala index bb01fe723..ef0354a36 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraph.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraph.scala @@ -223,10 +223,13 @@ class DependencyGraph extends ReadOnlyDependencyGraph { } private def exportEdges(fileName: String): Unit = { + val builder = new StringBuilder() + getDirectEdges foreach (e => e._2 foreach (s => builder.append(s.toString + "," + e._1.toString + ",direct" + "\n"))) + getEdgesConnectingMethods foreach (e => e._2 foreach (s => builder.append(s.toString + "," + e._1.toString + ",interprocedural" + "\n"))) + val writer = new PrintWriter(fileName) writer.println("source,target,label") - getDirectEdges foreach (e => e._2 foreach (s => writer.println(s.toString + "," + e._1.toString + ",direct"))) - getEdgesConnectingMethods foreach (e => e._2 foreach (s => writer.println(s.toString + "," + e._1.toString + ",interprocedural"))) + writer.println(builder.toString()) writer.close() } @@ -236,10 +239,13 @@ class DependencyGraph extends ReadOnlyDependencyGraph { val parts = mutable.Seq(node.id.toString, node.getNodeType, node.assumptionType.toString, node.getNodeString, node.sourceInfo.toString, node.sourceInfo.getPositionString, node.sourceInfo.getFineGrainedSource.toString, node.sourceInfo.getDescription) parts.map(_.replace("#", "@")).mkString(sep) } - val writer = new PrintWriter(fileName) val headerParts = mutable.Seq("id", "node type", "assumption type", "node info", "source info", "position", "fine grained source", "description") + val builder = new StringBuilder() + getNodes foreach (n => builder.append(getNodeExportString(n).replace("\n", " ") + "\n")) + + val writer = new PrintWriter(fileName) writer.println(headerParts.mkString(sep)) - getNodes foreach (n => writer.println(getNodeExportString(n).replace("\n", " "))) + writer.println(builder.result()) writer.close() } } diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala index 6ec0815b0..bd096924e 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala @@ -55,13 +55,12 @@ object DependencyGraphImporter { def runUserTool(args: Array[String], userTool: DependencyAnalysisUserTool): Unit = { val cmdsIndex = args.indexOf("--cmds") - val cmds = if (0 <= cmdsIndex && cmdsIndex < args.length - 1) args(cmdsIndex + 1).split(";").map(_.trim) - else Array.empty + val cmds = if (0 <= cmdsIndex && cmdsIndex < args.length - 1) Some(args(cmdsIndex + 1).split(";").map(_.trim)) else None if(cmds.isEmpty) userTool.run() else - cmds foreach {c => + cmds.get foreach {c => println(s"\n--------\nProcessing command \"$c\"...") userTool.run(c) } diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index 265f20962..ccc32ef93 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -670,7 +670,7 @@ class DefaultMainVerifier(config: Config, dependencyAnalysisResult = Some(result) if (Verifier.config.dependencyAnalysisExportPath.isDefined) { - result.dependencyGraphInterpreters foreach (_.exportGraph(program)) +// result.dependencyGraphInterpreters foreach (_.exportGraph(program)) // comment this in to get the individual methods' graphs result.getFullDependencyGraphInterpreter.exportGraph(program) } From f72b6c1f1d0bc4be7bb0f1906689d3e5c537bb6d Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 2 Mar 2026 17:51:30 +0100 Subject: [PATCH 362/474] comment out profiling artifacts for progress metric --- .../DependencyGraphInterpreter.scala | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 943dc4307..becd278d9 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -239,44 +239,44 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen val allSourceCodeNodes = toCompactUserLevelNodes(getNonInternalAssumptionNodes).filter(n => nonSourceCodeAssumptionTypes.intersect(n.assumptionTypes).isEmpty).map(_.source.getTopLevelSource) val coveredSourceCodeNodes = coveredNodes.map(_.source.getTopLevelSource).intersect(allSourceCodeNodes) - println(s"Covered Source Code:\n\t${coveredSourceCodeNodes.toList.sortBy(_.getLineNumber).mkString("\n\t")}") - println(s"Uncovered Source Code:\n\t${allSourceCodeNodes.diff(coveredSourceCodeNodes).toList.sortBy(_.getLineNumber).mkString("\n\t")}") +// println(s"Covered Source Code:\n\t${coveredSourceCodeNodes.toList.sortBy(_.getLineNumber).mkString("\n\t")}") +// println(s"Uncovered Source Code:\n\t${allSourceCodeNodes.diff(coveredSourceCodeNodes).toList.sortBy(_.getLineNumber).mkString("\n\t")}") println(s"Spec Quality = ${coveredSourceCodeNodes.size} / ${allSourceCodeNodes.size}") coveredSourceCodeNodes.size.toDouble / allSourceCodeNodes.size.toDouble } - - var perMethodDependencyRuntime: Long = 0L - var depsToPostcondRuntime: Long = 0L - var aggregationOfSummaryNodesRuntime: Long = 0L - var filteringNodesRuntime: Long = 0L +// TODO ake: remove profiling artifacts +// var perMethodDependencyRuntime: Long = 0L +// var depsToPostcondRuntime: Long = 0L +// var aggregationOfSummaryNodesRuntime: Long = 0L +// var filteringNodesRuntime: Long = 0L private lazy val sourceToAssertionNodes: Map[AnalysisSourceInfo, Set[DependencyAnalysisNode]] = getNonInternalAssertionNodes.groupBy(_.sourceInfo.getTopLevelSource) val deps: DAMemo[AnalysisSourceInfo, Set[CompactUserLevelDependencyAnalysisNode]] = DAMemo { assertionNode => - val startFilteringNodes0 = System.nanoTime() +// val startFilteringNodes0 = System.nanoTime() val allNonInternalAssertions = sourceToAssertionNodes.getOrElse(assertionNode, Set.empty) - filteringNodesRuntime = filteringNodesRuntime + (System.nanoTime() - startFilteringNodes0) - val startPerMethodDeps = System.nanoTime() +// filteringNodesRuntime = filteringNodesRuntime + (System.nanoTime() - startFilteringNodes0) +// val startPerMethodDeps = System.nanoTime() val intraMethodDependencyIds = dependencyGraph.getAllDependencies(allNonInternalAssertions.map(_.id), includeInfeasibilityNodes=true, includeIntraMethodEdges=false) - perMethodDependencyRuntime = perMethodDependencyRuntime + (System.nanoTime() - startPerMethodDeps) +// perMethodDependencyRuntime = perMethodDependencyRuntime + (System.nanoTime() - startPerMethodDeps) - val startFilteringNodes1 = System.nanoTime() +// val startFilteringNodes1 = System.nanoTime() val intraMethodDependencies = intraMethodDependencyIds.flatMap(nonInternalAssumptionNodesMap.get).filter(!_.sourceInfo.getTopLevelSource.equals(assertionNode)) - filteringNodesRuntime = filteringNodesRuntime + (System.nanoTime() - startFilteringNodes1) +// filteringNodesRuntime = filteringNodesRuntime + (System.nanoTime() - startFilteringNodes1) val startDepsToPostcond = System.nanoTime() val postconditionNodeIds = intraMethodDependencyIds.flatMap(n => dependencyGraph.getEdgesConnectingMethods.getOrElse(n, Set.empty)) - depsToPostcondRuntime = depsToPostcondRuntime + (System.nanoTime() - startDepsToPostcond) +// depsToPostcondRuntime = depsToPostcondRuntime + (System.nanoTime() - startDepsToPostcond) val startFilteringNodes2 = System.nanoTime() val postconditionNodes = postconditionNodeIds flatMap nodesMap.get - filteringNodesRuntime = filteringNodesRuntime + (System.nanoTime() - startFilteringNodes2) +// filteringNodesRuntime = filteringNodesRuntime + (System.nanoTime() - startFilteringNodes2) val transDeps = postconditionNodes.map(_.sourceInfo.getTopLevelSource).diff(Set(assertionNode)) flatMap deps - val startAggregation = System.nanoTime() +// val startAggregation = System.nanoTime() val res = reduceCompactUserLevelNodes(toCompactUserLevelNodes(intraMethodDependencies ++ postconditionNodes) ++ transDeps) - aggregationOfSummaryNodesRuntime = aggregationOfSummaryNodesRuntime + (System.nanoTime() - startAggregation) +// aggregationOfSummaryNodesRuntime = aggregationOfSummaryNodesRuntime + (System.nanoTime() - startAggregation) res } @@ -338,17 +338,17 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen val proofQualityLea = assertionQualitiesSum / numAssertions.toDouble val info = { - s"Assertions with dependencies on explicit assumptions:\n\t\t${assertionQualities.filterNot(_._1 == 1.0).sortBy(_._2.getLineNumber).mkString("\n\t\t")}" + "\n\n" + - s"Assertions with perfect proof quality:\n\t\t${fullyVerifiedAssertions.map(_._2).sortBy(_.getLineNumber).mkString("\n\t\t")}" + "\n\n" + +// s"Assertions with dependencies on explicit assumptions:\n\t\t${assertionQualities.filterNot(_._1 == 1.0).sortBy(_._2.getLineNumber).mkString("\n\t\t")}" + "\n\n" + +// s"Assertions with perfect proof quality:\n\t\t${fullyVerifiedAssertions.map(_._2).sortBy(_.getLineNumber).mkString("\n\t\t")}" + "\n\n" + s"specQuality = $specQuality\n" + s"proof quality (Peter): $numFullyVerifiedAssertions / $numAssertions = $proofQualityPeter\n" + s"proof quality (Lea): $assertionQualitiesSum / $numAssertions = $proofQualityLea\n" } - println(s"Runtimes:\n\tperMethodDependencyRuntime: ${perMethodDependencyRuntime/1e6}ms\n\t" + - s"depsToPostcondRuntime: ${depsToPostcondRuntime/1e6}ms\n\t" + - s"aggregationOfSummaryNodesRuntime: ${aggregationOfSummaryNodesRuntime/1e6}ms\n\t" + - s"filteringNodesRuntime: ${filteringNodesRuntime/1e6}ms\n\t") +// println(s"Runtimes:\n\tperMethodDependencyRuntime: ${perMethodDependencyRuntime/1e6}ms\n\t" + +// s"depsToPostcondRuntime: ${depsToPostcondRuntime/1e6}ms\n\t" + +// s"aggregationOfSummaryNodesRuntime: ${aggregationOfSummaryNodesRuntime/1e6}ms\n\t" + +// s"filteringNodesRuntime: ${filteringNodesRuntime/1e6}ms\n\t") (specQuality * proofQualityPeter, specQuality * proofQualityLea, info) } From 1881e4df3a067e8f481f052b51bd7807c5696b18 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 2 Mar 2026 20:45:13 +0100 Subject: [PATCH 363/474] minor fix regarding join nodes --- .../scala/dependencyAnalysis/DependencyAnalyzer.scala | 11 ++++++++--- .../DependencyGraphInterpreter.scala | 10 +++++----- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index 51c0c8233..485142051 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -1,6 +1,6 @@ package viper.silicon.dependencyAnalysis -import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType +import viper.silicon.dependencyAnalysis.AssumptionType.{AssumptionType, CustomInternal} import viper.silicon.dependencyAnalysis.DependencyAnalyzer.{runtimeOverheadPermissionNodes, startTimeMeasurement, stopTimeMeasurementAndAddToTotal, timeToAddTransitiveEdges, timeToMergeNodes, timeToProcessUnsatCore, timeToRemoveInternalNodes} import viper.silicon.interfaces.state.{Chunk, GeneralChunk} import viper.silicon.state.terms._ @@ -210,7 +210,8 @@ object DependencyAnalyzer { stopTimeMeasurementAndAddToTotal(startTime, timeForFunctionJoin) startTime = startTimeMeasurement() - + + val customInternalNodes = joinCandidateNodes.filter(_.assumptionType.equals(CustomInternal)).map(_.id).toSet // postconditions of methods assumed by every method call should depend on the assertions that justify them // hence, we add edges from assertions of method postconditions to assumptions of the same postcondition (at method calls) val relevantAssumptionNodes = joinCandidateNodes @@ -220,7 +221,11 @@ object DependencyAnalyzer { .toMap joinCandidateNodes.filter(node => AssumptionType.postconditionTypes.contains(node.assumptionType) || node.isJoinNode) .map(node => (node.id, relevantAssumptionNodes.getOrElse(node.sourceInfo.getTopLevelSource, Seq.empty))) - .foreach { case (src, targets) => newGraph.addEdgesConnectingMethods(src, targets)} + .foreach { case (src, targets) => + if (customInternalNodes.intersect(targets.toSet.union(Set(src))).isEmpty) newGraph.addEdgesConnectingMethods(src, targets) + else newGraph.addEdges(src, targets) + } + stopTimeMeasurementAndAddToTotal(startTime, timeForMethodJoin) diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index becd278d9..f18f14719 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -51,26 +51,26 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen result = result.union(directDependencyIds) } - getNonInternalAssumptionNodes.filter(node => result.contains(node.id) && !nodeIdsToAnalyze.contains(node.id)) + getNonInternalAssumptionNodes.filter(node => result.contains(node.id)) } def getAllNonInternalDependencies(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[DependencyAnalysisNode] = { - val allDependencies = dependencyGraph.getAllDependencies(nodeIdsToAnalyze, includeInfeasibilityNodes).diff(nodeIdsToAnalyze) + val allDependencies = dependencyGraph.getAllDependencies(nodeIdsToAnalyze, includeInfeasibilityNodes) allDependencies flatMap nonInternalAssumptionNodesMap.get } def getAllExplicitDependencies(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[DependencyAnalysisNode] = { - val allDependencies = dependencyGraph.getAllDependencies(nodeIdsToAnalyze, includeInfeasibilityNodes).diff(nodeIdsToAnalyze) + val allDependencies = dependencyGraph.getAllDependencies(nodeIdsToAnalyze, includeInfeasibilityNodes) getExplicitAssumptionNodes.filter(node => allDependencies.contains(node.id)) } def getAllNonInternalDependents(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[DependencyAnalysisNode] = { - val allDependents = dependencyGraph.getAllDependents(nodeIdsToAnalyze, includeInfeasibilityNodes).diff(nodeIdsToAnalyze) + val allDependents = dependencyGraph.getAllDependents(nodeIdsToAnalyze, includeInfeasibilityNodes) getNonInternalAssertionNodes.filter(node => allDependents.contains(node.id)) } def getAllExplicitDependents(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[DependencyAnalysisNode] = { - val allDependents = dependencyGraph.getAllDependents(nodeIdsToAnalyze, includeInfeasibilityNodes).diff(nodeIdsToAnalyze) + val allDependents = dependencyGraph.getAllDependents(nodeIdsToAnalyze, includeInfeasibilityNodes) getExplicitAssertionNodes.filter(node => allDependents.contains(node.id)) } From 4e8df6029bf04b0a8c98938de2f06d9ce84a0b3b Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 2 Mar 2026 20:59:50 +0100 Subject: [PATCH 364/474] fix guidance --- .../scala/dependencyAnalysis/DependencyGraphInterpreter.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index f18f14719..872728d18 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -435,7 +435,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen /* returns an ordered list of (Assumption, #dependents) */ def computeAssumptionRanking(): List[(String, Int)] = { - toUserLevelNodes(getExplicitAssumptionNodes).map(node => (node.toString, getAllNonInternalDependents(node.lowerLevelNodes.map(_.id)).size)) + toUserLevelNodes(getExplicitAssumptionNodes).map(node => (node.toString, toUserLevelNodes(getAllNonInternalDependents(node.lowerLevelNodes.map(_.id))).diff(Set(node)).size)) .toList.sortBy(_._2).reverse } From e3506067367ebaca043a6dedf9cdb5026d16d402 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 3 Mar 2026 12:35:49 +0100 Subject: [PATCH 365/474] attempt to automatically determine minimal dependencies for precision benchmarks --- .../DependencyAnalysisTestFramework.scala | 67 ++++++++++++++++++- src/test/scala/DependencyAnalysisTests.scala | 5 +- 2 files changed, 67 insertions(+), 5 deletions(-) diff --git a/src/test/scala/DependencyAnalysisTestFramework.scala b/src/test/scala/DependencyAnalysisTestFramework.scala index 4e1e67f83..ac4b85969 100644 --- a/src/test/scala/DependencyAnalysisTestFramework.scala +++ b/src/test/scala/DependencyAnalysisTestFramework.scala @@ -79,7 +79,7 @@ trait DependencyAnalysisTestFramework { * * Statements that are only required as a trigger need to be manually annotated with @trigger() by the user. */ - case class PruningTest(fileName: String, program: Program, fullGraphInterpreter: DependencyGraphInterpreter) { + class PruningTest(fileName: String, program: Program, fullGraphInterpreter: DependencyGraphInterpreter) { def execute(): Unit = { val triggerNodeLines = fullGraphInterpreter.getNodes.filter(node => node.getUserLevelRepresentation.contains("@trigger()")).flatMap(_.sourceInfo.getLineNumber) @@ -98,7 +98,7 @@ trait DependencyAnalysisTestFramework { val crucialNodes = relevantNodes ++ dependencies val (newProgram, pruningFactor) = fullGraphInterpreter.getPrunedProgram(crucialNodes, program) - val result = frontend.verifier.verify(newProgram) + val result = baselineFrontend.verifier.verify(newProgram) if(EXPORT_PRUNED_PROGRAMS) exportPrunedProgram(exportFileName, newProgram, pruningFactor, result) assert(!result.isInstanceOf[verifier.Failure], s"Failed to verify new program ${newProgram.toString()}") } @@ -112,6 +112,67 @@ trait DependencyAnalysisTestFramework { } } + class PrecisionEvaluation(filePrefix: String, fileName: String, program: Program, fullGraphInterpreter: DependencyGraphInterpreter) { + protected val folderName = s"src/test/resources/precision_groundTruths/$filePrefix" + + def execute(): Unit = { + Paths.get(folderName).toFile.mkdirs() + val triggerNodeLines = fullGraphInterpreter.getNodes.filter(node => node.getUserLevelRepresentation.contains("@trigger()")).flatMap(_.sourceInfo.getLineNumber) + var id: Int = 0 + // TODO ake: safer would be to work with position string instead of line numbers + fullGraphInterpreter.getExplicitAssertionNodes flatMap (_.sourceInfo.getLineNumber) foreach {line => + evaluatePrecision(Set(line) ++ triggerNodeLines) + resetBaselineFrontend() + id += 1 + } + } + + protected def evaluatePrecision(relevantLines: Set[Int]): Unit = { + val relevantNodes = relevantLines.flatMap(line => fullGraphInterpreter.getNodesByLine(line)) + val sourceInfoes = relevantNodes.groupBy(_.sourceInfo.getTopLevelSource).keySet + println(s"Evaluating precision of\n\t${sourceInfoes.mkString("\n\t")}") + + val reportedDependencies = fullGraphInterpreter.getAllNonInternalDependencies(relevantNodes.map(_.id)).diff(relevantNodes) + val verifies = pruneAndVerify(reportedDependencies ++ relevantNodes) + if(!verifies) { + println("Unsound. The program pruned with respect to all reported dependencies does not verify!") + return + } + + var minNumDeps = reportedDependencies.size + var bestDepSet = reportedDependencies + + for(currDepSubset <- reportedDependencies.subsets()){ + if(currDepSubset.size < minNumDeps){ + println(currDepSubset.size) + val verifies = pruneAndVerify(currDepSubset ++ relevantNodes) + if(verifies){ + minNumDeps = currDepSubset.size + bestDepSet = currDepSubset + } + } + } + + val (minProgram, _) = fullGraphInterpreter.getPrunedProgram(bestDepSet ++ relevantNodes, program) + + exportPrunedProgram(s"${fileName}_${relevantLines.mkString("_")}.vpr", minProgram) + val precision = minNumDeps.toDouble / reportedDependencies.size + println(s"Precision: $minNumDeps / ${reportedDependencies.size}=$precision") + } + + protected def pruneAndVerify(crucialNodes: Set[DependencyAnalysisNode]): Boolean = { + val (newProgram, _) = fullGraphInterpreter.getPrunedProgram(crucialNodes, program) + val result = baselineFrontend.verifier.verify(newProgram) + !result.isInstanceOf[verifier.Failure] + } + + protected def exportPrunedProgram(exportFileName: String, newProgram: Program): Unit = { + val writer = new PrintWriter(folderName + "/" + exportFileName) + writer.println(newProgram.toString()) + writer.close() + } + } + /** * Takes a Viper program and its dependency analysis results and checks whether the analysis found the * assumptions, assertions and dependencies between them, as annotated by the user. @@ -128,7 +189,7 @@ trait DependencyAnalysisTestFramework { * but multiple dependency/irrelevant annotations are allowed * */ - case class AnnotatedTest(program: Program, dependencyGraphInterpreters: List[DependencyGraphInterpreter], checkPrecision: Boolean) { + class AnnotatedTest(program: Program, dependencyGraphInterpreters: List[DependencyGraphInterpreter], checkPrecision: Boolean) { def execute(): Unit = { val stmtsWithAssumptionAnnotation: Set[Infoed] = extractAnnotatedStmts({ annotationInfo => annotationInfo.values.contains(irrelevantKeyword + "(\"") || annotationInfo.values.contains(dependencyKeyword) }) val allAssumptionNodes = dependencyGraphInterpreters.flatMap(_.getNonInternalAssumptionNodes) diff --git a/src/test/scala/DependencyAnalysisTests.scala b/src/test/scala/DependencyAnalysisTests.scala index 5b63f2034..61942a400 100644 --- a/src/test/scala/DependencyAnalysisTests.scala +++ b/src/test/scala/DependencyAnalysisTests.scala @@ -80,8 +80,9 @@ class DependencyAnalysisTests extends AnyFunSuite with DependencyAnalysisTestFra val dependencyGraphInterpreters = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].dependencyGraphInterpretersPerMember val joinedDependencyGraphInterpreter = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].joinedDependencyGraphInterpreter - AnnotatedTest(program, dependencyGraphInterpreters, CHECK_PRECISION).execute() - PruningTest(filePrefix + "/" + fileName, program, joinedDependencyGraphInterpreter.get).execute() + new AnnotatedTest(program, dependencyGraphInterpreters, CHECK_PRECISION).execute() + new PruningTest(filePrefix + "/" + fileName, program, joinedDependencyGraphInterpreter.get).execute() +// new PrecisionEvaluation(filePrefix, fileName, program, joinedDependencyGraphInterpreter.get).execute() } def executePerformanceBenchmark(filePrefix: String, From e7e15deb0b1ca8f01389ed9c76a648b813801aef Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 3 Mar 2026 14:00:58 +0100 Subject: [PATCH 366/474] fix guidance (function bodies) --- .../scala/dependencyAnalysis/DependencyGraphInterpreter.scala | 4 +++- src/main/scala/supporters/functions/FunctionData.scala | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 872728d18..230bdc552 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -440,7 +440,9 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen } def computeUncoveredStatements(): Int = { - val allSourceCodeStmts = UserLevelDependencyAnalysisNode.extractSourceCodeNodes(toUserLevelNodes(getNonInternalAssumptionNodes)).getSourceSet() + val allNodes = toUserLevelNodes(getNonInternalAssumptionNodes) + val allSourceCodeStmts = allNodes.getSourceSet().diff(UserLevelDependencyAnalysisNode.extractByAssumptionType(allNodes, + AssumptionType.explicitAssumptionTypes ++ AssumptionType.verificationAnnotationTypes ++ Set(AssumptionType.FunctionBody)).getSourceSet()) val coveredSourceCodeStmts = toUserLevelNodes(getAllNonInternalDependencies(getNodesWithIdenticalSource(getNonInternalAssertionNodes).map(_.id))).getSourceSet() allSourceCodeStmts.diff(coveredSourceCodeStmts).size } diff --git a/src/main/scala/supporters/functions/FunctionData.scala b/src/main/scala/supporters/functions/FunctionData.scala index 3e032a9e2..e927e0958 100644 --- a/src/main/scala/supporters/functions/FunctionData.scala +++ b/src/main/scala/supporters/functions/FunctionData.scala @@ -317,7 +317,7 @@ class FunctionData(val programFunction: ast.Function, val allTriggers = ( Seq(Trigger(functionApplication)) ++ actualPredicateTriggers) - (Forall(arguments, body, allTriggers), Option.when(isAnalysisEnabled)((AnalysisSourceInfo.createAnalysisSourceInfo(programFunction.body.get), AssumptionType.SourceCode))) + (Forall(arguments, body, allTriggers), Option.when(isAnalysisEnabled)((AnalysisSourceInfo.createAnalysisSourceInfo(programFunction.body.get), AssumptionType.FunctionBody))) }) } @@ -326,7 +326,7 @@ class FunctionData(val programFunction: ast.Function, val bodyPreconditions = if (programFunction.body.isDefined) optBody.map(translatedBody => { val body = Implies(pre, FunctionPreconditionTransformer.transform(translatedBody, program)) (Forall(arguments, body, Seq(Trigger(functionApplication))), - Option.when(isAnalysisEnabled)((ExpAnalysisSourceInfo(programFunction.body.get, programFunction.body.get.pos), AssumptionType.SourceCode))) + Option.when(isAnalysisEnabled)((ExpAnalysisSourceInfo(programFunction.body.get, programFunction.body.get.pos), AssumptionType.FunctionBody))) }) else None bodyPreconditions.toSeq } From ee7bb0a6fe4a376e65b363323211d948d9bfd4ef Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 3 Mar 2026 15:52:22 +0100 Subject: [PATCH 367/474] minor fix, function bodies -> verification annotation --- src/main/scala/dependencyAnalysis/AnalysisInfo.scala | 2 +- .../scala/dependencyAnalysis/DependencyGraphInterpreter.scala | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala index 44999ec8a..339acd507 100644 --- a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala +++ b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala @@ -12,7 +12,7 @@ object AssumptionType extends Enumeration { def explicitAssertionTypes: Set[AssumptionType] = Set(Explicit, ImplicitPostcondition, ExplicitPostcondition) def internalTypes: Set[AssumptionType] = Set(Internal, Trigger, CustomInternal) // will always be hidden from user def joinConditionTypes: Set[AssumptionType] = postconditionTypes ++ Set(FunctionBody) - def verificationAnnotationTypes: Set[AssumptionType] = Set(LoopInvariant, Rewrite, ExplicitPostcondition, ImplicitPostcondition, Precondition, Explicit, DomainAxiom, Ghost) + def verificationAnnotationTypes: Set[AssumptionType] = Set(FunctionBody, LoopInvariant, Rewrite, ExplicitPostcondition, ImplicitPostcondition, Precondition, Explicit, DomainAxiom, Ghost) def sourceCodeTypes: Set[AssumptionType] = AssumptionType.values.diff(explicitAssumptionTypes ++ explicitAssertionTypes ++ verificationAnnotationTypes ++ internalTypes) } diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 230bdc552..e913131c5 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -390,8 +390,9 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen val numRelevantAssertions = relevantAssertions.keySet.size.toDouble + val numAllSourceCodeStmts = coveredSourceCodeStmts.size.toDouble + uncoveredSourceCodeStmts.size.toDouble // Peter's metric - val specQuality = coveredSourceCodeStmts.size.toDouble / (coveredSourceCodeStmts.size.toDouble + uncoveredSourceCodeStmts.size.toDouble) + val specQuality = if(numAllSourceCodeStmts > 0) coveredSourceCodeStmts.size.toDouble / numAllSourceCodeStmts else 1.0 val proofQualityPeter = if(numRelevantAssertions > 0) fullyVerifiedAssertions.size.toDouble / numRelevantAssertions else 1.0 val verificationProgressPeter = specQuality * proofQualityPeter From c53794de7939803b38010ee9f3414da47a1f0c24 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 3 Mar 2026 17:15:32 +0100 Subject: [PATCH 368/474] fix Gobra method and function postcondition types --- src/main/scala/supporters/MethodSupporter.scala | 2 +- src/main/scala/supporters/functions/FunctionData.scala | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index c05155088..79a7c86d1 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -77,7 +77,7 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif new java.io.File(s"${Verifier.config.tempDirectory()}/${method.name}.dot")) } - val annotatedAssumptionTypeOpt = DependencyAnalyzer.extractAssumptionTypeFromInfo(method.info) + val annotatedAssumptionTypeOpt = DependencyAnalyzer.extractDependencyTypeFromInfo(method.info).map(_.assertionType) var postConditionType = annotatedAssumptionTypeOpt.getOrElse(if(method.body.isDefined) AssumptionType.ImplicitPostcondition else AssumptionType.ExplicitPostcondition) val daJoinNodeInfoOpt = method.info.getUniqueInfo[DependencyAnalysisJoinNodeInfo] diff --git a/src/main/scala/supporters/functions/FunctionData.scala b/src/main/scala/supporters/functions/FunctionData.scala index e927e0958..5b8055c2d 100644 --- a/src/main/scala/supporters/functions/FunctionData.scala +++ b/src/main/scala/supporters/functions/FunctionData.scala @@ -149,6 +149,9 @@ class FunctionData(val programFunction: ast.Function, (Forall(arguments, triggerFunctionApplication, Trigger(limitedFunctionApplication)), Option.when(isAnalysisEnabled)((StringAnalysisSourceInfo("Trigger Axiom", programFunction.pos), AssumptionType.Trigger))) + lazy val postconditionType: AssumptionType = DependencyAnalyzer.extractDependencyTypeFromInfo(programFunction.info).map(_.assertionType) + .getOrElse(if(programFunction.body.isDefined) AssumptionType.ImplicitPostcondition else AssumptionType.ExplicitPostcondition) + /* * Data collected during phases 1 (well-definedness checking) and 2 (verification) */ @@ -237,11 +240,10 @@ class FunctionData(val programFunction: ast.Function, def wrapBody(body: Term): Term = Let(toMap(bodyBindings), body) if(isAnalysisEnabled){ - val assumptionType = if(programFunction.body.isDefined) AssumptionType.ImplicitPostcondition else AssumptionType.ExplicitPostcondition (Forall(arguments, wrapBody(And(generateNestedDefinitionalAxioms)), Trigger(limitedFunctionApplication)), Option.empty[(AnalysisSourceInfo, AssumptionType)]) +: programFunction.posts.flatMap(_.topLevelConjuncts).map({p => val terms = expressionTranslator.translatePostcondition(program, Seq(p), this) - (And(Forall(arguments, wrapBody(Implies(pre, And(terms))), Trigger(limitedFunctionApplication)), True), Some((AnalysisSourceInfo.createAnalysisSourceInfo(p), assumptionType))) + (And(Forall(arguments, wrapBody(Implies(pre, And(terms))), Trigger(limitedFunctionApplication)), True), Some((AnalysisSourceInfo.createAnalysisSourceInfo(p), postconditionType))) }) }else{ val innermostBody = And(generateNestedDefinitionalAxioms ++ List(Implies(pre, And(translatedPosts)))) @@ -334,11 +336,10 @@ class FunctionData(val programFunction: ast.Function, lazy val postPreconditionPropagationAxiom: Seq[(Term, Option[(AnalysisSourceInfo, AssumptionType)])] = { val pre = preconditionFunctionApplication val postPreconditions = if (programFunction.posts.nonEmpty) { - val assumptionType = if(programFunction.body.isDefined) AssumptionType.ImplicitPostcondition else AssumptionType.ExplicitPostcondition val bodyBindings: Map[Var, Term] = Map(formalResult -> limitedFunctionApplication) val bodies = translatedPosts.map(tPost => Let(bodyBindings, Implies(pre, FunctionPreconditionTransformer.transform(tPost, program)))) bodies.map(b => (Forall(arguments, b, Seq(Trigger(limitedFunctionApplication))), - Option.when(isAnalysisEnabled)((StringAnalysisSourceInfo("postPreconditionPropagationAxiom", programFunction.pos), assumptionType)))) + Option.when(isAnalysisEnabled)((StringAnalysisSourceInfo("postPreconditionPropagationAxiom", programFunction.pos), postconditionType)))) } else Seq() postPreconditions } From 4360244f0e237bbd3e7c53ffb05fdf3a805ac392 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 4 Mar 2026 08:54:13 +0100 Subject: [PATCH 369/474] fix Gobra method and function postcondition types --- .../dependencyAnalysis/DependencyGraphInterpreter.scala | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index e913131c5..87f9bd212 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -441,11 +441,14 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen } def computeUncoveredStatements(): Int = { + val allAssertions = toUserLevelNodes(getNonInternalAssertionNodes) + // TODO ake: once the infinite loop in the deps() function is removed, we should use this optimized version + val allDependencies = allAssertions.flatMap(ass => toUserLevelNodes(getAllNonInternalDependencies(ass.lowerLevelNodes.map(_.id))).diffBySource(Set(ass))).getSourceSet() + val allNodes = toUserLevelNodes(getNonInternalAssumptionNodes) val allSourceCodeStmts = allNodes.getSourceSet().diff(UserLevelDependencyAnalysisNode.extractByAssumptionType(allNodes, AssumptionType.explicitAssumptionTypes ++ AssumptionType.verificationAnnotationTypes ++ Set(AssumptionType.FunctionBody)).getSourceSet()) - val coveredSourceCodeStmts = toUserLevelNodes(getAllNonInternalDependencies(getNodesWithIdenticalSource(getNonInternalAssertionNodes).map(_.id))).getSourceSet() - allSourceCodeStmts.diff(coveredSourceCodeStmts).size + allSourceCodeStmts.diff(allDependencies).size } } From 06316d7cda7012ae9035bd9fcf08f3d88616adb5 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 5 Mar 2026 08:16:35 +0100 Subject: [PATCH 370/474] improve guidance by estimating impact of assumptions on Lea's progress metric --- .../DependencyAnalysisUserTool.scala | 40 ++++++++++++++----- .../DependencyGraphInterpreter.scala | 18 ++++++++- .../guidanceTest/p1-b-proved.vpr | 29 ++++++++++++++ .../guidanceTest/p1-precond-added.vpr | 30 ++++++++++++++ .../guidanceTest/p1-res-proved.vpr | 30 ++++++++++++++ .../verificationProgress/guidanceTest/p1.vpr | 30 ++++++++++++++ .../guidanceTest/p2-a-proved.vpr | 29 ++++++++++++++ .../guidanceTest/results_1772694472442.csv | 6 +++ .../scala/VerificationProgressRunner.scala | 1 + 9 files changed, 201 insertions(+), 12 deletions(-) create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgress/guidanceTest/p1-b-proved.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgress/guidanceTest/p1-precond-added.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgress/guidanceTest/p1-res-proved.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgress/guidanceTest/p1.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgress/guidanceTest/p2-a-proved.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgress/guidanceTest/results_1772694472442.csv diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala index 73c09b82d..e49673a62 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala @@ -70,6 +70,8 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete handleVerificationProgressNaiveQuery() }else if (inputParts.head.equalsIgnoreCase("guidance") || inputParts.head.equalsIgnoreCase("guide")) { handleVerificationGuidanceQuery() + }else if (inputParts.head.equalsIgnoreCase("guideOld")) { + handleVerificationGuidanceOldQuery() }else if(inputParts.head.equalsIgnoreCase("prune")) { handlePruningRequest(inputParts.tail) }else if(inputParts.head.equalsIgnoreCase("benchmark")) { @@ -208,21 +210,22 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete } private def handleDependencyQuery(inputs: Set[String]): Unit = { - val queriedNodes = getQueriedNodesFromInput(inputs).filter(node => node.isInstanceOf[GeneralAssertionNode]) + val queriedNodes = getQueriedNodesFromInput(inputs) + val queriedAssertions = queriedNodes.filter(node => node.isInstanceOf[GeneralAssertionNode]) - val (directDependencies, timeDirect) = measureTime[Set[DependencyAnalysisNode]](fullGraphInterpreter.getDirectDependencies(queriedNodes.map(_.id))) - val (allDependencies, timeAll) = measureTime[Set[DependencyAnalysisNode]](fullGraphInterpreter.getAllNonInternalDependencies(queriedNodes.map(_.id))) - val (allDependenciesWithoutInfeasibility, timeWithoutInfeasibility) = measureTime[Set[DependencyAnalysisNode]](fullGraphInterpreter.getAllNonInternalDependencies(queriedNodes.map(_.id), includeInfeasibilityNodes=false)) - val (explicitDependencies, timeExplicit) = measureTime[Set[DependencyAnalysisNode]](fullGraphInterpreter.getAllExplicitDependencies(queriedNodes.map(_.id))) + val (directDependencies, timeDirect) = measureTime[Set[DependencyAnalysisNode]](fullGraphInterpreter.getDirectDependencies(queriedAssertions.map(_.id))) + val (allDependencies, timeAll) = measureTime[Set[DependencyAnalysisNode]](fullGraphInterpreter.getAllNonInternalDependencies(queriedAssertions.map(_.id))) + val (allDependenciesWithoutInfeasibility, timeWithoutInfeasibility) = measureTime[Set[DependencyAnalysisNode]](fullGraphInterpreter.getAllNonInternalDependencies(queriedAssertions.map(_.id), includeInfeasibilityNodes=false)) + val (explicitDependencies, timeExplicit) = measureTime[Set[DependencyAnalysisNode]](fullGraphInterpreter.getAllExplicitDependencies(queriedAssertions.map(_.id))) println(s"Queried:\n\t${getSourceInfoString(queriedNodes)}") - println(s"\nDirect Dependencies (${timeDirect}ms):\n\t${getSourceInfoString(directDependencies)}") - println(s"\nAll Dependencies (${timeAll}ms):\n\t${getSourceInfoString(allDependencies)}") - println(s"\nDependencies without infeasibility (${timeWithoutInfeasibility}ms):\n\t${getSourceInfoString(allDependenciesWithoutInfeasibility)}") - println(s"\nExplicit Dependencies (${timeExplicit}ms):\n\t${getSourceInfoString(explicitDependencies)}") + println(s"\nDirect Dependencies (${timeDirect}ms):\n\t${getSourceInfoString(directDependencies.diff(queriedNodes))}") + println(s"\nAll Dependencies (${timeAll}ms):\n\t${getSourceInfoString(allDependencies.diff(queriedNodes))}") + println(s"\nDependencies without infeasibility (${timeWithoutInfeasibility}ms):\n\t${getSourceInfoString(allDependenciesWithoutInfeasibility.diff(queriedNodes))}") + println(s"\nExplicit Dependencies (${timeExplicit}ms):\n\t${getSourceInfoString(explicitDependencies.diff(queriedNodes))}") - if(queriedNodes.exists(_.asInstanceOf[GeneralAssertionNode].hasFailed)) println("\nQueried assertions (partially) FAILED!\n") + if(queriedAssertions.exists(_.asInstanceOf[GeneralAssertionNode].hasFailed)) println("\nQueried assertions (partially) FAILED!\n") println("Done.") } @@ -316,7 +319,22 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete return } - val assumptionRanking = fullGraphInterpreter.computeAssumptionRanking().filter(_._2 > 0) + val assumptionRanking = fullGraphInterpreter.computeAssumptionRanking().filter(_._2 > 0.0) + println(s"Assumptions and their impact on progress:\n\t${assumptionRanking.mkString("\n\t")}\n") + + val memberCoverageRanking = memberInterpreters.filter(mInterpreter => mInterpreter.getMember.isDefined && mInterpreter.getMember.get.isInstanceOf[Method]) + .map(mInterpreter => (mInterpreter.getMember.get.name, mInterpreter.computeUncoveredStatements())) + .toList.filter(_._2 > 0).sortBy(_._2).reverse + println(s"Methods and the number of uncovered statements:\n\t${memberCoverageRanking.mkString("\n\t")}\n") + } + + def handleVerificationGuidanceOldQuery(): Unit = { + if(verificationErrors.nonEmpty) { + println(s"Fix verification failures first!") + return + } + + val assumptionRanking = fullGraphInterpreter.computeAssumptionRankingOld().filter(_._2 > 0) println(s"Assumptions and the number of dependents:\n\t${assumptionRanking.mkString("\n\t")}\n") val memberCoverageRanking = memberInterpreters.filter(mInterpreter => mInterpreter.getMember.isDefined && mInterpreter.getMember.get.isInstanceOf[Method]) diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 87f9bd212..dbb8ceef1 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -435,7 +435,23 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen } /* returns an ordered list of (Assumption, #dependents) */ - def computeAssumptionRanking(): List[(String, Int)] = { + def computeAssumptionRanking(): List[(String, Double)] = { + val allAssertions = toUserLevelNodes(getNonInternalAssertionNodes) + + val relevantDependenciesPerAssertion = allAssertions + .map(ass => (ass, toUserLevelNodes(getAllNonInternalDependencies(ass.lowerLevelNodes.map(_.id))).diffBySource(Set(ass)))).toMap + .filter{case (_, assumptions) => assumptions.nonEmpty} + val numAssertions = relevantDependenciesPerAssertion.size.toDouble + + val assumptionImpacts= relevantDependenciesPerAssertion.toList.flatMap { case (assertion, assumptions) => + val explicitDeps = UserLevelDependencyAnalysisNode.extractExplicitAssumptionNodes(assumptions) + explicitDeps.map(node => (node, 1.0/assumptions.size/numAssertions)).toList + }.groupBy(_._1).map{case (assumption, impacts) => (assumption.source.getTopLevelSource.toString, impacts.map(_._2).sum)}.toList + + assumptionImpacts.sortBy(_._2).reverse + } + + def computeAssumptionRankingOld(): List[(String, Int)] = { toUserLevelNodes(getExplicitAssumptionNodes).map(node => (node.toString, toUserLevelNodes(getAllNonInternalDependents(node.lowerLevelNodes.map(_.id))).diff(Set(node)).size)) .toList.sortBy(_._2).reverse } diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgress/guidanceTest/p1-b-proved.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgress/guidanceTest/p1-b-proved.vpr new file mode 100644 index 000000000..8006f9389 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgress/guidanceTest/p1-b-proved.vpr @@ -0,0 +1,29 @@ + +method gaussianSimple(n: Int) returns (res: Int) + requires 0 <= n + ensures res == n * (n + 1) / 2 +{ + inhale res == 0 + inhale n < 6 + var i: Int := 0 + while(i <= n) + invariant i <= (n + 1) + invariant i <= 6 + invariant res == (i - 1) * i / 2 + { + res := res + i + i := i + 1 + } +} + + +method client() +{ + var a: Int + var b: Int := 0 + + assume a == 0 + + assert b >= 0 + assert a + b >= 0 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgress/guidanceTest/p1-precond-added.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgress/guidanceTest/p1-precond-added.vpr new file mode 100644 index 000000000..14157ac1b --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgress/guidanceTest/p1-precond-added.vpr @@ -0,0 +1,30 @@ + +method gaussianSimple(n: Int) returns (res: Int) + requires 0 <= n + requires n < 6 + ensures res == n * (n + 1) / 2 +{ + inhale res == 0 + var i: Int := 0 + while(i <= n) + invariant i <= (n + 1) + invariant i <= 6 + invariant res == (i - 1) * i / 2 + { + res := res + i + i := i + 1 + } +} + + +method client() +{ + var a: Int + var b: Int + + assume a == 0 + assume b == 0 + + assert b >= 0 + assert a + b >= 0 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgress/guidanceTest/p1-res-proved.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgress/guidanceTest/p1-res-proved.vpr new file mode 100644 index 000000000..489eaff0e --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgress/guidanceTest/p1-res-proved.vpr @@ -0,0 +1,30 @@ + +method gaussianSimple(n: Int) returns (res: Int) + requires 0 <= n + ensures res == n * (n + 1) / 2 +{ + res := 0 + inhale n < 6 + var i: Int := 0 + while(i <= n) + invariant i <= (n + 1) + invariant i <= 6 + invariant res == (i - 1) * i / 2 + { + res := res + i + i := i + 1 + } +} + + +method client() +{ + var a: Int + var b: Int + + assume a == 0 + assume b == 0 + + assert b >= 0 + assert a + b >= 0 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgress/guidanceTest/p1.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgress/guidanceTest/p1.vpr new file mode 100644 index 000000000..1560701ce --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgress/guidanceTest/p1.vpr @@ -0,0 +1,30 @@ + +method gaussianSimple(n: Int) returns (res: Int) + requires 0 <= n + ensures res == n * (n + 1) / 2 +{ + inhale res == 0 + inhale n < 6 + var i: Int := 0 + while(i <= n) + invariant i <= (n + 1) + invariant i <= 6 + invariant res == (i - 1) * i / 2 + { + res := res + i + i := i + 1 + } +} + + +method client() +{ + var a: Int + var b: Int + + assume a == 0 + assume b == 0 + + assert b >= 0 + assert a + b >= 0 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgress/guidanceTest/p2-a-proved.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgress/guidanceTest/p2-a-proved.vpr new file mode 100644 index 000000000..8c5eb49f8 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgress/guidanceTest/p2-a-proved.vpr @@ -0,0 +1,29 @@ + +method gaussianSimple(n: Int) returns (res: Int) + requires 0 <= n + ensures res == n * (n + 1) / 2 +{ + inhale res == 0 + inhale n < 6 + var i: Int := 0 + while(i <= n) + invariant i <= (n + 1) + invariant i <= 6 + invariant res == (i - 1) * i / 2 + { + res := res + i + i := i + 1 + } +} + + +method client() +{ + var a: Int := 0 + var b: Int + + assume b == 0 + + assert b >= 0 + assert a + b >= 0 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgress/guidanceTest/results_1772694472442.csv b/src/test/resources/dependencyAnalysisTests/verificationProgress/guidanceTest/results_1772694472442.csv new file mode 100644 index 000000000..ddb08458d --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgress/guidanceTest/results_1772694472442.csv @@ -0,0 +1,6 @@ +File name Progress (Peter) Progress (Lea) verification successful? +p1-b-proved 0.333 0.815 true +p1-precond-added 0.333 0.606 true +p1-res-proved 0.500 0.625 true +p1 0.167 0.565 true +p2-a-proved 0.167 0.648 true diff --git a/src/test/scala/VerificationProgressRunner.scala b/src/test/scala/VerificationProgressRunner.scala index f483c99b1..3b8c297f9 100644 --- a/src/test/scala/VerificationProgressRunner.scala +++ b/src/test/scala/VerificationProgressRunner.scala @@ -17,6 +17,7 @@ object VerificationProgressRunner extends DependencyAnalysisTestFramework { val pathToTests: String = "src/test/resources/" val testDirectories: Seq[String] = Seq( "dependencyAnalysisTests/verificationProgress/incrRand", + "dependencyAnalysisTests/verificationProgress/guidanceTest", "dependencyAnalysisTests/verificationProgress/perms" ) From 0ccddd152b0d4de0c0dea9ab8b31b440b8056952 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 5 Mar 2026 12:23:35 +0100 Subject: [PATCH 371/474] fix div by 0 in progress computation --- .../dependencyAnalysis/DependencyGraphInterpreter.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index dbb8ceef1..53c19888a 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -238,6 +238,8 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen val nonSourceCodeAssumptionTypes = AssumptionType.explicitAssumptionTypes ++ AssumptionType.verificationAnnotationTypes val allSourceCodeNodes = toCompactUserLevelNodes(getNonInternalAssumptionNodes).filter(n => nonSourceCodeAssumptionTypes.intersect(n.assumptionTypes).isEmpty).map(_.source.getTopLevelSource) + if(allSourceCodeNodes.isEmpty) return 1.0 + val coveredSourceCodeNodes = coveredNodes.map(_.source.getTopLevelSource).intersect(allSourceCodeNodes) // println(s"Covered Source Code:\n\t${coveredSourceCodeNodes.toList.sortBy(_.getLineNumber).mkString("\n\t")}") // println(s"Uncovered Source Code:\n\t${allSourceCodeNodes.diff(coveredSourceCodeNodes).toList.sortBy(_.getLineNumber).mkString("\n\t")}") @@ -332,10 +334,10 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen val fullyVerifiedAssertions = assertionQualities.filter(_._1 == 1.0) val numFullyVerifiedAssertions = fullyVerifiedAssertions.size - val proofQualityPeter = numFullyVerifiedAssertions.toDouble / numAssertions.toDouble + val proofQualityPeter = if(numAssertions > 0) numFullyVerifiedAssertions.toDouble / numAssertions.toDouble else 1.0 val assertionQualitiesSum = assertionQualities.map(_._1).sum - val proofQualityLea = assertionQualitiesSum / numAssertions.toDouble + val proofQualityLea = if(numAssertions > 0) assertionQualitiesSum / numAssertions.toDouble else 1.0 val info = { // s"Assertions with dependencies on explicit assumptions:\n\t\t${assertionQualities.filterNot(_._1 == 1.0).sortBy(_._2.getLineNumber).mkString("\n\t\t")}" + "\n\n" + From 485a2cebcdcf0d952581f6dea9d72542d5da6076 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 6 Mar 2026 15:55:43 +0100 Subject: [PATCH 372/474] fix function axiom annotations --- src/main/scala/rules/Evaluator.scala | 2 +- src/main/scala/supporters/functions/FunctionData.scala | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index fb5ca58d2..2d9dedf8d 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -10,7 +10,7 @@ import viper.silicon import viper.silicon.Config.JoinMode import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp -import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyAnalysisJoinNodeInfo, DependencyAnalyzer, DependencyType} +import viper.silicon.dependencyAnalysis.{AssumptionType, DependencyAnalyzer, DependencyType} import viper.silicon.interfaces._ import viper.silicon.interfaces.state.ChunkIdentifer import viper.silicon.logger.records.data.{CondExpRecord, EvaluateRecord, ImpliesRecord} diff --git a/src/main/scala/supporters/functions/FunctionData.scala b/src/main/scala/supporters/functions/FunctionData.scala index 5b8055c2d..5f6792008 100644 --- a/src/main/scala/supporters/functions/FunctionData.scala +++ b/src/main/scala/supporters/functions/FunctionData.scala @@ -152,6 +152,9 @@ class FunctionData(val programFunction: ast.Function, lazy val postconditionType: AssumptionType = DependencyAnalyzer.extractDependencyTypeFromInfo(programFunction.info).map(_.assertionType) .getOrElse(if(programFunction.body.isDefined) AssumptionType.ImplicitPostcondition else AssumptionType.ExplicitPostcondition) + lazy val functionBodyType: AssumptionType = DependencyAnalyzer.extractDependencyTypeFromInfo(programFunction.info).map(_.assumptionType) + .getOrElse(AssumptionType.FunctionBody) + /* * Data collected during phases 1 (well-definedness checking) and 2 (verification) */ @@ -319,7 +322,7 @@ class FunctionData(val programFunction: ast.Function, val allTriggers = ( Seq(Trigger(functionApplication)) ++ actualPredicateTriggers) - (Forall(arguments, body, allTriggers), Option.when(isAnalysisEnabled)((AnalysisSourceInfo.createAnalysisSourceInfo(programFunction.body.get), AssumptionType.FunctionBody))) + (Forall(arguments, body, allTriggers), Option.when(isAnalysisEnabled)((AnalysisSourceInfo.createAnalysisSourceInfo(programFunction.body.get), functionBodyType))) }) } @@ -328,7 +331,7 @@ class FunctionData(val programFunction: ast.Function, val bodyPreconditions = if (programFunction.body.isDefined) optBody.map(translatedBody => { val body = Implies(pre, FunctionPreconditionTransformer.transform(translatedBody, program)) (Forall(arguments, body, Seq(Trigger(functionApplication))), - Option.when(isAnalysisEnabled)((ExpAnalysisSourceInfo(programFunction.body.get, programFunction.body.get.pos), AssumptionType.FunctionBody))) + Option.when(isAnalysisEnabled)((ExpAnalysisSourceInfo(programFunction.body.get, programFunction.body.get.pos), functionBodyType))) }) else None bodyPreconditions.toSeq } From 1af15bc1061f716f9d1a7454536ee9bc73f302c3 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 6 Mar 2026 17:27:52 +0100 Subject: [PATCH 373/474] add getMaxPriorityType --- src/main/scala/dependencyAnalysis/AnalysisInfo.scala | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala index 339acd507..5112a570d 100644 --- a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala +++ b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala @@ -14,6 +14,12 @@ object AssumptionType extends Enumeration { def joinConditionTypes: Set[AssumptionType] = postconditionTypes ++ Set(FunctionBody) def verificationAnnotationTypes: Set[AssumptionType] = Set(FunctionBody, LoopInvariant, Rewrite, ExplicitPostcondition, ImplicitPostcondition, Precondition, Explicit, DomainAxiom, Ghost) def sourceCodeTypes: Set[AssumptionType] = AssumptionType.values.diff(explicitAssumptionTypes ++ explicitAssertionTypes ++ verificationAnnotationTypes ++ internalTypes) + + def getMaxPriorityAssumptionType(types: Set[AssumptionType]): Option[AssumptionType] = { + val priorityList = List(ExplicitPostcondition, Explicit) ++ internalTypes.toList ++ verificationAnnotationTypes.toList ++ sourceCodeTypes.toList ++ values.toList + priorityList.find(t => types.contains(t)) + } + } import viper.silicon.dependencyAnalysis.AssumptionType._ @@ -54,6 +60,12 @@ object DependencyType { def get(exp: ast.Exp, dependencyType: DependencyType): DependencyType = DependencyAnalyzer.extractDependencyTypeFromInfo(exp.info).getOrElse(dependencyType) + def getMaxPriorityType(types: Set[DependencyType]): Option[DependencyType] = { + val assumptionType = AssumptionType.getMaxPriorityAssumptionType(types.map(_.assumptionType)) + val assertionType = AssumptionType.getMaxPriorityAssumptionType(types.map(_.assertionType)) + if(assumptionType.isDefined && assertionType.isDefined) Some(DependencyType(assumptionType.get, assertionType.get)) else None + } + } case class DependencyType(assumptionType: AssumptionType, assertionType: AssumptionType) From addf001ed813b1e05bce08d5e3cd77e69cd00c6e Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 7 Mar 2026 09:56:31 +0100 Subject: [PATCH 374/474] fix dependency type annotations for Gobra --- .../dependencyAnalysis/DependencyAnalyzer.scala | 8 ++++---- src/main/scala/supporters/MethodSupporter.scala | 2 +- .../functions/FunctionVerificationUnit.scala | 14 ++++++++------ 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index 485142051..a1c06dfcb 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -50,7 +50,7 @@ trait DependencyAnalyzer { * Adds dependencies between all pairs of sourceExps and targetExps, where sourceExps should be preconditions and * targetExps should be postconditions of an abstract function or method. */ - def addDependenciesForExplicitPostconditions(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp]): Unit + def addDependenciesForAbstractMembers(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp], postConditionType: AssumptionType = AssumptionType.ExplicitPostcondition): Unit /** * Adds edges connecting nodes representing function postconditions with the corresponding axiom nodes. @@ -398,9 +398,9 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { dependencyGraph.addEdges(sourceNodes map (_.id), targetNodes map (_.id)) } - override def addDependenciesForExplicitPostconditions(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp]): Unit = { + override def addDependenciesForAbstractMembers(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp], postConditionType: AssumptionType = AssumptionType.ExplicitPostcondition): Unit = { val sourceNodeIds = sourceExps.flatMap(e => addAssumption(True, AnalysisSourceInfo.createAnalysisSourceInfo(e), AssumptionType.Precondition, isJoinNode=false, None)) - val targetNodes = targetExps.flatMap(e => addAssertNode(True, AssumptionType.ExplicitPostcondition, AnalysisSourceInfo.createAnalysisSourceInfo(e))) + val targetNodes = targetExps.flatMap(e => addAssertNode(True, postConditionType, AnalysisSourceInfo.createAnalysisSourceInfo(e))) dependencyGraph.addEdges(sourceNodeIds, targetNodes) } @@ -517,7 +517,7 @@ class NoDependencyAnalyzer extends DependencyAnalyzer { override def addDependency(source: Option[Int], dest: Option[Int]): Unit = {} override def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit = {} override def addCustomTransitiveDependency(sourceSourceInfo: AnalysisSourceInfo, targetSourceInfo: AnalysisSourceInfo): Unit = {} - override def addDependenciesForExplicitPostconditions(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp]): Unit = {} + override def addDependenciesForAbstractMembers(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp], postConditionType: AssumptionType = AssumptionType.ExplicitPostcondition): Unit = {} override def addFunctionAxiomEdges(): Unit = {} override def buildFinalGraph(): Option[DependencyGraph] = None diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index 79a7c86d1..07a7e22e5 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -114,7 +114,7 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif Success())})}) } )})}) if(method.body.isEmpty){ - v.decider.dependencyAnalyzer.addDependenciesForExplicitPostconditions(method.pres.flatMap(_.topLevelConjuncts), method.posts.flatMap(_.topLevelConjuncts)) + v.decider.dependencyAnalyzer.addDependenciesForAbstractMembers(method.pres.flatMap(_.topLevelConjuncts), method.posts.flatMap(_.topLevelConjuncts), postConditionType) } val allErrors = (result :: result.previous.toList).filter(_.isInstanceOf[Failure]).map(_.asInstanceOf[Failure]) diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index b4317759c..6ec8b8921 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -207,7 +207,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver if (function.body.isEmpty) { decider.dependencyAnalyzer.addNodes(v.decider.prover.getPreambleAnalysisNodes) - decider.dependencyAnalyzer.addDependenciesForExplicitPostconditions(function.pres.flatMap(_.topLevelConjuncts), function.posts.flatMap(_.topLevelConjuncts)) + decider.dependencyAnalyzer.addDependenciesForAbstractMembers(function.pres.flatMap(_.topLevelConjuncts), function.posts.flatMap(_.topLevelConjuncts), getPostconditionType(function)) result1 } else { /* Phase 2: Verify the function's postcondition */ @@ -245,9 +245,6 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver var phase1Data: Seq[Phase1Data] = Vector.empty var recorders: Seq[FunctionRecorder] = Vector.empty - val postconditionType = DependencyAnalyzer.extractAssumptionTypeFromInfo(function.info) - .getOrElse(if(function.body.isDefined) ImplicitPostcondition else ExplicitPostcondition) - val result = executionFlowController.locally(s, v)((s0, _) => { val preMark = decider.setPathConditionMark() produces(s0, toSf(`?s`), pres, ContractNotWellformed, v, AssumptionType.Precondition)((s1, _) => { @@ -257,7 +254,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver // The postcondition must be produced with a fresh snapshot (different from `?s`) because // the postcondition's snapshot structure is most likely different than that of the // precondition - produces(s1, freshSnap, posts, ContractNotWellformed, v, postconditionType)((s2, _) => { + produces(s1, freshSnap, posts, ContractNotWellformed, v, getPostconditionType(function))((s2, _) => { recorders :+= s2.functionRecorder Success()})})}) @@ -266,6 +263,11 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver (result, phase1Data) } + private def getPostconditionType(function: ast.Function) = { + DependencyAnalyzer.extractAssumptionTypeFromInfo(function.info) + .getOrElse(if (function.body.isDefined) ImplicitPostcondition else ExplicitPostcondition) + } + private def verify(function: ast.Function, phase1data: Seq[Phase1Data]) : VerificationResult = { @@ -281,7 +283,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver var recorders: Seq[FunctionRecorder] = Vector.empty val wExp = evaluator.withExp val annotatedAssumptionTypeOpt = DependencyAnalyzer.extractAssumptionTypeFromInfo(function.info) - var postConditionType = annotatedAssumptionTypeOpt.getOrElse(if(function.body.isDefined) AssumptionType.ImplicitPostcondition else AssumptionType.ExplicitPostcondition) + var postConditionType = getPostconditionType(function) decider.dependencyAnalyzer.addNodes(v.decider.prover.getPreambleAnalysisNodes) val daJoinNodeInfoOpt = function.info.getUniqueInfo[DependencyAnalysisJoinNodeInfo] From 29457bacff218ec54ec855a6828690975812df50 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 7 Mar 2026 10:43:46 +0100 Subject: [PATCH 375/474] fix dependency type annotations for Gobra --- src/main/scala/dependencyAnalysis/AnalysisInfo.scala | 11 +++++++++++ src/main/scala/supporters/MethodSupporter.scala | 4 ++-- .../functions/FunctionVerificationUnit.scala | 3 +-- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala index 5112a570d..4c15349a8 100644 --- a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala +++ b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala @@ -20,6 +20,17 @@ object AssumptionType extends Enumeration { priorityList.find(t => types.contains(t)) } + def getPostcondType(isAbstractFunction: Boolean, dependencyType: Option[DependencyType]=None): AssumptionType = { + dependencyType.flatMap(_.assertionType match { + case AssumptionType.Explicit | AssumptionType.ExplicitPostcondition => Some(AssumptionType.ExplicitPostcondition) + case AssumptionType.Ghost | AssumptionType.ImplicitPostcondition => Some(AssumptionType.ImplicitPostcondition) + case AssumptionType.Internal => Some(AssumptionType.Internal) + case AssumptionType.CustomInternal => Some(AssumptionType.CustomInternal) + case _ => None + }).getOrElse( + if(isAbstractFunction) AssumptionType.ExplicitPostcondition else AssumptionType.ImplicitPostcondition + ) + } } import viper.silicon.dependencyAnalysis.AssumptionType._ diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index 07a7e22e5..8166a6fb8 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -77,8 +77,8 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif new java.io.File(s"${Verifier.config.tempDirectory()}/${method.name}.dot")) } - val annotatedAssumptionTypeOpt = DependencyAnalyzer.extractDependencyTypeFromInfo(method.info).map(_.assertionType) - var postConditionType = annotatedAssumptionTypeOpt.getOrElse(if(method.body.isDefined) AssumptionType.ImplicitPostcondition else AssumptionType.ExplicitPostcondition) + val annotatedAssumptionTypeOpt = DependencyAnalyzer.extractDependencyTypeFromInfo(method.info).map(_.assumptionType) + var postConditionType = AssumptionType.getPostcondType(method.body.isEmpty, DependencyAnalyzer.extractDependencyTypeFromInfo(method.info)) val daJoinNodeInfoOpt = method.info.getUniqueInfo[DependencyAnalysisJoinNodeInfo] if(daJoinNodeInfoOpt.isDefined){ diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index 6ec8b8921..ad3a6d821 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -264,8 +264,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver } private def getPostconditionType(function: ast.Function) = { - DependencyAnalyzer.extractAssumptionTypeFromInfo(function.info) - .getOrElse(if (function.body.isDefined) ImplicitPostcondition else ExplicitPostcondition) + AssumptionType.getPostcondType(function.body.isEmpty, DependencyAnalyzer.extractDependencyTypeFromInfo(function.info)) } private def verify(function: ast.Function, phase1data: Seq[Phase1Data]) From 88532c148663aeb9061e3222d4e3561b6315af77 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sat, 7 Mar 2026 12:45:48 +0100 Subject: [PATCH 376/474] fix infinite loop in progress computation --- .../DependencyAnalysisUserTool.scala | 3 +- .../DependencyGraphInterpreter.scala | 62 +++++++++++-------- 2 files changed, 38 insertions(+), 27 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala index e49673a62..f8eeed492 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala @@ -179,7 +179,8 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete println(s"$optInfo") println(s"Peter: $optProgressPeter; Lea: $optProgressLea") println(s"Finished in ${optTime}ms") - if(Math.abs(naiveProgressPeter - optProgressPeter) > 0.001 || Math.abs(naiveProgressLea - optProgressLea) > 0.001) println("Progress is not equal!") + if(Math.abs(naiveProgressPeter - optProgressPeter) > 0.001 || Math.abs(naiveProgressLea - optProgressLea) > 0.001) println("Fail: Progress is not equal!") + else println("Success: Progress is equal!") } private def handleVerificationProgressNaiveQuery(): Unit = { diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 53c19888a..f991ccafc 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -255,31 +255,33 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen private lazy val sourceToAssertionNodes: Map[AnalysisSourceInfo, Set[DependencyAnalysisNode]] = getNonInternalAssertionNodes.groupBy(_.sourceInfo.getTopLevelSource) val deps: DAMemo[AnalysisSourceInfo, Set[CompactUserLevelDependencyAnalysisNode]] = DAMemo { assertionNode => -// val startFilteringNodes0 = System.nanoTime() - val allNonInternalAssertions = sourceToAssertionNodes.getOrElse(assertionNode, Set.empty) -// filteringNodesRuntime = filteringNodesRuntime + (System.nanoTime() - startFilteringNodes0) -// val startPerMethodDeps = System.nanoTime() - val intraMethodDependencyIds = dependencyGraph.getAllDependencies(allNonInternalAssertions.map(_.id), includeInfeasibilityNodes=true, includeIntraMethodEdges=false) -// perMethodDependencyRuntime = perMethodDependencyRuntime + (System.nanoTime() - startPerMethodDeps) - -// val startFilteringNodes1 = System.nanoTime() - val intraMethodDependencies = intraMethodDependencyIds.flatMap(nonInternalAssumptionNodesMap.get).filter(!_.sourceInfo.getTopLevelSource.equals(assertionNode)) -// filteringNodesRuntime = filteringNodesRuntime + (System.nanoTime() - startFilteringNodes1) - - val startDepsToPostcond = System.nanoTime() - val postconditionNodeIds = intraMethodDependencyIds.flatMap(n => dependencyGraph.getEdgesConnectingMethods.getOrElse(n, Set.empty)) -// depsToPostcondRuntime = depsToPostcondRuntime + (System.nanoTime() - startDepsToPostcond) - val startFilteringNodes2 = System.nanoTime() - val postconditionNodes = postconditionNodeIds flatMap nodesMap.get -// filteringNodesRuntime = filteringNodesRuntime + (System.nanoTime() - startFilteringNodes2) - - - val transDeps = postconditionNodes.map(_.sourceInfo.getTopLevelSource).diff(Set(assertionNode)) flatMap deps - -// val startAggregation = System.nanoTime() - val res = reduceCompactUserLevelNodes(toCompactUserLevelNodes(intraMethodDependencies ++ postconditionNodes) ++ transDeps) -// aggregationOfSummaryNodesRuntime = aggregationOfSummaryNodesRuntime + (System.nanoTime() - startAggregation) - res + def computeDependencies(currentNode: AnalysisSourceInfo, visited: Set[AnalysisSourceInfo]): Set[CompactUserLevelDependencyAnalysisNode] = { + if (visited.contains(currentNode)) { + return Set.empty // break cycles to avoid infinite loops + } + + if (deps.contains(currentNode)) { + return deps(currentNode) + } + + val updatedVisited = visited + currentNode + val allNonInternalAssertions = sourceToAssertionNodes.getOrElse(currentNode, Set.empty) + + val intraMethodDependencyIds = dependencyGraph.getAllDependencies(allNonInternalAssertions.map(_.id), includeInfeasibilityNodes=true, includeIntraMethodEdges=false) + val intraMethodDependencies = intraMethodDependencyIds.flatMap(nonInternalAssumptionNodesMap.get).filterNot(_.sourceInfo.getTopLevelSource.equals(currentNode)) + + val postconditionNodeIds = intraMethodDependencyIds.flatMap(n => dependencyGraph.getEdgesConnectingMethods.getOrElse(n, Set.empty)) + val postconditionNodes = postconditionNodeIds.flatMap(nodesMap.get) + + val transDeps = postconditionNodes.map(_.sourceInfo.getTopLevelSource).filterNot(_.equals(currentNode)).flatMap(node => computeDependencies(node, updatedVisited)) + + val result = reduceCompactUserLevelNodes(toCompactUserLevelNodes(intraMethodDependencies ++ postconditionNodes) ++ transDeps) + + deps.put(currentNode, result) + result + } + + computeDependencies(assertionNode, Set.empty) } private def reduceCompactUserLevelNodes(inputNodes: Set[CompactUserLevelDependencyAnalysisNode]): Set[CompactUserLevelDependencyAnalysisNode] = { @@ -472,7 +474,15 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen case class DAMemo[A,B](f: A => B) extends (A => B) { private val cache = mutable.Map.empty[A, B] - def apply(x: A) = cache getOrElseUpdate (x, f(x)) + def apply(x: A): B = cache getOrElseUpdate (x, f(x)) + + def put(a: A, b: B): Option[B] = { + cache.put(a, b) + } + + def contains(a: A): Boolean = { + cache.contains(a) + } } From ac929ffc57c317d23ee911266f4f847a35889b8d Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sun, 8 Mar 2026 10:11:52 +0100 Subject: [PATCH 377/474] optimize graph joins --- .../DependencyAnalysisResult.scala | 2 +- .../DependencyAnalyzer.scala | 21 ++++++++++--------- .../DependencyGraphInterpreter.scala | 6 ++++-- .../scala/verifier/DefaultMainVerifier.scala | 2 +- 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisResult.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisResult.scala index d25ec3870..8ea71f7b6 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisResult.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisResult.scala @@ -3,7 +3,7 @@ package viper.silicon.dependencyAnalysis import viper.silver.ast.Program import viper.silver.dependencyAnalysis.AbstractDependencyAnalysisResult -case class DependencyAnalysisResult(programName: String, program: Program, dependencyGraphInterpreters: Iterable[DependencyGraphInterpreter]) +case class DependencyAnalysisResult(programName: String, program: Program, dependencyGraphInterpreters: Set[DependencyGraphInterpreter]) extends AbstractDependencyAnalysisResult(programName, program, dependencyGraphInterpreters){ protected lazy val fullDependencyGraphInterpreter: DependencyGraphInterpreter = diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index a1c06dfcb..23f92beb3 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -178,7 +178,7 @@ object DependencyAnalyzer { * The new graph is built by adding all existing nodes and edges of all input graphs and joining them via postconditions * of functions and methods. */ - def joinGraphsAndGetInterpreter(name: String, dependencyGraphInterpreters: Iterable[DependencyGraphInterpreter]): DependencyGraphInterpreter = { + def joinGraphsAndGetInterpreter(name: String, dependencyGraphInterpreters: Set[DependencyGraphInterpreter]): DependencyGraphInterpreter = { var startTime = startTimeMeasurement() val newGraph = new DependencyGraph @@ -189,19 +189,20 @@ object DependencyAnalyzer { dependencyGraphInterpreters foreach (interpreter => interpreter.getGraph.getAllEdges foreach {case (t, deps) => newGraph.addEdges(deps, t)}) stopTimeMeasurementAndAddToTotal(startTime, timeToAddEdges) startTime = startTimeMeasurement() - val joinCandidateNodes = dependencyGraphInterpreters flatMap(_.getJoinCandidateNodes) + val joinCandidateAssertions = dependencyGraphInterpreters flatMap(i => i.joinAssertionNodes) + val joinCandidateAssumptions = dependencyGraphInterpreters flatMap(i => i.joinAssumptionNodes) + val joinCandidateAxioms = dependencyGraphInterpreters flatMap(i => i.axiomNodes) + val joinCandidateNodes = joinCandidateAssumptions ++ joinCandidateAssertions stopTimeMeasurementAndAddToTotal(startTime, timeToExtractCandidateNodes) startTime = startTimeMeasurement() // axioms assumed by every method / function should depend on the assertions that justify them // hence, we add edges from function postconditions & bodies to the corresponding axioms - val axiomAssertionNodes = joinCandidateNodes - .filter(n => (n.isInstanceOf[GeneralAssertionNode] && AssumptionType.postconditionTypes.contains(n.assumptionType)) - || AssumptionType.FunctionBody.equals(n.assumptionType)) + val axiomAssertionNodes = (joinCandidateAssertions ++ joinCandidateAssumptions.filter(_.assumptionType.equals(AssumptionType.FunctionBody))) .groupBy(_.sourceInfo.getTopLevelSource) .view.mapValues(_.map(_.id)) .toMap - joinCandidateNodes.filter(_.isInstanceOf[AxiomAssumptionNode]) + joinCandidateAxioms .groupBy(n => n.sourceInfo) .map{case (sourceInfo, axiomNodes) => (axiomNodes.map(_.id), axiomAssertionNodes.getOrElse(sourceInfo.getTopLevelSource, Seq.empty))} .foreach{case (axiomNodeIds, assertionNodeIds) => @@ -211,15 +212,15 @@ object DependencyAnalyzer { stopTimeMeasurementAndAddToTotal(startTime, timeForFunctionJoin) startTime = startTimeMeasurement() - val customInternalNodes = joinCandidateNodes.filter(_.assumptionType.equals(CustomInternal)).map(_.id).toSet + val customInternalNodes = joinCandidateAssumptions.filter(_.assumptionType.equals(CustomInternal)).map(_.id) // postconditions of methods assumed by every method call should depend on the assertions that justify them // hence, we add edges from assertions of method postconditions to assumptions of the same postcondition (at method calls) - val relevantAssumptionNodes = joinCandidateNodes - .filter(node => node.isInstanceOf[GeneralAssumptionNode] && node.asInstanceOf[GeneralAssumptionNode].isJoinNode) + val relevantAssumptionNodes = joinCandidateAssumptions + .filter(_.isJoinNode) .groupBy(_.sourceInfo.getFineGrainedSource) .view.mapValues(_.map(_.id)) .toMap - joinCandidateNodes.filter(node => AssumptionType.postconditionTypes.contains(node.assumptionType) || node.isJoinNode) + joinCandidateNodes.diff(joinCandidateAxioms.toSet) .map(node => (node.id, relevantAssumptionNodes.getOrElse(node.sourceInfo.getTopLevelSource, Seq.empty))) .foreach { case (src, targets) => if (customInternalNodes.intersect(targets.toSet.union(Set(src))).isEmpty) newGraph.addEdgesConnectingMethods(src, targets) diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index f991ccafc..329e58ab1 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -29,9 +29,11 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen def getAssertionNodes: Set[DependencyAnalysisNode] = dependencyGraph.getAssertionNodes.toSet def getErrors: List[Failure] = errors - def getJoinCandidateNodes: Iterable[DependencyAnalysisNode] = joinCandidateNodes + val joinAssumptionNodes: Set[GeneralAssumptionNode] = getJoinCandidateNodes(dependencyGraph.getAssumptionNodes.toSet) + val joinAssertionNodes: Set[GeneralAssertionNode] = getJoinCandidateNodes(dependencyGraph.getAssertionNodes.toSet) + val axiomNodes: Set[GeneralAssumptionNode] = dependencyGraph.getAssumptionNodes.filter(_.isInstanceOf[AxiomAssumptionNode]).toSet - protected lazy val joinCandidateNodes: Seq[DependencyAnalysisNode] = dependencyGraph.getNodes.filter(node => node.isJoinNode || node.isInstanceOf[AxiomAssumptionNode] || AssumptionType.joinConditionTypes.contains(node.assumptionType)) + def getJoinCandidateNodes[T <: DependencyAnalysisNode](nodes: Set[T]): Set[T] = nodes.filter(node => node.isJoinNode || node.isInstanceOf[AxiomAssumptionNode] || AssumptionType.joinConditionTypes.contains(node.assumptionType)) private def toUserLevelNodes(nodes: Iterable[DependencyAnalysisNode]): Set[UserLevelDependencyAnalysisNode] = UserLevelDependencyAnalysisNode.from(nodes) diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index ccc32ef93..5c2d6bd9b 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -666,7 +666,7 @@ class DefaultMainVerifier(config: Config, val verificationErrors: List[Failure] = (verificationResults filter (_.isInstanceOf[Failure])) map (_.asInstanceOf[Failure]) // TODO ake: make sure we can access the name of frontend programs (instead of naming it "joined") - val result = DependencyAnalysisResult(inputFile.map(_.replaceAll("\\\\", "_").replaceAll("/", "_").replaceAll(".vpr", "")).getOrElse("joined"), program, dependencyGraphInterpreters) + val result = DependencyAnalysisResult(inputFile.map(_.replaceAll("\\\\", "_").replaceAll("/", "_").replaceAll(".vpr", "")).getOrElse("joined"), program, dependencyGraphInterpreters.toSet) dependencyAnalysisResult = Some(result) if (Verifier.config.dependencyAnalysisExportPath.isDefined) { From ec340c80e6ce87ccc8780ed31d8ceabfbc649040 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 9 Mar 2026 10:01:20 +0100 Subject: [PATCH 378/474] handle impure assertion failures --- src/main/scala/decider/Decider.scala | 12 +++++ .../DependencyAnalyzer.scala | 9 ++++ src/main/scala/rules/ChunkSupporter.scala | 17 ++++++- src/main/scala/rules/Evaluator.scala | 20 ++++++-- src/main/scala/rules/Executor.scala | 9 ++-- src/main/scala/rules/HavocSupporter.scala | 5 +- src/main/scala/rules/HeapSupporter.scala | 14 ++++-- src/main/scala/rules/MagicWandSupporter.scala | 4 +- .../rules/MoreCompleteExhaleSupporter.scala | 49 ++++++++++++++----- .../scala/rules/PermissionSupporter.scala | 9 +++- .../scala/rules/QuantifiedChunkSupport.scala | 47 +++++++++++++----- 11 files changed, 157 insertions(+), 38 deletions(-) diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index db7d6ce8b..7a1039d20 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -123,6 +123,7 @@ trait Decider { def removeDependencyAnalyzer(): Unit def getAnalysisInfo(assumptionType: AssumptionType): AnalysisInfo def isDependencyAnalysisEnabled: Boolean + def handleFailedAssertionForDependencyAnalysis(failedAssertion: Term, assertionType: AssumptionType, reportFurtherErrors: Boolean): Unit } /* @@ -551,6 +552,17 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } } + override def handleFailedAssertionForDependencyAnalysis(failedAssertion: Term, assertionType: AssumptionType, reportFurtherErrors: Boolean): Unit = { + dependencyAnalyzer.addAssertionFailedNode(failedAssertion, assertionType, analysisSourceInfoStack.getFullSourceInfo) + if(reportFurtherErrors){ + assume(failedAssertion, None, None, AssumptionType.Explicit) + failedAssertion match { + case False => checkSmokeAndSetInfeasibilityNode() + case _ => + } + } + } + def check(t: Term, timeout: Int): Boolean = check(t, timeout, analysisSourceInfoStack.getAssertionType) def check(t: Term, timeout: Int, assumptionType: AssumptionType): Boolean = { diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index 23f92beb3..259c85088 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -66,6 +66,8 @@ trait DependencyAnalyzer { * @return the final dependency graph representing all direct and transitive dependencies */ def buildFinalGraph(): Option[DependencyGraph] + + def addAssertionFailedNode(failedAssertion: Term, assertionType: AssumptionType, sourceInfo: AnalysisSourceInfo): Option[Int] } object DependencyAnalyzer { @@ -356,6 +358,12 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { Some(node.id) } + override def addAssertionFailedNode(failedAssertion: Term, assertionType: AssumptionType, sourceInfo: AnalysisSourceInfo): Option[Int] = { + val assertFailedNode = SimpleAssertionNode(failedAssertion, assertionType, sourceInfo, isClosed=false, hasFailed=true) + dependencyGraph.addNode(assertFailedNode) + Some(assertFailedNode.id) + } + // adding dependencies override def addDependency(source: Option[Int], dest: Option[Int]): Unit = { @@ -514,6 +522,7 @@ class NoDependencyAnalyzer extends DependencyAnalyzer { override def createAssertOrCheckNode(term: Term, assumptionType: AssumptionType, analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] = None override def addAssertFalseNode(isCheck: Boolean, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo): Option[Int] = None override def addInfeasibilityNode(isCheck: Boolean, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None + override def addAssertionFailedNode(failedAssertion: Term, assertionType: AssumptionType, sourceInfo: AnalysisSourceInfo): Option[Int] = None override def addDependency(source: Option[Int], dest: Option[Int]): Unit = {} override def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit = {} diff --git a/src/main/scala/rules/ChunkSupporter.scala b/src/main/scala/rules/ChunkSupporter.scala index a4d62a0f7..6e595009e 100644 --- a/src/main/scala/rules/ChunkSupporter.scala +++ b/src/main/scala/rules/ChunkSupporter.scala @@ -155,7 +155,13 @@ object chunkSupporter extends ChunkSupportRules { else Success() // TODO: Mark branch as dead? case _ => - createFailure(ve, v1, s1, "consuming chunk", true) + val failure = createFailure(ve, v1, s1, "consuming chunk", true) + if(s.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(False, dependencyType.assertionType, v1.reportFurtherErrors()) + if(s.retryLevel == 0 && v.reportFurtherErrors() && Verifier.config.disableInfeasibilityChecks()){ + failure combine QS(s1.copy(h = s.h), s1.h, None, v1) + }else{ + failure + } } } )(Q) @@ -283,7 +289,14 @@ object chunkSupporter extends ChunkSupportRules { Success() // TODO: Mark branch as dead? } case _ => - createFailure(ve, v, s, "looking up chunk", true) + val failure = createFailure(ve, v, s, "looking up chunk", true) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, assumptionType, v.reportFurtherErrors()) + if(s.retryLevel == 0 && v.reportFurtherErrors() && Verifier.config.disableInfeasibilityChecks()){ + val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) + failure combine Q(s, snap, v) + }else{ + failure + } } } diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 2d9dedf8d..01fbefe32 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -248,7 +248,10 @@ object evaluator extends EvaluationRules { lbl s.oldHeaps.get(heapName) match { case None => - createFailure(pve dueTo LabelledStateNotReached(ast.LabelledOld(e0, heapName)(old.pos, old.info, old.errT)), v, s, "labelled state reached") + val failure = createFailure(pve dueTo LabelledStateNotReached(ast.LabelledOld(e0, heapName)(old.pos, old.info, old.errT)), v, s, "labelled state reached") + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, v.decider.analysisSourceInfoStack.getAssertionType, v.reportFurtherErrors()) + val freshVar = v.decider.fresh(v.symbolConverter.toSort(old.typ), None) + if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, freshVar, None, v) else failure case _ => evalInOldState(s, heapName, e0, pve, v)((s1, t0, _, v1) => Q(s1, t0, Some(old), v1)) @@ -257,7 +260,10 @@ object evaluator extends EvaluationRules { case old @ ast.LabelledOld(e0, lbl) => s.oldHeaps.get(lbl) match { case None => - createFailure(pve dueTo LabelledStateNotReached(old), v, s, "labelled state reached") + val failure = createFailure(pve dueTo LabelledStateNotReached(old), v, s, "labelled state reached") + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, v.decider.analysisSourceInfoStack.getAssertionType, v.reportFurtherErrors()) + val freshVar = v.decider.fresh(v.symbolConverter.toSort(old.typ), None) + if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, freshVar, None, v) else failure case _ => evalInOldState(s, lbl, e0, pve, v)((s1, t0, e0New, v1) => Q(s1, t0, e0New.map(ast.LabelledOld(_, lbl)(old.pos, old.info, old.errT)), v1))} @@ -578,7 +584,10 @@ object evaluator extends EvaluationRules { if (v1.decider.checkSmoke(isAssert = true)) { Unreachable() } else { - createFailure(pve.dueTo(InternalReason(sourceQuant, "Quantifier evaluation failed.")), v1, s1, "quantifier could be evaluated") + val failure = createFailure(pve.dueTo(InternalReason(sourceQuant, "Quantifier evaluation failed.")), v1, s1, "quantifier could be evaluated") + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, v.decider.analysisSourceInfoStack.getAssertionType, v.reportFurtherErrors()) + val freshVar = v.decider.fresh(v.symbolConverter.toSort(sourceQuant.typ), None) + if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, freshVar, None, v) else failure } } @@ -776,7 +785,10 @@ object evaluator extends EvaluationRules { Q(s12, r12._1, r12._2, v7)}) case false => v2.decider.finishDebugSubExp(s"unfolded(${predicate.name})") - createFailure(pve dueTo NonPositivePermission(ePerm.get), v2, s2, IsPositive(tPerm), ePermNew.map(p => ast.PermGtCmp(p, ast.NoPerm()())(p.pos, p.info, p.errT)))})) + val failure = createFailure(pve dueTo NonPositivePermission(ePerm.get), v2, s2, IsPositive(tPerm), ePermNew.map(p => ast.PermGtCmp(p, ast.NoPerm()())(p.pos, p.info, p.errT))) + if(s.retryLevel == 0) v2.decider.handleFailedAssertionForDependencyAnalysis(False, v2.decider.analysisSourceInfoStack.getAssertionType, v2.reportFurtherErrors()) + if(s.retryLevel == 0 && v2.reportFurtherErrors() && Verifier.config.disableInfeasibilityChecks()) failure combine Q(s2, False /* TODO ake */, None, v2) else failure + })) } else { val unknownValue = v.decider.appliedFresh("recunf", v.symbolConverter.toSort(eIn.typ), s.relevantQuantifiedVariables.map(_._1)) val newFuncRec = s.functionRecorder.recordFreshSnapshot(unknownValue.applicable.asInstanceOf[Function]) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 7a577d273..901e098d8 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -11,7 +11,7 @@ import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.decider.RecordedPathConditions import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType -import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyAnalyzer, DependencyType} +import viper.silicon.dependencyAnalysis.{AssumptionType, DependencyAnalyzer, DependencyType} import viper.silicon.interfaces._ import viper.silicon.interfaces.state.{NonQuantifiedChunk, QuantifiedChunk} import viper.silicon.logger.records.data.{CommentRecord, ConditionalEdgeRecord, ExecuteRecord, MethodCallRecord} @@ -469,8 +469,11 @@ object executor extends ExecutionRules { executionFlowController.tryOrFail0(s.copy(h = magicWandSupporter.getEvalHeap(s)), v)((s1, v1, QS) => { if (v1.decider.checkSmoke(getAssertionType(AssumptionType.Explicit), isAssert = true)) QS(s1.copy(h = s.h), v1) - else - createFailure(AssertFailed(assert) dueTo AssertionFalse(a), v1, s1, False, true, Option.when(withExp)(a)) + else { + val failure = createFailure(AssertFailed(assert) dueTo AssertionFalse(a), v1, s1, False, true, Option.when(withExp)(a)) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(False, getAssertionType(AssumptionType.Explicit), v1.reportFurtherErrors()) + if(s1.retryLevel == 0 && v1.reportFurtherErrors()) failure combine QS(s1, v1) else failure + } })((s2, v2) => if (Verifier.config.disableInfeasibilityChecks()) Q(s2, v2) diff --git a/src/main/scala/rules/HavocSupporter.scala b/src/main/scala/rules/HavocSupporter.scala index 712aff817..720eea50f 100644 --- a/src/main/scala/rules/HavocSupporter.scala +++ b/src/main/scala/rules/HavocSupporter.scala @@ -127,7 +127,10 @@ object havocSupporter extends SymbolicExecutionRules { val injectivityDebugExp = Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)) v.decider.assume(FunctionPreconditionTransformer.transform(receiverInjectivityCheck, s.program), injectivityDebugExp, assumptionType) v.decider.assert(receiverInjectivityCheck) { - case false => createFailure(pve dueTo notInjectiveReason, v, s1, receiverInjectivityCheck, "QP receiver injective") + case false => + val failure = createFailure(pve dueTo notInjectiveReason, v, s1, receiverInjectivityCheck, "QP receiver injective") + if(s1.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(receiverInjectivityCheck, v.decider.analysisSourceInfoStack.getAssertionType, v.reportFurtherErrors()) + if(s1.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s1, v1) else failure case true => // Generate the inverse axioms val (inverseFunctions, imagesOfCodomain) = quantifiedChunkSupporter.getFreshInverseFunctions( diff --git a/src/main/scala/rules/HeapSupporter.scala b/src/main/scala/rules/HeapSupporter.scala index a83bb012a..448f8f422 100644 --- a/src/main/scala/rules/HeapSupporter.scala +++ b/src/main/scala/rules/HeapSupporter.scala @@ -241,7 +241,9 @@ class DefaultHeapSupportRules extends HeapSupportRules { val s5 = if (withExp) s4.copy(oldHeaps = s4.oldHeaps + (debugHeapName -> magicWandSupporter.getEvalHeap(s4))) else s4 Q(s5, v) case (Incomplete(_, _), s3, _) => - createFailure(ve, v, s3, "sufficient permission") + val failure = createFailure(ve, v, s3, "sufficient permission") + if(s3.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, dependencyType.assertionType, v.reportFurtherErrors()) + if(s3.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s3, v) else failure } } else { val description = s"consume ${ass.pos}: $ass" @@ -374,7 +376,10 @@ class DefaultHeapSupportRules extends HeapSupportRules { val toAssert = IsPositive(totalPermissions.replace(`?r`, tRcvr)) v.decider.assert(toAssert) { case false => - createFailure(ve, v, s, toAssert, Option.when(withExp)(perms.IsPositive(ast.CurrentPerm(fa)())())) + val failure = createFailure(ve, v, s, toAssert, Option.when(withExp)(perms.IsPositive(ast.CurrentPerm(fa)())())) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(toAssert, v.decider.analysisSourceInfoStack.getAssertionType, v.reportFurtherErrors()) + val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(fa.field, s, v), Option.when(withExp)(PUnknown())) + if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, snap, v) else failure case true => val fvfLookup = Lookup(fa.field.name, fvfDef.sm, tRcvr) val fr1 = s.functionRecorder.recordSnapshot(fa, v.decider.pcs.branchConditions, fvfLookup).recordFvfAndDomain(fvfDef) @@ -425,7 +430,10 @@ class DefaultHeapSupportRules extends HeapSupportRules { } v.decider.assert(permCheck) { case false => - createFailure(ve, v, s3, permCheck, permCheckExp) + val failure = createFailure(ve, v, s3, permCheck, permCheckExp) + if(s3.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(permCheck, v.decider.analysisSourceInfoStack.getAssertionType, v.reportFurtherErrors()) + val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(fa.field, s3, v), Option.when(withExp)(PUnknown())) + if(s3.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s3, snap, v) else failure case true => val smLookup = Lookup(fa.field.name, sm, tRcvr) val fr2 = diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 68cd37542..7fb554f27 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -211,7 +211,9 @@ object magicWandSupporter extends SymbolicExecutionRules { assert(heaps.length == hs.length) assert(consumedChunks.length == hs.length) Q(s1, heaps.reverse, consumedChunks.reverse, v) - case Incomplete(_, _) => failure + case Incomplete(_, _) => + if(s1.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, v.decider.analysisSourceInfoStack.getAssertionType, v.reportFurtherErrors()) + if(s1.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s1, heaps.reverse, consumedChunks.reverse, v) else failure } } diff --git a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala index 409e4d477..ef3affe56 100644 --- a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala +++ b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala @@ -6,9 +6,9 @@ package viper.silicon.rules -import viper.silicon.dependencyAnalysis.{AssumptionType, DependencyType} -import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType import viper.silicon.debugger.DebugExp +import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType +import viper.silicon.dependencyAnalysis.{AssumptionType, DependencyType} import viper.silicon.interfaces.state._ import viper.silicon.interfaces.{Success, VerificationResult} import viper.silicon.resources.{FieldID, NonQuantifiedPropertyInterpreter, Resources} @@ -215,7 +215,14 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { Success() // TODO: Mark branch as dead? } } else { - createFailure(ve, v, s, False, "branch is dead") + val failure = createFailure(ve, v, s, False, "branch is dead") + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, v.decider.analysisSourceInfoStack.getAssertionType, v.reportFurtherErrors()) + if(s.retryLevel == 0 && v.reportFurtherErrors() && Verifier.config.disableInfeasibilityChecks()){ + val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) + failure combine Q(s, snap, v) + }else{ + failure + } } } else { summarise(s, relevantChunks, resource, args, argsExp, None, v)((s1, snap, permSum, permSumExp, v1) => @@ -223,7 +230,9 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { case true => Q(s1, snap, v1) case false => - createFailure(ve, v, s1, IsPositive(permSum), permSumExp.map(IsPositive(_)())) + val failure = createFailure(ve, v, s1, IsPositive(permSum), permSumExp.map(IsPositive(_)())) + if(s1.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(IsPositive(permSum), assumptionType, v.reportFurtherErrors()) + if(s1.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s1, snap, v1) else failure }) } } @@ -266,11 +275,13 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { if (returnSnap) { summarise(s, relevantChunks, resource, args, argsExp, None, v)((s1, snap, permSum, permSumExp, v1) => - v.decider.assert(Implies(IsPositive(perm), IsPositive(permSum)), assertionType) { + v1.decider.assert(Implies(IsPositive(perm), IsPositive(permSum)), assertionType) { case true => Q(s1, h, Some(snap), v1) case false => - createFailure(ve, v, s1, IsPositive(permSum), permSumExp.map(IsPositive(_)())) + val failure = createFailure(ve, v, s1, IsPositive(permSum), permSumExp.map(IsPositive(_)())) + if(s1.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(Implies(IsPositive(perm), IsPositive(permSum)), assertionType, v.reportFurtherErrors()) + if(s1.retryLevel == 0 && v1.reportFurtherErrors()) failure combine Q(s1, h, Some(snap), v1) else failure }) } else { val (s1, permSum, permSumExp) = permSummariseOnly(s, relevantChunks, resource, args, argsExp) @@ -278,7 +289,9 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { case true => Q(s1, h, None, v) case false => - createFailure(ve, v, s1, IsPositive(permSum), permSumExp.map(IsPositive(_)())) + val failure = createFailure(ve, v, s1, IsPositive(permSum), permSumExp.map(IsPositive(_)())) + if(s1.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(Implies(IsPositive(perm), IsPositive(permSum)), assertionType, v.reportFurtherErrors()) + if(s1.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s1, h, None, v) else failure } } } @@ -309,7 +322,10 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { // if no permission is exhaled, return none v.decider.assert(perms === NoPerm, dependencyType.assertionType) { case true => Q(s, h, None, v) - case false => createFailure(ve, v, s, perms === NoPerm, permsExp.map(pe => ast.EqCmp(pe, ast.NoPerm()())(pe.pos, pe.info, pe.errT))) + case false => + val failure = createFailure(ve, v, s, perms === NoPerm, permsExp.map(pe => ast.EqCmp(pe, ast.NoPerm()())(pe.pos, pe.info, pe.errT))) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(perms === NoPerm, dependencyType.assertionType, v.reportFurtherErrors()) + if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, h, None, v) else failure } } else { if (!terms.utils.consumeExactRead(perms, s.constrainableARPs)) { @@ -410,7 +426,9 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { case true => Q(s1, newHeap, condSnap, v1) case false => - createFailure(ve, v1, s1, pNeeded === NoPerm, pNeededExp.map(pn => ast.EqCmp(pn, ast.NoPerm()())(pn.pos, pn.info, pn.errT))) + val failure = createFailure(ve, v1, s1, pNeeded === NoPerm, pNeededExp.map(pn => ast.EqCmp(pn, ast.NoPerm()())(pn.pos, pn.info, pn.errT))) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(pNeeded === NoPerm, dependencyType.assertionType, v1.reportFurtherErrors()) + if(s1.retryLevel == 0 && v1.reportFurtherErrors()) failure combine Q(s1, newHeap, condSnap, v1) else failure } } }) @@ -422,7 +440,9 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { case true => Q(s0, newHeap, None, v) case false => - createFailure(ve, v, s0, pNeeded === NoPerm, pNeededExp.map(pn => ast.EqCmp(pn, ast.NoPerm()())(pn.pos, pn.info, pn.errT))) + val failure = createFailure(ve, v, s0, pNeeded === NoPerm, pNeededExp.map(pn => ast.EqCmp(pn, ast.NoPerm()())(pn.pos, pn.info, pn.errT))) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(pNeeded === NoPerm, dependencyType.assertionType, v.reportFurtherErrors()) + if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s0, newHeap, None, v) else failure } } } @@ -513,7 +533,14 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { } case false => v.decider.finishDebugSubExp(s"consume permissions for ${resource.toString()}") - createFailure(ve, v, s, totalPermTaken !== NoPerm, totalPermTakenExp.map(tpt => ast.NeCmp(tpt, ast.NoPerm()())())) + val failure = createFailure(ve, v, s, totalPermTaken !== NoPerm, totalPermTakenExp.map(tpt => ast.NeCmp(tpt, ast.NoPerm()())())) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(Implies(PermLess(NoPerm, perms), totalPermTaken !== NoPerm), dependencyType.assertionType, v.reportFurtherErrors()) + if(s.retryLevel == 0 && v.reportFurtherErrors()){ + val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) + failure combine Q(s1, updatedChunks, if(returnSnap) Some(snap) else None, v) + }else{ + failure + } } } diff --git a/src/main/scala/rules/PermissionSupporter.scala b/src/main/scala/rules/PermissionSupporter.scala index 58c386fe9..347290f29 100644 --- a/src/main/scala/rules/PermissionSupporter.scala +++ b/src/main/scala/rules/PermissionSupporter.scala @@ -27,7 +27,9 @@ object permissionSupporter extends SymbolicExecutionRules { case true => Q(s, v) case false => val assertExp = ePermNew.map(ep => perms.IsNonNegative(ep)(ep.pos, ep.info, ep.errT)) - createFailure(pve dueTo NegativePermission(ePerm), v, s, perms.IsNonNegative(tPerm), assertExp) + val failure = createFailure(pve dueTo NegativePermission(ePerm), v, s, perms.IsNonNegative(tPerm), assertExp) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(perms.IsNonNegative(tPerm), v.decider.analysisSourceInfoStack.getAssertionType, v.reportFurtherErrors()) + if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, v) else failure } } } @@ -42,7 +44,10 @@ object permissionSupporter extends SymbolicExecutionRules { case _ => v.decider.assert(perms.IsPositive(tPerm)) { case true => Q(s, v) - case false => createFailure(pve dueTo NonPositivePermission(ePerm), v, s, perms.IsPositive(tPerm), Option.when(withExp)(perms.IsPositive(ePerm)())) + case false => + val failure = createFailure(pve dueTo NonPositivePermission(ePerm), v, s, perms.IsPositive(tPerm), Option.when(withExp)(perms.IsPositive(ePerm)())) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(perms.IsPositive(tPerm), v.decider.analysisSourceInfoStack.getAssertionType, v.reportFurtherErrors()) + if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, v) else failure } } } diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index bdf419729..ed556c116 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -7,12 +7,11 @@ package viper.silicon.rules import viper.silicon -import viper.silicon.debugger.DebugExp import viper.silicon.Map -import viper.silicon.dependencyAnalysis.{AssumptionType, DependencyType} -import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp +import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType +import viper.silicon.dependencyAnalysis.{AssumptionType, DependencyType} import viper.silicon.interfaces.VerificationResult import viper.silicon.interfaces.state._ import viper.silicon.logger.records.data.CommentRecord @@ -1054,12 +1053,17 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { conservedPcs = conservedPcs, smCache = smCache1) Q(s1, v) - case false => { - createFailure(pve dueTo notInjectiveReason, v, s, receiverInjectivityCheck, "QP receiver is injective") - } + case false => + val failure = createFailure(pve dueTo notInjectiveReason, v, s, receiverInjectivityCheck, "QP receiver is injective") + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(completeReceiverInjectivityCheck, v.decider.analysisSourceInfoStack.getAssertionType, v.reportFurtherErrors()) + if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, v) else failure } case false => - createFailure(pve dueTo negativePermissionReason, v, s, nonNegImplication, nonNegImplicationExp)} + val failure = createFailure(pve dueTo negativePermissionReason, v, s, nonNegImplication, nonNegImplicationExp) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(nonNegTerm, v.decider.analysisSourceInfoStack.getAssertionType, v.reportFurtherErrors()) + if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, v) else failure + } + } def produceSingleLocation(s: State, @@ -1391,12 +1395,31 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { Q(s2, h3, None, v) } case (Incomplete(_, _), s2, _) => - createFailure(pve dueTo insufficientPermissionReason, v, s2, "QP consume")} + val failure = createFailure(pve dueTo insufficientPermissionReason, v, s2, "QP consume") + if(s2.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, dependencyType.assertionType, v.reportFurtherErrors()) + if(s2.retryLevel == 0 && v.reportFurtherErrors() && Verifier.config.disableInfeasibilityChecks()) failure combine Q(s2, s2.h, None, v) else failure + } } case false => - createFailure(pve dueTo notInjectiveReason, v, s, receiverInjectivityCheck, "QP receiver injective")} + val failure = createFailure(pve dueTo notInjectiveReason, v, s, receiverInjectivityCheck, "QP receiver injective") + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(receiverInjectivityCheck, dependencyType.assertionType, v.reportFurtherErrors()) + if(s.retryLevel == 0 && v.reportFurtherErrors()){ + val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) + failure combine Q(s, s.h, if(returnSnap) Some(snap) else None, v) + }else{ + failure + } + } case false => - createFailure(pve dueTo negativePermissionReason, v, s, nonNegTerm, nonNegExp)} + val failure = createFailure(pve dueTo negativePermissionReason, v, s, nonNegTerm, nonNegExp) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(nonNegTerm, dependencyType.assertionType, v.reportFurtherErrors()) + if(s.retryLevel == 0 && v.reportFurtherErrors()){ + val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) + failure combine Q(s, s.h, if(returnSnap) Some(snap) else None, v) + }else{ + failure + } + } } def consumeSingleLocation(s: State, @@ -1522,11 +1545,13 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { Q(s1, h1, None, v) } case (Incomplete(_, _), _, _) => - resourceAccess match { + val failure = resourceAccess match { case locAcc: ast.LocationAccess => createFailure(pve dueTo InsufficientPermission(locAcc), v, s, "single QP consume") case wand: ast.MagicWand => createFailure(pve dueTo MagicWandChunkNotFound(wand), v, s, "single QP consume") case _ => sys.error(s"Found resource $resourceAccess, which is not yet supported as a quantified resource.") } + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, dependencyType.assertionType, v.reportFurtherErrors()) + if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, s.h, None, v) else failure } } } From 47e551d97d163b4ac80ee0b78310fd8c28359ee5 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 9 Mar 2026 11:37:16 +0100 Subject: [PATCH 379/474] add support for verification failures --- src/main/scala/decider/Decider.scala | 14 ++-- .../DependencyAnalysisUserTool.scala | 17 ++--- .../DependencyGraphInterpreter.scala | 33 ++++++-- src/main/scala/rules/Consumer.scala | 1 + src/main/scala/rules/Evaluator.scala | 8 ++ .../verificationFailures/failures.vpr | 76 +++++++++++++++++++ 6 files changed, 124 insertions(+), 25 deletions(-) create mode 100644 src/test/resources/dependencyAnalysisTests/verificationFailures/failures.vpr diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 7a1039d20..d8a1164cd 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -123,7 +123,7 @@ trait Decider { def removeDependencyAnalyzer(): Unit def getAnalysisInfo(assumptionType: AssumptionType): AnalysisInfo def isDependencyAnalysisEnabled: Boolean - def handleFailedAssertionForDependencyAnalysis(failedAssertion: Term, assertionType: AssumptionType, reportFurtherErrors: Boolean): Unit + def handleFailedAssertionForDependencyAnalysis(failedAssertion: Term, assertionType: AssumptionType, assumeFailedAssertion: Boolean): Unit } /* @@ -552,9 +552,9 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } } - override def handleFailedAssertionForDependencyAnalysis(failedAssertion: Term, assertionType: AssumptionType, reportFurtherErrors: Boolean): Unit = { + override def handleFailedAssertionForDependencyAnalysis(failedAssertion: Term, assertionType: AssumptionType, assumeFailedAssertion: Boolean): Unit = { dependencyAnalyzer.addAssertionFailedNode(failedAssertion, assertionType, analysisSourceInfoStack.getFullSourceInfo) - if(reportFurtherErrors){ + if(assumeFailedAssertion){ assume(failedAssertion, None, None, AssumptionType.Explicit) failedAssertion match { case False => checkSmokeAndSetInfeasibilityNode() @@ -598,11 +598,13 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => val result = asserted || proverAssert(t, timeout, DependencyAnalyzer.createAssertionLabel(assertNode map (_.id))) - if(result){ + if(result) { assertNode foreach dependencyAnalyzer.addAssertionNode - }else if(!isCheck){ // TODO ake: only for asserts? - assertNode foreach {node => dependencyAnalyzer.addAssertionNode(node.getAssertFailedNode())} } +// }else if(!isCheck){ +// // An assertion only truly fails once state.retryLevel == 0. Instead of here, we add AssertFailedNodes at the caller-side. +// assertNode foreach {node => dependencyAnalyzer.addAssertionNode(node.getAssertFailedNode())} +// } symbExLog.closeScope(sepIdentifier) (result, assertNode) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala index f8eeed492..c99fc794e 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala @@ -154,7 +154,6 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete } private def handleVerificationProgressQuery(): Unit = { - if(verificationErrors.nonEmpty) println(s"Fix verification failures first!") val ((optProgressPeter, optProgressLea, optInfo), optTime) = measureTime(fullGraphInterpreter.computeVerificationProgress()) // println(s"Overall verification progress: $progress") @@ -164,7 +163,6 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete } private def handleVerificationProgressDEBUGQuery(): Unit = { - if(verificationErrors.nonEmpty) println(s"Fix verification failures first!") println("\nNaive implementation") val ((naiveProgressPeter, naiveProgressLea, naiveInfo), naiveTime) = measureTime(fullGraphInterpreter.computeVerificationProgressNaive()) @@ -184,7 +182,6 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete } private def handleVerificationProgressNaiveQuery(): Unit = { - if(verificationErrors.nonEmpty) println(s"Fix verification failures first!") val ((naiveProgressPeter, naiveProgressLea, naiveInfo), naiveTime) = measureTime(fullGraphInterpreter.computeVerificationProgressNaive()) // println(s"Overall verification progress: $progress") @@ -315,10 +312,9 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete } def handleVerificationGuidanceQuery(): Unit = { - if(verificationErrors.nonEmpty) { - println(s"Fix verification failures first!") - return - } + + val failedAssertions = fullGraphInterpreter.getFailedAssertions + println(s"Failed assertions:\n\t${failedAssertions.mkString("\n\t")}\n") val assumptionRanking = fullGraphInterpreter.computeAssumptionRanking().filter(_._2 > 0.0) println(s"Assumptions and their impact on progress:\n\t${assumptionRanking.mkString("\n\t")}\n") @@ -330,10 +326,9 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete } def handleVerificationGuidanceOldQuery(): Unit = { - if(verificationErrors.nonEmpty) { - println(s"Fix verification failures first!") - return - } + + val failedAssertions = fullGraphInterpreter.getFailedAssertions + println(s"Failed assertions:\n\t${failedAssertions.mkString("\n\t")}\n") val assumptionRanking = fullGraphInterpreter.computeAssumptionRankingOld().filter(_._2 > 0) println(s"Assumptions and the number of dependents:\n\t${assumptionRanking.mkString("\n\t")}\n") diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 329e58ab1..c048eb809 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -320,7 +320,13 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen )}.toSet } - private def computeAssertionQuality(allDependencies: Set[CompactUserLevelDependencyAnalysisNode]): Double = { + private def computeAssertionQuality(allDependencies: Set[CompactUserLevelDependencyAnalysisNode], assertion: AnalysisSourceInfo): Double = { + val assertionNodes = sourceToAssertionNodes.getOrElse(assertion, Set.empty).filter(node => node.isInstanceOf[GeneralAssertionNode]) + val failedAssertionNodes = assertionNodes.filter(node => node.asInstanceOf[GeneralAssertionNode].hasFailed) + // assertions with failures have quality = 0.0 + if(failedAssertionNodes.nonEmpty) + return 0.0 + val explicitDeps = allDependencies.filter(_.assumptionTypes.intersect(AssumptionType.explicitAssumptionTypes).nonEmpty).map(_.source) val numDepsTotal = allDependencies.map(_.source).size (numDepsTotal - explicitDeps.size).toDouble / numDepsTotal.toDouble @@ -333,7 +339,8 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen val specQuality = computeSpecQuality(assertionDeps.flatMap(_._1).toSet) - val assertionQualities = assertionDeps filterNot (_._1.isEmpty) map (ass => (computeAssertionQuality(ass._1), ass._2)) filterNot (n => isNaN(n._1)) + val assertionQualities1 = assertionDeps map (ass => (computeAssertionQuality(ass._1, ass._2), ass._2)) + val assertionQualities = assertionQualities1 filterNot (n => isNaN(n._1)) val numAssertions = assertionQualities.size val fullyVerifiedAssertions = assertionQualities.filter(_._1 == 1.0) val numFullyVerifiedAssertions = fullyVerifiedAssertions.size @@ -369,7 +376,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen // TODO ake: this is suuuper slow. Can we reuse previously computed results? Caching? val relevantDependenciesPerAssertion = allAssertions .map(ass => (ass, toUserLevelNodes(getAllNonInternalDependencies(ass.lowerLevelNodes.map(_.id))).diffBySource(Set(ass)))).toMap - .filter{case (_, assumptions) => assumptions.nonEmpty} // filter out trivial assertions like `assert true` + .filter{case (ass, assumptions) => assumptions.nonEmpty || ass.hasFailures} // filter out trivial assertions like `assert true` // val endTime = System.nanoTime() // println(s"Runtime of computing dependencies per assertion: ${(endTime-startTime)/1e6}ms") @@ -391,8 +398,9 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen // assertions val relevantAssertions = relevantDependenciesPerAssertion - val assertionsWithExplicitDeps = relevantAssertions.filter(deps => deps._2.exists(d => AssumptionType.explicitAssumptionTypes.intersect(d.assumptionTypes).nonEmpty)).keySet.getSourceSet() - val fullyVerifiedAssertions = relevantAssertions.keySet.getSourceSet().diff(assertionsWithExplicitDeps) + val assertionsWithFailures = relevantAssertions.filter(assertion => assertion._1.hasFailures).keySet.getSourceSet() + val assertionsWithExplicitDeps = relevantAssertions.filter(deps => !deps._1.hasFailures && deps._2.exists(d => AssumptionType.explicitAssumptionTypes.intersect(d.assumptionTypes).nonEmpty)).keySet.getSourceSet().diff(assertionsWithFailures) + val fullyVerifiedAssertions = relevantAssertions.keySet.getSourceSet().diff(assertionsWithExplicitDeps).diff(assertionsWithFailures) val numRelevantAssertions = relevantAssertions.keySet.size.toDouble @@ -404,8 +412,11 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen // Lea's metric val proofQualityPerAssertion = relevantAssertions.toList.map { case (assertion, assumptions) => - val nonExplicitDeps = UserLevelDependencyAnalysisNode.extractNonExplicitAssumptionNodes(assumptions) - (nonExplicitDeps.size.toDouble / assumptions.size.toDouble, assertion) + if(assertion.hasFailures) (0.0, assertion) + else { + val nonExplicitDeps = UserLevelDependencyAnalysisNode.extractNonExplicitAssumptionNodes(assumptions) + (nonExplicitDeps.size.toDouble / assumptions.size.toDouble, assertion) + } } val proofQualityLea = if(numRelevantAssertions > 0) proofQualityPerAssertion.map(_._1).sum / numRelevantAssertions else 1.0 @@ -429,6 +440,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen "\n" + s"Fully verified assertions:\n\t\t${getString(fullyVerifiedAssertions)}" + "\n\n" + s"Assertions depending on explicit assumptions:\n\t\t${getString(assertionsWithExplicitDeps)}" + "\n\n" + + s"Assertions with failures:\n\t\t${getString(assertionsWithFailures)}" + "\n\n" + "\n" + s"Assertion Qualities:\n\t\t${proofQualityPerAssertion.filterNot(_._1 == 1.0).sortBy(_._2.source.getLineNumber).mkString("\n\t\t")}" + "\n\n" + "\n" + @@ -446,7 +458,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen val relevantDependenciesPerAssertion = allAssertions .map(ass => (ass, toUserLevelNodes(getAllNonInternalDependencies(ass.lowerLevelNodes.map(_.id))).diffBySource(Set(ass)))).toMap - .filter{case (_, assumptions) => assumptions.nonEmpty} + .filter{case (assertion, assumptions) => assumptions.nonEmpty || assertion.hasFailures} val numAssertions = relevantDependenciesPerAssertion.size.toDouble val assumptionImpacts= relevantDependenciesPerAssertion.toList.flatMap { case (assertion, assumptions) => @@ -462,6 +474,11 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen .toList.sortBy(_._2).reverse } + def getFailedAssertions: List[String] = { + val allAssertions = toUserLevelNodes(getNonInternalAssertionNodes) + allAssertions.filter(_.hasFailures).getSourceSet().toList.sortBy(_.getLineNumber).map(_.getTopLevelSource.toString) + } + def computeUncoveredStatements(): Int = { val allAssertions = toUserLevelNodes(getNonInternalAssertionNodes) // TODO ake: once the infinite loop in the deps() function is removed, we should use this optimized version diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index 7e21316e3..cd1cf8cbe 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -435,6 +435,7 @@ object consumer extends ConsumptionRules { QS(s3, v2) case false => val failure = createFailure(pve dueTo AssertionFalse(e), v2, s3, termToAssert, eNew) + if(s3.retryLevel == 0) v2.decider.handleFailedAssertionForDependencyAnalysis(t, dependencyType.assertionType, assumeFailedAssertion=false) if (s3.retryLevel == 0 && v2.reportFurtherErrors()){ v2.decider.assume(t, Option.when(withExp)(e), eNew, AssumptionType.Explicit) failure combine QS(s3, v2) diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 01fbefe32..551889abb 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -829,6 +829,7 @@ object evaluator extends EvaluationRules { case false => val assertExp2 = Option.when(withExp)(ast.LtCmp(e1, ast.SeqLength(e0)())(e1.pos, e1.info, e1.errT)) val failure = createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, Less(t1, SeqLength(t0)), assertExp2) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(Less(t1, SeqLength(t0)), v1.decider.analysisSourceInfoStack.getAssertionType, assumeFailedAssertion=false) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { val assertExp2 = Option.when(withExp)(ast.LeCmp(e1, ast.SeqLength(e0)())()) val assertExp2New = esNew.map(es => ast.LeCmp(es(1), ast.SeqLength(es.head)())()) @@ -839,6 +840,7 @@ object evaluator extends EvaluationRules { val assertExp1 = Option.when(withExp)(ast.GeCmp(e1, ast.IntLit(0)())(e1.pos, e1.info, e1.errT)) val assertExp1New = Option.when(withExp)(ast.GeCmp(esNew.get(1), ast.IntLit(0)())(e1.pos, e1.info, e1.errT)) val failure1 = createFailure(pve dueTo SeqIndexNegative(e0, e1), v1, s1, AtLeast(t1, IntLiteral(0)), assertExp1New) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(AtLeast(t1, IntLiteral(0)), v1.decider.analysisSourceInfoStack.getAssertionType, assumeFailedAssertion=false) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { v1.decider.assume(AtLeast(t1, IntLiteral(0)), assertExp1, assertExp1New, AssumptionType.Explicit) val assertExp2 = Option.when(withExp)(ast.LtCmp(e1, ast.SeqLength(e0)())(e1.pos, e1.info, e1.errT)) @@ -848,6 +850,7 @@ object evaluator extends EvaluationRules { failure1 combine Q(s1, SeqAt(t0, t1), eNew, v1) case false => val failure2 = failure1 combine createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, Less(t1, SeqLength(t0)), assertExp2New) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(Less(t1, SeqLength(t0)), v1.decider.analysisSourceInfoStack.getAssertionType, assumeFailedAssertion=false) if (v1.reportFurtherErrors()) { v1.decider.assume(Less(t1, SeqLength(t0)), assertExp2, assertExp2New, AssumptionType.Explicit) failure2 combine Q(s1, SeqAt(t0, t1), eNew, v1) @@ -882,6 +885,7 @@ object evaluator extends EvaluationRules { Q(s1, SeqUpdate(t0, t1, t2), eNew, v1) case false => val failure = createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, Less(t1, SeqLength(t0)), assertExp2New) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(Less(t1, SeqLength(t0)), v1.decider.analysisSourceInfoStack.getAssertionType, assumeFailedAssertion=false) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { val assertExp3 = Option.when(withExp)(ast.LeCmp(e1, ast.SeqLength(e0)())()) val assertExp3New = Option.when(withExp)(ast.LeCmp(esNew.get(1), ast.SeqLength(esNew.get(0))())()) @@ -890,6 +894,7 @@ object evaluator extends EvaluationRules { else failure} case false => val failure1 = createFailure(pve dueTo SeqIndexNegative(e0, e1), v1, s1, AtLeast(t1, IntLiteral(0)), assertExpNew) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(AtLeast(t1, IntLiteral(0)), v1.decider.analysisSourceInfoStack.getAssertionType, assumeFailedAssertion=false) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { v1.decider.assume(AtLeast(t1, IntLiteral(0)), assertExp, assertExpNew, AssumptionType.Explicit) val assertExp2 = Option.when(withExp)(ast.LtCmp(e1, ast.SeqLength(e0)())(e1.pos, e1.info, e1.errT)) @@ -899,6 +904,7 @@ object evaluator extends EvaluationRules { failure1 combine Q(s1, SeqUpdate(t0, t1, t2), eNew, v1) case false => val failure2 = failure1 combine createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, Less(t1, SeqLength(t0)), assertExp2New) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(Less(t1, SeqLength(t0)), v1.decider.analysisSourceInfoStack.getAssertionType, assumeFailedAssertion=false) if (v1.reportFurtherErrors()) { v1.decider.assume(Less(t1, SeqLength(t0)), assertExp2, assertExp2New, AssumptionType.Explicit) failure2 combine Q(s1, SeqUpdate(t0, t1, t2), eNew, v1) @@ -1018,6 +1024,7 @@ object evaluator extends EvaluationRules { val assertExp = Option.when(withExp)(ast.MapContains(key, base)(ml.pos, ml.info, ml.errT)) val assertExpNew = Option.when(withExp)(ast.MapContains(esNew.get(1), esNew.get(0))(ml.pos, ml.info, ml.errT)) val failure1 = createFailure(pve dueTo MapKeyNotContained(base, key), v1, s1, SetIn(keyT, MapDomain(baseT)), assertExpNew) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(SetIn(keyT, MapDomain(baseT)), v1.decider.analysisSourceInfoStack.getAssertionType, assumeFailedAssertion=false) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { v1.decider.assume(SetIn(keyT, MapDomain(baseT)), assertExp, assertExpNew, AssumptionType.Explicit) failure1 combine Q(s1, MapLookup(baseT, keyT), eNew, v1) @@ -1247,6 +1254,7 @@ object evaluator extends EvaluationRules { (Some(ast.NeCmp(eDivisor, ast.IntLit(0)())(eDivisor.pos, eDivisor.info, eDivisor.errT)), Some(ast.NeCmp(eDivisorNew.get, ast.IntLit(0)())(eDivisor.pos, eDivisor.info, eDivisor.errT))) } else { (None, None) } val failure = createFailure(pve dueTo DivisionByZero(eDivisor), v, s, tDivisor !== tZero, notZeroExpNew) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(tDivisor !== tZero, v.decider.analysisSourceInfoStack.getAssertionType, assumeFailedAssertion=false) if (s.retryLevel == 0 && v.reportFurtherErrors()) { v.decider.assume(tDivisor !== tZero, notZeroExp, notZeroExpNew, AssumptionType.Explicit) failure combine Q(s, t, v) diff --git a/src/test/resources/dependencyAnalysisTests/verificationFailures/failures.vpr b/src/test/resources/dependencyAnalysisTests/verificationFailures/failures.vpr new file mode 100644 index 000000000..d0e129318 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationFailures/failures.vpr @@ -0,0 +1,76 @@ +field f: Int + + +method foo(x: Ref) + requires acc(x.f, 1/2) + ensures acc(x.f, 1/2) +{ + var a: Int + a := x.f + if(x.f > 0){ + x.f := 1 + x.f := 2 + assert a == 0 + } + + assert a == x.f +} + + +method divBy0(a: Int) + requires a < 100 +{ + + var b: Int + if(a > 0){ + b := 10 + } + + b := 100 / a + + assert a < 100 + + assert a != 0 + + assert b >= 0 + +} + +method divBy0_2(a: Int, x: Ref) + requires a < 100 +{ + + var b: Int + b := 100 / a + + x.f := 0 + + assert a < 100 + + assert a != 0 + + assert b >= 0 + +} + + +method client() +{ + var x: Ref := new(f) + x.f := 0 + + foo(x) + + assert x.f == 0 + exhale acc(x.f) +} + + +method permissions(x: Ref, y: Ref, z: Ref) + requires acc(x.f, 1/2) && acc(y.f, 1/2) && acc(z.f, 1/2) +{ + assume x == y + + assert z != y + +} \ No newline at end of file From cc6a430ccdc6d330787a7a2e82a2b8346d08588c Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 9 Mar 2026 17:21:46 +0100 Subject: [PATCH 380/474] minor fix for verification failure handling --- src/main/scala/rules/ChunkSupporter.scala | 4 ++-- src/main/scala/rules/Evaluator.scala | 11 ++++++----- .../scala/rules/MoreCompleteExhaleSupporter.scala | 14 +++++++------- src/main/scala/rules/QuantifiedChunkSupport.scala | 2 +- .../DependencyAnalysisPrecisionBenchmark.scala | 6 +++--- 5 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/main/scala/rules/ChunkSupporter.scala b/src/main/scala/rules/ChunkSupporter.scala index 6e595009e..54572ff9c 100644 --- a/src/main/scala/rules/ChunkSupporter.scala +++ b/src/main/scala/rules/ChunkSupporter.scala @@ -156,8 +156,8 @@ object chunkSupporter extends ChunkSupportRules { Success() // TODO: Mark branch as dead? case _ => val failure = createFailure(ve, v1, s1, "consuming chunk", true) - if(s.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(False, dependencyType.assertionType, v1.reportFurtherErrors()) - if(s.retryLevel == 0 && v.reportFurtherErrors() && Verifier.config.disableInfeasibilityChecks()){ + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(False, dependencyType.assertionType, v1.reportFurtherErrors()) + if(s1.retryLevel == 0 && v1.reportFurtherErrors() && Verifier.config.disableInfeasibilityChecks()){ failure combine QS(s1.copy(h = s.h), s1.h, None, v1) }else{ failure diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 551889abb..732af3021 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -585,9 +585,9 @@ object evaluator extends EvaluationRules { Unreachable() } else { val failure = createFailure(pve.dueTo(InternalReason(sourceQuant, "Quantifier evaluation failed.")), v1, s1, "quantifier could be evaluated") - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, v.decider.analysisSourceInfoStack.getAssertionType, v.reportFurtherErrors()) - val freshVar = v.decider.fresh(v.symbolConverter.toSort(sourceQuant.typ), None) - if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, freshVar, None, v) else failure + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(False, v1.decider.analysisSourceInfoStack.getAssertionType, v1.reportFurtherErrors()) + val freshVar = v1.decider.fresh(v1.symbolConverter.toSort(sourceQuant.typ), None) + if(s1.retryLevel == 0 && v1.reportFurtherErrors()) failure combine Q(s1, freshVar, None, v1) else failure } } @@ -786,8 +786,9 @@ object evaluator extends EvaluationRules { case false => v2.decider.finishDebugSubExp(s"unfolded(${predicate.name})") val failure = createFailure(pve dueTo NonPositivePermission(ePerm.get), v2, s2, IsPositive(tPerm), ePermNew.map(p => ast.PermGtCmp(p, ast.NoPerm()())(p.pos, p.info, p.errT))) - if(s.retryLevel == 0) v2.decider.handleFailedAssertionForDependencyAnalysis(False, v2.decider.analysisSourceInfoStack.getAssertionType, v2.reportFurtherErrors()) - if(s.retryLevel == 0 && v2.reportFurtherErrors() && Verifier.config.disableInfeasibilityChecks()) failure combine Q(s2, False /* TODO ake */, None, v2) else failure + if(s2.retryLevel == 0) v2.decider.handleFailedAssertionForDependencyAnalysis(False, v2.decider.analysisSourceInfoStack.getAssertionType, v2.reportFurtherErrors()) + val freshVar = v2.decider.fresh(v2.symbolConverter.toSort(e.typ), None) // TODO ake: function recorder + if(s2.retryLevel == 0 && v2.reportFurtherErrors() && Verifier.config.disableInfeasibilityChecks()) failure combine Q(s2, freshVar, None, v2) else failure })) } else { val unknownValue = v.decider.appliedFresh("recunf", v.symbolConverter.toSort(eIn.typ), s.relevantQuantifiedVariables.map(_._1)) diff --git a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala index ef3affe56..524916978 100644 --- a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala +++ b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala @@ -231,8 +231,8 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { Q(s1, snap, v1) case false => val failure = createFailure(ve, v, s1, IsPositive(permSum), permSumExp.map(IsPositive(_)())) - if(s1.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(IsPositive(permSum), assumptionType, v.reportFurtherErrors()) - if(s1.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s1, snap, v1) else failure + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(IsPositive(permSum), assumptionType, v1.reportFurtherErrors()) + if(s1.retryLevel == 0 && v1.reportFurtherErrors()) failure combine Q(s1, snap, v1) else failure }) } } @@ -275,12 +275,12 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { if (returnSnap) { summarise(s, relevantChunks, resource, args, argsExp, None, v)((s1, snap, permSum, permSumExp, v1) => - v1.decider.assert(Implies(IsPositive(perm), IsPositive(permSum)), assertionType) { + v.decider.assert(Implies(IsPositive(perm), IsPositive(permSum)), assertionType) { case true => Q(s1, h, Some(snap), v1) case false => val failure = createFailure(ve, v, s1, IsPositive(permSum), permSumExp.map(IsPositive(_)())) - if(s1.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(Implies(IsPositive(perm), IsPositive(permSum)), assertionType, v.reportFurtherErrors()) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(Implies(IsPositive(perm), IsPositive(permSum)), assertionType, v1.reportFurtherErrors()) if(s1.retryLevel == 0 && v1.reportFurtherErrors()) failure combine Q(s1, h, Some(snap), v1) else failure }) } else { @@ -441,8 +441,8 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { Q(s0, newHeap, None, v) case false => val failure = createFailure(ve, v, s0, pNeeded === NoPerm, pNeededExp.map(pn => ast.EqCmp(pn, ast.NoPerm()())(pn.pos, pn.info, pn.errT))) - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(pNeeded === NoPerm, dependencyType.assertionType, v.reportFurtherErrors()) - if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s0, newHeap, None, v) else failure + if(s0.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(pNeeded === NoPerm, dependencyType.assertionType, v.reportFurtherErrors()) + if(s0.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s0, newHeap, None, v) else failure } } } @@ -536,7 +536,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { val failure = createFailure(ve, v, s, totalPermTaken !== NoPerm, totalPermTakenExp.map(tpt => ast.NeCmp(tpt, ast.NoPerm()())())) if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(Implies(PermLess(NoPerm, perms), totalPermTaken !== NoPerm), dependencyType.assertionType, v.reportFurtherErrors()) if(s.retryLevel == 0 && v.reportFurtherErrors()){ - val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) + val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) // TODO ake: function recorder failure combine Q(s1, updatedChunks, if(returnSnap) Some(snap) else None, v) }else{ failure diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index ed556c116..f2723c6a6 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -1404,7 +1404,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val failure = createFailure(pve dueTo notInjectiveReason, v, s, receiverInjectivityCheck, "QP receiver injective") if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(receiverInjectivityCheck, dependencyType.assertionType, v.reportFurtherErrors()) if(s.retryLevel == 0 && v.reportFurtherErrors()){ - val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) + val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) // TODO ake: function recorder failure combine Q(s, s.h, if(returnSnap) Some(snap) else None, v) }else{ failure diff --git a/src/test/scala/DependencyAnalysisPrecisionBenchmark.scala b/src/test/scala/DependencyAnalysisPrecisionBenchmark.scala index f04787e78..4a0da189a 100644 --- a/src/test/scala/DependencyAnalysisPrecisionBenchmark.scala +++ b/src/test/scala/DependencyAnalysisPrecisionBenchmark.scala @@ -71,8 +71,8 @@ object DependencyAnalysisPrecisionBenchmark extends DependencyAnalysisTestFramew } protected def computePrecision(dependencyGraphInterpreter: DependencyGraphInterpreter): Double = { - val assumptionNodes = getTestIrrelevantAssumptionNodes(dependencyGraphInterpreter.getNonInternalAssumptionNodes) - val assumptionsPerSource = assumptionNodes groupBy (n => extractSourceLine(n.sourceInfo.getPosition)) + val irrelevantAssumptionNodes = getTestIrrelevantAssumptionNodes(dependencyGraphInterpreter.getNonInternalAssumptionNodes) + val irrelevantAssumptionsPerSource = irrelevantAssumptionNodes groupBy (n => extractSourceLine(n.sourceInfo.getPosition)) val assertionNodes = getTestAssertionNodes(dependencyGraphInterpreter.getNonInternalAssertionNodes) val dependencies = dependencyGraphInterpreter.getAllNonInternalDependencies(assertionNodes.map(_.id)) @@ -80,7 +80,7 @@ object DependencyAnalysisPrecisionBenchmark extends DependencyAnalysisTestFramew val dependencyIds = dependencies.map(_.id) if(dependenciesPerSource.nonEmpty){ - val wrongDependencies = assumptionsPerSource.filter({ case (_, assumptions) => dependencyIds.intersect(assumptions.map(_.id)).nonEmpty }) + val wrongDependencies = irrelevantAssumptionsPerSource.filter({ case (_, irrelevantAssumptions) => dependencyIds.intersect(irrelevantAssumptions.map(_.id)).nonEmpty }) 1.0 - (wrongDependencies.size.toDouble / dependenciesPerSource.size.toDouble) }else{ 1.0 From 1857b7139922aa49ce7ad8da2e8f160e77060fe4 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 10 Mar 2026 11:36:10 +0100 Subject: [PATCH 381/474] annotate imported methods --- src/main/scala/dependencyAnalysis/AnalysisInfo.scala | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala index 4c15349a8..4a6ad4122 100644 --- a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala +++ b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala @@ -3,16 +3,17 @@ package viper.silicon.dependencyAnalysis object AssumptionType extends Enumeration { type AssumptionType = Value - val Explicit, LoopInvariant, PathCondition, Rewrite, SourceCode, DomainAxiom, Implicit, Internal, Trigger, ExplicitPostcondition, ImplicitPostcondition, MethodCall, FunctionBody, Precondition, Ghost, CustomInternal, Unknown = Value + val Explicit, LoopInvariant, PathCondition, Rewrite, SourceCode, DomainAxiom, Implicit, Internal, Trigger, ExplicitPostcondition, ImplicitPostcondition, ImportedPostcondition, MethodCall, FunctionBody, Precondition, Ghost, CustomInternal, Unknown = Value def fromString(s: String): Option[Value] = values.find(_.toString == s) def explicitAssumptionTypes: Set[AssumptionType] = Set(Explicit, ExplicitPostcondition) - def postconditionTypes: Set[AssumptionType] = Set(ImplicitPostcondition, ExplicitPostcondition) + def postconditionTypes: Set[AssumptionType] = Set(ImplicitPostcondition, ExplicitPostcondition, ImportedPostcondition) def explicitAssertionTypes: Set[AssumptionType] = Set(Explicit, ImplicitPostcondition, ExplicitPostcondition) def internalTypes: Set[AssumptionType] = Set(Internal, Trigger, CustomInternal) // will always be hidden from user def joinConditionTypes: Set[AssumptionType] = postconditionTypes ++ Set(FunctionBody) - def verificationAnnotationTypes: Set[AssumptionType] = Set(FunctionBody, LoopInvariant, Rewrite, ExplicitPostcondition, ImplicitPostcondition, Precondition, Explicit, DomainAxiom, Ghost) + def importedTypes: Set[AssumptionType] = Set(ImportedPostcondition) + def verificationAnnotationTypes: Set[AssumptionType] = Set(FunctionBody, LoopInvariant, Rewrite, ExplicitPostcondition, ImplicitPostcondition, ImportedPostcondition, Precondition, Explicit, DomainAxiom, Ghost) def sourceCodeTypes: Set[AssumptionType] = AssumptionType.values.diff(explicitAssumptionTypes ++ explicitAssertionTypes ++ verificationAnnotationTypes ++ internalTypes) def getMaxPriorityAssumptionType(types: Set[AssumptionType]): Option[AssumptionType] = { @@ -20,9 +21,12 @@ object AssumptionType extends Enumeration { priorityList.find(t => types.contains(t)) } - def getPostcondType(isAbstractFunction: Boolean, dependencyType: Option[DependencyType]=None): AssumptionType = { + def getPostcondType(isAbstractFunction: Boolean, dependencyType: Option[DependencyType]=None, isImported: Boolean=false): AssumptionType = { + if(isImported) return ImportedPostcondition + dependencyType.flatMap(_.assertionType match { case AssumptionType.Explicit | AssumptionType.ExplicitPostcondition => Some(AssumptionType.ExplicitPostcondition) + case AssumptionType.ImportedPostcondition => Some(AssumptionType.ImportedPostcondition) case AssumptionType.Ghost | AssumptionType.ImplicitPostcondition => Some(AssumptionType.ImplicitPostcondition) case AssumptionType.Internal => Some(AssumptionType.Internal) case AssumptionType.CustomInternal => Some(AssumptionType.CustomInternal) From ae769dd2cb60008643aaeb1e394ea2cd2ac01955 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 10 Mar 2026 17:34:56 +0100 Subject: [PATCH 382/474] treat ghost code identical to normal code --- src/main/scala/dependencyAnalysis/AnalysisInfo.scala | 5 +++-- src/main/scala/supporters/functions/FunctionData.scala | 5 ++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala index 4a6ad4122..a3bd5ea5e 100644 --- a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala +++ b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala @@ -13,7 +13,7 @@ object AssumptionType extends Enumeration { def internalTypes: Set[AssumptionType] = Set(Internal, Trigger, CustomInternal) // will always be hidden from user def joinConditionTypes: Set[AssumptionType] = postconditionTypes ++ Set(FunctionBody) def importedTypes: Set[AssumptionType] = Set(ImportedPostcondition) - def verificationAnnotationTypes: Set[AssumptionType] = Set(FunctionBody, LoopInvariant, Rewrite, ExplicitPostcondition, ImplicitPostcondition, ImportedPostcondition, Precondition, Explicit, DomainAxiom, Ghost) + def verificationAnnotationTypes: Set[AssumptionType] = Set(FunctionBody, LoopInvariant, Rewrite, ExplicitPostcondition, ImplicitPostcondition, ImportedPostcondition, Precondition, Explicit, DomainAxiom) def sourceCodeTypes: Set[AssumptionType] = AssumptionType.values.diff(explicitAssumptionTypes ++ explicitAssertionTypes ++ verificationAnnotationTypes ++ internalTypes) def getMaxPriorityAssumptionType(types: Set[AssumptionType]): Option[AssumptionType] = { @@ -27,9 +27,10 @@ object AssumptionType extends Enumeration { dependencyType.flatMap(_.assertionType match { case AssumptionType.Explicit | AssumptionType.ExplicitPostcondition => Some(AssumptionType.ExplicitPostcondition) case AssumptionType.ImportedPostcondition => Some(AssumptionType.ImportedPostcondition) - case AssumptionType.Ghost | AssumptionType.ImplicitPostcondition => Some(AssumptionType.ImplicitPostcondition) + case AssumptionType.ImplicitPostcondition => Some(AssumptionType.ImplicitPostcondition) case AssumptionType.Internal => Some(AssumptionType.Internal) case AssumptionType.CustomInternal => Some(AssumptionType.CustomInternal) + case AssumptionType.Ghost => None case _ => None }).getOrElse( if(isAbstractFunction) AssumptionType.ExplicitPostcondition else AssumptionType.ImplicitPostcondition diff --git a/src/main/scala/supporters/functions/FunctionData.scala b/src/main/scala/supporters/functions/FunctionData.scala index 5f6792008..e8972813e 100644 --- a/src/main/scala/supporters/functions/FunctionData.scala +++ b/src/main/scala/supporters/functions/FunctionData.scala @@ -152,8 +152,7 @@ class FunctionData(val programFunction: ast.Function, lazy val postconditionType: AssumptionType = DependencyAnalyzer.extractDependencyTypeFromInfo(programFunction.info).map(_.assertionType) .getOrElse(if(programFunction.body.isDefined) AssumptionType.ImplicitPostcondition else AssumptionType.ExplicitPostcondition) - lazy val functionBodyType: AssumptionType = DependencyAnalyzer.extractDependencyTypeFromInfo(programFunction.info).map(_.assumptionType) - .getOrElse(AssumptionType.FunctionBody) + lazy val functionBodyType: AssumptionType = AssumptionType.FunctionBody // TODO ake: maybe extract from programFunction.info but make sure both ghost and non-ghost function bodies are treated as verification annotations /* * Data collected during phases 1 (well-definedness checking) and 2 (verification) @@ -342,7 +341,7 @@ class FunctionData(val programFunction: ast.Function, val bodyBindings: Map[Var, Term] = Map(formalResult -> limitedFunctionApplication) val bodies = translatedPosts.map(tPost => Let(bodyBindings, Implies(pre, FunctionPreconditionTransformer.transform(tPost, program)))) bodies.map(b => (Forall(arguments, b, Seq(Trigger(limitedFunctionApplication))), - Option.when(isAnalysisEnabled)((StringAnalysisSourceInfo("postPreconditionPropagationAxiom", programFunction.pos), postconditionType)))) + Option.when(isAnalysisEnabled)((StringAnalysisSourceInfo("postPreconditionPropagationAxiom", programFunction.pos), AssumptionType.ImplicitPostcondition)))) } else Seq() postPreconditions } From 392473c9d8393ae8893e0c673cd95fedc18b10a6 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 10 Mar 2026 17:40:32 +0100 Subject: [PATCH 383/474] adjust progress and guidance for abstract and imported methods --- .../DependencyAnalysisUserTool.scala | 8 +--- .../DependencyGraphInterpreter.scala | 41 +++++++++++-------- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala index c99fc794e..da39b7beb 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala @@ -313,11 +313,8 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete def handleVerificationGuidanceQuery(): Unit = { - val failedAssertions = fullGraphInterpreter.getFailedAssertions - println(s"Failed assertions:\n\t${failedAssertions.mkString("\n\t")}\n") - val assumptionRanking = fullGraphInterpreter.computeAssumptionRanking().filter(_._2 > 0.0) - println(s"Assumptions and their impact on progress:\n\t${assumptionRanking.mkString("\n\t")}\n") + println(s"Assumptions/unverified assertions and the number of dependents:\n\t${assumptionRanking.mkString("\n\t")}\n") val memberCoverageRanking = memberInterpreters.filter(mInterpreter => mInterpreter.getMember.isDefined && mInterpreter.getMember.get.isInstanceOf[Method]) .map(mInterpreter => (mInterpreter.getMember.get.name, mInterpreter.computeUncoveredStatements())) @@ -327,9 +324,6 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete def handleVerificationGuidanceOldQuery(): Unit = { - val failedAssertions = fullGraphInterpreter.getFailedAssertions - println(s"Failed assertions:\n\t${failedAssertions.mkString("\n\t")}\n") - val assumptionRanking = fullGraphInterpreter.computeAssumptionRankingOld().filter(_._2 > 0) println(s"Assumptions and the number of dependents:\n\t${assumptionRanking.mkString("\n\t")}\n") diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index c048eb809..5d056f89a 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -322,7 +322,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen private def computeAssertionQuality(allDependencies: Set[CompactUserLevelDependencyAnalysisNode], assertion: AnalysisSourceInfo): Double = { val assertionNodes = sourceToAssertionNodes.getOrElse(assertion, Set.empty).filter(node => node.isInstanceOf[GeneralAssertionNode]) - val failedAssertionNodes = assertionNodes.filter(node => node.asInstanceOf[GeneralAssertionNode].hasFailed) + val failedAssertionNodes = assertionNodes.filter(node => node.asInstanceOf[GeneralAssertionNode].hasFailed || node.assumptionType.equals(AssumptionType.ExplicitPostcondition)) // assertions with failures have quality = 0.0 if(failedAssertionNodes.nonEmpty) return 0.0 @@ -332,9 +332,11 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen (numDepsTotal - explicitDeps.size).toDouble / numDepsTotal.toDouble } + def getAssertionsRelevantForProgress: Map[AnalysisSourceInfo, Set[DependencyAnalysisNode]] = sourceToAssertionNodes.filter(ass => ass._2.map(_.assumptionType).intersect(AssumptionType.importedTypes).isEmpty) + def computeVerificationProgressOptimized(): (Double, Double, String) = { - val allAssertions = sourceToAssertionNodes.keySet.toList + val allAssertions = getAssertionsRelevantForProgress.keySet.toList val assertionDeps = allAssertions map (ass => (deps(ass), ass)) val specQuality = computeSpecQuality(assertionDeps.flatMap(_._1).toSet) @@ -368,7 +370,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen def computeVerificationProgressNaive(): (Double, Double, String) = { - val allAssertions = toUserLevelNodes(getNonInternalAssertionNodes) + val allAssertions = toUserLevelNodes(getNonInternalAssertionNodes).filter(ass => ass.assertionTypes.intersect(AssumptionType.importedTypes).isEmpty) // println(s"#assertions: ${allAssertions.size}") @@ -376,7 +378,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen // TODO ake: this is suuuper slow. Can we reuse previously computed results? Caching? val relevantDependenciesPerAssertion = allAssertions .map(ass => (ass, toUserLevelNodes(getAllNonInternalDependencies(ass.lowerLevelNodes.map(_.id))).diffBySource(Set(ass)))).toMap - .filter{case (ass, assumptions) => assumptions.nonEmpty || ass.hasFailures} // filter out trivial assertions like `assert true` + .filter{case (ass, assumptions) => assumptions.nonEmpty || ass.hasFailures || ass.assertionTypes.contains(AssumptionType.ExplicitPostcondition)} // filter out trivial assertions like `assert true` // val endTime = System.nanoTime() // println(s"Runtime of computing dependencies per assertion: ${(endTime-startTime)/1e6}ms") @@ -399,8 +401,10 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen // assertions val relevantAssertions = relevantDependenciesPerAssertion val assertionsWithFailures = relevantAssertions.filter(assertion => assertion._1.hasFailures).keySet.getSourceSet() - val assertionsWithExplicitDeps = relevantAssertions.filter(deps => !deps._1.hasFailures && deps._2.exists(d => AssumptionType.explicitAssumptionTypes.intersect(d.assumptionTypes).nonEmpty)).keySet.getSourceSet().diff(assertionsWithFailures) - val fullyVerifiedAssertions = relevantAssertions.keySet.getSourceSet().diff(assertionsWithExplicitDeps).diff(assertionsWithFailures) + val explicitPostconditions = relevantAssertions.filter(assertion => assertion._1.assertionTypes.contains(AssumptionType.ExplicitPostcondition)).keySet.getSourceSet().diff(assertionsWithFailures) + val assertionsWithZeroProofQuality = assertionsWithFailures.union(explicitPostconditions) + val assertionsWithExplicitDeps = relevantAssertions.filter(deps => !deps._1.hasFailures && deps._2.exists(d => AssumptionType.explicitAssumptionTypes.intersect(d.assumptionTypes).nonEmpty)).keySet.getSourceSet().diff(assertionsWithZeroProofQuality) + val fullyVerifiedAssertions = relevantAssertions.keySet.getSourceSet().diff(assertionsWithExplicitDeps).diff(assertionsWithZeroProofQuality) val numRelevantAssertions = relevantAssertions.keySet.size.toDouble @@ -412,7 +416,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen // Lea's metric val proofQualityPerAssertion = relevantAssertions.toList.map { case (assertion, assumptions) => - if(assertion.hasFailures) (0.0, assertion) + if(assertionsWithZeroProofQuality.contains(assertion.source)) (0.0, assertion) else { val nonExplicitDeps = UserLevelDependencyAnalysisNode.extractNonExplicitAssumptionNodes(assumptions) (nonExplicitDeps.size.toDouble / assumptions.size.toDouble, assertion) @@ -441,6 +445,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen s"Fully verified assertions:\n\t\t${getString(fullyVerifiedAssertions)}" + "\n\n" + s"Assertions depending on explicit assumptions:\n\t\t${getString(assertionsWithExplicitDeps)}" + "\n\n" + s"Assertions with failures:\n\t\t${getString(assertionsWithFailures)}" + "\n\n" + + s"Explicit Postcondition:\n\t\t${getString(explicitPostconditions)}" + "\n\n" + "\n" + s"Assertion Qualities:\n\t\t${proofQualityPerAssertion.filterNot(_._1 == 1.0).sortBy(_._2.source.getLineNumber).mkString("\n\t\t")}" + "\n\n" + "\n" + @@ -454,19 +459,24 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen /* returns an ordered list of (Assumption, #dependents) */ def computeAssumptionRanking(): List[(String, Double)] = { - val allAssertions = toUserLevelNodes(getNonInternalAssertionNodes) + val allAssertions = toUserLevelNodes(getNonInternalAssertionNodes).filter(ass => ass.assertionTypes.intersect(AssumptionType.importedTypes).isEmpty) val relevantDependenciesPerAssertion = allAssertions .map(ass => (ass, toUserLevelNodes(getAllNonInternalDependencies(ass.lowerLevelNodes.map(_.id))).diffBySource(Set(ass)))).toMap - .filter{case (assertion, assumptions) => assumptions.nonEmpty || assertion.hasFailures} + .filter{case (assertion, assumptions) => assumptions.nonEmpty || assertion.hasFailures || assertion.assertionTypes.contains(AssumptionType.ExplicitPostcondition)} val numAssertions = relevantDependenciesPerAssertion.size.toDouble - val assumptionImpacts= relevantDependenciesPerAssertion.toList.flatMap { case (assertion, assumptions) => + val assumptionImpacts= relevantDependenciesPerAssertion.toList.flatMap { case (_, assumptions) => val explicitDeps = UserLevelDependencyAnalysisNode.extractExplicitAssumptionNodes(assumptions) - explicitDeps.map(node => (node, 1.0/assumptions.size/numAssertions)).toList - }.groupBy(_._1).map{case (assumption, impacts) => (assumption.source.getTopLevelSource.toString, impacts.map(_._2).sum)}.toList + explicitDeps.map(node => (node.source, 1.0/assumptions.size/numAssertions)).toList + } + + val unverifiedAssertionImpacts = getAssertionsWithZeroQuality.map(assertion => (assertion, 1.0/numAssertions)).toList + + val totalImpacts1 = (assumptionImpacts ++ unverifiedAssertionImpacts).groupBy(_._1) + val totalImpacts = totalImpacts1.map{case (assumption, impacts) => (assumption.getTopLevelSource.toString, impacts.map(_._2).sum)}.toList - assumptionImpacts.sortBy(_._2).reverse + totalImpacts.sortBy(_._2).reverse } def computeAssumptionRankingOld(): List[(String, Int)] = { @@ -474,14 +484,13 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen .toList.sortBy(_._2).reverse } - def getFailedAssertions: List[String] = { + def getAssertionsWithZeroQuality: Set[AnalysisSourceInfo] = { val allAssertions = toUserLevelNodes(getNonInternalAssertionNodes) - allAssertions.filter(_.hasFailures).getSourceSet().toList.sortBy(_.getLineNumber).map(_.getTopLevelSource.toString) + allAssertions.filter(assertion => assertion.hasFailures || assertion.assertionTypes.contains(AssumptionType.ExplicitPostcondition)).getSourceSet() } def computeUncoveredStatements(): Int = { val allAssertions = toUserLevelNodes(getNonInternalAssertionNodes) - // TODO ake: once the infinite loop in the deps() function is removed, we should use this optimized version val allDependencies = allAssertions.flatMap(ass => toUserLevelNodes(getAllNonInternalDependencies(ass.lowerLevelNodes.map(_.id))).diffBySource(Set(ass))).getSourceSet() val allNodes = toUserLevelNodes(getNonInternalAssumptionNodes) From bedaee1d37fc0c6260ca58f9e1941f1d29627f4e Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 10 Mar 2026 20:59:46 +0100 Subject: [PATCH 384/474] nodes with failed obligations are always explicit assumptions --- src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index 259c85088..6f1a40c58 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -359,8 +359,11 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { } override def addAssertionFailedNode(failedAssertion: Term, assertionType: AssumptionType, sourceInfo: AnalysisSourceInfo): Option[Int] = { + val assumeNode = SimpleAssumptionNode(failedAssertion, None, sourceInfo, AssumptionType.Explicit, isClosed=false, isJoinNode=false) val assertFailedNode = SimpleAssertionNode(failedAssertion, assertionType, sourceInfo, isClosed=false, hasFailed=true) + dependencyGraph.addNode(assumeNode) dependencyGraph.addNode(assertFailedNode) + dependencyGraph.addEdges(Set(assumeNode.id), assertFailedNode.id) Some(assertFailedNode.id) } From 73fcd94fd0802573c5e11171381539f446cd7e3d Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 11 Mar 2026 09:08:31 +0100 Subject: [PATCH 385/474] fix spec quality computation --- .../dependencyAnalysis/AnalysisInfo.scala | 7 +-- .../DependencyAnalysisUserTool.scala | 3 +- .../DependencyGraphInterpreter.scala | 47 +++++++++++-------- .../UserLevelDependencyAnalysisNode.scala | 2 +- 4 files changed, 34 insertions(+), 25 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala index a3bd5ea5e..0517dfe3e 100644 --- a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala +++ b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala @@ -3,7 +3,7 @@ package viper.silicon.dependencyAnalysis object AssumptionType extends Enumeration { type AssumptionType = Value - val Explicit, LoopInvariant, PathCondition, Rewrite, SourceCode, DomainAxiom, Implicit, Internal, Trigger, ExplicitPostcondition, ImplicitPostcondition, ImportedPostcondition, MethodCall, FunctionBody, Precondition, Ghost, CustomInternal, Unknown = Value + val Explicit, LoopInvariant, PathCondition, Rewrite, SourceCode, DomainAxiom, Implicit, Internal, Trigger, ExplicitPostcondition, ImplicitPostcondition, ImportedPostcondition, MethodCall, FunctionBody, Precondition, Ghost, Annotation, CustomInternal, Unknown = Value def fromString(s: String): Option[Value] = values.find(_.toString == s) @@ -13,7 +13,7 @@ object AssumptionType extends Enumeration { def internalTypes: Set[AssumptionType] = Set(Internal, Trigger, CustomInternal) // will always be hidden from user def joinConditionTypes: Set[AssumptionType] = postconditionTypes ++ Set(FunctionBody) def importedTypes: Set[AssumptionType] = Set(ImportedPostcondition) - def verificationAnnotationTypes: Set[AssumptionType] = Set(FunctionBody, LoopInvariant, Rewrite, ExplicitPostcondition, ImplicitPostcondition, ImportedPostcondition, Precondition, Explicit, DomainAxiom) + def verificationAnnotationTypes: Set[AssumptionType] = Set(FunctionBody, LoopInvariant, Rewrite, ExplicitPostcondition, ImplicitPostcondition, ImportedPostcondition, Precondition, Explicit, DomainAxiom, Annotation) def sourceCodeTypes: Set[AssumptionType] = AssumptionType.values.diff(explicitAssumptionTypes ++ explicitAssertionTypes ++ verificationAnnotationTypes ++ internalTypes) def getMaxPriorityAssumptionType(types: Set[AssumptionType]): Option[AssumptionType] = { @@ -30,7 +30,7 @@ object AssumptionType extends Enumeration { case AssumptionType.ImplicitPostcondition => Some(AssumptionType.ImplicitPostcondition) case AssumptionType.Internal => Some(AssumptionType.Internal) case AssumptionType.CustomInternal => Some(AssumptionType.CustomInternal) - case AssumptionType.Ghost => None + case AssumptionType.Annotation | AssumptionType.Ghost => None case _ => None }).getOrElse( if(isAbstractFunction) AssumptionType.ExplicitPostcondition else AssumptionType.ImplicitPostcondition @@ -56,6 +56,7 @@ object DependencyType { val Trigger: DependencyType = DependencyType(AssumptionType.Trigger, AssumptionType.Trigger) val MethodCall: DependencyType = DependencyType(AssumptionType.MethodCall, AssumptionType.MethodCall) val Ghost: DependencyType = DependencyType.make(AssumptionType.Ghost) + val Annotation: DependencyType = DependencyType.make(AssumptionType.Annotation) def make(singleType: AssumptionType): DependencyType = DependencyType(singleType, singleType) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala index da39b7beb..c4ab99241 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala @@ -316,10 +316,11 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete val assumptionRanking = fullGraphInterpreter.computeAssumptionRanking().filter(_._2 > 0.0) println(s"Assumptions/unverified assertions and the number of dependents:\n\t${assumptionRanking.mkString("\n\t")}\n") + println("Uncovered source code per method: ") val memberCoverageRanking = memberInterpreters.filter(mInterpreter => mInterpreter.getMember.isDefined && mInterpreter.getMember.get.isInstanceOf[Method]) .map(mInterpreter => (mInterpreter.getMember.get.name, mInterpreter.computeUncoveredStatements())) .toList.filter(_._2 > 0).sortBy(_._2).reverse - println(s"Methods and the number of uncovered statements:\n\t${memberCoverageRanking.mkString("\n\t")}\n") + println(s"\nMethods and the number of uncovered statements:\n\t${memberCoverageRanking.mkString("\n\t")}\n") } def handleVerificationGuidanceOldQuery(): Unit = { diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 5d056f89a..dfe2c191c 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -108,7 +108,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen exportProgram(program, Verifier.config.dependencyAnalysisExportPath() + "/" + name) } - private def exportProgram(program: Program, path: String) = { + private def exportProgram(program: Program, path: String): Unit = { // TODO ake: we should copy the original source file in order to keep the line numbering! val writer = new PrintWriter(path + "/program.vpr") writer.println(program.toString()) @@ -235,19 +235,6 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen computeVerificationProgressOptimized() } - private def computeSpecQuality(coveredNodes: Set[CompactUserLevelDependencyAnalysisNode]): Double = { - - val nonSourceCodeAssumptionTypes = AssumptionType.explicitAssumptionTypes ++ AssumptionType.verificationAnnotationTypes - val allSourceCodeNodes = toCompactUserLevelNodes(getNonInternalAssumptionNodes).filter(n => nonSourceCodeAssumptionTypes.intersect(n.assumptionTypes).isEmpty).map(_.source.getTopLevelSource) - - if(allSourceCodeNodes.isEmpty) return 1.0 - - val coveredSourceCodeNodes = coveredNodes.map(_.source.getTopLevelSource).intersect(allSourceCodeNodes) -// println(s"Covered Source Code:\n\t${coveredSourceCodeNodes.toList.sortBy(_.getLineNumber).mkString("\n\t")}") -// println(s"Uncovered Source Code:\n\t${allSourceCodeNodes.diff(coveredSourceCodeNodes).toList.sortBy(_.getLineNumber).mkString("\n\t")}") - println(s"Spec Quality = ${coveredSourceCodeNodes.size} / ${allSourceCodeNodes.size}") - coveredSourceCodeNodes.size.toDouble / allSourceCodeNodes.size.toDouble - } // TODO ake: remove profiling artifacts // var perMethodDependencyRuntime: Long = 0L // var depsToPostcondRuntime: Long = 0L @@ -353,8 +340,8 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen val proofQualityLea = if(numAssertions > 0) assertionQualitiesSum / numAssertions.toDouble else 1.0 val info = { -// s"Assertions with dependencies on explicit assumptions:\n\t\t${assertionQualities.filterNot(_._1 == 1.0).sortBy(_._2.getLineNumber).mkString("\n\t\t")}" + "\n\n" + -// s"Assertions with perfect proof quality:\n\t\t${fullyVerifiedAssertions.map(_._2).sortBy(_.getLineNumber).mkString("\n\t\t")}" + "\n\n" + +// s"Assertions with dependencies on explicit assumptions:\n\t\t${assertionQualities.filterNot(_._1 == 1.0).sortBy(n => (n._2.getLineNumber, n._2.toString())).mkString("\n\t\t")}" + "\n\n" + +// s"Assertions with perfect proof quality:\n\t\t${fullyVerifiedAssertions.map(_._2).sortBy(n => (n.getLineNumber, n.toString())).mkString("\n\t\t")}" + "\n\n" + s"specQuality = $specQuality\n" + s"proof quality (Peter): $numFullyVerifiedAssertions / $numAssertions = $proofQualityPeter\n" + s"proof quality (Lea): $assertionQualitiesSum / $numAssertions = $proofQualityLea\n" @@ -369,6 +356,22 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen } + private def computeSpecQuality(coveredNodes: Set[CompactUserLevelDependencyAnalysisNode]): Double = { + + val explicitAssertions = toCompactUserLevelNodes(getExplicitAssertionNodes) + val nonSourceCodeAssumptionTypes = AssumptionType.explicitAssumptionTypes ++ AssumptionType.verificationAnnotationTypes + val allSourceCodeNodes = toCompactUserLevelNodes(getNonInternalAssumptionNodes).filter(n => nonSourceCodeAssumptionTypes.intersect(n.assumptionTypes).isEmpty).map(_.source.getTopLevelSource).diff(explicitAssertions.map(_.source.getTopLevelSource)) + + if(allSourceCodeNodes.isEmpty) return 1.0 + + val coveredSourceCodeNodes = coveredNodes.map(_.source.getTopLevelSource).intersect(allSourceCodeNodes) +// println(s"Covered Source Code:\n\t${coveredSourceCodeNodes.toList.sortBy(n => (n.getLineNumber, n.toString())).mkString("\n\t")}") +// println(s"Uncovered Source Code:\n\t${allSourceCodeNodes.diff(coveredSourceCodeNodes).toList.sortBy(n => (n.getLineNumber, n.toString())).mkString("\n\t")}") + println(s"Spec Quality = ${coveredSourceCodeNodes.size} / ${allSourceCodeNodes.size}") + coveredSourceCodeNodes.size.toDouble / allSourceCodeNodes.size.toDouble + } + + def computeVerificationProgressNaive(): (Double, Double, String) = { val allAssertions = toUserLevelNodes(getNonInternalAssertionNodes).filter(ass => ass.assertionTypes.intersect(AssumptionType.importedTypes).isEmpty) @@ -428,7 +431,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen def getString(nodes: Set[AnalysisSourceInfo]): String = { - nodes.toList.sortBy(_.getLineNumber).mkString("\n\t\t") + nodes.toList.sortBy(n => (n.getLineNumber, n.toString())).mkString("\n\t\t") } val info = { @@ -447,7 +450,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen s"Assertions with failures:\n\t\t${getString(assertionsWithFailures)}" + "\n\n" + s"Explicit Postcondition:\n\t\t${getString(explicitPostconditions)}" + "\n\n" + "\n" + - s"Assertion Qualities:\n\t\t${proofQualityPerAssertion.filterNot(_._1 == 1.0).sortBy(_._2.source.getLineNumber).mkString("\n\t\t")}" + "\n\n" + + s"Assertion Qualities:\n\t\t${proofQualityPerAssertion.filterNot(_._1 == 1.0).sortBy(n => (n._2.source.getLineNumber, n._2.toString())).mkString("\n\t\t")}" + "\n\n" + "\n" + s"Verification Progress (Peter):\n\t${coveredSourceCodeStmts.size}/${coveredSourceCodeStmts.size + uncoveredSourceCodeStmts.size} * " + s"${fullyVerifiedAssertions.size}/${relevantAssertions.keySet.size} = ${"%.2f".format(verificationProgressPeter)}" + "\n" + @@ -493,10 +496,14 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen val allAssertions = toUserLevelNodes(getNonInternalAssertionNodes) val allDependencies = allAssertions.flatMap(ass => toUserLevelNodes(getAllNonInternalDependencies(ass.lowerLevelNodes.map(_.id))).diffBySource(Set(ass))).getSourceSet() + val explicitAssertions = toUserLevelNodes(getExplicitAssertionNodes) val allNodes = toUserLevelNodes(getNonInternalAssumptionNodes) val allSourceCodeStmts = allNodes.getSourceSet().diff(UserLevelDependencyAnalysisNode.extractByAssumptionType(allNodes, - AssumptionType.explicitAssumptionTypes ++ AssumptionType.verificationAnnotationTypes ++ Set(AssumptionType.FunctionBody)).getSourceSet()) - allSourceCodeStmts.diff(allDependencies).size + AssumptionType.explicitAssumptionTypes ++ AssumptionType.verificationAnnotationTypes).getSourceSet()).diff(explicitAssertions.getSourceSet()) + val uncoveredSourceCodeStmts = allSourceCodeStmts.diff(allDependencies) + if(uncoveredSourceCodeStmts.nonEmpty) + println(s"$name:\n\t${allSourceCodeStmts.diff(allDependencies).toList.sortBy(n => (n.getLineNumber, n.toString())).mkString("\n\t")}") + uncoveredSourceCodeStmts.size } } diff --git a/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala b/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala index 725495bcb..515d6010e 100644 --- a/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala +++ b/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala @@ -41,7 +41,7 @@ object UserLevelDependencyAnalysisNode { } def mkString(nodes: Set[UserLevelDependencyAnalysisNode], sep: String = "\n"): String = { - nodes.toList.sortBy(_.source.getLineNumber).mkString(sep) + nodes.toList.sortBy(n => (n.source.getLineNumber, n.source.toString)).mkString(sep) } def mkUserLevelString(nodes: Set[DependencyAnalysisNode], sep: String = "\n"): String = { From bc73399a8460f285c3ac5231859718f2b02cc37b Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 11 Mar 2026 12:09:36 +0100 Subject: [PATCH 386/474] minor fix for verification failures --- src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala | 3 ++- .../dependencyAnalysis/DependencyGraphInterpreter.scala | 1 + src/main/scala/rules/ExecutionFlowController.scala | 5 +++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index 6f1a40c58..9400c6400 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -359,7 +359,8 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { } override def addAssertionFailedNode(failedAssertion: Term, assertionType: AssumptionType, sourceInfo: AnalysisSourceInfo): Option[Int] = { - val assumeNode = SimpleAssumptionNode(failedAssertion, None, sourceInfo, AssumptionType.Explicit, isClosed=false, isJoinNode=false) + val assumptionType = if(AssumptionType.postconditionTypes.contains(assertionType)) AssumptionType.ExplicitPostcondition else AssumptionType.Explicit + val assumeNode = SimpleAssumptionNode(failedAssertion, None, sourceInfo, assumptionType, isClosed=false, isJoinNode=false) val assertFailedNode = SimpleAssertionNode(failedAssertion, assertionType, sourceInfo, isClosed=false, hasFailed=true) dependencyGraph.addNode(assumeNode) dependencyGraph.addNode(assertFailedNode) diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index dfe2c191c..16a8e4ec3 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -342,6 +342,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen val info = { // s"Assertions with dependencies on explicit assumptions:\n\t\t${assertionQualities.filterNot(_._1 == 1.0).sortBy(n => (n._2.getLineNumber, n._2.toString())).mkString("\n\t\t")}" + "\n\n" + // s"Assertions with perfect proof quality:\n\t\t${fullyVerifiedAssertions.map(_._2).sortBy(n => (n.getLineNumber, n.toString())).mkString("\n\t\t")}" + "\n\n" + +// s"Assertion qualities\n\t\t${assertionQualities.sortBy(n => (n._2.getLineNumber, n._2.toString())).mkString("\n\t\t")}" + "\n\n" + s"specQuality = $specQuality\n" + s"proof quality (Peter): $numFullyVerifiedAssertions / $numAssertions = $proofQualityPeter\n" + s"proof quality (Lea): $assertionQualitiesSum / $numAssertions = $proofQualityLea\n" diff --git a/src/main/scala/rules/ExecutionFlowController.scala b/src/main/scala/rules/ExecutionFlowController.scala index 96d4a4de7..e5d1862bd 100644 --- a/src/main/scala/rules/ExecutionFlowController.scala +++ b/src/main/scala/rules/ExecutionFlowController.scala @@ -107,6 +107,9 @@ object executionFlowController extends ExecutionFlowRules { var localActionSuccess = false + val analysisSourceInfoStackPrev = v.decider.analysisSourceInfoStack.getAnalysisSourceInfos + val analysisSourceInfoStackForcedSource = v.decider.analysisSourceInfoStack.getForcedSource + /* TODO: Consider how to handle situations where the action branches and the first branch * succeeds, i.e. localActionSuccess has been set to true, but the second fails. * Currently, the verification will fail without attempting to remedy the situation, @@ -140,6 +143,8 @@ object executionFlowController extends ExecutionFlowRules { Verifier.config.exhaleMode != ExhaleMode.Greedy } + v.decider.analysisSourceInfoStack.setAnalysisSourceInfo(analysisSourceInfoStackPrev) + v.decider.analysisSourceInfoStack.setForcedSource(analysisSourceInfoStackForcedSource) action(s0.copy(retrying = true, retryLevel = s.retryLevel, moreCompleteExhale = temporaryMCE), v, (s1, r, v1) => { v1.symbExLog.closeScope(sepIdentifier) Q(s1.copy(retrying = false, moreCompleteExhale = s0.moreCompleteExhale), r, v1) From 815b84488a24fe8b2932ef1919e5c83f1abfbae2 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 11 Mar 2026 19:11:09 +0100 Subject: [PATCH 387/474] add precision eval query --- .../DependencyAnalysisUserTool.scala | 79 ++++++++++++++++++- .../DependencyGraphInterpreter.scala | 6 ++ 2 files changed, 84 insertions(+), 1 deletion(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala index c4ab99241..7dea82f7d 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala @@ -5,9 +5,11 @@ import viper.silicon.interfaces.Failure import viper.silver.ast import viper.silver.ast.Method -import java.io.PrintWriter +import java.io.{BufferedWriter, FileWriter, PrintWriter} import scala.annotation.tailrec +import scala.io.Source import scala.io.StdIn.readLine +import scala.util.matching.Regex class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterpreter, memberInterpreters: Seq[DependencyGraphInterpreter], program: ast.Program, verificationErrors: List[Failure]) { @@ -76,6 +78,8 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete handlePruningRequest(inputParts.tail) }else if(inputParts.head.equalsIgnoreCase("benchmark")) { handleBenchmarkQuery() + }else if(inputParts.head.equalsIgnoreCase("precisionEval")) { + handlePrecisionEval(inputParts.tail) }else if(inputParts.head.equalsIgnoreCase("graphSize")){ if(inputParts.tail.isEmpty) { handleGraphSizeQuery(fullGraphInterpreter) @@ -227,6 +231,79 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete println("Done.") } + private def handlePrecisionEval(inputs: Seq[String]): Unit = { + val labelPattern: Regex = """@label\("([^"]+)"\)""".r + val header = "Assertion Label,Sound?,#True Dependencies,#Reported Dependencies,#False-Positives,Runtime" + + def readFile(path: String): Map[String, List[String]] = { + val src = Source.fromFile(path) + try { + src.getLines() + .filter(_.trim.nonEmpty) // skip empty lines + .map { line => + val Array(left, right) = line.split("=", 2) // split into key and rest + val key = left.trim + val values = right.split(",").map(_.trim).toList + key -> values + } + .toMap + } finally { + src.close() + } + } + + def addOutput(bw: BufferedWriter, output: String): Unit = { + bw.write(output) + bw.newLine() + println(output) + } + + def evalSingleAssertion(assertionLabel: String, dependencyLabels: List[String], bw: BufferedWriter): Unit = { + val startAnalysis = System.nanoTime() + val queriedAssertions = fullGraphInterpreter.getAssertionNodesByLabel(assertionLabel) + val allDependencies = fullGraphInterpreter.getAllNonInternalDependencies(queriedAssertions.map(_.id)) + val sourceDependencies = UserLevelDependencyAnalysisNode.from(allDependencies).getSourceSet().diff(UserLevelDependencyAnalysisNode.from(queriedAssertions).getSourceSet()) + + val endAnalysis = System.nanoTime() + val durationMs = (endAnalysis - startAnalysis) / 1e6 + + val sourceDependenciesString = sourceDependencies.mkString("\n\t") + + val isSound = dependencyLabels.forall(sourceDependenciesString.contains) + val imprecise = sourceDependencies.filter(node => + labelPattern.findFirstMatchIn(node.toString) match { + case Some(label) => !dependencyLabels.contains(label.group(1)) + case _ => true + }) + + addOutput(bw, s"$assertionLabel,${if(isSound) "YES" else "NO"},${dependencyLabels.size},${sourceDependencies.size},${imprecise.size},${durationMs}ms") + +// println(s"Queried:\n\t${getSourceInfoString(queriedAssertions)}") +// println(s"\nAll Dependencies (${timeAll}ms):\n\t$sourceDependenciesString") +// +// if(queriedAssertions.exists(_.asInstanceOf[GeneralAssertionNode].hasFailed)) println("\nQueried assertions (partially) FAILED!\n") + } + + assert(inputs.size == 1) + val pathToGroundTruth = inputs.head + + val bw = new BufferedWriter(new FileWriter(pathToGroundTruth.replace(".txt", "_result.csv"))) + + try { + val groundTruths = readFile(pathToGroundTruth) + addOutput(bw, header) + groundTruths.foreach { case (assertionLabel, dependencyLabels) => evalSingleAssertion(assertionLabel, dependencyLabels, bw) } + + bw.close() + println("Done.") + }catch { + case _ => println("Failed.") + }finally { + bw.close() + } + } + + private def handleDependentsQuery(inputs: Set[String]): Unit = { val queriedNodes = getQueriedNodesFromInput(inputs).intersect(fullGraphInterpreter.getNonInternalAssumptionNodes) diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 16a8e4ec3..09aa3889d 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -43,6 +43,12 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen def getNodesByPosition(file: String, line: Int): Set[DependencyAnalysisNode] = getNodes.filter(n => !AssumptionType.internalTypes.contains(n.assumptionType)).filter(node => node.sourceInfo.getLineNumber.isDefined && node.sourceInfo.getLineNumber.get == line && node.sourceInfo.getPositionString.startsWith(file + ".")) + + def getAssertionNodesByLabel(label: String): Set[DependencyAnalysisNode] = { + val fullAnnotation = s"@label(\"$label\")" + getAssertionNodes.filter(node => node.toString.contains(label)) + } + def getDirectDependencies(nodeIdsToAnalyze: Set[Int]): Set[DependencyAnalysisNode] = { var queue = nodeIdsToAnalyze var result: Set[Int] = Set.empty From f2c95bc41a0dbc7a5d540a94ac97b4d0f9819e15 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 11 Mar 2026 22:01:37 +0100 Subject: [PATCH 388/474] add annotate query --- .../DependencyAnalysisUserTool.scala | 63 ++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala index 7dea82f7d..a0714b70f 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala @@ -3,7 +3,7 @@ package viper.silicon.dependencyAnalysis import dependencyAnalysis.UserLevelDependencyAnalysisNode import viper.silicon.interfaces.Failure import viper.silver.ast -import viper.silver.ast.Method +import viper.silver.ast.{AbstractAssign, AnnotationInfo, AnonymousDomainAxiom, Apply, Assert, Assume, Exhale, ExtensionStmt, Fold, Goto, If, Inhale, Label, LocalVarDeclStmt, MakeInfoPair, Method, MethodCall, NamedDomainAxiom, NewStmt, Package, Quasihavoc, Quasihavocall, Seqn, Unfold, While} import java.io.{BufferedWriter, FileWriter, PrintWriter} import scala.annotation.tailrec @@ -80,6 +80,8 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete handleBenchmarkQuery() }else if(inputParts.head.equalsIgnoreCase("precisionEval")) { handlePrecisionEval(inputParts.tail) + }else if(inputParts.head.equalsIgnoreCase("annotate")) { + handleAnnotateQuery(inputParts.tail) }else if(inputParts.head.equalsIgnoreCase("graphSize")){ if(inputParts.tail.isEmpty) { handleGraphSizeQuery(fullGraphInterpreter) @@ -303,6 +305,65 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete } } + private def handleAnnotateQuery(inputs: Seq[String]): Unit = { + var n = 0 + def nextN: Int = { + n = n + 1 + n + } + + def newInfo(info: ast.Info): ast.Info = MakeInfoPair(AnnotationInfo(Map(("label", Seq(s"L$nextN")))), info) + + + def annotateConjungts(exp: ast.Exp): ast.Exp = { + exp match { + case ast.And(l, r) => ast.And(annotateConjungts(l), annotateConjungts(r))(exp.pos, exp.info, exp.errT) + case _ => annotateExp(exp) + } + } + + def annotateExp(exp: ast.Exp): ast.Exp = exp.withMeta((exp.pos, newInfo(exp.info), exp.errT)) + + def annotateSeqn(seqn: ast.Seqn):ast.Seqn = Seqn(seqn.ss.map(annotateStmt), seqn.scopedSeqnDeclarations)(seqn.pos, seqn.info, seqn.errT) + + def annotateStmt(stmt: ast.Stmt): ast.Stmt = { + stmt match { + case Inhale(exp) => Inhale(annotateConjungts(exp))(exp.pos, exp.info, exp.errT) + case Assume(exp) => Assume(annotateConjungts(exp))(exp.pos, exp.info, exp.errT) + case seqn: Seqn => annotateSeqn(seqn) + case If(cond, thn, els) => If(annotateExp(cond), annotateSeqn(thn), annotateSeqn(els))(stmt.pos, stmt.info, stmt.errT) + case While(cond, invs, body) => While(annotateExp(cond), invs.map(annotateConjungts), annotateSeqn(body))(stmt.pos, stmt.info, stmt.errT) + case Label(name, invs) => Label(name, invs.map(annotateConjungts))(stmt.pos, stmt.info, stmt.errT) + case _: Goto | _: LocalVarDeclStmt => stmt + case Package(wand, proofScript) => Package(wand, annotateSeqn(proofScript))(stmt.pos, newInfo(stmt.info), stmt.errT) + case _ => stmt.withMeta((stmt.pos, newInfo(stmt.info), stmt.errT)) + } + } + + def annotateDomain(domain: ast.Domain): ast.Domain = { + def annotateAxiom(axiom: ast.DomainAxiom): ast.DomainAxiom = axiom match { + case NamedDomainAxiom(name, exp) => NamedDomainAxiom(name, annotateExp(exp))(axiom.pos, axiom.info, axiom.domainName, axiom.errT) + case AnonymousDomainAxiom(exp) => AnonymousDomainAxiom(annotateExp(exp))(axiom.pos, axiom.info, axiom.domainName, axiom.errT) + } + domain.copy(axioms = domain.axioms.map(annotateAxiom))(domain.pos, domain.info, domain.errT) + } + + def annotateFunction(function: ast.Function): ast.Function = + function.copy(pres=function.pres.map(annotateConjungts), posts=function.posts.map(annotateConjungts), body=function.body.map(annotateExp))(function.pos, function.info, function.errT) + + def annotateMethod(method: ast.Method): ast.Method = + method.copy(pres=method.pres.map(annotateConjungts), posts=method.posts.map(annotateConjungts), body=method.body.map(annotateSeqn))(method.pos, method.info, method.errT) + + val newProgram: ast.Program = program.copy(domains=program.domains.map(annotateDomain), functions=program.functions.map(annotateFunction), + methods=program.methods.map(annotateMethod))(program.pos, program.info, program.errT) + + val writer = new PrintWriter(inputs.head) + writer.println(newProgram.toString()) + writer.close() + println("Done.") + + } + private def handleDependentsQuery(inputs: Set[String]): Unit = { From c36fe68dff6c797a058558f058837ccf07d4d640 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 12 Mar 2026 10:03:06 +0100 Subject: [PATCH 389/474] update precisionEval query --- .../DependencyAnalysisUserTool.scala | 32 +++++++++++++------ .../DependencyGraphInterpreter.scala | 2 +- src/test/scala/DependencyAnalysisTests.scala | 12 +++++++ 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala index a0714b70f..01326ef14 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala @@ -3,9 +3,12 @@ package viper.silicon.dependencyAnalysis import dependencyAnalysis.UserLevelDependencyAnalysisNode import viper.silicon.interfaces.Failure import viper.silver.ast -import viper.silver.ast.{AbstractAssign, AnnotationInfo, AnonymousDomainAxiom, Apply, Assert, Assume, Exhale, ExtensionStmt, Fold, Goto, If, Inhale, Label, LocalVarDeclStmt, MakeInfoPair, Method, MethodCall, NamedDomainAxiom, NewStmt, Package, Quasihavoc, Quasihavocall, Seqn, Unfold, While} +import viper.silver.ast.{AnnotationInfo, AnonymousDomainAxiom, Assume, Goto, If, Inhale, Label, LocalVarDeclStmt, MakeInfoPair, Method, NamedDomainAxiom, Package, Seqn, While} import java.io.{BufferedWriter, FileWriter, PrintWriter} +import java.nio.file.{Path, Paths} +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter import scala.annotation.tailrec import scala.io.Source import scala.io.StdIn.readLine @@ -235,7 +238,7 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete private def handlePrecisionEval(inputs: Seq[String]): Unit = { val labelPattern: Regex = """@label\("([^"]+)"\)""".r - val header = "Assertion Label,Sound?,#True Dependencies,#Reported Dependencies,#False-Positives,Runtime" + val header = "Assertion Label,Sound?,#True Dependencies,#Reported Dependencies,#False-Positives,Call Graph Size,Runtime" def readFile(path: String): Map[String, List[String]] = { val src = Source.fromFile(path) @@ -260,7 +263,7 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete println(output) } - def evalSingleAssertion(assertionLabel: String, dependencyLabels: List[String], bw: BufferedWriter): Unit = { + def evalSingleAssertion(assertionLabel: String, groundTruthLabels: List[String], callGraphLabels: List[String], bw: BufferedWriter): Unit = { val startAnalysis = System.nanoTime() val queriedAssertions = fullGraphInterpreter.getAssertionNodesByLabel(assertionLabel) val allDependencies = fullGraphInterpreter.getAllNonInternalDependencies(queriedAssertions.map(_.id)) @@ -271,14 +274,14 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete val sourceDependenciesString = sourceDependencies.mkString("\n\t") - val isSound = dependencyLabels.forall(sourceDependenciesString.contains) + val isSound = groundTruthLabels.forall(sourceDependenciesString.contains) val imprecise = sourceDependencies.filter(node => labelPattern.findFirstMatchIn(node.toString) match { - case Some(label) => !dependencyLabels.contains(label.group(1)) + case Some(label) => !groundTruthLabels.contains(label.group(1)) case _ => true }) - addOutput(bw, s"$assertionLabel,${if(isSound) "YES" else "NO"},${dependencyLabels.size},${sourceDependencies.size},${imprecise.size},${durationMs}ms") + addOutput(bw, s"$assertionLabel,${if(isSound) "YES" else "NO"},${groundTruthLabels.size},${sourceDependencies.size},${imprecise.size},${callGraphLabels.size},${durationMs}ms") // println(s"Queried:\n\t${getSourceInfoString(queriedAssertions)}") // println(s"\nAll Dependencies (${timeAll}ms):\n\t$sourceDependenciesString") @@ -287,14 +290,23 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete } assert(inputs.size == 1) - val pathToGroundTruth = inputs.head - val bw = new BufferedWriter(new FileWriter(pathToGroundTruth.replace(".txt", "_result.csv"))) + val dir: Path = Paths.get(inputs.head) + + val pathToGroundTruth = dir.resolve("ground-truth.txt") + val pathToCallGraphs = dir.resolve("call-graphs.txt") + + val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HHmmss") + val timestamp = LocalDateTime.now().format(formatter) + + val output: Path = dir.resolve( s"result_$timestamp.csv") + val bw = new BufferedWriter(new FileWriter(output.toUri.getPath)) try { - val groundTruths = readFile(pathToGroundTruth) + val groundTruths = readFile(pathToGroundTruth.toUri.getPath) + val callGraphs = readFile(pathToCallGraphs.toUri.getPath) addOutput(bw, header) - groundTruths.foreach { case (assertionLabel, dependencyLabels) => evalSingleAssertion(assertionLabel, dependencyLabels, bw) } + groundTruths.foreach { case (assertionLabel, dependencyLabels) => evalSingleAssertion(assertionLabel, dependencyLabels, callGraphs.getOrElse(assertionLabel, List()), bw) } bw.close() println("Done.") diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 09aa3889d..49bda55d1 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -46,7 +46,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen def getAssertionNodesByLabel(label: String): Set[DependencyAnalysisNode] = { val fullAnnotation = s"@label(\"$label\")" - getAssertionNodes.filter(node => node.toString.contains(label)) + getAssertionNodes.filter(node => node.toString.contains(fullAnnotation)) } def getDirectDependencies(nodeIdsToAnalyze: Set[Int]): Set[DependencyAnalysisNode] = { diff --git a/src/test/scala/DependencyAnalysisTests.scala b/src/test/scala/DependencyAnalysisTests.scala index 61942a400..c3c045b6c 100644 --- a/src/test/scala/DependencyAnalysisTests.scala +++ b/src/test/scala/DependencyAnalysisTests.scala @@ -18,15 +18,20 @@ class DependencyAnalysisTests extends AnyFunSuite with DependencyAnalysisTestFra val CHECK_PRECISION = false val EXECUTE_TEST = true val EXECUTE_PERFORMANCE_BENCHMARK = false + val ANNOTATE_PROGRAMS = false override val EXPORT_PRUNED_PROGRAMS: Boolean = false val ignores: Seq[String] = Seq() val testDirectories: Seq[String] = Seq( "dependencyAnalysisTests/all", "dependencyAnalysisTests/unitTests", "dependencyAnalysisTests/real-world-examples", +// "dependencyAnalysisTests/viper-online-examples" ) +// override val ignores = Seq("quickselect", "adts", "graph-marking-qps", "linked-list-iterator", "linked-list-predicates") + + if(EXECUTE_PERFORMANCE_BENCHMARK) test("performance benchmark") { @@ -80,6 +85,13 @@ class DependencyAnalysisTests extends AnyFunSuite with DependencyAnalysisTestFra val dependencyGraphInterpreters = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].dependencyGraphInterpretersPerMember val joinedDependencyGraphInterpreter = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].joinedDependencyGraphInterpreter + if(ANNOTATE_PROGRAMS){ + val basePathAnnotatedPrograms = "src/test/resources/dependencyAnalysisTests/viper-online-examples-annotated" + val directory = new File(basePathAnnotatedPrograms) + directory.mkdir() + val dependencyAnalysisUserTool = new DependencyAnalysisUserTool(joinedDependencyGraphInterpreter.get, dependencyGraphInterpreters, program, List()) + dependencyAnalysisUserTool.run(s"annotate $basePathAnnotatedPrograms/$fileName") + } new AnnotatedTest(program, dependencyGraphInterpreters, CHECK_PRECISION).execute() new PruningTest(filePrefix + "/" + fileName, program, joinedDependencyGraphInterpreter.get).execute() // new PrecisionEvaluation(filePrefix, fileName, program, joinedDependencyGraphInterpreter.get).execute() From ce03dff87db7abf328a12412697d451b7ab592b3 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 12 Mar 2026 10:11:44 +0100 Subject: [PATCH 390/474] fix precision test case annotation helper --- src/test/scala/DependencyAnalysisTests.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/test/scala/DependencyAnalysisTests.scala b/src/test/scala/DependencyAnalysisTests.scala index c3c045b6c..87146ffd9 100644 --- a/src/test/scala/DependencyAnalysisTests.scala +++ b/src/test/scala/DependencyAnalysisTests.scala @@ -89,8 +89,10 @@ class DependencyAnalysisTests extends AnyFunSuite with DependencyAnalysisTestFra val basePathAnnotatedPrograms = "src/test/resources/dependencyAnalysisTests/viper-online-examples-annotated" val directory = new File(basePathAnnotatedPrograms) directory.mkdir() + val directoryTestCase = new File(s"$basePathAnnotatedPrograms/$fileName") + directoryTestCase.mkdir() val dependencyAnalysisUserTool = new DependencyAnalysisUserTool(joinedDependencyGraphInterpreter.get, dependencyGraphInterpreters, program, List()) - dependencyAnalysisUserTool.run(s"annotate $basePathAnnotatedPrograms/$fileName") + dependencyAnalysisUserTool.run(s"annotate $basePathAnnotatedPrograms/$fileName/$fileName.vpr") } new AnnotatedTest(program, dependencyGraphInterpreters, CHECK_PRECISION).execute() new PruningTest(filePrefix + "/" + fileName, program, joinedDependencyGraphInterpreter.get).execute() From d2f2a28266408af65987d8ee8b9d44fda3453c53 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 12 Mar 2026 17:13:37 +0100 Subject: [PATCH 391/474] fix precision eval query --- .../DependencyAnalysisUserTool.scala | 28 +++++++++---------- .../DependencyGraphInterpreter.scala | 4 +-- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala index 01326ef14..7117e58d8 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala @@ -237,10 +237,10 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete } private def handlePrecisionEval(inputs: Seq[String]): Unit = { - val labelPattern: Regex = """@label\("([^"]+)"\)""".r + val labelPattern: Regex = """@label\(\s*("?)([^")\s]+)\1\s*\)""".r val header = "Assertion Label,Sound?,#True Dependencies,#Reported Dependencies,#False-Positives,Call Graph Size,Runtime" - def readFile(path: String): Map[String, List[String]] = { + def readFile(path: String): Map[String, Set[String]] = { val src = Source.fromFile(path) try { src.getLines() @@ -248,7 +248,7 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete .map { line => val Array(left, right) = line.split("=", 2) // split into key and rest val key = left.trim - val values = right.split(",").map(_.trim).toList + val values = right.split(",").map(_.trim).toSet key -> values } .toMap @@ -263,7 +263,7 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete println(output) } - def evalSingleAssertion(assertionLabel: String, groundTruthLabels: List[String], callGraphLabels: List[String], bw: BufferedWriter): Unit = { + def evalSingleAssertion(assertionLabel: String, groundTruthLabels: Set[String], callGraphLabels: Set[String], bw: BufferedWriter): Unit = { val startAnalysis = System.nanoTime() val queriedAssertions = fullGraphInterpreter.getAssertionNodesByLabel(assertionLabel) val allDependencies = fullGraphInterpreter.getAllNonInternalDependencies(queriedAssertions.map(_.id)) @@ -272,16 +272,16 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete val endAnalysis = System.nanoTime() val durationMs = (endAnalysis - startAnalysis) / 1e6 - val sourceDependenciesString = sourceDependencies.mkString("\n\t") + val labelsInReportedDeps: Set[String] = sourceDependencies.flatMap(node => + labelPattern.findAllMatchIn(node.toString).map(_.group(2))) - val isSound = groundTruthLabels.forall(sourceDependenciesString.contains) - val imprecise = sourceDependencies.filter(node => - labelPattern.findFirstMatchIn(node.toString) match { - case Some(label) => !groundTruthLabels.contains(label.group(1)) - case _ => true - }) + val isSound = groundTruthLabels.diff(labelsInReportedDeps).isEmpty + val imprecise = labelsInReportedDeps.diff(groundTruthLabels) + + assert(!isSound || groundTruthLabels.size + imprecise.size == labelsInReportedDeps.size, s"Imprecision calculation is wrong.") + assert(labelsInReportedDeps.size <= callGraphLabels.size, "Call graph size is smaller than reported dependencies.") - addOutput(bw, s"$assertionLabel,${if(isSound) "YES" else "NO"},${groundTruthLabels.size},${sourceDependencies.size},${imprecise.size},${callGraphLabels.size},${durationMs}ms") + addOutput(bw, s"$assertionLabel,${if(isSound) "YES" else "NO"},${groundTruthLabels.size},${labelsInReportedDeps.size},${imprecise.size},${callGraphLabels.size},${durationMs}ms") // println(s"Queried:\n\t${getSourceInfoString(queriedAssertions)}") // println(s"\nAll Dependencies (${timeAll}ms):\n\t$sourceDependenciesString") @@ -306,12 +306,12 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete val groundTruths = readFile(pathToGroundTruth.toUri.getPath) val callGraphs = readFile(pathToCallGraphs.toUri.getPath) addOutput(bw, header) - groundTruths.foreach { case (assertionLabel, dependencyLabels) => evalSingleAssertion(assertionLabel, dependencyLabels, callGraphs.getOrElse(assertionLabel, List()), bw) } + callGraphs.foreach { case (assertionLabel, callGraphLabels) => evalSingleAssertion(assertionLabel, groundTruths.getOrElse(assertionLabel, Set.empty), callGraphLabels, bw) } bw.close() println("Done.") }catch { - case _ => println("Failed.") + case e: Throwable => println(s"Failed. ${e.getMessage}") }finally { bw.close() } diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 49bda55d1..2157a3012 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -45,8 +45,8 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen def getAssertionNodesByLabel(label: String): Set[DependencyAnalysisNode] = { - val fullAnnotation = s"@label(\"$label\")" - getAssertionNodes.filter(node => node.toString.contains(fullAnnotation)) + val fullAnnotation = ("""@label\(\s*"?""" + java.util.regex.Pattern.quote(label) + """"?\s*\)""").r + getAssertionNodes.filter(node => fullAnnotation.findFirstIn(node.toString).isDefined) } def getDirectDependencies(nodeIdsToAnalyze: Set[Int]): Set[DependencyAnalysisNode] = { From 373d60e0b72b57c16abe4460515281da4b30af74 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sun, 15 Mar 2026 11:09:46 +0100 Subject: [PATCH 392/474] update precision evaluation query --- .../scala/dependencyAnalysis/DependencyAnalysisUserTool.scala | 4 ++-- .../scala/dependencyAnalysis/DependencyGraphInterpreter.scala | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala index 7117e58d8..3b9bbb000 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala @@ -265,7 +265,7 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete def evalSingleAssertion(assertionLabel: String, groundTruthLabels: Set[String], callGraphLabels: Set[String], bw: BufferedWriter): Unit = { val startAnalysis = System.nanoTime() - val queriedAssertions = fullGraphInterpreter.getAssertionNodesByLabel(assertionLabel) + val queriedAssertions = fullGraphInterpreter.getNodesByLabel(assertionLabel) val allDependencies = fullGraphInterpreter.getAllNonInternalDependencies(queriedAssertions.map(_.id)) val sourceDependencies = UserLevelDependencyAnalysisNode.from(allDependencies).getSourceSet().diff(UserLevelDependencyAnalysisNode.from(queriedAssertions).getSourceSet()) @@ -306,7 +306,7 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete val groundTruths = readFile(pathToGroundTruth.toUri.getPath) val callGraphs = readFile(pathToCallGraphs.toUri.getPath) addOutput(bw, header) - callGraphs.foreach { case (assertionLabel, callGraphLabels) => evalSingleAssertion(assertionLabel, groundTruths.getOrElse(assertionLabel, Set.empty), callGraphLabels, bw) } + callGraphs.foreach { case (assertionLabel, callGraphLabels) => evalSingleAssertion(assertionLabel, groundTruths(assertionLabel), callGraphLabels, bw) } bw.close() println("Done.") diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 2157a3012..59777a2e0 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -44,9 +44,9 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen getNodes.filter(n => !AssumptionType.internalTypes.contains(n.assumptionType)).filter(node => node.sourceInfo.getLineNumber.isDefined && node.sourceInfo.getLineNumber.get == line && node.sourceInfo.getPositionString.startsWith(file + ".")) - def getAssertionNodesByLabel(label: String): Set[DependencyAnalysisNode] = { + def getNodesByLabel(label: String): Set[DependencyAnalysisNode] = { val fullAnnotation = ("""@label\(\s*"?""" + java.util.regex.Pattern.quote(label) + """"?\s*\)""").r - getAssertionNodes.filter(node => fullAnnotation.findFirstIn(node.toString).isDefined) + getNodes.filter(node => fullAnnotation.findFirstIn(node.toString).isDefined) } def getDirectDependencies(nodeIdsToAnalyze: Set[Int]): Set[DependencyAnalysisNode] = { From ae88f7ace7a321065334872a8758bc80d6010db0 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sun, 15 Mar 2026 21:31:10 +0100 Subject: [PATCH 393/474] fixes for interfaces --- src/main/scala/decider/Decider.scala | 2 +- .../dependencyAnalysis/DependencyAnalyzer.scala | 6 +++--- .../FrontendDependencyAnalysisInfo.scala | 6 +++++- src/main/scala/supporters/MethodSupporter.scala | 16 +++++++++++----- .../functions/FunctionVerificationUnit.scala | 13 +++++++++---- 5 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index d8a1164cd..6ab000a23 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -384,7 +384,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => // add assertions and assumptions nodes that can be used for a graph join val joinNodeInfo = info.getUniqueInfo[DependencyAnalysisJoinNodeInfo].get val currentTopLevelSource = analysisSourceInfoStack.getFullSourceInfo.getTopLevelSource - val assertionNode = joinNodeInfo.getAssertionNode(currentTopLevelSource) + val assertionNode = joinNodeInfo.getAssertionNode(currentTopLevelSource, analysisSourceInfoStack.getDependencyType.assertionType) val assumptionNode = joinNodeInfo.getAssumptionNode(currentTopLevelSource) dependencyAnalyzer.addAssertionNode(assertionNode) dependencyAnalyzer.addAssumptionNode(assumptionNode) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index 9400c6400..5bbb1617d 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -479,10 +479,10 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { nodeMap.put(n.id, n.id) mergedGraph.addAssertionNode(n) } - val assertionNodesBySource = dependencyGraph.getAssertionNodes.filter(!keepNode(_)).groupBy(n => (n.sourceInfo, n.assumptionType)) - assertionNodesBySource foreach { case ((sourceInfo, assumptionType), assertionNodes) => + val assertionNodesBySource = dependencyGraph.getAssertionNodes.filter(!keepNode(_)).groupBy(n => (n.sourceInfo, n.assumptionType, n.isJoinNode)) + assertionNodesBySource foreach { case ((sourceInfo, assumptionType, isJoinNode), assertionNodes) => if (assertionNodes.nonEmpty) { - val newNode = SimpleAssertionNode(True, assumptionType, sourceInfo, isClosed = false, hasFailed=assertionNodes.exists(_.hasFailed)) + val newNode = SimpleAssertionNode(True, assumptionType, sourceInfo, isClosed = false, hasFailed=assertionNodes.exists(_.hasFailed), isJoinNode=isJoinNode) assertionNodes foreach (n => nodeMap.put(n.id, newNode.id)) mergedGraph.addAssertionNode(newNode) } diff --git a/src/main/scala/dependencyAnalysis/FrontendDependencyAnalysisInfo.scala b/src/main/scala/dependencyAnalysis/FrontendDependencyAnalysisInfo.scala index d97dd16c2..036d3d47c 100644 --- a/src/main/scala/dependencyAnalysis/FrontendDependencyAnalysisInfo.scala +++ b/src/main/scala/dependencyAnalysis/FrontendDependencyAnalysisInfo.scala @@ -1,5 +1,6 @@ package viper.silicon.dependencyAnalysis +import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType import viper.silicon.state.terms.True import viper.silver.ast.{Info, Position} @@ -29,13 +30,16 @@ case class SimpleFrontendDependencyAnalysisInfo(sourceInfo: AnalysisSourceInfo, The idea is that these nodes can be used to join graphs without the need for an explicit method call. We use this, for example, for Gobra interfaces and implementation proofs. */ -case class DependencyAnalysisJoinNodeInfo(sourceInfo: AnalysisSourceInfo, _dependencyType: DependencyType = DependencyType.make(AssumptionType.CustomInternal)) extends FrontendDependencyAnalysisInfo { +case class DependencyAnalysisJoinNodeInfo(sourceInfo: AnalysisSourceInfo, _dependencyType: DependencyType) extends FrontendDependencyAnalysisInfo { def getAssertionNode: GeneralAssertionNode = SimpleAssertionNode(True, AssumptionType.CustomInternal, sourceInfo, isClosed=false, isJoinNode=true) def getAssertionNode(outerSourceInfo: AnalysisSourceInfo): GeneralAssertionNode = SimpleAssertionNode(True, AssumptionType.CustomInternal, CompositeAnalysisSourceInfo(outerSourceInfo, sourceInfo), isClosed=false, isJoinNode=true) + def getAssertionNode(outerSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): GeneralAssertionNode = + SimpleAssertionNode(True, assumptionType, CompositeAnalysisSourceInfo(outerSourceInfo, sourceInfo), isClosed=false, isJoinNode=true) + def getAssumptionNode(outerSourceInfo: AnalysisSourceInfo): GeneralAssumptionNode = SimpleAssumptionNode(True, None, CompositeAnalysisSourceInfo(outerSourceInfo, sourceInfo), AssumptionType.CustomInternal, isClosed=false, isJoinNode=true) diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index 8166a6fb8..03e9c69f3 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -9,11 +9,12 @@ package viper.silicon.supporters import com.typesafe.scalalogging.Logger import viper.silicon.Map import viper.silicon.decider.Decider -import viper.silicon.dependencyAnalysis.{AssumptionType, DependencyAnalysisJoinNodeInfo, DependencyAnalyzer, DependencyGraphInterpreter, DependencyType} +import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyAnalysisJoinNodeInfo, DependencyAnalyzer, DependencyGraphInterpreter, DependencyType, SimpleAssumptionNode} import viper.silicon.interfaces._ import viper.silicon.logger.records.data.WellformednessCheckRecord import viper.silicon.rules.{consumer, executionFlowController, executor, producer} import viper.silicon.state.State.OldHeaps +import viper.silicon.state.terms.True import viper.silicon.state.{State, Store} import viper.silicon.utils.freshSnap import viper.silicon.verifier.{Verifier, VerifierComponent} @@ -83,9 +84,14 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif val daJoinNodeInfoOpt = method.info.getUniqueInfo[DependencyAnalysisJoinNodeInfo] if(daJoinNodeInfoOpt.isDefined){ val infodaJoinNodeInfo = daJoinNodeInfoOpt.get - v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(infodaJoinNodeInfo.sourceInfo, DependencyType.make(AssumptionType.CustomInternal)) - postConditionType = AssumptionType.CustomInternal - v.decider.dependencyAnalyzer.addAssertionNode(infodaJoinNodeInfo.getAssertionNode) + // v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(infodaJoinNodeInfo.sourceInfo, DependencyType.make(AssumptionType.CustomInternal)) +// postConditionType = AssumptionType.CustomInternal + val postCondNodes = posts.flatMap(_.topLevelConjuncts).map(pc => SimpleAssumptionNode(True, None, AnalysisSourceInfo.createAnalysisSourceInfo(pc), AssumptionType.ImplicitPostcondition, isClosed=false, isJoinNode=true)) + val customJoinNode = infodaJoinNodeInfo.getAssertionNode + postCondNodes foreach v.decider.dependencyAnalyzer.addAssumptionNode + v.decider.dependencyAnalyzer.addAssertionNode(customJoinNode) + postCondNodes foreach (n => v.decider.dependencyAnalyzer.addDependency(Some(customJoinNode.id), Some(n.id))) + postCondNodes foreach (n => v.decider.dependencyAnalyzer.addDependency(Some(n.id), Some(customJoinNode.id))) } errorsReportedSoFar.set(0) @@ -121,7 +127,7 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif result.dependencyGraphInterpreter = v.decider.dependencyAnalyzer.buildFinalGraph().map(new DependencyGraphInterpreter(method.name, _, allErrors, Some(method))) - if(daJoinNodeInfoOpt.isDefined) v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(daJoinNodeInfoOpt.get.sourceInfo) + // if(daJoinNodeInfoOpt.isDefined) v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(daJoinNodeInfoOpt.get.sourceInfo) v.decider.resetProverOptions() diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index ad3a6d821..d1491d19b 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -288,9 +288,14 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver val daJoinNodeInfoOpt = function.info.getUniqueInfo[DependencyAnalysisJoinNodeInfo] if(daJoinNodeInfoOpt.isDefined){ val infodaJoinNodeInfo = daJoinNodeInfoOpt.get - v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(infodaJoinNodeInfo.sourceInfo, DependencyType.make(AssumptionType.CustomInternal)) - postConditionType = AssumptionType.CustomInternal - v.decider.dependencyAnalyzer.addAssertionNode(infodaJoinNodeInfo.getAssertionNode) +// v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(infodaJoinNodeInfo.sourceInfo, DependencyType.make(AssumptionType.CustomInternal)) +// postConditionType = AssumptionType.CustomInternal + val postCondNodes = posts.flatMap(_.topLevelConjuncts).map(pc => SimpleAssumptionNode(True, None, AnalysisSourceInfo.createAnalysisSourceInfo(pc), AssumptionType.ImplicitPostcondition, isClosed=false, isJoinNode=true)) + val customJoinNode = infodaJoinNodeInfo.getAssertionNode + postCondNodes foreach v.decider.dependencyAnalyzer.addAssumptionNode + v.decider.dependencyAnalyzer.addAssertionNode(customJoinNode) + postCondNodes foreach (n => v.decider.dependencyAnalyzer.addDependency(Some(customJoinNode.id), Some(n.id))) + postCondNodes foreach (n => v.decider.dependencyAnalyzer.addDependency(Some(n.id), Some(customJoinNode.id))) } val result = phase1data.foldLeft(Success(): VerificationResult) { @@ -315,7 +320,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver recorders :+= s3.functionRecorder Success()})})})} - if(daJoinNodeInfoOpt.isDefined) v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(daJoinNodeInfoOpt.get.sourceInfo) +// if(daJoinNodeInfoOpt.isDefined) v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(daJoinNodeInfoOpt.get.sourceInfo) data.advancePhase(recorders) From f01110a964426b818834b0490d2f8843f82fd56c Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sun, 15 Mar 2026 21:33:58 +0100 Subject: [PATCH 394/474] update precision evaluation query for gobra --- .../DependencyAnalysisUserTool.scala | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala index 3b9bbb000..08acb3ebb 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala @@ -238,7 +238,7 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete private def handlePrecisionEval(inputs: Seq[String]): Unit = { val labelPattern: Regex = """@label\(\s*("?)([^")\s]+)\1\s*\)""".r - val header = "Assertion Label,Sound?,#True Dependencies,#Reported Dependencies,#False-Positives,Call Graph Size,Runtime" + val header = "Assertion Label,Sound?,#True Dependencies,#Reported Dependencies,#False-Positives,Call Graph Size,Runtime,Noise" def readFile(path: String): Map[String, Set[String]] = { val src = Source.fromFile(path) @@ -272,16 +272,18 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete val endAnalysis = System.nanoTime() val durationMs = (endAnalysis - startAnalysis) / 1e6 - val labelsInReportedDeps: Set[String] = sourceDependencies.flatMap(node => - labelPattern.findAllMatchIn(node.toString).map(_.group(2))) + val labelsInReportedDeps: Set[Set[String]] = sourceDependencies.map(node => labelPattern.findAllMatchIn(node.toString).map(_.group(2)).toSet) - val isSound = groundTruthLabels.diff(labelsInReportedDeps).isEmpty - val imprecise = labelsInReportedDeps.diff(groundTruthLabels) + val actualLabelInReportedDeps = labelsInReportedDeps.filter(_.size == 1).flatten + val noise = labelsInReportedDeps.filterNot(_.size == 1) - assert(!isSound || groundTruthLabels.size + imprecise.size == labelsInReportedDeps.size, s"Imprecision calculation is wrong.") - assert(labelsInReportedDeps.size <= callGraphLabels.size, "Call graph size is smaller than reported dependencies.") + val isSound = groundTruthLabels.diff(actualLabelInReportedDeps).isEmpty + val imprecise = actualLabelInReportedDeps.diff(groundTruthLabels) - addOutput(bw, s"$assertionLabel,${if(isSound) "YES" else "NO"},${groundTruthLabels.size},${labelsInReportedDeps.size},${imprecise.size},${callGraphLabels.size},${durationMs}ms") + assert(!isSound || groundTruthLabels.size + imprecise.size == actualLabelInReportedDeps.size, s"Imprecision calculation is wrong.") + assert(actualLabelInReportedDeps.size <= callGraphLabels.size, "Call graph size is smaller than reported dependencies.") + + addOutput(bw, s"$assertionLabel,${if(isSound) "YES" else "NO"},${groundTruthLabels.size},${actualLabelInReportedDeps.size},${imprecise.size},${callGraphLabels.size},${durationMs}ms,${noise.size}") // println(s"Queried:\n\t${getSourceInfoString(queriedAssertions)}") // println(s"\nAll Dependencies (${timeAll}ms):\n\t$sourceDependenciesString") From 2a7124a885cfb6c947ee3570b88f7a33754942cf Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 16 Mar 2026 15:38:32 +0100 Subject: [PATCH 395/474] fix for gobra interfaces --- src/main/scala/supporters/MethodSupporter.scala | 10 ++++++++-- .../functions/FunctionVerificationUnit.scala | 8 +++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index 03e9c69f3..c14bd90e3 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -9,7 +9,7 @@ package viper.silicon.supporters import com.typesafe.scalalogging.Logger import viper.silicon.Map import viper.silicon.decider.Decider -import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyAnalysisJoinNodeInfo, DependencyAnalyzer, DependencyGraphInterpreter, DependencyType, SimpleAssumptionNode} +import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyAnalysisJoinNodeInfo, DependencyAnalyzer, DependencyGraphInterpreter, DependencyType, SimpleAssertionNode, SimpleAssumptionNode} import viper.silicon.interfaces._ import viper.silicon.logger.records.data.WellformednessCheckRecord import viper.silicon.rules.{consumer, executionFlowController, executor, producer} @@ -86,12 +86,18 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif val infodaJoinNodeInfo = daJoinNodeInfoOpt.get // v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(infodaJoinNodeInfo.sourceInfo, DependencyType.make(AssumptionType.CustomInternal)) // postConditionType = AssumptionType.CustomInternal - val postCondNodes = posts.flatMap(_.topLevelConjuncts).map(pc => SimpleAssumptionNode(True, None, AnalysisSourceInfo.createAnalysisSourceInfo(pc), AssumptionType.ImplicitPostcondition, isClosed=false, isJoinNode=true)) + val postCondNodes = (posts ++ pres).flatMap(_.topLevelConjuncts).map(pc => SimpleAssumptionNode(True, None, AnalysisSourceInfo.createAnalysisSourceInfo(pc), AssumptionType.ImplicitPostcondition, isClosed=false, isJoinNode=true)) + val postCondAssertNodes = (posts ++ pres).flatMap(_.topLevelConjuncts).map(pc => SimpleAssertionNode(True, AssumptionType.ImplicitPostcondition, AnalysisSourceInfo.createAnalysisSourceInfo(pc), isClosed=false, isJoinNode=true)) val customJoinNode = infodaJoinNodeInfo.getAssertionNode postCondNodes foreach v.decider.dependencyAnalyzer.addAssumptionNode + postCondAssertNodes foreach v.decider.dependencyAnalyzer.addAssertionNode + v.decider.dependencyAnalyzer.addAssertionNode(customJoinNode) + postCondNodes foreach (n => v.decider.dependencyAnalyzer.addDependency(Some(customJoinNode.id), Some(n.id))) postCondNodes foreach (n => v.decider.dependencyAnalyzer.addDependency(Some(n.id), Some(customJoinNode.id))) + postCondAssertNodes foreach (n => v.decider.dependencyAnalyzer.addDependency(Some(customJoinNode.id), Some(n.id))) + postCondAssertNodes foreach (n => v.decider.dependencyAnalyzer.addDependency(Some(n.id), Some(customJoinNode.id))) } errorsReportedSoFar.set(0) diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index d1491d19b..0fbbb9fcb 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -290,12 +290,18 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver val infodaJoinNodeInfo = daJoinNodeInfoOpt.get // v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(infodaJoinNodeInfo.sourceInfo, DependencyType.make(AssumptionType.CustomInternal)) // postConditionType = AssumptionType.CustomInternal - val postCondNodes = posts.flatMap(_.topLevelConjuncts).map(pc => SimpleAssumptionNode(True, None, AnalysisSourceInfo.createAnalysisSourceInfo(pc), AssumptionType.ImplicitPostcondition, isClosed=false, isJoinNode=true)) + val postCondNodes = (posts ++ function.pres).flatMap(_.topLevelConjuncts).map(pc => SimpleAssumptionNode(True, None, AnalysisSourceInfo.createAnalysisSourceInfo(pc), AssumptionType.ImplicitPostcondition, isClosed=false, isJoinNode=true)) + val postCondAssertNodes = (posts ++ function.pres).flatMap(_.topLevelConjuncts).map(pc => SimpleAssertionNode(True, AssumptionType.ImplicitPostcondition, AnalysisSourceInfo.createAnalysisSourceInfo(pc), isClosed=false, isJoinNode=true)) val customJoinNode = infodaJoinNodeInfo.getAssertionNode + postCondNodes foreach v.decider.dependencyAnalyzer.addAssumptionNode + postCondAssertNodes foreach v.decider.dependencyAnalyzer.addAssertionNode v.decider.dependencyAnalyzer.addAssertionNode(customJoinNode) + postCondNodes foreach (n => v.decider.dependencyAnalyzer.addDependency(Some(customJoinNode.id), Some(n.id))) postCondNodes foreach (n => v.decider.dependencyAnalyzer.addDependency(Some(n.id), Some(customJoinNode.id))) + postCondAssertNodes foreach (n => v.decider.dependencyAnalyzer.addDependency(Some(customJoinNode.id), Some(n.id))) + postCondAssertNodes foreach (n => v.decider.dependencyAnalyzer.addDependency(Some(n.id), Some(customJoinNode.id))) } val result = phase1data.foldLeft(Success(): VerificationResult) { From 42428469fc9fc895cf64f360339ef668324e4dd5 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 16 Mar 2026 16:49:59 +0100 Subject: [PATCH 396/474] fix method call without assumptions --- src/main/scala/rules/Executor.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 901e098d8..fb62234de 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -543,6 +543,9 @@ object executor extends ExecutionRules { val pveCall = CallFailed(call) val pveCallTransformed = pveCall.withReasonNodeTransformed(reasonTransformer) + v.decider.dependencyAnalyzer.addAssumption(True, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getAssumptionType, + isJoinNode=false, None) + val mcLog = new MethodCallRecord(call, s, v.decider.pcs) val sepIdentifier = v.symbExLog.openScope(mcLog) val paramLog = new CommentRecord("Parameters", s, v.decider.pcs) From fe79c63c69c21e4e18fc4398ee3d42e6c230c28a Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 17 Mar 2026 18:58:00 +0100 Subject: [PATCH 397/474] fix for preconditions --- src/main/scala/decider/Decider.scala | 12 +-- .../dependencyAnalysis/AnalysisInfo.scala | 3 +- .../DependencyAnalysisNode.scala | 2 +- .../DependencyAnalyzer.scala | 89 ++++++++++++++----- .../DependencyGraphInterpreter.scala | 5 +- .../dependencyAnalysis/SourceInfoStack.scala | 2 +- src/main/scala/rules/Brancher.scala | 6 +- src/main/scala/rules/ChunkSupporter.scala | 4 +- src/main/scala/rules/Consumer.scala | 4 +- src/main/scala/rules/Evaluator.scala | 10 ++- src/main/scala/rules/Executor.scala | 12 ++- src/main/scala/rules/HeapSupporter.scala | 10 +-- src/main/scala/rules/Producer.scala | 2 +- .../scala/supporters/MethodSupporter.scala | 4 + .../functions/FunctionVerificationUnit.scala | 4 + 15 files changed, 119 insertions(+), 50 deletions(-) diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 6ab000a23..9d0988e9b 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -174,7 +174,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => analysisSourceInfoStack = AnalysisSourceInfoStack() } - def getAnalysisInfo(assumptionType: AssumptionType): AnalysisInfo = AnalysisInfo(this, dependencyAnalyzer, analysisSourceInfoStack.getFullSourceInfo, assumptionType, analysisSourceInfoStack.isJoinRelevantAssumption) + def getAnalysisInfo(assumptionType: AssumptionType): AnalysisInfo = AnalysisInfo(this, dependencyAnalyzer, analysisSourceInfoStack.getFullSourceInfo, assumptionType, analysisSourceInfoStack.isJoinRelevantNode) def functionDecls: Set[FunctionDecl] = _declaredFreshFunctions def macroDecls: Vector[MacroDecl] = _declaredFreshMacros @@ -421,7 +421,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } val filteredAssumptionsWithLabels = filteredAssumptions map{case (t, _) => - val assumptionId: Option[Int] = dependencyAnalyzer.addAssumption(t, analysisSourceInfo, assumptionType, analysisSourceInfoStack.isJoinRelevantAssumption) + val assumptionId: Option[Int] = dependencyAnalyzer.addAssumption(t, analysisSourceInfo, assumptionType, analysisSourceInfoStack.isJoinRelevantNode) (t, DependencyAnalyzer.createAssumptionLabel(assumptionId)) } @@ -457,7 +457,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => private def addAssumptionLabels(filteredTerms: Iterable[Term], assumptionType: AssumptionType) = { filteredTerms map (t => { - val assumptionIds = dependencyAnalyzer.addAssumption(t, analysisSourceInfoStack.getFullSourceInfo, assumptionType, analysisSourceInfoStack.isJoinRelevantAssumption) + val assumptionIds = dependencyAnalyzer.addAssumption(t, analysisSourceInfoStack.getFullSourceInfo, assumptionType, analysisSourceInfoStack.isJoinRelevantNode) (t, DependencyAnalyzer.createAssumptionLabel(assumptionIds)) }) } @@ -519,7 +519,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => def checkSmoke(isAssert: Boolean=false): Boolean = checkSmoke(analysisSourceInfoStack.getAssertionType, isAssert) def checkSmoke(assumptionType: AssumptionType, isAssert: Boolean): Boolean = { - val checkNode = dependencyAnalyzer.createAssertOrCheckNode(False, assumptionType, analysisSourceInfoStack.getFullSourceInfo, !isAssert) + val checkNode = dependencyAnalyzer.createAssertOrCheckNode(False, assumptionType, analysisSourceInfoStack.getFullSourceInfo, !isAssert, analysisSourceInfoStack.isJoinRelevantNode) val label = DependencyAnalyzer.createAssertionLabel(checkNode.map(_.id)) val timeout = if (isAssert) Verifier.config.assertTimeout.toOption else Verifier.config.checkTimeout.toOption @@ -553,7 +553,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } override def handleFailedAssertionForDependencyAnalysis(failedAssertion: Term, assertionType: AssumptionType, assumeFailedAssertion: Boolean): Unit = { - dependencyAnalyzer.addAssertionFailedNode(failedAssertion, assertionType, analysisSourceInfoStack.getFullSourceInfo) + dependencyAnalyzer.addAssertionFailedNode(failedAssertion, assertionType, analysisSourceInfoStack.getFullSourceInfo, analysisSourceInfoStack.isJoinRelevantNode) if(assumeFailedAssertion){ assume(failedAssertion, None, None, AssumptionType.Explicit) failedAssertion match { @@ -594,7 +594,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => val asserted = if(isDependencyAnalysisEnabled) t.equals(True) else isKnownToBeTrue(t) - val assertNode = if(!asserted) dependencyAnalyzer.createAssertOrCheckNode(t, assumptionType, decider.analysisSourceInfoStack.getFullSourceInfo, isCheck) else None + val assertNode = if(!asserted) dependencyAnalyzer.createAssertOrCheckNode(t, assumptionType, decider.analysisSourceInfoStack.getFullSourceInfo, isCheck, analysisSourceInfoStack.isJoinRelevantNode) else None val result = asserted || proverAssert(t, timeout, DependencyAnalyzer.createAssertionLabel(assertNode map (_.id))) diff --git a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala index 0517dfe3e..dd296c61a 100644 --- a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala +++ b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala @@ -9,9 +9,10 @@ object AssumptionType extends Enumeration { def explicitAssumptionTypes: Set[AssumptionType] = Set(Explicit, ExplicitPostcondition) def postconditionTypes: Set[AssumptionType] = Set(ImplicitPostcondition, ExplicitPostcondition, ImportedPostcondition) + def preconditionTypes: Set[AssumptionType] = Set(Precondition) def explicitAssertionTypes: Set[AssumptionType] = Set(Explicit, ImplicitPostcondition, ExplicitPostcondition) def internalTypes: Set[AssumptionType] = Set(Internal, Trigger, CustomInternal) // will always be hidden from user - def joinConditionTypes: Set[AssumptionType] = postconditionTypes ++ Set(FunctionBody) + def joinConditionTypes: Set[AssumptionType] = preconditionTypes ++ postconditionTypes ++ Set(FunctionBody, MethodCall) def importedTypes: Set[AssumptionType] = Set(ImportedPostcondition) def verificationAnnotationTypes: Set[AssumptionType] = Set(FunctionBody, LoopInvariant, Rewrite, ExplicitPostcondition, ImplicitPostcondition, ImportedPostcondition, Precondition, Explicit, DomainAxiom, Annotation) def sourceCodeTypes: Set[AssumptionType] = AssumptionType.values.diff(explicitAssumptionTypes ++ explicitAssertionTypes ++ verificationAnnotationTypes ++ internalTypes) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala index 5bf564ec6..7cbcb4542 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala @@ -98,7 +98,7 @@ case class AxiomAssumptionNode(term: Term, description: Option[String], sourceIn case class SimpleAssertionNode(term: Term, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo, isClosed: Boolean, hasFailed: Boolean = false, isJoinNode: Boolean = false, _id: Option[Int]=None) extends GeneralAssertionNode { override def getNodeString: String = "assert " + term.toString - override def getAssertFailedNode(): GeneralAssertionNode = SimpleAssertionNode(term, assumptionType, sourceInfo, isClosed, hasFailed=true) + override def getAssertFailedNode(): GeneralAssertionNode = SimpleAssertionNode(term, assumptionType, sourceInfo, isClosed, hasFailed=true, isJoinNode=isJoinNode) } case class SimpleCheckNode(term: Term, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo, isClosed: Boolean, hasFailed: Boolean = false, isJoinNode: Boolean = false, _id: Option[Int]=None) extends GeneralAssertionNode { diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index 5bbb1617d..5ffba44b8 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -6,6 +6,9 @@ import viper.silicon.interfaces.state.{Chunk, GeneralChunk} import viper.silicon.state.terms._ import viper.silicon.verifier.Verifier import viper.silver.ast +import viper.silver.ast.utility.Expressions.isKnownWellDefined +import viper.silver.ast.{Apply, Applying, Assert, Asserting, Assume, Div, Exhale, Exp, ExtensionStmt, FieldAccess, FieldAccessPredicate, FieldAssign, Fold, FuncApp, Goto, If, Inhale, Label, LocalVarAssign, LocalVarDeclStmt, MapLookup, MethodCall, Mod, NewStmt, Package, Program, Quasihavoc, Quasihavocall, SeqIndex, Seqn, Stmt, Unfold, Unfolding, While} +import viper.silver.ast.utility.{Expressions, ViperStrategy} import java.util.concurrent.atomic.AtomicLong import scala.collection.mutable @@ -37,8 +40,8 @@ trait DependencyAnalyzer { def registerExhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, labelNodeOpt: Option[LabelNode], analysisInfo: AnalysisInfo): CH = buildChunk(perm) def createLabelNode(label: Var, sourceChunks: Iterable[Chunk], sourceTerms: Iterable[Term]): Option[LabelNode] - def createAssertOrCheckNode(term: Term, assumptionType: AssumptionType, analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] - def addAssertFalseNode(isCheck: Boolean, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo): Option[Int] + def createAssertOrCheckNode(term: Term, assumptionType: AssumptionType, analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean, isJoinNode: Boolean): Option[GeneralAssertionNode] + def addAssertFalseNode(isCheck: Boolean, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo, isJoinNode: Boolean): Option[Int] def addInfeasibilityNode(isCheck: Boolean, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] def addDependency(source: Option[Int], dest: Option[Int]): Unit @@ -60,14 +63,14 @@ trait DependencyAnalyzer { /** * Adds an assertion and assumption node with the given analysis source info and dependencies to the current infeasibility node. */ - def addAssertionWithDepToInfeasNode(infeasNodeId: Option[Int], analysisSourceInfo: AnalysisSourceInfo, dependencyType: DependencyType): Unit = {} + def addAssertionWithDepToInfeasNode(infeasNodeId: Option[Int], analysisSourceInfo: AnalysisSourceInfo, dependencyType: DependencyType, isJoinNode: Boolean): Unit = {} /** * @return the final dependency graph representing all direct and transitive dependencies */ def buildFinalGraph(): Option[DependencyGraph] - def addAssertionFailedNode(failedAssertion: Term, assertionType: AssumptionType, sourceInfo: AnalysisSourceInfo): Option[Int] + def addAssertionFailedNode(failedAssertion: Term, assertionType: AssumptionType, sourceInfo: AnalysisSourceInfo, isJoinNode: Boolean): Option[Int] } object DependencyAnalyzer { @@ -229,12 +232,56 @@ object DependencyAnalyzer { else newGraph.addEdges(src, targets) } + val relevantAssertionNodes = joinCandidateAssertions // method call pres assertions + .filter(_.isJoinNode) + .groupBy(_.sourceInfo.getFineGrainedSource) + .view.mapValues(_.map(_.id)) + .toMap + joinCandidateNodes + .map(node => (node.id, relevantAssertionNodes.getOrElse(node.sourceInfo.getTopLevelSource, Seq.empty))) + .foreach { case (target, sources) => + if (customInternalNodes.intersect(sources.toSet.union(Set(target))).isEmpty) newGraph.addEdgesConnectingMethods(sources, target) + else newGraph.addEdges(sources, target) + } + stopTimeMeasurementAndAddToTotal(startTime, timeForMethodJoin) val newInterpreter = new DependencyGraphInterpreter(name, newGraph, dependencyGraphInterpreters.toList.flatMap(_.getErrors)) newInterpreter } + + def extractAssertionsForJoin(exp: ast.Exp, program: ast.Program): Seq[ast.Exp] = { + exp match { + case FieldAccessPredicate(FieldAccess(rcv, _), prm) => + // Extra case for field access predicates because the contained field access does NOT require already having the field permission. + extractAssertionsForJoin(rcv, program) ++ extractAssertionsForJoin(prm.get, program) + case f: FuncApp => + program.findFunction(f.funcname).pres + case other => other.subExps.flatMap(extractAssertionsForJoin(_, program)) + } + } + + def extractAssertionsForJoin(s: Stmt, p: Program): Seq[ast.Exp] = { + def goE(exp: Exp): Seq[ast.Exp] = extractAssertionsForJoin(exp, p) + + def goEs(exps: Seq[Exp]): Seq[ast.Exp] = exps flatMap goE + + s match { + case NewStmt(lhs, _) => goE(lhs) + case LocalVarAssign(lhs, rhs) => goE(lhs) ++ goE(rhs) + case MethodCall(methodName, args, targets) => + p.findMethod(methodName).pres.flatMap(_.topLevelConjuncts) ++ goEs(args) ++ goEs(targets) + case Inhale(exp) => goE(exp) + case Assume(exp) => goE(exp) + case Seqn(ss, _) => ss flatMap (extractAssertionsForJoin(_, p)) + case If(cond, thn, els) => goE(cond) ++ extractAssertionsForJoin(thn, p) ++ extractAssertionsForJoin(els, p) + case While(cond, invs, body) => goEs(invs) ++ goE(cond) ++ extractAssertionsForJoin(body, p) + case Label(_, invs) => goEs(invs) + case _ => goEs(s.subnodes.filter(_.isInstanceOf[ast.Exp]).map( + _.asInstanceOf[ast.Exp])) + } + } } class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { @@ -333,21 +380,21 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { } // adding assertion nodes - override def createAssertOrCheckNode(term: Term, assumptionType: AssumptionType, analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] = { + override def createAssertOrCheckNode(term: Term, assumptionType: AssumptionType, analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean, isJoinNode: Boolean): Option[GeneralAssertionNode] = { if(isCheck) - Some(SimpleCheckNode(term, assumptionType, analysisSourceInfo, isClosed_)) + Some(SimpleCheckNode(term, assumptionType, analysisSourceInfo, isClosed_, isJoinNode=isJoinNode)) else - Some(SimpleAssertionNode(term, assumptionType, analysisSourceInfo, isClosed_)) + Some(SimpleAssertionNode(term, assumptionType, analysisSourceInfo, isClosed_, isJoinNode=isJoinNode)) } - def addAssertNode(term: Term, assumptionType: AssumptionType, analysisSourceInfo: AnalysisSourceInfo): Option[Int] = { - val node = createAssertOrCheckNode(term, assumptionType, analysisSourceInfo, isCheck=false) + def addAssertNode(term: Term, assumptionType: AssumptionType, analysisSourceInfo: AnalysisSourceInfo, isJoinNode: Boolean): Option[Int] = { + val node = createAssertOrCheckNode(term, assumptionType, analysisSourceInfo, isCheck=false, isJoinNode=isJoinNode) node foreach addAssertionNode node map (_.id) } - override def addAssertFalseNode(isCheck: Boolean, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo): Option[Int] = { - val node = createAssertOrCheckNode(False, assumptionType, sourceInfo, isCheck) + override def addAssertFalseNode(isCheck: Boolean, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo, isJoinNode: Boolean): Option[Int] = { + val node = createAssertOrCheckNode(False, assumptionType, sourceInfo, isCheck, isJoinNode) addAssertionNode(node.get) node.map(_.id) } @@ -358,10 +405,10 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { Some(node.id) } - override def addAssertionFailedNode(failedAssertion: Term, assertionType: AssumptionType, sourceInfo: AnalysisSourceInfo): Option[Int] = { + override def addAssertionFailedNode(failedAssertion: Term, assertionType: AssumptionType, sourceInfo: AnalysisSourceInfo, isJoinNode: Boolean): Option[Int] = { val assumptionType = if(AssumptionType.postconditionTypes.contains(assertionType)) AssumptionType.ExplicitPostcondition else AssumptionType.Explicit - val assumeNode = SimpleAssumptionNode(failedAssertion, None, sourceInfo, assumptionType, isClosed=false, isJoinNode=false) - val assertFailedNode = SimpleAssertionNode(failedAssertion, assertionType, sourceInfo, isClosed=false, hasFailed=true) + val assumeNode = SimpleAssumptionNode(failedAssertion, None, sourceInfo, assumptionType, isClosed=false, isJoinNode=isJoinNode) + val assertFailedNode = SimpleAssertionNode(failedAssertion, assertionType, sourceInfo, isClosed=false, hasFailed=true, isJoinNode=isJoinNode) dependencyGraph.addNode(assumeNode) dependencyGraph.addNode(assertFailedNode) dependencyGraph.addEdges(Set(assumeNode.id), assertFailedNode.id) @@ -412,8 +459,8 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { } override def addDependenciesForAbstractMembers(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp], postConditionType: AssumptionType = AssumptionType.ExplicitPostcondition): Unit = { - val sourceNodeIds = sourceExps.flatMap(e => addAssumption(True, AnalysisSourceInfo.createAnalysisSourceInfo(e), AssumptionType.Precondition, isJoinNode=false, None)) - val targetNodes = targetExps.flatMap(e => addAssertNode(True, postConditionType, AnalysisSourceInfo.createAnalysisSourceInfo(e))) + val sourceNodeIds = sourceExps.flatMap(e => addAssumption(True, AnalysisSourceInfo.createAnalysisSourceInfo(e), AssumptionType.Precondition, isJoinNode=true, None)) + val targetNodes = targetExps.flatMap(e => addAssertNode(True, postConditionType, AnalysisSourceInfo.createAnalysisSourceInfo(e), isJoinNode=true)) dependencyGraph.addEdges(sourceNodeIds, targetNodes) } @@ -500,8 +547,8 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { * Adds an assertion node with the given analysis source info and dependencies to the current infeasibility node. * The resulting assertion node is required to detect dependencies of the source statement/expression on infeasible paths. */ - override def addAssertionWithDepToInfeasNode(infeasNodeId: Option[Int], analysisSourceInfo: AnalysisSourceInfo, dependencyType: DependencyType): Unit = { - val newAssertionNodeId = addAssertNode(False, dependencyType.assertionType, analysisSourceInfo) + override def addAssertionWithDepToInfeasNode(infeasNodeId: Option[Int], analysisSourceInfo: AnalysisSourceInfo, dependencyType: DependencyType, isJoinNode: Boolean): Unit = { + val newAssertionNodeId = addAssertNode(False, dependencyType.assertionType, analysisSourceInfo, isJoinNode) addDependency(infeasNodeId, newAssertionNodeId) } @@ -523,10 +570,10 @@ class NoDependencyAnalyzer extends DependencyAnalyzer { override def addAxiom(assumption: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, description: Option[String]): Option[Int] = None override def createLabelNode(labelTerm: Var, sourceChunks: Iterable[Chunk], sourceTerms: Iterable[Term]): Option[LabelNode] = None - override def createAssertOrCheckNode(term: Term, assumptionType: AssumptionType, analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean): Option[GeneralAssertionNode] = None - override def addAssertFalseNode(isCheck: Boolean, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo): Option[Int] = None + override def createAssertOrCheckNode(term: Term, assumptionType: AssumptionType, analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean, isJoinNode: Boolean): Option[GeneralAssertionNode] = None + override def addAssertFalseNode(isCheck: Boolean, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo, isJoinNode: Boolean): Option[Int] = None override def addInfeasibilityNode(isCheck: Boolean, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None - override def addAssertionFailedNode(failedAssertion: Term, assertionType: AssumptionType, sourceInfo: AnalysisSourceInfo): Option[Int] = None + override def addAssertionFailedNode(failedAssertion: Term, assertionType: AssumptionType, sourceInfo: AnalysisSourceInfo, isJoinNode: Boolean): Option[Int] = None override def addDependency(source: Option[Int], dest: Option[Int]): Unit = {} override def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit = {} diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 59777a2e0..6399329ab 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -86,7 +86,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen def getNonInternalAssumptionNodes(nodes: Set[DependencyAnalysisNode]): Set[DependencyAnalysisNode] = nodes filter (node => (node.isInstanceOf[GeneralAssumptionNode] && !AssumptionType.internalTypes.contains(node.assumptionType)) - || AssumptionType.postconditionTypes.contains(node.assumptionType) // postconditions act as assumptions for callers + || AssumptionType.postconditionTypes.contains(node.assumptionType) || node.isJoinNode // postconditions act as assumptions for callers ) def getExplicitAssumptionNodes: Set[DependencyAnalysisNode] = getNonInternalAssumptionNodes filter (node => @@ -98,7 +98,8 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen .exists(node => dependencyGraph.existsAnyDependency(Set(node.id), nodesToAnalyze map (_.id) filter (_ != node.id), includeInfeasibilityNodes)) - def getNonInternalAssertionNodes: Set[DependencyAnalysisNode] = getAssertionNodes filter (node => !AssumptionType.internalTypes.contains(node.assumptionType)) + def getNonInternalAssertionNodes: Set[DependencyAnalysisNode] = getAssertionNodes filter (node => + !AssumptionType.internalTypes.contains(node.assumptionType) || node.isJoinNode) def getExplicitAssertionNodes: Set[DependencyAnalysisNode] = getNonInternalAssertionNodes.filter(node => AssumptionType.explicitAssertionTypes.contains(node.assumptionType)) diff --git a/src/main/scala/dependencyAnalysis/SourceInfoStack.scala b/src/main/scala/dependencyAnalysis/SourceInfoStack.scala index dbe89526b..2bbc94954 100644 --- a/src/main/scala/dependencyAnalysis/SourceInfoStack.scala +++ b/src/main/scala/dependencyAnalysis/SourceInfoStack.scala @@ -48,7 +48,7 @@ trait SourceInfoStack { case class AnalysisSourceInfoStack() extends SourceInfoStack { private var sourceInfos: List[(AnalysisSourceInfo, DependencyType)] = List.empty private var forcedMainSource: Option[(AnalysisSourceInfo, DependencyType)] = None - var isJoinRelevantAssumption: Boolean = false + var isJoinRelevantNode: Boolean = false override def getAnalysisSourceInfos: List[(AnalysisSourceInfo, DependencyType)] = sourceInfos diff --git a/src/main/scala/rules/Brancher.scala b/src/main/scala/rules/Brancher.scala index f2321c235..d56628237 100644 --- a/src/main/scala/rules/Brancher.scala +++ b/src/main/scala/rules/Brancher.scala @@ -9,7 +9,7 @@ package viper.silicon.rules import viper.silicon.common.concurrency._ import viper.silicon.decider.PathConditionStack import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType -import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyAnalyzer, DependencyType} +import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, CompositeAnalysisSourceInfo, DependencyAnalyzer, DependencyType} import viper.silicon.interfaces.{Unreachable, VerificationResult} import viper.silicon.reporting.condenseToViperResult import viper.silicon.state.State @@ -48,8 +48,10 @@ object brancher extends BranchingRules { if(v.decider.isPathInfeasible()){ val analysisSourceInfo = v.decider.pushAndGetAnalysisSourceInfo(conditionExp._1, Some(DependencyAnalyzer.extractDependencyTypeFromInfo(conditionExp._1.info).getOrElse(DependencyType.PathCondition))) + val assertionNodesForJoin = DependencyAnalyzer.extractAssertionsForJoin(conditionExp._1, s.program) + assertionNodesForJoin.foreach(n => v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, CompositeAnalysisSourceInfo(v.decider.analysisSourceInfoStack.getFullSourceInfo.getTopLevelSource, AnalysisSourceInfo.createAnalysisSourceInfo(n)), v.decider.analysisSourceInfoStack.getDependencyType, isJoinNode=true)) if(!Expressions.isKnownWellDefined(conditionExp._1, Some(s.program))){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType, assertionNodesForJoin.nonEmpty || v.decider.analysisSourceInfoStack.isJoinRelevantNode) } v.decider.dependencyAnalyzer.addAssumption(condition, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getAssumptionType, isJoinNode=false) v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(analysisSourceInfo) diff --git a/src/main/scala/rules/ChunkSupporter.scala b/src/main/scala/rules/ChunkSupporter.scala index 54572ff9c..0b3c32d6f 100644 --- a/src/main/scala/rules/ChunkSupporter.scala +++ b/src/main/scala/rules/ChunkSupporter.scala @@ -84,7 +84,7 @@ object chunkSupporter extends ChunkSupportRules { (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { if(v.decider.isPathInfeasible()){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType, v.decider.analysisSourceInfoStack.isJoinRelevantNode) return Q(s, h, Option.when(returnSnap)(Unit), v) } @@ -252,7 +252,7 @@ object chunkSupporter extends ChunkSupportRules { (Q: (State, Heap, Term, Verifier) => VerificationResult) : VerificationResult = { if(v.decider.isPathInfeasible()){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType, v.decider.analysisSourceInfoStack.isJoinRelevantNode) return Q(s, h, Unit, v) } diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index cd1cf8cbe..16ff0249b 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -200,7 +200,7 @@ object consumer extends ConsumptionRules { */ if(v.decider.isPathInfeasible()){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, dependencyType) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, dependencyType, isJoinNode=false) return Q(s, h, Option.when(returnSnap)(Unit), v) } @@ -381,7 +381,7 @@ object consumer extends ConsumptionRules { State.mergeHeap( entry1.data._1, And(entry1.pathConditions.branchConditions), Option.when(withExp)(BigAnd(entry1.pathConditions.branchConditionExps.map(_._2.get))), entry2.data._1, And(entry2.pathConditions.branchConditions), Option.when(withExp)(BigAnd(entry2.pathConditions.branchConditionExps.map(_._2.get))), - AnalysisInfo(v.decider, v.decider.dependencyAnalyzer, StringAnalysisSourceInfo("conditional join", e0.pos), dependencyType.assumptionType, v.decider.analysisSourceInfoStack.isJoinRelevantAssumption) + AnalysisInfo(v.decider, v.decider.dependencyAnalyzer, StringAnalysisSourceInfo("conditional join", e0.pos), dependencyType.assumptionType, v.decider.analysisSourceInfoStack.isJoinRelevantNode) ), // Assume that entry1.pcs is inverse of entry2.pcs (entry1.data._2, entry2.data._2) match { diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 732af3021..f41a73867 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -10,7 +10,7 @@ import viper.silicon import viper.silicon.Config.JoinMode import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp -import viper.silicon.dependencyAnalysis.{AssumptionType, DependencyAnalyzer, DependencyType} +import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, CompositeAnalysisSourceInfo, DependencyAnalyzer, DependencyType} import viper.silicon.interfaces._ import viper.silicon.interfaces.state.ChunkIdentifer import viper.silicon.logger.records.data.{CondExpRecord, EvaluateRecord, ImpliesRecord} @@ -100,8 +100,11 @@ object evaluator extends EvaluationRules { : VerificationResult = { if(v.decider.isPathInfeasible()){ + val assertionNodesForJoin = DependencyAnalyzer.extractAssertionsForJoin(e, s.program) + assertionNodesForJoin.foreach(n => v.decider.dependencyAnalyzer.addAssumption(True, CompositeAnalysisSourceInfo(v.decider.analysisSourceInfoStack.getFullSourceInfo, AnalysisSourceInfo.createAnalysisSourceInfo(n)), v.decider.analysisSourceInfoStack.getAssumptionType, isJoinNode=true)) + if(!Expressions.isKnownWellDefined(e, Some(s.program))){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType, v.decider.analysisSourceInfoStack.isJoinRelevantNode) } val sort = v.symbolConverter.toSort(e.typ) val newVar = v.decider.fresh(sort, None) // just make sure the returned term typechecks @@ -667,7 +670,10 @@ object evaluator extends EvaluationRules { moreJoins = JoinMode.Off, assertReadAccessOnly = if (Verifier.config.respectFunctionPrePermAmounts()) s2.assertReadAccessOnly /* should currently always be false */ else true) + val oldIsRelevantJoinNode = v2.decider.analysisSourceInfoStack.isJoinRelevantNode + v2.decider.analysisSourceInfoStack.isJoinRelevantNode = true consumes(s3, pres, true, _ => pvePre, v2, v2.decider.analysisSourceInfoStack.getDependencyType)((s4, snap, v3) => { + v3.decider.analysisSourceInfoStack.isJoinRelevantNode = oldIsRelevantJoinNode val snap1 = snap.get.convert(sorts.Snap) val preFApp = App(functionSupporter.preconditionVersion(v3.symbolConverter.toFunction(func)), snap1 :: tArgs) val preExp = Option.when(withExp)({ diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index fb62234de..3e46277ec 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -11,7 +11,7 @@ import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.decider.RecordedPathConditions import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType -import viper.silicon.dependencyAnalysis.{AssumptionType, DependencyAnalyzer, DependencyType} +import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, CompositeAnalysisSourceInfo, DependencyAnalyzer, DependencyType, SimpleAssumptionNode} import viper.silicon.interfaces._ import viper.silicon.interfaces.state.{NonQuantifiedChunk, QuantifiedChunk} import viper.silicon.logger.records.data.{CommentRecord, ConditionalEdgeRecord, ExecuteRecord, MethodCallRecord} @@ -348,8 +348,11 @@ object executor extends ExecutionRules { : VerificationResult = { if(v.decider.isPathInfeasible()){ + val assertionNodesForJoin = DependencyAnalyzer.extractAssertionsForJoin(stmt, state.program) + assertionNodesForJoin.foreach(n => v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, CompositeAnalysisSourceInfo(v.decider.analysisSourceInfoStack.getFullSourceInfo.getTopLevelSource, AnalysisSourceInfo.createAnalysisSourceInfo(n)), v.decider.analysisSourceInfoStack.getDependencyType, isJoinNode=true)) + if(Statements.hasProofObligations(stmt, state.program)){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType, isJoinNode=false) } if(Statements.introducesSmtAssumptions(stmt)){ v.decider.dependencyAnalyzer.addAssumption(True, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getAssumptionType, isJoinNode=false) @@ -565,6 +568,8 @@ object executor extends ExecutionRules { tArgs zip Seq.fill(tArgs.size)(None) val s2 = s1.copy(g = Store(fargs.zip(argsWithExp)), recordVisited = true) + + v1.decider.analysisSourceInfoStack.isJoinRelevantNode = true consumes(s2, meth.pres, false, _ => pvePre, v1, getDependencyType(DependencyType.MethodCall))((s3, _, v2) => { v2.symbExLog.closeScope(preCondId) val postCondLog = new CommentRecord("Postcondition", s3, v2.decider.pcs) @@ -572,9 +577,8 @@ object executor extends ExecutionRules { val outs = meth.formalReturns.map(_.localVar) val gOuts = Store(outs.map(x => (x, v2.decider.fresh(x))).toMap) val s4 = s3.copy(g = s3.g + gOuts, oldHeaps = s3.oldHeaps + (Verifier.PRE_STATE_LABEL -> magicWandSupporter.getEvalHeap(s1))) - v2.decider.analysisSourceInfoStack.isJoinRelevantAssumption = true produces(s4, freshSnap, meth.posts, _ => pveCallTransformed, v2, getAssumptionType(AssumptionType.MethodCall))((s5, v3) => { - v3.decider.analysisSourceInfoStack.isJoinRelevantAssumption = false + v3.decider.analysisSourceInfoStack.isJoinRelevantNode = false v3.symbExLog.closeScope(postCondId) v3.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) val gLhs = Store(lhs.zip(outs) diff --git a/src/main/scala/rules/HeapSupporter.scala b/src/main/scala/rules/HeapSupporter.scala index 448f8f422..a06f2835a 100644 --- a/src/main/scala/rules/HeapSupporter.scala +++ b/src/main/scala/rules/HeapSupporter.scala @@ -188,7 +188,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { (Q: (State, Verifier) => VerificationResult) : VerificationResult = { if(v.decider.isPathInfeasible()){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType, v.decider.analysisSourceInfoStack.isJoinRelevantNode) v.decider.dependencyAnalyzer.addAssumption(False, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getAssumptionType, isJoinNode=false) return Q(s, v) } @@ -274,7 +274,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { (Q: (State, Term, Verifier) => VerificationResult): VerificationResult = { if(v.decider.isPathInfeasible()){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType, v.decider.analysisSourceInfoStack.isJoinRelevantNode) return Q(s, NoPerm, v) } @@ -341,7 +341,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { (Q: (State, Term, Verifier) => VerificationResult) : VerificationResult = { if(v.decider.isPathInfeasible()){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType, v.decider.analysisSourceInfoStack.isJoinRelevantNode) val sort = v.symbolConverter.toSort(fa.field.typ) val newVar = v.decider.fresh(sort, None) // just make sure the returned term typechecks @@ -546,7 +546,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { dependencyType: DependencyType) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult): VerificationResult = { if(v.decider.isPathInfeasible()){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType, v.decider.analysisSourceInfoStack.isJoinRelevantNode) return Q(s, h, Some(Unit), v) } @@ -662,7 +662,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { dependencyType: DependencyType) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult): VerificationResult = { if(v.decider.isPathInfeasible()){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType, v.decider.analysisSourceInfoStack.isJoinRelevantNode) return Q(s, h, Some(Unit), v) } diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index 4218ec2d6..932b06535 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -213,7 +213,7 @@ object producer extends ProductionRules { if(v.decider.isPathInfeasible()){ if(!Expressions.isKnownWellDefined(a, Some(s.program))){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType, v.decider.analysisSourceInfoStack.isJoinRelevantNode) } v.decider.dependencyAnalyzer.addAssumption(True, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getAssumptionType, isJoinNode=false) diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index c14bd90e3..b2334197b 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -81,6 +81,10 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif val annotatedAssumptionTypeOpt = DependencyAnalyzer.extractDependencyTypeFromInfo(method.info).map(_.assumptionType) var postConditionType = AssumptionType.getPostcondType(method.body.isEmpty, DependencyAnalyzer.extractDependencyTypeFromInfo(method.info)) + val presAssertionNodeForJoin = pres.flatMap(_.topLevelConjuncts).map(pc => SimpleAssertionNode(True, AssumptionType.Precondition, AnalysisSourceInfo.createAnalysisSourceInfo(pc), isClosed=false, isJoinNode=true)) + presAssertionNodeForJoin foreach v.decider.dependencyAnalyzer.addAssertionNode + + val daJoinNodeInfoOpt = method.info.getUniqueInfo[DependencyAnalysisJoinNodeInfo] if(daJoinNodeInfoOpt.isDefined){ val infodaJoinNodeInfo = daJoinNodeInfoOpt.get diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index 0fbbb9fcb..879194606 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -191,6 +191,10 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver conservingSnapshotGeneration = true, assertReadAccessOnly = !Verifier.config.respectFunctionPrePermAmounts()) + + val presAssertionNodeForJoin = function.pres.flatMap(_.topLevelConjuncts).map(pc => SimpleAssertionNode(True, AssumptionType.Precondition, AnalysisSourceInfo.createAnalysisSourceInfo(pc), isClosed=false, isJoinNode=true)) + presAssertionNodeForJoin foreach v.decider.dependencyAnalyzer.addAssertionNode + /* Phase 1: Check well-definedness of the specifications */ checkSpecificationWelldefinedness(s, function) match { case (result1: FatalResult, _) => From ac356df7fa7a18c4a67b5c07b1bbebfe02fedb14 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 17 Mar 2026 23:13:05 +0100 Subject: [PATCH 398/474] make interprocedural dependencies context-sensitive --- .../DependencyAnalyzer.scala | 6 +- .../dependencyAnalysis/DependencyGraph.scala | 73 +++++++++++++------ .../DependencyGraphImporter.scala | 2 +- .../DependencyGraphInterpreter.scala | 68 +++++++++++------ 4 files changed, 100 insertions(+), 49 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index 5ffba44b8..7471f4684 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -211,7 +211,7 @@ object DependencyAnalyzer { .groupBy(n => n.sourceInfo) .map{case (sourceInfo, axiomNodes) => (axiomNodes.map(_.id), axiomAssertionNodes.getOrElse(sourceInfo.getTopLevelSource, Seq.empty))} .foreach{case (axiomNodeIds, assertionNodeIds) => - newGraph.addEdgesConnectingMethods(assertionNodeIds, axiomNodeIds) // TODO ake: maybe we could merge the axiom nodes here since they represent the same axiom? + newGraph.addEdgesConnectingMethodsDownwards(assertionNodeIds, axiomNodeIds) // TODO ake: maybe we could merge the axiom nodes here since they represent the same axiom? } stopTimeMeasurementAndAddToTotal(startTime, timeForFunctionJoin) @@ -228,7 +228,7 @@ object DependencyAnalyzer { joinCandidateNodes.diff(joinCandidateAxioms.toSet) .map(node => (node.id, relevantAssumptionNodes.getOrElse(node.sourceInfo.getTopLevelSource, Seq.empty))) .foreach { case (src, targets) => - if (customInternalNodes.intersect(targets.toSet.union(Set(src))).isEmpty) newGraph.addEdgesConnectingMethods(src, targets) + if (customInternalNodes.intersect(targets.toSet.union(Set(src))).isEmpty) newGraph.addEdgesConnectingMethodsDownwards(src, targets) else newGraph.addEdges(src, targets) } @@ -240,7 +240,7 @@ object DependencyAnalyzer { joinCandidateNodes .map(node => (node.id, relevantAssertionNodes.getOrElse(node.sourceInfo.getTopLevelSource, Seq.empty))) .foreach { case (target, sources) => - if (customInternalNodes.intersect(sources.toSet.union(Set(target))).isEmpty) newGraph.addEdgesConnectingMethods(sources, target) + if (customInternalNodes.intersect(sources.toSet.union(Set(target))).isEmpty) newGraph.addEdgesConnectingMethodsUpwards(sources, target) else newGraph.addEdges(sources, target) } diff --git a/src/main/scala/dependencyAnalysis/DependencyGraph.scala b/src/main/scala/dependencyAnalysis/DependencyGraph.scala index ef0354a36..37645f6ee 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraph.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraph.scala @@ -21,12 +21,15 @@ trait ReadOnlyDependencyGraph extends AbstractReadOnlyDependencyGraph { def getAssumptionNodes: Seq[GeneralAssumptionNode] def getAssertionNodes: Seq[GeneralAssertionNode] def getDirectEdges: Map[Int, Set[Int]] // target -> direct dependencies - def getEdgesConnectingMethods: Map[Int, Set[Int]] + def getEdgesConnectingMethodsDownwards: Map[Int, Set[Int]] + def getEdgesConnectingMethodsUpwards: Map[Int, Set[Int]] def getAllEdges: Map[Int, Set[Int]] // target -> direct dependencies + def getAllEdges(includeUpwardEdges: Boolean, includeDownwardEdges: Boolean): Map[Int, Set[Int]] // target -> direct dependencies + @deprecated // needs to be adapted to the notion of upward and downward edges def existsAnyDependency(sources: Set[Int], targets: Set[Int], includeInfeasibilityNodes: Boolean): Boolean - def getAllDependencies(sources: Set[Int], includeInfeasibilityNodes: Boolean, includeIntraMethodEdges: Boolean=true): Set[Int] - def getAllDependents(sources: Set[Int], includeInfeasibilityNodes: Boolean): Set[Int] + def getAllDependencies(sources: Set[Int], includeInfeasibilityNodes: Boolean, includeUpwardEdges: Boolean, includeDownwardEdges: Boolean): Set[Int] + def getAllDependents(sources: Set[Int], includeInfeasibilityNodes: Boolean, includeUpwardEdges: Boolean, includeDownwardEdges: Boolean): Set[Int] def exportGraph(dirName: String): Unit } @@ -35,13 +38,15 @@ class DependencyGraph extends ReadOnlyDependencyGraph { private var assumptionNodes: mutable.Seq[GeneralAssumptionNode] = mutable.Seq() private var assertionNodes: mutable.Seq[GeneralAssertionNode] = mutable.Seq() private val edges: mutable.Map[Int, Set[Int]] = mutable.Map.empty - private val edgesConnectingMethods: mutable.Map[Int, Set[Int]] = mutable.Map.empty // keep this, it's relevant for computing verification progress + private val edgesConnectingMethodsDownwards: mutable.Map[Int, Set[Int]] = mutable.Map.empty // keep this, it's relevant for computing verification progress + private val edgesConnectingMethodsUpwards: mutable.Map[Int, Set[Int]] = mutable.Map.empty // keep this, it's relevant for computing verification progress def getNodes: Seq[DependencyAnalysisNode] = getAssumptionNodes ++ getAssertionNodes def getAssumptionNodes: Seq[GeneralAssumptionNode] = assumptionNodes.toSeq def getAssertionNodes: Seq[GeneralAssertionNode] = assertionNodes.toSeq def getDirectEdges: Map[Int, Set[Int]] = edges.toMap - def getEdgesConnectingMethods: Map[Int, Set[Int]] = edgesConnectingMethods.toMap + def getEdgesConnectingMethodsDownwards: Map[Int, Set[Int]] = edgesConnectingMethodsDownwards.toMap + def getEdgesConnectingMethodsUpwards: Map[Int, Set[Int]] = edgesConnectingMethodsUpwards.toMap def getIntraMethodEdges: Map[Int, Set[Int]] = { val keys = edges.keySet @@ -54,17 +59,24 @@ class DependencyGraph extends ReadOnlyDependencyGraph { def getAllEdges: Map[Int, Set[Int]] = { val intraMethodEdges = getIntraMethodEdges - val keys = intraMethodEdges.keySet ++ edgesConnectingMethods.keySet + val keys = intraMethodEdges.keySet ++ edgesConnectingMethodsDownwards.keySet ++ edgesConnectingMethodsUpwards.keySet val allEdges = mutable.Map[Int, Set[Int]]() keys foreach {key => - allEdges.update(key, intraMethodEdges.getOrElse(key, Set()) ++ edgesConnectingMethods.getOrElse(key, Set())) + allEdges.update(key, intraMethodEdges.getOrElse(key, Set()) ++ edgesConnectingMethodsDownwards.getOrElse(key, Set()) ++ edgesConnectingMethodsUpwards.getOrElse(key, Set())) } allEdges.toMap } - // TODO ake: remove - def setEdges(newEdges: Map[Int, Set[Int]]): Unit = { - newEdges.foreach(e => edges.update(e._1, e._2)) + def getAllEdges(includeDownwardEdges: Boolean, includeUpwardEdges: Boolean): Map[Int, Set[Int]] = { + val intraMethodEdges = getIntraMethodEdges + val upwardEdges: mutable.Map[Int, Set[Int]] = if(includeUpwardEdges) edgesConnectingMethodsUpwards else mutable.Map.empty + val downwardEdges: mutable.Map[Int, Set[Int]] = if(includeDownwardEdges) edgesConnectingMethodsDownwards else mutable.Map.empty + val keys = intraMethodEdges.keySet ++ downwardEdges.keySet ++ upwardEdges.keySet + val allEdges = mutable.Map[Int, Set[Int]]() + keys foreach {key => + allEdges.update(key, intraMethodEdges.getOrElse(key, Set()) ++ downwardEdges.getOrElse(key, Set()) ++ upwardEdges.getOrElse(key, Set())) + } + allEdges.toMap } def addAssumptionNode(node: GeneralAssumptionNode): Unit = { @@ -106,21 +118,37 @@ class DependencyGraph extends ReadOnlyDependencyGraph { targets foreach (addEdges(sources, _)) } - def addEdgesConnectingMethods(sources: Iterable[Int], target: Int): Unit = { - val oldSources = edgesConnectingMethods.getOrElse(target, Set.empty) + def addEdgesConnectingMethodsDownwards(sources: Iterable[Int], target: Int): Unit = { + val oldSources = edgesConnectingMethodsDownwards.getOrElse(target, Set.empty) + val newSources = sources.filter(_ != target) + if(newSources.nonEmpty) + edgesConnectingMethodsDownwards.update(target, oldSources ++ newSources) + } + + def addEdgesConnectingMethodsDownwards(sources: Iterable[Int], targets: Iterable[Int]): Unit = { + targets foreach (addEdgesConnectingMethodsDownwards(sources, _)) + } + + def addEdgesConnectingMethodsDownwards(source: Int, targets: Iterable[Int]): Unit = { + addEdgesConnectingMethodsDownwards(Set(source), targets) + } + + def addEdgesConnectingMethodsUpwards(sources: Iterable[Int], target: Int): Unit = { + val oldSources = edgesConnectingMethodsUpwards.getOrElse(target, Set.empty) val newSources = sources.filter(_ != target) if(newSources.nonEmpty) - edgesConnectingMethods.update(target, oldSources ++ newSources) + edgesConnectingMethodsUpwards.update(target, oldSources ++ newSources) } - def addEdgesConnectingMethods(sources: Iterable[Int], targets: Iterable[Int]): Unit = { - targets foreach (addEdgesConnectingMethods(sources, _)) + def addEdgesConnectingMethodsUpwards(sources: Iterable[Int], targets: Iterable[Int]): Unit = { + targets foreach (addEdgesConnectingMethodsUpwards(sources, _)) } - def addEdgesConnectingMethods(source: Int, targets: Iterable[Int]): Unit = { - addEdgesConnectingMethods(Set(source), targets) + def addEdgesConnectingMethodsUpwards(source: Int, targets: Iterable[Int]): Unit = { + addEdgesConnectingMethodsUpwards(Set(source), targets) } + @deprecated // needs to be adapted to the notion of upward and downward edges def existsAnyDependency(sources: Set[Int], targets: Set[Int], includeInfeasibilityNodes: Boolean): Boolean = { val infeasibilityNodeIds: Set[Int] = if(includeInfeasibilityNodes) Set.empty else (getAssumptionNodes filter (_.isInstanceOf[InfeasibilityNode]) map (_.id)).toSet var visited: Set[Int] = Set.empty @@ -137,11 +165,11 @@ class DependencyGraph extends ReadOnlyDependencyGraph { false } - def getAllDependencies(targets: Set[Int], includeInfeasibilityNodes: Boolean, includeEdgesConnectingMethods: Boolean=true): Set[Int] = { + def getAllDependencies(targets: Set[Int], includeInfeasibilityNodes: Boolean, includeUpwardEdges: Boolean, includeDownwardEdges: Boolean): Set[Int] = { val infeasibilityNodeIds: Set[Int] = if(includeInfeasibilityNodes) Set.empty else (getAssumptionNodes filter (_.isInstanceOf[InfeasibilityNode]) map (_.id)).toSet var visited: Set[Int] = Set.empty var queue: List[Int] = targets.toList - val allEdges = if(includeEdgesConnectingMethods) getAllEdges else getIntraMethodEdges + val allEdges = getAllEdges(includeDownwardEdges, includeUpwardEdges) while(queue.nonEmpty){ val curr = queue.head val newVisits = allEdges.getOrElse(curr, Set()).diff(infeasibilityNodeIds) @@ -151,11 +179,11 @@ class DependencyGraph extends ReadOnlyDependencyGraph { visited } - def getAllDependents(sources: Set[Int], includeInfeasibilityNodes: Boolean): Set[Int] = { + def getAllDependents(sources: Set[Int], includeInfeasibilityNodes: Boolean, includeUpwardEdges: Boolean, includeDownwardEdges: Boolean): Set[Int] = { val infeasibilityNodeIds: Set[Int] = if(includeInfeasibilityNodes) Set.empty else (getAssumptionNodes filter (_.isInstanceOf[InfeasibilityNode]) map (_.id)).toSet var visited: Set[Int] = Set.empty var queue: Set[Int] = sources - val allEdges = getAllEdges + val allEdges = getAllEdges(includeDownwardEdges, includeUpwardEdges) while(queue.nonEmpty){ val newVisits = allEdges.filter{case (t, s) => s.intersect(queue).nonEmpty && !infeasibilityNodeIds.contains(t)}.keys.toSet visited = visited ++ queue @@ -225,7 +253,8 @@ class DependencyGraph extends ReadOnlyDependencyGraph { private def exportEdges(fileName: String): Unit = { val builder = new StringBuilder() getDirectEdges foreach (e => e._2 foreach (s => builder.append(s.toString + "," + e._1.toString + ",direct" + "\n"))) - getEdgesConnectingMethods foreach (e => e._2 foreach (s => builder.append(s.toString + "," + e._1.toString + ",interprocedural" + "\n"))) + getEdgesConnectingMethodsDownwards foreach (e => e._2 foreach (s => builder.append(s.toString + "," + e._1.toString + ",interprocedural downward" + "\n"))) + getEdgesConnectingMethodsUpwards foreach (e => e._2 foreach (s => builder.append(s.toString + "," + e._1.toString + ",interprocedural upward" + "\n"))) val writer = new PrintWriter(fileName) writer.println("source,target,label") diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala index bd096924e..7660db674 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala @@ -120,7 +120,7 @@ object DependencyGraphImporter { tag match { case "direct" => graph.addEdges(List(sourceId.toInt), targetId.toInt) - case "interprocedural" => graph.addEdgesConnectingMethods(List(sourceId.toInt), targetId.toInt) + case "interprocedural" => graph.addEdgesConnectingMethodsDownwards(List(sourceId.toInt), targetId.toInt) case _ => throw new IllegalArgumentException(s"Unknown tag: $tag") } diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 6399329ab..a3ba4df12 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -1,6 +1,7 @@ package viper.silicon.dependencyAnalysis import dependencyAnalysis.{CompactUserLevelDependencyAnalysisNode, UserLevelDependencyAnalysisNode} +import viper.silicon.dependencyAnalysis.DATraversalMode.{DATraversalMode, Downwards, Upwards} import viper.silicon.interfaces.Failure import viper.silicon.verifier.Verifier import viper.silver.ast @@ -15,6 +16,13 @@ import java.nio.file.Paths import scala.collection.mutable + +object DATraversalMode extends Enumeration { + type DATraversalMode = Value + val Upwards, Downwards = Value +} + + class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependencyGraph, errors: List[Failure], member: Option[ast.Member]=None) extends AbstractDependencyGraphInterpreter{ def getGraph: ReadOnlyDependencyGraph = dependencyGraph @@ -63,23 +71,27 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen } def getAllNonInternalDependencies(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[DependencyAnalysisNode] = { - val allDependencies = dependencyGraph.getAllDependencies(nodeIdsToAnalyze, includeInfeasibilityNodes) - allDependencies flatMap nonInternalAssumptionNodesMap.get + val allDependenciesUpwards = dependencyGraph.getAllDependencies(nodeIdsToAnalyze, includeInfeasibilityNodes, includeUpwardEdges=true, includeDownwardEdges=false) + val allDependenciesDownwards = dependencyGraph.getAllDependencies(nodeIdsToAnalyze, includeInfeasibilityNodes, includeUpwardEdges=false, includeDownwardEdges=true) + (allDependenciesUpwards ++ allDependenciesDownwards) flatMap nonInternalAssumptionNodesMap.get } def getAllExplicitDependencies(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[DependencyAnalysisNode] = { - val allDependencies = dependencyGraph.getAllDependencies(nodeIdsToAnalyze, includeInfeasibilityNodes) - getExplicitAssumptionNodes.filter(node => allDependencies.contains(node.id)) + val allDependenciesUpwards = dependencyGraph.getAllDependencies(nodeIdsToAnalyze, includeInfeasibilityNodes, includeUpwardEdges=true, includeDownwardEdges=false) + val allDependenciesDownwards = dependencyGraph.getAllDependencies(nodeIdsToAnalyze, includeInfeasibilityNodes, includeUpwardEdges=false, includeDownwardEdges=true) + getExplicitAssumptionNodes.filter(node => (allDependenciesUpwards ++ allDependenciesDownwards).contains(node.id)) } def getAllNonInternalDependents(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[DependencyAnalysisNode] = { - val allDependents = dependencyGraph.getAllDependents(nodeIdsToAnalyze, includeInfeasibilityNodes) - getNonInternalAssertionNodes.filter(node => allDependents.contains(node.id)) + val allDependentsUpwards = dependencyGraph.getAllDependents(nodeIdsToAnalyze, includeInfeasibilityNodes, includeUpwardEdges=true, includeDownwardEdges=false) + val allDependentsDownwards = dependencyGraph.getAllDependents(nodeIdsToAnalyze, includeInfeasibilityNodes, includeUpwardEdges=false, includeDownwardEdges=true) + getNonInternalAssertionNodes.filter(node => (allDependentsUpwards ++ allDependentsDownwards).contains(node.id)) } def getAllExplicitDependents(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[DependencyAnalysisNode] = { - val allDependents = dependencyGraph.getAllDependents(nodeIdsToAnalyze, includeInfeasibilityNodes) - getExplicitAssertionNodes.filter(node => allDependents.contains(node.id)) + val allDependentsUpwards = dependencyGraph.getAllDependents(nodeIdsToAnalyze, includeInfeasibilityNodes, includeUpwardEdges=true, includeDownwardEdges=false) + val allDependentsDownwards = dependencyGraph.getAllDependents(nodeIdsToAnalyze, includeInfeasibilityNodes, includeUpwardEdges=false, includeDownwardEdges=true) + getExplicitAssertionNodes.filter(node => (allDependentsUpwards ++ allDependentsDownwards).contains(node.id)) } def getNonInternalAssumptionNodes: Set[DependencyAnalysisNode] = nonInternalAssumptionNodesMap.values.toSet @@ -93,6 +105,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen AssumptionType.explicitAssumptionTypes.contains(node.assumptionType) ) + @deprecated // needs to be adapted to the notion of upward and downward edges def hasAnyDependency(nodesToAnalyze: Set[DependencyAnalysisNode], includeInfeasibilityNodes: Boolean = true): Boolean = nodesToAnalyze.intersect(getNonInternalAssumptionNodes) .exists(node => dependencyGraph.existsAnyDependency(Set(node.id), nodesToAnalyze map (_.id) filter (_ != node.id), includeInfeasibilityNodes)) @@ -134,7 +147,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen def computeProofCoverage(assertionNodes: Set[DependencyAnalysisNode]): (Double, Set[String]) = { val assertionNodeIds = assertionNodes map (_.id) - val dependencies = dependencyGraph.getAllDependencies(assertionNodeIds, includeInfeasibilityNodes=true) + val dependencies = dependencyGraph.getAllDependencies(assertionNodeIds, includeInfeasibilityNodes=true, includeUpwardEdges=true, includeDownwardEdges=true) val coveredNodes = dependencies ++ assertionNodeIds val userLevelNodes = toUserLevelNodes(getNonInternalAssumptionNodes.filterNot(_.isInstanceOf[AxiomAssumptionNode])) @@ -250,34 +263,40 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen private lazy val sourceToAssertionNodes: Map[AnalysisSourceInfo, Set[DependencyAnalysisNode]] = getNonInternalAssertionNodes.groupBy(_.sourceInfo.getTopLevelSource) - val deps: DAMemo[AnalysisSourceInfo, Set[CompactUserLevelDependencyAnalysisNode]] = DAMemo { assertionNode => - def computeDependencies(currentNode: AnalysisSourceInfo, visited: Set[AnalysisSourceInfo]): Set[CompactUserLevelDependencyAnalysisNode] = { - if (visited.contains(currentNode)) { + + + val deps: DAMemo[(AnalysisSourceInfo, DATraversalMode), Set[CompactUserLevelDependencyAnalysisNode]] = DAMemo { case (assertionNode, mode) => + def computeDependencies(currentNode: AnalysisSourceInfo, visited: Set[(AnalysisSourceInfo, DATraversalMode)], traversalMode: DATraversalMode): Set[CompactUserLevelDependencyAnalysisNode] = { + if (visited.contains((currentNode, traversalMode))) { return Set.empty // break cycles to avoid infinite loops } - if (deps.contains(currentNode)) { - return deps(currentNode) + if (deps.contains(currentNode, traversalMode)) { + return deps((currentNode, traversalMode)) } - val updatedVisited = visited + currentNode + val updatedVisited = visited ++ Set((currentNode, traversalMode)) val allNonInternalAssertions = sourceToAssertionNodes.getOrElse(currentNode, Set.empty) - val intraMethodDependencyIds = dependencyGraph.getAllDependencies(allNonInternalAssertions.map(_.id), includeInfeasibilityNodes=true, includeIntraMethodEdges=false) + val intraMethodDependencyIds = dependencyGraph.getAllDependencies(allNonInternalAssertions.map(_.id), includeInfeasibilityNodes=true, includeUpwardEdges=false, includeDownwardEdges=false) val intraMethodDependencies = intraMethodDependencyIds.flatMap(nonInternalAssumptionNodesMap.get).filterNot(_.sourceInfo.getTopLevelSource.equals(currentNode)) - val postconditionNodeIds = intraMethodDependencyIds.flatMap(n => dependencyGraph.getEdgesConnectingMethods.getOrElse(n, Set.empty)) + val postconditionNodeIds = intraMethodDependencyIds.flatMap(n => dependencyGraph.getEdgesConnectingMethodsDownwards.getOrElse(n, Set.empty)) val postconditionNodes = postconditionNodeIds.flatMap(nodesMap.get) - val transDeps = postconditionNodes.map(_.sourceInfo.getTopLevelSource).filterNot(_.equals(currentNode)).flatMap(node => computeDependencies(node, updatedVisited)) + val preconditionNodeIds = intraMethodDependencyIds.flatMap(n => dependencyGraph.getEdgesConnectingMethodsUpwards.getOrElse(n, Set.empty)) + val preconditionNodes = preconditionNodeIds.flatMap(nodesMap.get) - val result = reduceCompactUserLevelNodes(toCompactUserLevelNodes(intraMethodDependencies ++ postconditionNodes) ++ transDeps) + val transDepsDownwards = postconditionNodes.map(_.sourceInfo.getTopLevelSource).filterNot(_.equals(currentNode)).flatMap(node => computeDependencies(node, updatedVisited, DATraversalMode.Downwards)) + val transDepsUpwards = preconditionNodes.map(_.sourceInfo.getTopLevelSource).filterNot(_.equals(currentNode)).flatMap(node => computeDependencies(node, updatedVisited, DATraversalMode.Upwards)) - deps.put(currentNode, result) + val result = reduceCompactUserLevelNodes(toCompactUserLevelNodes(intraMethodDependencies ++ postconditionNodes) ++ transDepsDownwards ++ transDepsUpwards) + + deps.put((currentNode, traversalMode), result) result } - computeDependencies(assertionNode, Set.empty) + computeDependencies(assertionNode, Set.empty, mode) } private def reduceCompactUserLevelNodes(inputNodes: Set[CompactUserLevelDependencyAnalysisNode]): Set[CompactUserLevelDependencyAnalysisNode] = { @@ -331,7 +350,11 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen def computeVerificationProgressOptimized(): (Double, Double, String) = { val allAssertions = getAssertionsRelevantForProgress.keySet.toList - val assertionDeps = allAssertions map (ass => (deps(ass), ass)) + val assertionDeps = allAssertions map (ass => ({ + val ups = deps((ass, Upwards)) + val downs = deps((ass, Downwards)) + reduceCompactUserLevelNodes(ups ++ downs) + }, ass)) val specQuality = computeSpecQuality(assertionDeps.flatMap(_._1).toSet) @@ -529,4 +552,3 @@ case class DAMemo[A,B](f: A => B) extends (A => B) { } - From c95a2fdf157b4d16e65de32ad81ea783edf85412 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 23 Mar 2026 09:20:15 +0100 Subject: [PATCH 399/474] refactoring --- .../DependencyAnalysisUserTool.scala | 69 ++++++++----------- 1 file changed, 29 insertions(+), 40 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala index 08acb3ebb..20079cdc0 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala @@ -57,48 +57,37 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete private def handleUserInput(userInput: String): Unit = { val inputParts = userInput.split(" ").toSeq - if (inputParts.head.equalsIgnoreCase("dep")) { - handleDependencyQuery(inputParts.tail.toSet) - } else if (inputParts.head.equalsIgnoreCase("downDep")) { - handleDependentsQuery(inputParts.tail.toSet) - } else if (inputParts.head.equalsIgnoreCase("hasDep")) { - handleHasDependencyQuery(inputParts.tail.toSet) - } else if (inputParts.head.equalsIgnoreCase("coverage") || inputParts.head.equalsIgnoreCase("cov")) { - handleProofCoverageQuery(inputParts.tail) - }else if (inputParts.head.equalsIgnoreCase("covLines") || inputParts.head.equalsIgnoreCase("covL")) { - handleProofCoverageLineQuery(inputParts.tail) - }else if (inputParts.head.equalsIgnoreCase("progress") || inputParts.head.equalsIgnoreCase("prog")) { - handleVerificationProgressQuery() - }else if (inputParts.head.equalsIgnoreCase("progressDebug")) { - handleVerificationProgressDEBUGQuery() - }else if (inputParts.head.equalsIgnoreCase("progressNaive")) { - handleVerificationProgressNaiveQuery() - }else if (inputParts.head.equalsIgnoreCase("guidance") || inputParts.head.equalsIgnoreCase("guide")) { - handleVerificationGuidanceQuery() - }else if (inputParts.head.equalsIgnoreCase("guideOld")) { - handleVerificationGuidanceOldQuery() - }else if(inputParts.head.equalsIgnoreCase("prune")) { - handlePruningRequest(inputParts.tail) - }else if(inputParts.head.equalsIgnoreCase("benchmark")) { - handleBenchmarkQuery() - }else if(inputParts.head.equalsIgnoreCase("precisionEval")) { - handlePrecisionEval(inputParts.tail) - }else if(inputParts.head.equalsIgnoreCase("annotate")) { - handleAnnotateQuery(inputParts.tail) - }else if(inputParts.head.equalsIgnoreCase("graphSize")){ - if(inputParts.tail.isEmpty) { - handleGraphSizeQuery(fullGraphInterpreter) - }else{ - memberInterpreters.filter(aa => aa.getMember.isDefined && aa.getMember.exists { - case meth: Method => meth.body.isDefined && inputParts.tail.contains(meth.name) - case func: ast.Function => func.body.isDefined && inputParts.tail.contains(func.name) - case _ => false - }) - .foreach(aa => handleGraphSizeQuery(aa)) + if (inputParts.nonEmpty) { + inputParts.head.toLowerCase match { + case "dep" => handleDependencyQuery(inputParts.tail.toSet) + case "downdep" => handleDependentsQuery(inputParts.tail.toSet) + case "hasdep" => handleHasDependencyQuery(inputParts.tail.toSet) + case "coverage" | "cov" => handleProofCoverageQuery(inputParts.tail) + case "covlines" | "covl" => handleProofCoverageLineQuery(inputParts.tail) + case "progress" | "prog" => handleVerificationProgressQuery() + case "progressdebug" => handleVerificationProgressDEBUGQuery() + case "progressnaive" => handleVerificationProgressNaiveQuery() + case "guidance" | "guide" => handleVerificationGuidanceQuery() + case "guideold" => handleVerificationGuidanceOldQuery() + case "prune" => handlePruningRequest(inputParts.tail) + case "benchmark" => handleBenchmarkQuery() + case "precisioneval" => handlePrecisionEval(inputParts.tail) + case "annotate" => handleAnnotateQuery(inputParts.tail) + case "graphsize" => + if (inputParts.tail.isEmpty) { + handleGraphSizeQuery(fullGraphInterpreter) + } else { + memberInterpreters.filter(aa => aa.getMember.isDefined && + aa.getMember.exists { + case meth: Method => meth.body.isDefined && inputParts.tail.contains(meth.name); + case func: ast.Function => func.body.isDefined && inputParts.tail.contains(func.name); + case _ => false }) + .foreach(aa => handleGraphSizeQuery(aa)) + } + case _ => println("Invalid input."); println(infoString) } } else { - println("Invalid input.") - println(infoString) + println("Invalid input."); println(infoString) } } From 43501c331aaf3c8486878d046dc6f264b97d7f90 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 25 Mar 2026 17:47:01 +0100 Subject: [PATCH 400/474] create DependencyAnalysisInfo and propagate it --- src/main/scala/decider/Decider.scala | 119 +++-- .../dependencyAnalysis/AnalysisInfo.scala | 7 +- .../DependencyAnalysisInfo.scala | 152 +++++++ .../DependencyAnalysisNode.scala | 8 +- .../DependencyAnalyzer.scala | 76 ++-- src/main/scala/rules/Brancher.scala | 28 +- src/main/scala/rules/ChunkSupporter.scala | 81 ++-- src/main/scala/rules/Consumer.scala | 100 ++--- src/main/scala/rules/Evaluator.scala | 421 +++++++++--------- src/main/scala/rules/Executor.scala | 137 +++--- src/main/scala/rules/HavocSupporter.scala | 28 +- src/main/scala/rules/HeapSupporter.scala | 131 +++--- src/main/scala/rules/Joiner.scala | 11 +- src/main/scala/rules/LetSupporter.scala | 24 +- src/main/scala/rules/MagicWandSupporter.scala | 81 ++-- .../rules/MoreCompleteExhaleSupporter.scala | 89 ++-- .../scala/rules/PermissionSupporter.scala | 17 +- src/main/scala/rules/PredicateSupporter.scala | 61 ++- src/main/scala/rules/Producer.scala | 102 +++-- .../scala/rules/QuantifiedChunkSupport.scala | 183 ++++---- src/main/scala/rules/StateConsolidator.scala | 102 ++--- .../PredicateVerificationUnit.scala | 6 +- .../scala/supporters/SnapshotSupporter.scala | 13 +- 23 files changed, 1055 insertions(+), 922 deletions(-) create mode 100644 src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 9d0988e9b..362370163 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -8,10 +8,10 @@ package viper.silicon.decider import com.typesafe.scalalogging.Logger import viper.silicon._ -import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType -import viper.silicon.dependencyAnalysis._ import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp +import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType +import viper.silicon.dependencyAnalysis._ import viper.silicon.interfaces._ import viper.silicon.interfaces.decider._ import viper.silicon.interfaces.state.{Chunk, GeneralChunk} @@ -52,11 +52,10 @@ trait Decider { def pushScope(): Unit def popScope(): Unit - def checkSmoke(assumptionType: AssumptionType): Boolean - def checkSmoke(assumptionType: AssumptionType, isAssert: Boolean): Boolean - def checkSmoke(isAssert: Boolean = false): Boolean + def checkSmoke(dAInfo: DependencyAnalysisInfo): Boolean + def checkSmoke(dAInfo: DependencyAnalysisInfo, isAssert: Boolean = false): Boolean - def setCurrentBranchCondition(t: Term, te: (ast.Exp, Option[ast.Exp]), assumptionType: AssumptionType): Unit + def setCurrentBranchCondition(t: Term, te: (ast.Exp, Option[ast.Exp]), dAInfo: DependencyAnalysisInfo): Unit def setPathConditionMark(): Mark def finishDebugSubExp(description : String): Unit @@ -70,25 +69,24 @@ trait Decider { def pushAndGetAnalysisSourceInfo(stmt: ast.Stmt, dependencyType: Option[DependencyType]): AnalysisSourceInfo def isPathInfeasible(): Boolean - def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp], assumptionType: AssumptionType): Unit - def assume(t: Term, debugExp: Option[DebugExp], assumptionType: AssumptionType): Unit - def assume(assumptions: Iterable[Term], debugExps: Option[Iterable[DebugExp]], description: String, enforceAssumption: Boolean, assumptionType: AssumptionType): Unit - def assume(terms: Seq[Term], debugExps: Option[Seq[DebugExp]], assumptionType: AssumptionType): Unit - def assumeDefinition(t: Term, debugExp: Option[DebugExp], assumptionType: AssumptionType): Unit - def assume(terms: Iterable[Term], debugExp: Option[DebugExp], enforceAssumption: Boolean, assumptionType: AssumptionType): Unit + def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp], dAInfo: DependencyAnalysisInfo): Unit + def assume(t: Term, debugExp: Option[DebugExp], dAInfo: DependencyAnalysisInfo): Unit + def assume(assumptions: Iterable[Term], debugExps: Option[Iterable[DebugExp]], description: String, enforceAssumption: Boolean, dAInfo: DependencyAnalysisInfo): Unit + def assume(terms: Seq[Term], debugExps: Option[Seq[DebugExp]], dAInfo: DependencyAnalysisInfo): Unit + def assumeDefinition(t: Term, debugExp: Option[DebugExp], dAInfo: DependencyAnalysisInfo): Unit + def assume(terms: Iterable[Term], debugExp: Option[DebugExp], enforceAssumption: Boolean, dAInfo: DependencyAnalysisInfo): Unit def assumeLabel(term: Term, assumptionLabel: String): Unit - def check(t: Term, timeout: Int): Boolean - def check(t: Term, timeout: Int, assumptionType: AssumptionType): Boolean - def checkSmokeAndSetInfeasibilityNode(): Unit + def check(t: Term, timeout: Int, dAInfo: DependencyAnalysisInfo): Boolean + def checkSmokeAndSetInfeasibilityNode(dAInfo: DependencyAnalysisInfo): Unit /* TODO: Consider changing assert such that * 1. It passes State and Operations to the continuation * 2. The implementation reacts to a failing assertion by e.g. a state consolidation */ - def assert(t: Term, timeout: Option[Int] = None)(Q: Boolean => VerificationResult): VerificationResult - def assert(t: Term, assumptionType: AssumptionType)(Q: Boolean => VerificationResult): VerificationResult - def assert(t: Term, assumptionType: AssumptionType, timeout: Option[Int])(Q: Boolean => VerificationResult): VerificationResult + + def assert(t: Term, dAInfo: DependencyAnalysisInfo)(Q: Boolean => VerificationResult): VerificationResult + def assert(t: Term, dAInfo: DependencyAnalysisInfo, timeout: Option[Int])(Q: Boolean => VerificationResult): VerificationResult def fresh(id: String, sort: Sort, ptype: Option[PType]): Var def fresh(id: String, argSorts: Seq[Sort], resultSort: Sort): Function @@ -121,9 +119,9 @@ trait Decider { var analysisSourceInfoStack: AnalysisSourceInfoStack def initDependencyAnalyzer(member: Member, preambleNodes: Iterable[DependencyAnalysisNode]): Unit def removeDependencyAnalyzer(): Unit - def getAnalysisInfo(assumptionType: AssumptionType): AnalysisInfo + def getAnalysisInfo(daInfo: DependencyAnalysisInfo): AnalysisInfo def isDependencyAnalysisEnabled: Boolean - def handleFailedAssertionForDependencyAnalysis(failedAssertion: Term, assertionType: AssumptionType, assumeFailedAssertion: Boolean): Unit + def handleFailedAssertionForDependencyAnalysis(failedAssertion: Term, dAInfo: DependencyAnalysisInfo, assumeFailedAssertion: Boolean): Unit } /* @@ -174,7 +172,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => analysisSourceInfoStack = AnalysisSourceInfoStack() } - def getAnalysisInfo(assumptionType: AssumptionType): AnalysisInfo = AnalysisInfo(this, dependencyAnalyzer, analysisSourceInfoStack.getFullSourceInfo, assumptionType, analysisSourceInfoStack.isJoinRelevantNode) + def getAnalysisInfo(dAInfo: DependencyAnalysisInfo): AnalysisInfo = AnalysisInfo(this, dependencyAnalyzer, dAInfo) def functionDecls: Set[FunctionDecl] = _declaredFreshFunctions def macroDecls: Vector[MacroDecl] = _declaredFreshMacros @@ -295,11 +293,11 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => //symbExLog.closeScope(sepIdentifier) } - def setCurrentBranchCondition(t: Term, te: (ast.Exp, Option[ast.Exp]), assumptionType: AssumptionType): Unit = { + def setCurrentBranchCondition(t: Term, te: (ast.Exp, Option[ast.Exp]), dAInfo: DependencyAnalysisInfo): Unit = { if(isPathInfeasible()) return pathConditions.setCurrentBranchCondition(t, te) - assume(t, Option.when(te._2.isDefined)(te._1), te._2, assumptionType) + assume(t, Option.when(te._2.isDefined)(te._1), te._2, dAInfo) } def setPathConditionMark(): Mark = pathConditions.mark() @@ -394,23 +392,23 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => - def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp], assumptionType: AssumptionType): Unit = { + def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp], dAInfo: DependencyAnalysisInfo): Unit = { if (finalExp.isDefined) { - assume(assumptions=InsertionOrderedSet((t, Some(DebugExp.createInstance(e.get, finalExp.get)))), analysisSourceInfoStack.getFullSourceInfo, enforceAssumption = false, isDefinition = false, assumptionType) + assume(assumptions=InsertionOrderedSet((t, Some(DebugExp.createInstance(e.get, finalExp.get)))), analysisSourceInfoStack.getFullSourceInfo, enforceAssumption = false, isDefinition = false, dAInfo) } else { - assume(assumptions=InsertionOrderedSet((t, None)), analysisSourceInfoStack.getFullSourceInfo, enforceAssumption = false, isDefinition = false, assumptionType) + assume(assumptions=InsertionOrderedSet((t, None)), analysisSourceInfoStack.getFullSourceInfo, enforceAssumption = false, isDefinition = false, dAInfo) } } - def assume(t: Term, debugExp: Option[DebugExp], assumptionType: AssumptionType): Unit = { - assume(InsertionOrderedSet(Seq((t, debugExp))), analysisSourceInfoStack.getFullSourceInfo, enforceAssumption = false, isDefinition = false, assumptionType) + def assume(t: Term, debugExp: Option[DebugExp], dAInfo: DependencyAnalysisInfo): Unit = { + assume(InsertionOrderedSet(Seq((t, debugExp))), analysisSourceInfoStack.getFullSourceInfo, enforceAssumption = false, isDefinition = false, dAInfo) } - def assumeDefinition(t: Term, debugExp: Option[DebugExp], assumptionType: AssumptionType): Unit = { - assume(InsertionOrderedSet(Seq((t, debugExp))), analysisSourceInfoStack.getFullSourceInfo, enforceAssumption=false, isDefinition=true, assumptionType) + def assumeDefinition(t: Term, debugExp: Option[DebugExp], dAInfo: DependencyAnalysisInfo): Unit = { + assume(InsertionOrderedSet(Seq((t, debugExp))), analysisSourceInfoStack.getFullSourceInfo, enforceAssumption=false, isDefinition=true, dAInfo) } - def assume(assumptions: InsertionOrderedSet[(Term, Option[DebugExp])], analysisSourceInfo: AnalysisSourceInfo, enforceAssumption: Boolean, isDefinition: Boolean, assumptionType: AssumptionType): Unit = { + def assume(assumptions: InsertionOrderedSet[(Term, Option[DebugExp])], analysisSourceInfo: AnalysisSourceInfo, enforceAssumption: Boolean, isDefinition: Boolean, dAInfo: DependencyAnalysisInfo): Unit = { val filteredAssumptions = if (enforceAssumption) assumptions else assumptions filterNot (a => isKnownToBeTrue(a._1)) @@ -421,15 +419,15 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } val filteredAssumptionsWithLabels = filteredAssumptions map{case (t, _) => - val assumptionId: Option[Int] = dependencyAnalyzer.addAssumption(t, analysisSourceInfo, assumptionType, analysisSourceInfoStack.isJoinRelevantNode) + val assumptionId: Option[Int] = dependencyAnalyzer.addAssumption(t, dAInfo) (t, DependencyAnalyzer.createAssumptionLabel(assumptionId)) } if (filteredAssumptions.nonEmpty) assumeWithoutSmokeChecks(filteredAssumptionsWithLabels, isDefinition=isDefinition) } - def assume(assumptions: Seq[Term], debugExps: Option[Seq[DebugExp]], assumptionType: AssumptionType): Unit = { - val assumptionsWithLabels = addAssumptionLabels(assumptions, assumptionType) + def assume(assumptions: Seq[Term], debugExps: Option[Seq[DebugExp]], dAInfo: DependencyAnalysisInfo): Unit = { + val assumptionsWithLabels = addAssumptionLabels(assumptions, dAInfo) assumeWithoutSmokeChecks(InsertionOrderedSet(assumptionsWithLabels)) if (debugMode) { @@ -437,7 +435,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } } - def assume(assumptions: Iterable[Term], debugExps: Option[Iterable[DebugExp]], description: String, enforceAssumption: Boolean, assumptionType: AssumptionType): Unit = { + def assume(assumptions: Iterable[Term], debugExps: Option[Iterable[DebugExp]], description: String, enforceAssumption: Boolean, dAInfo: DependencyAnalysisInfo): Unit = { val debugExp = Option.when(debugExps.isDefined)(DebugExp.createInstance(description, InsertionOrderedSet(debugExps.get))) val filteredTerms = @@ -446,7 +444,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => if(filteredTerms.isEmpty) return - val assumptionsWithLabels = addAssumptionLabels(filteredTerms, assumptionType) + val assumptionsWithLabels = addAssumptionLabels(filteredTerms, dAInfo) assumeWithoutSmokeChecks(InsertionOrderedSet(assumptionsWithLabels)) @@ -455,14 +453,14 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } } - private def addAssumptionLabels(filteredTerms: Iterable[Term], assumptionType: AssumptionType) = { + private def addAssumptionLabels(filteredTerms: Iterable[Term], dAInfo: DependencyAnalysisInfo) = { filteredTerms map (t => { - val assumptionIds = dependencyAnalyzer.addAssumption(t, analysisSourceInfoStack.getFullSourceInfo, assumptionType, analysisSourceInfoStack.isJoinRelevantNode) + val assumptionIds = dependencyAnalyzer.addAssumption(t, dAInfo) (t, DependencyAnalyzer.createAssumptionLabel(assumptionIds)) }) } - def assume(terms: Iterable[Term], debugExp: Option[DebugExp], enforceAssumption: Boolean, assumptionType: AssumptionType): Unit = { + def assume(terms: Iterable[Term], debugExp: Option[DebugExp], enforceAssumption: Boolean, dAInfo: DependencyAnalysisInfo): Unit = { val filteredTerms = if (enforceAssumption) terms else terms filterNot isKnownToBeTrue @@ -472,7 +470,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => if (debugMode) { addDebugExp(debugExp.get.withTerm(And(filteredTerms))) } - val termsWithLabel = addAssumptionLabels(filteredTerms, assumptionType) + val termsWithLabel = addAssumptionLabels(filteredTerms, dAInfo) assumeWithoutSmokeChecks(InsertionOrderedSet(termsWithLabel)) } @@ -514,12 +512,10 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => /* Asserting facts */ - def checkSmoke(assumptionType: AssumptionType): Boolean = checkSmoke(assumptionType, isAssert = false) - - def checkSmoke(isAssert: Boolean=false): Boolean = checkSmoke(analysisSourceInfoStack.getAssertionType, isAssert) + def checkSmoke(dAInfo: DependencyAnalysisInfo): Boolean = checkSmoke(dAInfo, isAssert = false) - def checkSmoke(assumptionType: AssumptionType, isAssert: Boolean): Boolean = { - val checkNode = dependencyAnalyzer.createAssertOrCheckNode(False, assumptionType, analysisSourceInfoStack.getFullSourceInfo, !isAssert, analysisSourceInfoStack.isJoinRelevantNode) + def checkSmoke(dAInfo: DependencyAnalysisInfo, isAssert: Boolean=false): Boolean = { + val checkNode = dependencyAnalyzer.createAssertOrCheckNode(False, dAInfo, !isAssert) val label = DependencyAnalyzer.createAssertionLabel(checkNode.map(_.id)) val timeout = if (isAssert) Verifier.config.assertTimeout.toOption else Verifier.config.checkTimeout.toOption @@ -531,7 +527,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => }else if(result){ checkNode foreach dependencyAnalyzer.addAssertionNode dependencyAnalyzer.processUnsatCoreAndAddDependencies(prover.getLastUnsatCore, label) - val infeasibleNodeId = dependencyAnalyzer.addInfeasibilityNode(!isAssert, analysisSourceInfoStack.getFullSourceInfo, assumptionType) + val infeasibleNodeId = dependencyAnalyzer.addInfeasibilityNode(!isAssert, dAInfo) // THIS WOULD BE UNSOUND! Unsoundness is introduced when infeasibility is introduced while executing a package statements and pontentially in other cases as well. // assumeWithoutSmokeChecks(InsertionOrderedSet((False, DependencyAnalyzer.createAssumptionLabel(infeasibleNodeId)))) dependencyAnalyzer.addDependency(checkNode.map(_.id), infeasibleNodeId) @@ -540,41 +536,38 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => result } - def checkSmokeAndSetInfeasibilityNode(): Unit = { + def checkSmokeAndSetInfeasibilityNode(dAInfo: DependencyAnalysisInfo): Unit = { var infeasibilityNodeId: Option[Int] = pcs.getCurrentInfeasibilityNode if(infeasibilityNodeId.isDefined) return - val (success, checkNode) = deciderAssert(False, AssumptionType.Internal, Some(Verifier.config.checkTimeout()), isCheck=true) + val (success, checkNode) = deciderAssert(False, dAInfo, Some(Verifier.config.checkTimeout()), isCheck=true) if(success){ - infeasibilityNodeId = dependencyAnalyzer.addInfeasibilityNode(isCheck = true, analysisSourceInfoStack.getFullSourceInfo, AssumptionType.CustomInternal) + infeasibilityNodeId = dependencyAnalyzer.addInfeasibilityNode(isCheck = true, dAInfo) dependencyAnalyzer.addDependency(checkNode.map(_.id), infeasibilityNodeId) pcs.setCurrentInfeasibilityNode(infeasibilityNodeId) } } - override def handleFailedAssertionForDependencyAnalysis(failedAssertion: Term, assertionType: AssumptionType, assumeFailedAssertion: Boolean): Unit = { - dependencyAnalyzer.addAssertionFailedNode(failedAssertion, assertionType, analysisSourceInfoStack.getFullSourceInfo, analysisSourceInfoStack.isJoinRelevantNode) + override def handleFailedAssertionForDependencyAnalysis(failedAssertion: Term, dAInfo: DependencyAnalysisInfo, assumeFailedAssertion: Boolean): Unit = { + dependencyAnalyzer.addAssertionFailedNode(failedAssertion, dAInfo) if(assumeFailedAssertion){ - assume(failedAssertion, None, None, AssumptionType.Explicit) + assume(failedAssertion, None, None, dAInfo) failedAssertion match { - case False => checkSmokeAndSetInfeasibilityNode() + case False => checkSmokeAndSetInfeasibilityNode(dAInfo) case _ => } } } - def check(t: Term, timeout: Int): Boolean = check(t, timeout, analysisSourceInfoStack.getAssertionType) - - def check(t: Term, timeout: Int, assumptionType: AssumptionType): Boolean = { - deciderAssert(t, assumptionType, Some(timeout), isCheck=true)._1 + def check(t: Term, timeout: Int, dAInfo: DependencyAnalysisInfo): Boolean = { + deciderAssert(t, dAInfo, Some(timeout), isCheck=true)._1 } - def assert(t: Term, timeout: Option[Int] = Verifier.config.assertTimeout.toOption)(Q: Boolean => VerificationResult): VerificationResult = assert(t, analysisSourceInfoStack.getAssertionType, timeout)(Q) - def assert(t: Term, assumptionType: AssumptionType)(Q: Boolean => VerificationResult): VerificationResult = assert(t, assumptionType, timeout=Verifier.config.assertTimeout.toOption)(Q) + def assert(t: Term, dAInfo: DependencyAnalysisInfo)(Q: Boolean => VerificationResult): VerificationResult = assert(t, dAInfo, timeout=Verifier.config.assertTimeout.toOption)(Q) - def assert(t: Term, assumptionType: AssumptionType, timeout: Option[Int])(Q: Boolean => VerificationResult): VerificationResult = { - val (success, _) = deciderAssert(t, assumptionType, timeout) + def assert(t: Term, dAInfo: DependencyAnalysisInfo, timeout: Option[Int])(Q: Boolean => VerificationResult): VerificationResult = { + val (success, _) = deciderAssert(t, dAInfo, timeout) // If the SMT query was not successful, store it (possibly "overwriting" // any previously saved query), otherwise discard any query we had saved @@ -588,13 +581,13 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => Q(success) } - private def deciderAssert(t: Term, assumptionType: AssumptionType, timeout: Option[Int], isCheck: Boolean=false) = { + private def deciderAssert(t: Term, dAInfo: DependencyAnalysisInfo, timeout: Option[Int], isCheck: Boolean=false) = { val assertRecord = new DeciderAssertRecord(t, timeout) val sepIdentifier = symbExLog.openScope(assertRecord) val asserted = if(isDependencyAnalysisEnabled) t.equals(True) else isKnownToBeTrue(t) - val assertNode = if(!asserted) dependencyAnalyzer.createAssertOrCheckNode(t, assumptionType, decider.analysisSourceInfoStack.getFullSourceInfo, isCheck, analysisSourceInfoStack.isJoinRelevantNode) else None + val assertNode = if(!asserted) dependencyAnalyzer.createAssertOrCheckNode(t, dAInfo, isCheck) else None val result = asserted || proverAssert(t, timeout, DependencyAnalyzer.createAssertionLabel(assertNode map (_.id))) diff --git a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala index dd296c61a..087de4c24 100644 --- a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala +++ b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala @@ -88,10 +88,5 @@ object DependencyType { case class DependencyType(assumptionType: AssumptionType, assertionType: AssumptionType) -case class AnalysisInfo(decider: Decider, dependencyAnalyzer: DependencyAnalyzer, sourceInfo: AnalysisSourceInfo, - assumptionType: AssumptionType, isJoinNode: Boolean) { - def withAssumptionType(newAssumptionType: AssumptionType): AnalysisInfo = { - copy(assumptionType=newAssumptionType) - } -} +case class AnalysisInfo(decider: Decider, dependencyAnalyzer: DependencyAnalyzer, dAInfo: DependencyAnalysisInfo) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala new file mode 100644 index 000000000..fbb2c7c27 --- /dev/null +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala @@ -0,0 +1,152 @@ +package viper.silicon.dependencyAnalysis + +import jdk.jshell.spi.ExecutionControl.NotImplementedException +import viper.silicon.dependencyAnalysis.JoinType.JoinType +import viper.silver.ast + +trait DependencyAnalysisInfo { + def getSourceInfo: AnalysisSourceInfo + def getDependencyType: DependencyType + def getMergeInfo: DependencyAnalysisMergeInfo + def getJoinInfo: DependencyAnalysisJoinInfo + + def withDependencyType(newDependencyType: DependencyType): DependencyAnalysisInfo + + def withSource(sourceInfo: AnalysisSourceInfo): DependencyAnalysisInfo + + def withAdditionalEvalNode(analysisSourceInfo: AnalysisSourceInfo): DependencyAnalysisInfo +} + + +case class NoDependencyAnalysisInfo() extends DependencyAnalysisInfo { + override def getSourceInfo: AnalysisSourceInfo = throw new NotImplementedException("NoDependencyAnalysisInfo.getSourceInfo") + + override def getDependencyType: DependencyType = throw new NotImplementedException("NoDependencyAnalysisInfo.getDependencyType") + + override def getMergeInfo: DependencyAnalysisMergeInfo = throw new NotImplementedException("NoDependencyAnalysisInfo.getMergeInfo") + + override def getJoinInfo: DependencyAnalysisJoinInfo = throw new NotImplementedException("NoDependencyAnalysisInfo.getJoinInfo") + + override def withDependencyType(newDependencyType: DependencyType): NoDependencyAnalysisInfo = this + + override def withSource(sourceInfo: AnalysisSourceInfo): DependencyAnalysisInfo = this + + override def withAdditionalEvalNode(analysisSourceInfo: AnalysisSourceInfo): NoDependencyAnalysisInfo = this +} + +case class DependencyAnalysisInfoWithoutSource( dependencyType: DependencyType, + mergeInfo: Option[DependencyAnalysisMergeInfo]=None, + joinInfo: DependencyAnalysisJoinInfo=NoDependencyAnalysisJoin(), + // additionalNodes: Set[CustomDependencyAnalysisNode]=Set.empty + ) extends DependencyAnalysisInfo { + + override def getSourceInfo: AnalysisSourceInfo = throw new NotImplementedException("NoDependencyAnalysisInfo.getSourceInfo") + + override def getDependencyType: DependencyType = dependencyType + + override def getMergeInfo: DependencyAnalysisMergeInfo = mergeInfo.get + + override def getJoinInfo: DependencyAnalysisJoinInfo = joinInfo + + override def withDependencyType(newDependencyType: DependencyType): DependencyAnalysisInfoWithoutSource = + this.copy(dependencyType = newDependencyType) + + def withIsJoinNode(newJoinInfo: DependencyAnalysisJoinInfo): DependencyAnalysisInfoWithoutSource = + this.copy(joinInfo = newJoinInfo) + + override def withAdditionalEvalNode(analysisSourceInfo: AnalysisSourceInfo): FullDependencyAnalysisInfo = + FullDependencyAnalysisInfo(analysisSourceInfo, List(analysisSourceInfo), dependencyType, mergeInfo.getOrElse(SimpleDependencyAnalysisMerge(analysisSourceInfo)), joinInfo) + + override def withSource(sourceInfo: AnalysisSourceInfo): DependencyAnalysisInfo = withAdditionalEvalNode(sourceInfo) + +} + + + +case class FullDependencyAnalysisInfo(sourceInfo: AnalysisSourceInfo, + evaluationStack: List[AnalysisSourceInfo], /* TODO ake: do we need this? */ + dependencyType: DependencyType, + mergeInfo: DependencyAnalysisMergeInfo, /* required for lifting the low-level graph */ + joinInfo: DependencyAnalysisJoinInfo=NoDependencyAnalysisJoin(), /* required for interprocedural edges */ + //additionalNodes: Set[CustomDependencyAnalysisNode]=Set.empty, /* TODO ake: should be part of the AST node info but does not need to be propagated */ + ) extends DependencyAnalysisInfo { + + override def getSourceInfo: AnalysisSourceInfo = sourceInfo + + override def getDependencyType: DependencyType = dependencyType + + override def getMergeInfo: DependencyAnalysisMergeInfo = mergeInfo + + override def getJoinInfo: DependencyAnalysisJoinInfo = joinInfo + + override def withAdditionalEvalNode(analysisSourceInfo: AnalysisSourceInfo): FullDependencyAnalysisInfo = + this.copy(evaluationStack=analysisSourceInfo+:evaluationStack) + override def withDependencyType(newDependencyType: DependencyType): FullDependencyAnalysisInfo = + this.copy(dependencyType=newDependencyType) + def withMergeInfo(newMergeInfo: DependencyAnalysisMergeInfo): FullDependencyAnalysisInfo = + this.copy(mergeInfo=newMergeInfo) + def withJoinInfo(newJoinInfo: DependencyAnalysisJoinInfo): FullDependencyAnalysisInfo = + this.copy(joinInfo=newJoinInfo) + + + override def withSource(sourceInfo: AnalysisSourceInfo): DependencyAnalysisInfo = + this.copy(sourceInfo=sourceInfo) + +} + +object FullDependencyAnalysisInfo { + def create(sourceString: String, dependencyType: DependencyType, mergeInfo: DependencyAnalysisMergeInfo, joinNodeInfo: DependencyAnalysisJoinInfo) = { + val source = AnalysisSourceInfo.createAnalysisSourceInfo(sourceString, ast.NoPosition) + FullDependencyAnalysisInfo(source, List(source), dependencyType, mergeInfo, joinNodeInfo) + } + + def create(exp: ast.Exp) = { + val source = AnalysisSourceInfo.createAnalysisSourceInfo(exp) + val dependencyType = DependencyType.Implicit // FIXME ake: extract info from exp and initialize accordingly + FullDependencyAnalysisInfo(source, List(source), dependencyType, SimpleDependencyAnalysisMerge(source), NoDependencyAnalysisJoin()) + } + + def create(stmt: ast.Stmt) = { + val source = AnalysisSourceInfo.createAnalysisSourceInfo(stmt) + val dependencyType = DependencyType.get(stmt) // FIXME ake: extract info from exp and initialize accordingly + FullDependencyAnalysisInfo(source, List(source), dependencyType, SimpleDependencyAnalysisMerge(source), NoDependencyAnalysisJoin()) + } +} + + +object JoinType extends Enumeration { + type JoinType = Value + val Source, Sink = Value +} + +trait DependencyAnalysisJoinInfo { + + def isJoin: Boolean = true + +} + +case class NoDependencyAnalysisJoin() extends DependencyAnalysisJoinInfo { + override def isJoin: Boolean = false +} + +case class EvalStackDependencyAnalysisJoin(joinType: JoinType) extends DependencyAnalysisJoinInfo + +case class SimpleDependencyAnalysisJoin(sourceInfo: AnalysisSourceInfo, joinType: JoinType) extends DependencyAnalysisJoinInfo + + +trait DependencyAnalysisMergeInfo { + + def isMerge: Boolean = true +} + +case class NoDependencyAnalysisMerge() extends DependencyAnalysisMergeInfo { + override def isMerge: Boolean = false +} + +case class SimpleDependencyAnalysisMerge(sourceInfo: AnalysisSourceInfo) extends DependencyAnalysisMergeInfo + + + +class CustomDependencyAnalysisNode(description: String, sourceInfoOpt: Option[AnalysisSourceInfo], dependencyTypeOpt: Option[DependencyType], + createAssertionNode: Boolean, createAssumptionNode: Boolean, + mergeInfoOpt: Option[DependencyAnalysisMergeInfo], joinInfoOpt: Option[DependencyAnalysisJoinInfo]) \ No newline at end of file diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala index 7cbcb4542..2e9bc65da 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala @@ -95,17 +95,17 @@ case class AxiomAssumptionNode(term: Term, description: Option[String], sourceIn override def getNodeType: String = "Axiom" } -case class SimpleAssertionNode(term: Term, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo, isClosed: Boolean, hasFailed: Boolean = false, isJoinNode: Boolean = false, _id: Option[Int]=None) extends GeneralAssertionNode { +case class SimpleAssertionNode(term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean, hasFailed: Boolean = false, isJoinNode: Boolean = false, _id: Option[Int]=None) extends GeneralAssertionNode { override def getNodeString: String = "assert " + term.toString - override def getAssertFailedNode(): GeneralAssertionNode = SimpleAssertionNode(term, assumptionType, sourceInfo, isClosed, hasFailed=true, isJoinNode=isJoinNode) + override def getAssertFailedNode(): GeneralAssertionNode = SimpleAssertionNode(term, sourceInfo, assumptionType, isClosed, hasFailed=true, isJoinNode=isJoinNode) } -case class SimpleCheckNode(term: Term, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo, isClosed: Boolean, hasFailed: Boolean = false, isJoinNode: Boolean = false, _id: Option[Int]=None) extends GeneralAssertionNode { +case class SimpleCheckNode(term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean, hasFailed: Boolean = false, isJoinNode: Boolean = false, _id: Option[Int]=None) extends GeneralAssertionNode { override def getNodeString: String = "check " + term override def getNodeType: String = "Check" - override def getAssertFailedNode(): GeneralAssertionNode = SimpleCheckNode(term, assumptionType, sourceInfo, isClosed, hasFailed=true) + override def getAssertFailedNode(): GeneralAssertionNode = SimpleCheckNode(term, sourceInfo, assumptionType, isClosed, hasFailed=true) } case class PermissionInhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean, labelNode: LabelNode, isJoinNode: Boolean, _id: Option[Int]=None) extends GeneralAssumptionNode with ChunkAnalysisInfo { diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index 7471f4684..35c7a671c 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -34,15 +34,15 @@ trait DependencyAnalyzer { def addNodes(nodes: Iterable[DependencyAnalysisNode]): Unit def addAssertionNode(node: GeneralAssertionNode): Unit def addAssumptionNode(node: GeneralAssumptionNode): Unit - def addAssumption(assumption: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isJoinNode: Boolean, description: Option[String] = None): Option[Int] - def addAxiom(assumption: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, description: Option[String] = None): Option[Int] + def addAssumption(assumption: Term, dAInfo: DependencyAnalysisInfo, description: Option[String] = None): Option[Int] + def addAxiom(assumption: Term, dAInfo: DependencyAnalysisInfo, description: Option[String] = None): Option[Int] def registerInhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, labelNode: Option[LabelNode], analysisInfo: AnalysisInfo): CH = buildChunk(perm) def registerExhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, labelNodeOpt: Option[LabelNode], analysisInfo: AnalysisInfo): CH = buildChunk(perm) def createLabelNode(label: Var, sourceChunks: Iterable[Chunk], sourceTerms: Iterable[Term]): Option[LabelNode] - def createAssertOrCheckNode(term: Term, assumptionType: AssumptionType, analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean, isJoinNode: Boolean): Option[GeneralAssertionNode] - def addAssertFalseNode(isCheck: Boolean, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo, isJoinNode: Boolean): Option[Int] - def addInfeasibilityNode(isCheck: Boolean, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] + def createAssertOrCheckNode(term: Term, dAInfo: DependencyAnalysisInfo, isCheck: Boolean): Option[GeneralAssertionNode] + def addAssertFalseNode(isCheck: Boolean, dAInfo: DependencyAnalysisInfo): Option[Int] + def addInfeasibilityNode(isCheck: Boolean, dAInfo: DependencyAnalysisInfo): Option[Int] def addDependency(source: Option[Int], dest: Option[Int]): Unit def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit @@ -53,7 +53,7 @@ trait DependencyAnalyzer { * Adds dependencies between all pairs of sourceExps and targetExps, where sourceExps should be preconditions and * targetExps should be postconditions of an abstract function or method. */ - def addDependenciesForAbstractMembers(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp], postConditionType: AssumptionType = AssumptionType.ExplicitPostcondition): Unit + def addDependenciesForAbstractMembers(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp], dAInfo: DependencyAnalysisInfo): Unit /** * Adds edges connecting nodes representing function postconditions with the corresponding axiom nodes. @@ -63,14 +63,14 @@ trait DependencyAnalyzer { /** * Adds an assertion and assumption node with the given analysis source info and dependencies to the current infeasibility node. */ - def addAssertionWithDepToInfeasNode(infeasNodeId: Option[Int], analysisSourceInfo: AnalysisSourceInfo, dependencyType: DependencyType, isJoinNode: Boolean): Unit = {} + def addAssertionWithDepToInfeasNode(infeasNodeId: Option[Int], dAInfo: DependencyAnalysisInfo): Unit = {} /** * @return the final dependency graph representing all direct and transitive dependencies */ def buildFinalGraph(): Option[DependencyGraph] - def addAssertionFailedNode(failedAssertion: Term, assertionType: AssumptionType, sourceInfo: AnalysisSourceInfo, isJoinNode: Boolean): Option[Int] + def addAssertionFailedNode(failedAssertion: Term, dAInfo: DependencyAnalysisInfo): Option[Int] } object DependencyAnalyzer { @@ -323,14 +323,14 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { override def addAssertionNode(node: GeneralAssertionNode): Unit = dependencyGraph.addAssertionNode(node) // adding assumption nodes - override def addAssumption(assumption: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isJoinNode: Boolean, description: Option[String]): Option[Int] = { - val node = SimpleAssumptionNode(assumption, description, analysisSourceInfo, assumptionType, isClosed_, isJoinNode) + override def addAssumption(assumption: Term, dAInfo: DependencyAnalysisInfo, description: Option[String]): Option[Int] = { + val node = SimpleAssumptionNode(assumption, description, dAInfo.getSourceInfo, dAInfo.getDependencyType.assumptionType, dAInfo.getMergeInfo.isMerge, dAInfo.getJoinInfo.isJoin) addAssumptionNode(node) Some(node.id) } - override def addAxiom(assumption: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, description: Option[String]): Option[Int] = { - val node = AxiomAssumptionNode(assumption, description, analysisSourceInfo, assumptionType, isClosed_) + override def addAxiom(assumption: Term, dAInfo: DependencyAnalysisInfo, description: Option[String]): Option[Int] = { + val node = AxiomAssumptionNode(assumption, description, dAInfo.getSourceInfo, dAInfo.getDependencyType.assumptionType, dAInfo.getMergeInfo.isMerge, dAInfo.getJoinInfo.isJoin) addAssumptionNode(node) Some(node.id) } @@ -380,35 +380,35 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { } // adding assertion nodes - override def createAssertOrCheckNode(term: Term, assumptionType: AssumptionType, analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean, isJoinNode: Boolean): Option[GeneralAssertionNode] = { + override def createAssertOrCheckNode(term: Term, dAInfo: DependencyAnalysisInfo, isCheck: Boolean): Option[GeneralAssertionNode] = { if(isCheck) - Some(SimpleCheckNode(term, assumptionType, analysisSourceInfo, isClosed_, isJoinNode=isJoinNode)) + Some(SimpleCheckNode(term, dAInfo.getSourceInfo, dAInfo.getDependencyType.assumptionType, dAInfo.getMergeInfo.isMerge, dAInfo.getJoinInfo.isJoin)) else - Some(SimpleAssertionNode(term, assumptionType, analysisSourceInfo, isClosed_, isJoinNode=isJoinNode)) + Some(SimpleAssertionNode(term, dAInfo.getSourceInfo, dAInfo.getDependencyType.assumptionType, dAInfo.getMergeInfo.isMerge, dAInfo.getJoinInfo.isJoin)) } - def addAssertNode(term: Term, assumptionType: AssumptionType, analysisSourceInfo: AnalysisSourceInfo, isJoinNode: Boolean): Option[Int] = { - val node = createAssertOrCheckNode(term, assumptionType, analysisSourceInfo, isCheck=false, isJoinNode=isJoinNode) + def addAssertNode(term: Term, dAInfo: DependencyAnalysisInfo): Option[Int] = { + val node = createAssertOrCheckNode(term, dAInfo, isCheck=false) node foreach addAssertionNode node map (_.id) } - override def addAssertFalseNode(isCheck: Boolean, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo, isJoinNode: Boolean): Option[Int] = { - val node = createAssertOrCheckNode(False, assumptionType, sourceInfo, isCheck, isJoinNode) + override def addAssertFalseNode(isCheck: Boolean, dAInfo: DependencyAnalysisInfo): Option[Int] = { + val node = createAssertOrCheckNode(False, dAInfo, isCheck) addAssertionNode(node.get) node.map(_.id) } - override def addInfeasibilityNode(isCheck: Boolean, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = { - val node = InfeasibilityNode(sourceInfo, assumptionType) + override def addInfeasibilityNode(isCheck: Boolean, dAInfo: DependencyAnalysisInfo): Option[Int] = { + val node = InfeasibilityNode(dAInfo.getSourceInfo, dAInfo.getDependencyType.assumptionType) addAssumptionNode(node) Some(node.id) } - override def addAssertionFailedNode(failedAssertion: Term, assertionType: AssumptionType, sourceInfo: AnalysisSourceInfo, isJoinNode: Boolean): Option[Int] = { - val assumptionType = if(AssumptionType.postconditionTypes.contains(assertionType)) AssumptionType.ExplicitPostcondition else AssumptionType.Explicit - val assumeNode = SimpleAssumptionNode(failedAssertion, None, sourceInfo, assumptionType, isClosed=false, isJoinNode=isJoinNode) - val assertFailedNode = SimpleAssertionNode(failedAssertion, assertionType, sourceInfo, isClosed=false, hasFailed=true, isJoinNode=isJoinNode) + override def addAssertionFailedNode(failedAssertion: Term, dAInfo: DependencyAnalysisInfo): Option[Int] = { + val assumptionType = if(AssumptionType.postconditionTypes.contains(dAInfo.getDependencyType.assumptionType)) AssumptionType.ExplicitPostcondition else AssumptionType.Explicit + val assumeNode = SimpleAssumptionNode(failedAssertion, None, dAInfo.getSourceInfo, dAInfo.getDependencyType.assumptionType, dAInfo.getMergeInfo.isMerge, dAInfo.getJoinInfo.isJoin) + val assertFailedNode = SimpleAssertionNode(failedAssertion, dAInfo.getSourceInfo, dAInfo.getDependencyType.assumptionType, dAInfo.getMergeInfo.isMerge, dAInfo.getJoinInfo.isJoin, hasFailed=true) dependencyGraph.addNode(assumeNode) dependencyGraph.addNode(assertFailedNode) dependencyGraph.addEdges(Set(assumeNode.id), assertFailedNode.id) @@ -458,9 +458,9 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { dependencyGraph.addEdges(sourceNodes map (_.id), targetNodes map (_.id)) } - override def addDependenciesForAbstractMembers(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp], postConditionType: AssumptionType = AssumptionType.ExplicitPostcondition): Unit = { - val sourceNodeIds = sourceExps.flatMap(e => addAssumption(True, AnalysisSourceInfo.createAnalysisSourceInfo(e), AssumptionType.Precondition, isJoinNode=true, None)) - val targetNodes = targetExps.flatMap(e => addAssertNode(True, postConditionType, AnalysisSourceInfo.createAnalysisSourceInfo(e), isJoinNode=true)) + override def addDependenciesForAbstractMembers(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp], dAInfo: DependencyAnalysisInfo): Unit = { + val sourceNodeIds = sourceExps.flatMap(e => addAssumption(True, dAInfo)) + val targetNodes = targetExps.flatMap(e => addAssertNode(True, dAInfo)) dependencyGraph.addEdges(sourceNodeIds, targetNodes) } @@ -529,7 +529,7 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { val assertionNodesBySource = dependencyGraph.getAssertionNodes.filter(!keepNode(_)).groupBy(n => (n.sourceInfo, n.assumptionType, n.isJoinNode)) assertionNodesBySource foreach { case ((sourceInfo, assumptionType, isJoinNode), assertionNodes) => if (assertionNodes.nonEmpty) { - val newNode = SimpleAssertionNode(True, assumptionType, sourceInfo, isClosed = false, hasFailed=assertionNodes.exists(_.hasFailed), isJoinNode=isJoinNode) + val newNode = SimpleAssertionNode(True, sourceInfo, assumptionType, isClosed = false, hasFailed=assertionNodes.exists(_.hasFailed), isJoinNode=isJoinNode) assertionNodes foreach (n => nodeMap.put(n.id, newNode.id)) mergedGraph.addAssertionNode(newNode) } @@ -547,8 +547,8 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { * Adds an assertion node with the given analysis source info and dependencies to the current infeasibility node. * The resulting assertion node is required to detect dependencies of the source statement/expression on infeasible paths. */ - override def addAssertionWithDepToInfeasNode(infeasNodeId: Option[Int], analysisSourceInfo: AnalysisSourceInfo, dependencyType: DependencyType, isJoinNode: Boolean): Unit = { - val newAssertionNodeId = addAssertNode(False, dependencyType.assertionType, analysisSourceInfo, isJoinNode) + override def addAssertionWithDepToInfeasNode(infeasNodeId: Option[Int], dAInfo: DependencyAnalysisInfo): Unit = { + val newAssertionNodeId = addAssertNode(False, dAInfo) addDependency(infeasNodeId, newAssertionNodeId) } @@ -566,19 +566,19 @@ class NoDependencyAnalyzer extends DependencyAnalyzer { override def addNodes(nodes: Iterable[DependencyAnalysisNode]): Unit = {} override def addAssertionNode(node: GeneralAssertionNode): Unit = {} override def addAssumptionNode(node: GeneralAssumptionNode): Unit = {} - override def addAssumption(assumption: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isJoinNode: Boolean, description: Option[String] = None): Option[Int] = None - override def addAxiom(assumption: Term, analysisSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, description: Option[String]): Option[Int] = None + override def addAssumption(assumption: Term, dAInfo: DependencyAnalysisInfo, description: Option[String] = None): Option[Int] = None + override def addAxiom(assumption: Term, dAInfo: DependencyAnalysisInfo, description: Option[String]): Option[Int] = None override def createLabelNode(labelTerm: Var, sourceChunks: Iterable[Chunk], sourceTerms: Iterable[Term]): Option[LabelNode] = None - override def createAssertOrCheckNode(term: Term, assumptionType: AssumptionType, analysisSourceInfo: AnalysisSourceInfo, isCheck: Boolean, isJoinNode: Boolean): Option[GeneralAssertionNode] = None - override def addAssertFalseNode(isCheck: Boolean, assumptionType: AssumptionType, sourceInfo: AnalysisSourceInfo, isJoinNode: Boolean): Option[Int] = None - override def addInfeasibilityNode(isCheck: Boolean, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): Option[Int] = None - override def addAssertionFailedNode(failedAssertion: Term, assertionType: AssumptionType, sourceInfo: AnalysisSourceInfo, isJoinNode: Boolean): Option[Int] = None + override def createAssertOrCheckNode(term: Term, dAInfo: DependencyAnalysisInfo, isCheck: Boolean): Option[GeneralAssertionNode] = None + override def addAssertFalseNode(isCheck: Boolean, dAInfo: DependencyAnalysisInfo): Option[Int] = None + override def addInfeasibilityNode(isCheck: Boolean, dAInfo: DependencyAnalysisInfo): Option[Int] = None + override def addAssertionFailedNode(failedAssertion: Term, dAInfo: DependencyAnalysisInfo): Option[Int] = None override def addDependency(source: Option[Int], dest: Option[Int]): Unit = {} override def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit = {} override def addCustomTransitiveDependency(sourceSourceInfo: AnalysisSourceInfo, targetSourceInfo: AnalysisSourceInfo): Unit = {} - override def addDependenciesForAbstractMembers(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp], postConditionType: AssumptionType = AssumptionType.ExplicitPostcondition): Unit = {} + override def addDependenciesForAbstractMembers(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp], dAInfo: DependencyAnalysisInfo): Unit = {} override def addFunctionAxiomEdges(): Unit = {} override def buildFinalGraph(): Option[DependencyGraph] = None diff --git a/src/main/scala/rules/Brancher.scala b/src/main/scala/rules/Brancher.scala index d56628237..ec68ea485 100644 --- a/src/main/scala/rules/Brancher.scala +++ b/src/main/scala/rules/Brancher.scala @@ -8,8 +8,7 @@ package viper.silicon.rules import viper.silicon.common.concurrency._ import viper.silicon.decider.PathConditionStack -import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType -import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, CompositeAnalysisSourceInfo, DependencyAnalyzer, DependencyType} +import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfo, DependencyAnalyzer, DependencyType} import viper.silicon.interfaces.{Unreachable, VerificationResult} import viper.silicon.reporting.condenseToViperResult import viper.silicon.state.State @@ -28,7 +27,7 @@ trait BranchingRules extends SymbolicExecutionRules { condition: Term, conditionExp: (ast.Exp, Option[ast.Exp]), v: Verifier, - assumptionType: AssumptionType, + dAInfo: DependencyAnalysisInfo, fromShortCircuitingAnd: Boolean = false) (fTrue: (State, Verifier) => VerificationResult, fFalse: (State, Verifier) => VerificationResult) @@ -40,7 +39,7 @@ object brancher extends BranchingRules { condition: Term, conditionExp: (ast.Exp, Option[ast.Exp]), v: Verifier, - assumptionType: AssumptionType, + dAInfo: DependencyAnalysisInfo, fromShortCircuitingAnd: Boolean = false) (fThen: (State, Verifier) => VerificationResult, fElse: (State, Verifier) => VerificationResult) @@ -48,12 +47,13 @@ object brancher extends BranchingRules { if(v.decider.isPathInfeasible()){ val analysisSourceInfo = v.decider.pushAndGetAnalysisSourceInfo(conditionExp._1, Some(DependencyAnalyzer.extractDependencyTypeFromInfo(conditionExp._1.info).getOrElse(DependencyType.PathCondition))) - val assertionNodesForJoin = DependencyAnalyzer.extractAssertionsForJoin(conditionExp._1, s.program) - assertionNodesForJoin.foreach(n => v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, CompositeAnalysisSourceInfo(v.decider.analysisSourceInfoStack.getFullSourceInfo.getTopLevelSource, AnalysisSourceInfo.createAnalysisSourceInfo(n)), v.decider.analysisSourceInfoStack.getDependencyType, isJoinNode=true)) +// FIXME ake: infeasible path + // val assertionNodesForJoin = DependencyAnalyzer.extractAssertionsForJoin(conditionExp._1, s.program) +// assertionNodesForJoin.foreach(n => v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, CompositeAnalysisSourceInfo(v.decider.analysisSourceInfoStack.getFullSourceInfo.getTopLevelSource, AnalysisSourceInfo.createAnalysisSourceInfo(n)), v.decider.analysisSourceInfoStack.getDependencyType, isJoinNode=true)) if(!Expressions.isKnownWellDefined(conditionExp._1, Some(s.program))){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType, assertionNodesForJoin.nonEmpty || v.decider.analysisSourceInfoStack.isJoinRelevantNode) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, dAInfo) } - v.decider.dependencyAnalyzer.addAssumption(condition, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getAssumptionType, isJoinNode=false) + v.decider.dependencyAnalyzer.addAssumption(condition, dAInfo) v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(analysisSourceInfo) return fThen(s, v).combine(fElse(s, v)) } @@ -77,13 +77,13 @@ object brancher extends BranchingRules { /* True if the then-branch is to be explored */ val executeThenBranch = ( skipPathFeasibilityCheck - || !v.decider.check(negatedCondition, Verifier.config.checkTimeout(), AssumptionType.Internal)) + || !v.decider.check(negatedCondition, Verifier.config.checkTimeout(), dAInfo.withDependencyType(DependencyType.Internal))) /* False if the then-branch is to be explored */ val executeElseBranch = ( !executeThenBranch /* Assumes that ast least one branch is feasible */ || skipPathFeasibilityCheck - || !v.decider.check(condition, Verifier.config.checkTimeout(), AssumptionType.Internal)) + || !v.decider.check(condition, Verifier.config.checkTimeout(), dAInfo.withDependencyType(DependencyType.Internal))) v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) @@ -166,8 +166,8 @@ object brancher extends BranchingRules { executionFlowController.locally(s, v0)((s1, v1) => { v1.decider.prover.comment(s"[else-branch: $cnt | $negatedCondition]") val sourceInfo = v.decider.pushAndGetAnalysisSourceInfo(conditionExp._1, Some(DependencyType.get(conditionExp._1, DependencyType.PathCondition))) - v1.decider.setCurrentBranchCondition(negatedCondition, (negatedConditionExp, negatedConditionExpNew), assumptionType) - if(v.decider.isDependencyAnalysisEnabled && !executeElseBranch) v.decider.checkSmokeAndSetInfeasibilityNode() + v1.decider.setCurrentBranchCondition(negatedCondition, (negatedConditionExp, negatedConditionExpNew), dAInfo) + if(v.decider.isDependencyAnalysisEnabled && !executeElseBranch) v.decider.checkSmokeAndSetInfeasibilityNode(dAInfo) v1.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) var functionsOfElseBranchdDeciderBefore: Set[FunctionDecl] = null @@ -220,8 +220,8 @@ object brancher extends BranchingRules { executionFlowController.locally(s, v)((s1, v1) => { v1.decider.prover.comment(s"[then-branch: $cnt | $condition]") val sourceInfo = v.decider.pushAndGetAnalysisSourceInfo(conditionExp._1, Some(DependencyType.get(conditionExp._1, DependencyType.PathCondition))) - v1.decider.setCurrentBranchCondition(condition, conditionExp, assumptionType) - if(v.decider.isDependencyAnalysisEnabled && !executeThenBranch) v.decider.checkSmokeAndSetInfeasibilityNode() + v1.decider.setCurrentBranchCondition(condition, conditionExp, dAInfo) + if(v.decider.isDependencyAnalysisEnabled && !executeThenBranch) v.decider.checkSmokeAndSetInfeasibilityNode(dAInfo) v1.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) fThen(v1.stateConsolidator(s1).consolidateOptionally(s1, v1), v1) diff --git a/src/main/scala/rules/ChunkSupporter.scala b/src/main/scala/rules/ChunkSupporter.scala index 0b3c32d6f..4c8bde746 100644 --- a/src/main/scala/rules/ChunkSupporter.scala +++ b/src/main/scala/rules/ChunkSupporter.scala @@ -6,9 +6,8 @@ package viper.silicon.rules -import viper.silicon.dependencyAnalysis.{AssumptionType, DependencyType} -import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType import viper.silicon.debugger.DebugExp +import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfo, DependencyType} import viper.silicon.interfaces.state._ import viper.silicon.interfaces.{Success, VerificationResult} import viper.silicon.resources.{NonQuantifiedPropertyInterpreter, Resources} @@ -35,7 +34,7 @@ trait ChunkSupportRules extends SymbolicExecutionRules { ve: VerificationError, v: Verifier, description: String, - dependencyType: DependencyType) + dAInfo: DependencyAnalysisInfo) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult @@ -50,7 +49,7 @@ trait ChunkSupportRules extends SymbolicExecutionRules { argsExp: Option[Seq[ast.Exp]], ve: VerificationError, v: Verifier, - assumptionType: AssumptionType) + dAInfo: DependencyAnalysisInfo) (Q: (State, Heap, Term, Verifier) => VerificationResult) : VerificationResult @@ -58,7 +57,8 @@ trait ChunkSupportRules extends SymbolicExecutionRules { (chunks: Iterable[Chunk], id: ChunkIdentifer, args: Iterable[Term], - v: Verifier) + v: Verifier, + dAInfo: DependencyAnalysisInfo) : Option[CH] def findChunksWithID[CH <: NonQuantifiedChunk: ClassTag] @@ -80,15 +80,15 @@ object chunkSupporter extends ChunkSupportRules { ve: VerificationError, v: Verifier, description: String, - dependencyType: DependencyType) + dAInfo: DependencyAnalysisInfo) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { if(v.decider.isPathInfeasible()){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType, v.decider.analysisSourceInfoStack.isJoinRelevantNode) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, dAInfo) return Q(s, h, Option.when(returnSnap)(Unit), v) } - consume2(s, h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, dependencyType)((s2, h2, optSnap, v2) => + consume2(s, h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, dAInfo)((s2, h2, optSnap, v2) => optSnap match { case Some(snap) => Q(s2, h2, Some(snap.convert(sorts.Snap)), v2) @@ -117,14 +117,14 @@ object chunkSupporter extends ChunkSupportRules { returnSnap: Boolean, ve: VerificationError, v: Verifier, - dependencyType: DependencyType) + dAInfo: DependencyAnalysisInfo) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { val id = ChunkIdentifier(resource, s.program) if (s.exhaleExt) { val failure = createFailure(ve, v, s, "chunk consume in package") - magicWandSupporter.transfer(s, perms, permsExp, failure, Seq(), v)(consumeGreedy(_, _, id, args, _, _, _, dependencyType))((s1, optCh, v1) => + magicWandSupporter.transfer(s, perms, permsExp, failure, Seq(), v, dAInfo)(consumeGreedy(_, _, id, args, _, _, _, dAInfo))((s1, optCh, v1) => if (returnSnap){ Q(s1, h, optCh.flatMap(ch => Some(ch.snap)), v1) } else { @@ -133,15 +133,15 @@ object chunkSupporter extends ChunkSupportRules { } else { executionFlowController.tryOrFail2[Heap, Option[Term]](s.copy(h = h), v)((s1, v1, QS) => if (s1.moreCompleteExhale) { - moreCompleteExhaleSupporter.consumeComplete(s1, s1.h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v1, dependencyType)((s2, h2, snap2, v2) => { + moreCompleteExhaleSupporter.consumeComplete(s1, s1.h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v1, dAInfo)((s2, h2, snap2, v2) => { QS(s2.copy(h = s.h), h2, snap2, v2) }) } else { - consumeGreedy(s1, s1.h, id, args, perms, permsExp, v1, dependencyType) match { + consumeGreedy(s1, s1.h, id, args, perms, permsExp, v1, dAInfo) match { case (Complete(), s2, h2, optCh2) => val snap = optCh2 match { case Some(ch) if returnSnap => - if (v1.decider.check(IsPositive(perms), Verifier.config.checkTimeout())) { + if (v1.decider.check(IsPositive(perms), Verifier.config.checkTimeout(), dAInfo)) { Some(ch.snap) } else { Some(Ite(IsPositive(perms), ch.snap.convert(sorts.Snap), Unit)) @@ -149,14 +149,14 @@ object chunkSupporter extends ChunkSupportRules { case _ => None } QS(s2.copy(h = s.h), h2, snap, v1) - case (_, s2, h2, _) if v1.decider.checkSmoke(dependencyType.assertionType, isAssert = true) => + case (_, s2, h2, _) if v1.decider.checkSmoke(dAInfo, isAssert = true) => if(Verifier.config.disableInfeasibilityChecks()) QS(s2.copy(h = s.h), h2, None, v1) else Success() // TODO: Mark branch as dead? case _ => val failure = createFailure(ve, v1, s1, "consuming chunk", true) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(False, dependencyType.assertionType, v1.reportFurtherErrors()) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(False, dAInfo, v1.reportFurtherErrors()) if(s1.retryLevel == 0 && v1.reportFurtherErrors() && Verifier.config.disableInfeasibilityChecks()){ failure combine QS(s1.copy(h = s.h), s1.h, None, v1) }else{ @@ -175,7 +175,7 @@ object chunkSupporter extends ChunkSupportRules { perms: Term, permsExp: Option[ast.Exp], v: Verifier, - dependencyType: DependencyType) + dAInfo: DependencyAnalysisInfo) : (ConsumptionResult, State, Heap, Option[NonQuantifiedChunk]) = { val consumeExact = terms.utils.consumeExactRead(perms, s.constrainableARPs) @@ -184,13 +184,13 @@ object chunkSupporter extends ChunkSupportRules { val interpreter = new NonQuantifiedPropertyInterpreter(heap.values, v) val resource = Resources.resourceDescriptions(chunk.resourceID) val pathCond = interpreter.buildPathConditionsForChunk(chunk, resource.instanceProperties(s.mayAssumeUpperBounds)) - pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), dependencyType.assumptionType)) + pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), dAInfo)) } - findChunk[NonQuantifiedChunk](h.values, id, args, v) match { + findChunk[NonQuantifiedChunk](h.values, id, args, v, dAInfo) match { case Some(ch) => if (s.assertReadAccessOnly) { - if (v.decider.check(Implies(IsPositive(perms), IsPositive(ch.perm)), Verifier.config.assertTimeout.getOrElse(0), dependencyType.assertionType)) { + if (v.decider.check(Implies(IsPositive(perms), IsPositive(ch.perm)), Verifier.config.assertTimeout.getOrElse(0), dAInfo)) { (Complete(), s, h, Some(ch)) } else { (Incomplete(perms, permsExp), s, h, None) @@ -199,22 +199,22 @@ object chunkSupporter extends ChunkSupportRules { val toTake = PermMin(ch.perm, perms) val toTakeExp = permsExp.map(pe => buildMinExp(Seq(ch.permExp.get, pe), ast.Perm)) val newPermExp = permsExp.map(pe => ast.PermSub(ch.permExp.get, toTakeExp.get)(pe.pos, pe.info, pe.errT)) - val newChunk = NonQuantifiedChunk.withPerm(ch, PermMinus(ch.perm, toTake), newPermExp, v.decider.getAnalysisInfo(dependencyType.assumptionType)) - val takenChunk = Some(NonQuantifiedChunk.withPerm(ch, toTake, toTakeExp, v.decider.getAnalysisInfo(dependencyType.assertionType), isExhale=true)) + val newChunk = NonQuantifiedChunk.withPerm(ch, PermMinus(ch.perm, toTake), newPermExp, v.decider.getAnalysisInfo(dAInfo)) + val takenChunk = Some(NonQuantifiedChunk.withPerm(ch, toTake, toTakeExp, v.decider.getAnalysisInfo(dAInfo), isExhale=true)) var newHeap = h - ch - if (!v.decider.check(newChunk.perm === NoPerm, Verifier.config.checkTimeout(), AssumptionType.Internal)) { + if (!v.decider.check(newChunk.perm === NoPerm, Verifier.config.checkTimeout(), dAInfo.withDependencyType(DependencyType.Internal))) { newHeap = newHeap + newChunk assumeProperties(newChunk, newHeap) } val remainingExp = permsExp.map(pe => ast.PermSub(pe, toTakeExp.get)(pe.pos, pe.info, pe.errT)) (ConsumptionResult(PermMinus(perms, toTake), remainingExp, Seq(), v, 0), s, newHeap, takenChunk) } else { - if (v.decider.check(ch.perm !== NoPerm, Verifier.config.checkTimeout(), dependencyType.assertionType)) { + if (v.decider.check(ch.perm !== NoPerm, Verifier.config.checkTimeout(), dAInfo)) { val constraintExp = permsExp.map(pe => ast.PermLtCmp(pe, ch.permExp.get)(pe.pos, pe.info, pe.errT)) - v.decider.assume(PermLess(perms, ch.perm), Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp)), dependencyType.assumptionType) + v.decider.assume(PermLess(perms, ch.perm), Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp)), dAInfo) val newPermExp = permsExp.map(pe => ast.PermSub(ch.permExp.get, pe)(pe.pos, pe.info, pe.errT)) - val newChunk = NonQuantifiedChunk.withPerm(ch, PermMinus(ch.perm, perms), newPermExp, v.decider.getAnalysisInfo(dependencyType.assumptionType)) - val takenChunk = NonQuantifiedChunk.withPerm(ch, perms, permsExp, v.decider.getAnalysisInfo(dependencyType.assertionType), isExhale=true) + val newChunk = NonQuantifiedChunk.withPerm(ch, PermMinus(ch.perm, perms), newPermExp, v.decider.getAnalysisInfo(dAInfo)) + val takenChunk = NonQuantifiedChunk.withPerm(ch, perms, permsExp, v.decider.getAnalysisInfo(dAInfo), isExhale=true) val newHeap = h - ch + newChunk assumeProperties(newChunk, newHeap) (Complete(), s, newHeap, Some(takenChunk)) @@ -223,7 +223,7 @@ object chunkSupporter extends ChunkSupportRules { } } case None => - if (consumeExact && s.retrying && v.decider.check(perms === NoPerm, Verifier.config.checkTimeout(), dependencyType.assertionType)) { + if (consumeExact && s.retrying && v.decider.check(perms === NoPerm, Verifier.config.checkTimeout(), dAInfo)) { (Complete(), s, h, None) } else { (Incomplete(perms, permsExp), s, h, None) @@ -237,7 +237,7 @@ object chunkSupporter extends ChunkSupportRules { // Try to merge the chunk into the heap by finding an alias. // In any case, property assumptions are added after the merge step. - val (fr1, h1) = v.stateConsolidator(s).merge(s.functionRecorder, s, h, ch, v) + val (fr1, h1) = v.stateConsolidator(s).merge(s.functionRecorder, s, h, ch, v, dAInfo) Q(s.copy(functionRecorder = fr1), h1, v) } @@ -248,11 +248,11 @@ object chunkSupporter extends ChunkSupportRules { argsExp: Option[Seq[ast.Exp]], ve: VerificationError, v: Verifier, - assumptionType: AssumptionType) + dAInfo: DependencyAnalysisInfo) (Q: (State, Heap, Term, Verifier) => VerificationResult) : VerificationResult = { if(v.decider.isPathInfeasible()){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType, v.decider.analysisSourceInfoStack.isJoinRelevantNode) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, dAInfo) return Q(s, h, Unit, v) } @@ -260,7 +260,7 @@ object chunkSupporter extends ChunkSupportRules { val lookupFunction = if (s1.moreCompleteExhale) moreCompleteExhaleSupporter.lookupComplete _ else lookupGreedy _ - lookupFunction(s1, s1.h, resource, args, argsExp, ve, v1, assumptionType)((s2, tSnap, v2) => + lookupFunction(s1, s1.h, resource, args, argsExp, ve, v1, dAInfo)((s2, tSnap, v2) => QS(s2.copy(h = s.h), s2.h, tSnap, v2)) })(Q) } @@ -272,16 +272,16 @@ object chunkSupporter extends ChunkSupportRules { argsExp: Option[Seq[ast.Exp]], ve: VerificationError, v: Verifier, - assumptionType: AssumptionType) + dAInfo: DependencyAnalysisInfo) (Q: (State, Term, Verifier) => VerificationResult) : VerificationResult = { val id = ChunkIdentifier(resource, s.program) - val findRes = findChunk[NonQuantifiedChunk](h.values, id, args, v) + val findRes = findChunk[NonQuantifiedChunk](h.values, id, args, v, dAInfo) findRes match { - case Some(ch) if v.decider.check(IsPositive(ch.perm), Verifier.config.assertTimeout.getOrElse(0), assumptionType) => + case Some(ch) if v.decider.check(IsPositive(ch.perm), Verifier.config.assertTimeout.getOrElse(0), dAInfo) => Q(s, ch.snap, v) - case _ if v.decider.checkSmoke(assumptionType, isAssert = true) => + case _ if v.decider.checkSmoke(dAInfo, isAssert = true) => if (s.isInPackage || Verifier.config.disableInfeasibilityChecks()) { val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) Q(s, snap, v) @@ -290,7 +290,7 @@ object chunkSupporter extends ChunkSupportRules { } case _ => val failure = createFailure(ve, v, s, "looking up chunk", true) - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, assumptionType, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, dAInfo, v.reportFurtherErrors()) if(s.retryLevel == 0 && v.reportFurtherErrors() && Verifier.config.disableInfeasibilityChecks()){ val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) failure combine Q(s, snap, v) @@ -304,10 +304,11 @@ object chunkSupporter extends ChunkSupportRules { (chunks: Iterable[Chunk], id: ChunkIdentifer, args: Iterable[Term], - v: Verifier) + v: Verifier, + dAInfo: DependencyAnalysisInfo) : Option[CH] = { val relevantChunks = findChunksWithID[CH](chunks, id) - findChunkLiterally(relevantChunks, args) orElse findChunkWithProver(relevantChunks, args, v) + findChunkLiterally(relevantChunks, args) orElse findChunkWithProver(relevantChunks, args, v, dAInfo) } def findChunksWithID[CH <: NonQuantifiedChunk: ClassTag](chunks: Iterable[Chunk], id: ChunkIdentifer): Iterable[CH] = { @@ -344,9 +345,9 @@ object chunkSupporter extends ChunkSupportRules { chunks find (ch => ch.args == args) } - private def findChunkWithProver[CH <: NonQuantifiedChunk](chunks: Iterable[CH], args: Iterable[Term], v: Verifier) = { + private def findChunkWithProver[CH <: NonQuantifiedChunk](chunks: Iterable[CH], args: Iterable[Term], v: Verifier, dAInfo: DependencyAnalysisInfo) = { chunks find (ch => args.size == ch.args.size && - v.decider.check(And(ch.args zip args map (x => x._1 === x._2)), Verifier.config.checkTimeout())) + v.decider.check(And(ch.args zip args map (x => x._1 === x._2)), Verifier.config.checkTimeout(), dAInfo)) } } diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index 16ff0249b..f49de11ac 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -37,7 +37,7 @@ trait ConsumptionRules extends SymbolicExecutionRules { * consumed partial heap. * @return The result of the continuation. */ - def consume(s: State, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, dependencyType: DependencyType) + def consume(s: State, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, dAInfo: DependencyAnalysisInfo) (Q: (State, Option[Term], Verifier) => VerificationResult) : VerificationResult @@ -62,7 +62,7 @@ trait ConsumptionRules extends SymbolicExecutionRules { returnSnap: Boolean, pvef: ast.Exp => PartialVerificationError, v: Verifier, - dependencyType: DependencyType) + dAInfo: DependencyAnalysisInfo) (Q: (State, Option[Term], Verifier) => VerificationResult) : VerificationResult } @@ -76,11 +76,11 @@ object consumer extends ConsumptionRules { */ /** @inheritdoc */ - def consume(s: State, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, dependencyType: DependencyType) + def consume(s: State, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, dAInfo: DependencyAnalysisInfo) (Q: (State, Option[Term], Verifier) => VerificationResult) : VerificationResult = { - consumeR(s, s.h, a.whenExhaling, returnSnap, pve, v, dependencyType)((s1, h1, snap, v1) => { + consumeR(s, s.h, a.whenExhaling, returnSnap, pve, v, dAInfo)((s1, h1, snap, v1) => { val s2 = s1.copy(h = h1, partiallyConsumedHeap = s.partiallyConsumedHeap) Q(s2, snap, v1)}) @@ -92,7 +92,7 @@ object consumer extends ConsumptionRules { returnSnap: Boolean, pvef: ast.Exp => PartialVerificationError, v: Verifier, - dependencyType: DependencyType) + dAInfo: DependencyAnalysisInfo) (Q: (State, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -107,7 +107,7 @@ object consumer extends ConsumptionRules { allPves ++= pves }) - consumeTlcs(s, s.h, allTlcs.result(), returnSnap, allPves.result(), v, dependencyType)((s1, h1, snap1, v1) => { + consumeTlcs(s, s.h, allTlcs.result(), returnSnap, allPves.result(), v, dAInfo)((s1, h1, snap1, v1) => { val s2 = s1.copy(h = h1, partiallyConsumedHeap = s.partiallyConsumedHeap) Q(s2, snap1, v1) @@ -120,7 +120,7 @@ object consumer extends ConsumptionRules { returnSnap: Boolean, pves: Seq[PartialVerificationError], v: Verifier, - dependencyType: DependencyType) + dAInfo: DependencyAnalysisInfo) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -131,10 +131,10 @@ object consumer extends ConsumptionRules { val pve = pves.head if (tlcs.tail.isEmpty) - wrappedConsumeTlc(s, h, a, returnSnap, pve, v, dependencyType)(Q) + wrappedConsumeTlc(s, h, a, returnSnap, pve, v, dAInfo)(Q) else - wrappedConsumeTlc(s, h, a, returnSnap, pve, v, dependencyType)((s1, h1, snap1, v1) => { - consumeTlcs(s1, h1, tlcs.tail, returnSnap, pves.tail, v1, dependencyType)((s2, h2, snap2, v2) => + wrappedConsumeTlc(s, h, a, returnSnap, pve, v, dAInfo)((s1, h1, snap1, v1) => { + consumeTlcs(s1, h1, tlcs.tail, returnSnap, pves.tail, v1, dAInfo)((s2, h2, snap2, v2) => (snap1, snap2) match { case (Some(sn1), Some(sn2)) if returnSnap => Q(s2, h2, Some(Combine(sn1, sn2)), v2) @@ -145,14 +145,14 @@ object consumer extends ConsumptionRules { } } - private def consumeR(s: State, h: Heap, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, dependencyType: DependencyType) + private def consumeR(s: State, h: Heap, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, dAInfo: DependencyAnalysisInfo) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { val tlcs = a.topLevelConjuncts val pves = Seq.fill(tlcs.length)(pve) - consumeTlcs(s, h, tlcs, returnSnap, pves, v, dependencyType)(Q) + consumeTlcs(s, h, tlcs, returnSnap, pves, v, dAInfo)(Q) } /** Wrapper/decorator for consume that injects the following operations: @@ -165,7 +165,7 @@ object consumer extends ConsumptionRules { returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, - dependencyType: DependencyType) + dAInfo: DependencyAnalysisInfo) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -179,16 +179,16 @@ object consumer extends ConsumptionRules { val s1 = s0.copy(h = s.h) /* s1 is s, but the retrying flag might be set */ val sepIdentifier = v1.symbExLog.openScope(new ConsumeRecord(a, s1, v.decider.pcs)) - val sourceInfo = v1.decider.pushAndGetAnalysisSourceInfo(a, Some(DependencyType.get(a, dependencyType))) + val sourceInfo = v1.decider.pushAndGetAnalysisSourceInfo(a, Some(DependencyType.get(a, dAInfo))) - consumeTlc(s1, h0, a, returnSnap, pve, v1, dependencyType)((s2, h2, snap2, v2) => { + consumeTlc(s1, h0, a, returnSnap, pve, v1, dAInfo)((s2, h2, snap2, v2) => { v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) v2.symbExLog.closeScope(sepIdentifier) QS(s2, h2, snap2, v2)}) })(Q) } - private def consumeTlc(s: State, h: Heap, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, dependencyType: DependencyType) + private def consumeTlc(s: State, h: Heap, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, dAInfo: DependencyAnalysisInfo) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -200,7 +200,7 @@ object consumer extends ConsumptionRules { */ if(v.decider.isPathInfeasible()){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, dependencyType, isJoinNode=false) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, dAInfo) return Q(s, h, Option.when(returnSnap)(Unit), v) } @@ -214,15 +214,15 @@ object consumer extends ConsumptionRules { case imp @ ast.Implies(e0, a0) if !a.isPure && s.moreJoins.id >= JoinMode.Impure.id => val impliesRecord = new ImpliesRecord(imp, s, v.decider.pcs, "consume") val uidImplies = v.symbExLog.openScope(impliesRecord) - consumeConditionalTlcMoreJoins(s, h, e0, a0, None, uidImplies, returnSnap, pve, v, dependencyType)(Q) + consumeConditionalTlcMoreJoins(s, h, e0, a0, None, uidImplies, returnSnap, pve, v, dAInfo)(Q) case imp @ ast.Implies(e0, a0) if !a.isPure => val impliesRecord = new ImpliesRecord(imp, s, v.decider.pcs, "consume") val uidImplies = v.symbExLog.openScope(impliesRecord) - evaluator.eval(s, e0, pve, v)((s1, t0, e0New, v1) => - branch(s1, t0, (e0, e0New), v1, dependencyType.assumptionType)( - (s2, v2) => consumeR(s2, h, a0, returnSnap, pve, v2, dependencyType)((s3, h1, t1, v3) => { + evaluator.eval(s, e0, pve, v, dAInfo)((s1, t0, e0New, v1) => + branch(s1, t0, (e0, e0New), v1, dAInfo)( + (s2, v2) => consumeR(s2, h, a0, returnSnap, pve, v2, dAInfo)((s3, h1, t1, v3) => { v3.symbExLog.closeScope(uidImplies) Q(s3, h1, t1, v3) }), @@ -234,19 +234,19 @@ object consumer extends ConsumptionRules { case ite @ ast.CondExp(e0, a1, a2) if !a.isPure && s.moreJoins.id >= JoinMode.Impure.id => val condExpRecord = new CondExpRecord(ite, s, v.decider.pcs, "consume") val uidCondExp = v.symbExLog.openScope(condExpRecord) - consumeConditionalTlcMoreJoins(s, h, e0, a1, Some(a2), uidCondExp, returnSnap, pve, v, dependencyType)(Q) + consumeConditionalTlcMoreJoins(s, h, e0, a1, Some(a2), uidCondExp, returnSnap, pve, v, dAInfo)(Q) case ite @ ast.CondExp(e0, a1, a2) if !a.isPure => val condExpRecord = new CondExpRecord(ite, s, v.decider.pcs, "consume") val uidCondExp = v.symbExLog.openScope(condExpRecord) - eval(s, e0, pve, v)((s1, t0, e0New, v1) => - branch(s1, t0, (e0, e0New), v1, dependencyType.assumptionType)( - (s2, v2) => consumeR(s2, h, a1, returnSnap, pve, v2, dependencyType)((s3, h1, t1, v3) => { + eval(s, e0, pve, v, dAInfo)((s1, t0, e0New, v1) => + branch(s1, t0, (e0, e0New), v1, dAInfo)( + (s2, v2) => consumeR(s2, h, a1, returnSnap, pve, v2, dAInfo)((s3, h1, t1, v3) => { v3.symbExLog.closeScope(uidCondExp) Q(s3, h1, t1, v3) }), - (s2, v2) => consumeR(s2, h, a2, returnSnap, pve, v2, dependencyType)((s3, h1, t1, v3) => { + (s2, v2) => consumeR(s2, h, a2, returnSnap, pve, v2, dAInfo)((s3, h1, t1, v3) => { v3.symbExLog.closeScope(uidCondExp) Q(s3, h1, t1, v3) }))) @@ -256,17 +256,17 @@ object consumer extends ConsumptionRules { val resource = accPred.res(s.program) val ePerm = accPred.perm - evals(s, eArgs, _ => pve, v)((s1, tArgs, eArgsNew, v1) => - eval(s1, ePerm, pve, v1)((s1a, tPerm, ePermNew, v1a) => - permissionSupporter.assertNotNegative(s1a, tPerm, ePerm, ePermNew, pve, v1a)((s2, v2) => { + evals(s, eArgs, _ => pve, v, dAInfo)((s1, tArgs, eArgsNew, v1) => + eval(s1, ePerm, pve, v1, dAInfo)((s1a, tPerm, ePermNew, v1a) => + permissionSupporter.assertNotNegative(s1a, tPerm, ePerm, ePermNew, pve, v1a, dAInfo)((s2, v2) => { val loss = if (!Verifier.config.unsafeWildcardOptimization() || (resource.isInstanceOf[ast.Location] && s2.permLocations.contains(resource.asInstanceOf[ast.Location]))) PermTimes(tPerm, s2.permissionScalingFactor) else WildcardSimplifyingPermTimes(tPerm, s2.permissionScalingFactor) val lossExp = ePermNew.map(p => ast.PermMul(p, s2.permissionScalingFactorExp.get)(p.pos, p.info, p.errT)) - val s3 = v2.heapSupporter.triggerResourceIfNeeded(s2, accPred.loc, tArgs, eArgsNew, v2) - v2.heapSupporter.consumeSingle(s3, h, accPred.loc, tArgs, eArgsNew, loss, lossExp, returnSnap, pve, v2, dependencyType)((s4, h4, snap, v4) => { + val s3 = v2.heapSupporter.triggerResourceIfNeeded(s2, accPred.loc, tArgs, eArgsNew, v2, dAInfo) + v2.heapSupporter.consumeSingle(s3, h, accPred.loc, tArgs, eArgsNew, loss, lossExp, returnSnap, pve, v2, dAInfo)((s4, h4, snap, v4) => { val s5 = s4.copy(constrainableARPs = s.constrainableARPs, partiallyConsumedHeap = Some(h4)) Q(s5, h4, snap, v4) @@ -296,7 +296,7 @@ object consumer extends ConsumptionRules { if (forall.triggers.isEmpty) None else Some(forall.triggers) val s0 = s.copy(functionRecorder = s.functionRecorder.enterQuantifiedExp(qpa)) - evalQuantified(s0, Forall, forall.variables, Seq(cond), ePerm +: eArgs, optTrigger, qid, pve, v) { + evalQuantified(s0, Forall, forall.variables, Seq(cond), ePerm +: eArgs, optTrigger, qid, pve, v, dAInfo) { case (s1, qvars, qvarExps, Seq(tCond), condNew, Some((Seq(tPerm, tArgs@_*), permArgsNew, tTriggers, (auxGlobals, auxNonGlobals), auxExps)), v1) => v1.heapSupporter.consumeQuantified( s = s1, @@ -325,7 +325,7 @@ object consumer extends ConsumptionRules { notInjectiveReason = QPAssertionNotInjective(resAcc), insufficientPermissionReason = insuffReason, v1, - dependencyType)((s2, h2, snap, v2) => { + dAInfo)((s2, h2, snap, v2) => { val s3 = s2.copy(constrainableARPs = s.constrainableARPs, functionRecorder = s2.functionRecorder.leaveQuantifiedExp(qpa)) Q(s3, h2, snap, v2) }) @@ -333,15 +333,15 @@ object consumer extends ConsumptionRules { } case let: ast.Let if !let.isPure => - letSupporter.handle[ast.Exp](s, let, pve, v)((s1, g1, body, v1) => { + letSupporter.handle[ast.Exp](s, let, pve, v, dAInfo)((s1, g1, body, v1) => { val s2 = s1.copy(g = s1.g + g1) - consumeR(s2, h, body, returnSnap, pve, v1, dependencyType)(Q)}) + consumeR(s2, h, body, returnSnap, pve, v1, dAInfo)(Q)}) case _: ast.InhaleExhaleExp => createFailure(viper.silicon.utils.consistency.createUnexpectedInhaleExhaleExpressionError(a), v, s, "valid AST") case _ => - evalAndAssert(s, a, returnSnap, pve, v, dependencyType)((s1, t, v1) => { + evalAndAssert(s, a, returnSnap, pve, v, dAInfo)((s1, t, v1) => { Q(s1, h, t, v1) }) } @@ -351,20 +351,20 @@ object consumer extends ConsumptionRules { private def consumeConditionalTlcMoreJoins(s: State, h: Heap, e0: ast.Exp, a1: ast.Exp, a2: Option[ast.Exp], scopeUid: Int, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, - dependencyType: DependencyType) + dAInfo: DependencyAnalysisInfo) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { - eval(s, e0, pve, v)((s1, t0, e0New, v1) => - joiner.join[(Heap, Option[Term]), (Heap, Option[Term])](s1, v1, dependencyType.assumptionType, resetState = false)((s1, v1, QB) => { - branch(s1.copy(parallelizeBranches = false), t0, (e0, e0New), v1, dependencyType.assumptionType)( + eval(s, e0, pve, v, dAInfo)((s1, t0, e0New, v1) => + joiner.join[(Heap, Option[Term]), (Heap, Option[Term])](s1, v1, dAInfo, resetState = false)((s1, v1, QB) => { + branch(s1.copy(parallelizeBranches = false), t0, (e0, e0New), v1, dAInfo)( (s2, v2) => - consumeR(s2.copy(parallelizeBranches = s1.parallelizeBranches), h, a1, returnSnap, pve, v2, dependencyType)((s3, h1, t1, v3) => { + consumeR(s2.copy(parallelizeBranches = s1.parallelizeBranches), h, a1, returnSnap, pve, v2, dAInfo)((s3, h1, t1, v3) => { v3.symbExLog.closeScope(scopeUid) QB(s3, (h1, t1), v3) }), (s2, v2) => a2 match { - case Some(a2) => consumeR(s2.copy(parallelizeBranches = s1.parallelizeBranches), h, a2, returnSnap, pve, v2, dependencyType)((s3, h1, t1, v3) => { + case Some(a2) => consumeR(s2.copy(parallelizeBranches = s1.parallelizeBranches), h, a2, returnSnap, pve, v2, dAInfo)((s3, h1, t1, v3) => { v3.symbExLog.closeScope(scopeUid) QB(s3, (h1, t1), v3) }) @@ -381,7 +381,7 @@ object consumer extends ConsumptionRules { State.mergeHeap( entry1.data._1, And(entry1.pathConditions.branchConditions), Option.when(withExp)(BigAnd(entry1.pathConditions.branchConditionExps.map(_._2.get))), entry2.data._1, And(entry2.pathConditions.branchConditions), Option.when(withExp)(BigAnd(entry2.pathConditions.branchConditionExps.map(_._2.get))), - AnalysisInfo(v.decider, v.decider.dependencyAnalyzer, StringAnalysisSourceInfo("conditional join", e0.pos), dependencyType.assumptionType, v.decider.analysisSourceInfoStack.isJoinRelevantNode) + AnalysisInfo(v.decider, v.decider.dependencyAnalyzer, dAInfo.withSource(StringAnalysisSourceInfo("conditional join", e0.pos))) ), // Assume that entry1.pcs is inverse of entry2.pcs (entry1.data._2, entry2.data._2) match { @@ -402,7 +402,7 @@ object consumer extends ConsumptionRules { } - private def evalAndAssert(s: State, e: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, dependencyType: DependencyType) + private def evalAndAssert(s: State, e: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, dAInfo: DependencyAnalysisInfo) (Q: (State, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -421,23 +421,23 @@ object consumer extends ConsumptionRules { exhaleExt = false) executionFlowController.tryOrFail0(s1, v)((s2, v1, QS) => { - eval(s2, e, pve, v1)((s3, t, eNew, v2) => { + eval(s2, e, pve, v1, dAInfo)((s3, t, eNew, v2) => { val termToAssert = t match { case Quantification(q, vars, body, trgs, name, isGlob, weight) => val transformed = FunctionPreconditionTransformer.transform(body, s3.program) - v2.decider.assume(Quantification(q, vars, transformed, trgs, name+"_precondition", isGlob, weight), Option.when(withExp)(e), eNew, dependencyType.assumptionType) + v2.decider.assume(Quantification(q, vars, transformed, trgs, name+"_precondition", isGlob, weight), Option.when(withExp)(e), eNew, dAInfo) Quantification(q, vars, Implies(transformed, body), trgs, name, isGlob, weight) case _ => t } - v2.decider.assert(termToAssert, dependencyType.assertionType) { + v2.decider.assert(termToAssert, dAInfo) { case true => - v2.decider.assume(t, Option.when(withExp)(e), eNew, AssumptionType.Internal) + v2.decider.assume(t, Option.when(withExp)(e), eNew, dAInfo.withDependencyType(DependencyType.Internal)) QS(s3, v2) case false => val failure = createFailure(pve dueTo AssertionFalse(e), v2, s3, termToAssert, eNew) - if(s3.retryLevel == 0) v2.decider.handleFailedAssertionForDependencyAnalysis(t, dependencyType.assertionType, assumeFailedAssertion=false) + if(s3.retryLevel == 0) v2.decider.handleFailedAssertionForDependencyAnalysis(t, dAInfo, assumeFailedAssertion=false) if (s3.retryLevel == 0 && v2.reportFurtherErrors()){ - v2.decider.assume(t, Option.when(withExp)(e), eNew, AssumptionType.Explicit) + v2.decider.assume(t, Option.when(withExp)(e), eNew, dAInfo.withDependencyType(DependencyType.Explicit)) failure combine QS(s3, v2) } else failure}}) })((s4, v4) => { diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index f41a73867..ed494a03a 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -10,7 +10,7 @@ import viper.silicon import viper.silicon.Config.JoinMode import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp -import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, CompositeAnalysisSourceInfo, DependencyAnalyzer, DependencyType} +import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfo, DependencyType} import viper.silicon.interfaces._ import viper.silicon.interfaces.state.ChunkIdentifer import viper.silicon.logger.records.data.{CondExpRecord, EvaluateRecord, ImpliesRecord} @@ -38,11 +38,11 @@ import viper.silver.verifier.{CounterexampleTransformer, PartialVerificationErro */ trait EvaluationRules extends SymbolicExecutionRules { - def evals(s: State, es: Seq[ast.Exp], pvef: ast.Exp => PartialVerificationError, v: Verifier) + def evals(s: State, es: Seq[ast.Exp], pvef: ast.Exp => PartialVerificationError, v: Verifier, dAInfo: DependencyAnalysisInfo) (Q: (State, List[Term], Option[List[ast.Exp]], Verifier) => VerificationResult) : VerificationResult - def eval(s: State, e: ast.Exp, pve: PartialVerificationError, v: Verifier, dependencyType: Option[DependencyType]=None) + def eval(s: State, e: ast.Exp, pve: PartialVerificationError, v: Verifier, dAInfo: DependencyAnalysisInfo) (Q: (State, Term, Option[ast.Exp], Verifier) => VerificationResult) : VerificationResult @@ -54,7 +54,8 @@ trait EvaluationRules extends SymbolicExecutionRules { optTriggers: Option[Seq[ast.Trigger]], name: String, pve: PartialVerificationError, - v: Verifier) + v: Verifier, + dAInfo: DependencyAnalysisInfo) (Q: (State, Seq[Var], Option[Seq[ast.LocalVarDecl]], Seq[Term], Option[Seq[ast.Exp]], Option[(Seq[Term], Option[Seq[ast.Exp]], Seq[Trigger], (Seq[Term], Seq[Quantification]), Option[(InsertionOrderedSet[DebugExp], InsertionOrderedSet[DebugExp])])], Verifier) => VerificationResult) : VerificationResult } @@ -63,48 +64,49 @@ object evaluator extends EvaluationRules { import consumer._ import producer._ - def evals(s: State, es: Seq[ast.Exp], pvef: ast.Exp => PartialVerificationError, v: Verifier) + def evals(s: State, es: Seq[ast.Exp], pvef: ast.Exp => PartialVerificationError, v: Verifier, dAInfo: DependencyAnalysisInfo) (Q: (State, List[Term], Option[List[ast.Exp]], Verifier) => VerificationResult) : VerificationResult = - evals2(s, es, Nil, pvef, v)(Q) + evals2(s, es, Nil, pvef, v, dAInfo)(Q) - private def evals2(s: State, es: Seq[ast.Exp], ts: List[Term], pvef: ast.Exp => PartialVerificationError, v: Verifier) + private def evals2(s: State, es: Seq[ast.Exp], ts: List[Term], pvef: ast.Exp => PartialVerificationError, v: Verifier, dAInfo: DependencyAnalysisInfo) (Q: (State, List[Term], Option[List[ast.Exp]], Verifier) => VerificationResult) : VerificationResult = { if (es.isEmpty) Q(s, ts.reverse, if (withExp) Some(List.empty) else None, v) else - eval(s, es.head, pvef(es.head), v)((s1, t, eNew, v1) => - evals2(s1, es.tail, t :: ts, pvef, v1)((s2, ts2, es2, v2) => Q(s2, ts2, eNew.map(eN => eN :: es2.get), v2))) + eval(s, es.head, pvef(es.head), v, dAInfo)((s1, t, eNew, v1) => + evals2(s1, es.tail, t :: ts, pvef, v1, dAInfo)((s2, ts2, es2, v2) => Q(s2, ts2, eNew.map(eN => eN :: es2.get), v2))) } /** Wrapper Method for eval, for logging. See Executor.scala for explanation of analogue. **/ @inline - def eval(s: State, e: ast.Exp, pve: PartialVerificationError, v: Verifier, dependencyType: Option[DependencyType]=None) + def eval(s: State, e: ast.Exp, pve: PartialVerificationError, v: Verifier, dAInfo: DependencyAnalysisInfo) (Q: (State, Term, Option[ast.Exp], Verifier) => VerificationResult) : VerificationResult = { val sepIdentifier = v.symbExLog.openScope(new EvaluateRecord(e, s, v.decider.pcs)) val sourceInfo = v.decider.pushAndGetAnalysisSourceInfo(e, dependencyType) - eval3(s, e, pve, v)((s1, t, eNew, v1) => { + eval3(s, e, pve, v, dAInfo)((s1, t, eNew, v1) => { v1.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) v1.symbExLog.closeScope(sepIdentifier) Q(s1, t, eNew, v1)}) } - def eval3(s: State, e: ast.Exp, pve: PartialVerificationError, v: Verifier) + def eval3(s: State, e: ast.Exp, pve: PartialVerificationError, v: Verifier, dAInfo: DependencyAnalysisInfo) (Q: (State, Term, Option[ast.Exp], Verifier) => VerificationResult) : VerificationResult = { if(v.decider.isPathInfeasible()){ - val assertionNodesForJoin = DependencyAnalyzer.extractAssertionsForJoin(e, s.program) - assertionNodesForJoin.foreach(n => v.decider.dependencyAnalyzer.addAssumption(True, CompositeAnalysisSourceInfo(v.decider.analysisSourceInfoStack.getFullSourceInfo, AnalysisSourceInfo.createAnalysisSourceInfo(n)), v.decider.analysisSourceInfoStack.getAssumptionType, isJoinNode=true)) +// FIXME ake: infeasible paths +// val assertionNodesForJoin = DependencyAnalyzer.extractAssertionsForJoin(e, s.program) +// assertionNodesForJoin.foreach(n => v.decider.dependencyAnalyzer.addAssumption(True, CompositeAnalysisSourceInfo(v.decider.analysisSourceInfoStack.getFullSourceInfo, AnalysisSourceInfo.createAnalysisSourceInfo(n)), v.decider.analysisSourceInfoStack.getAssumptionType, isJoinNode=true)) if(!Expressions.isKnownWellDefined(e, Some(s.program))){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType, v.decider.analysisSourceInfoStack.isJoinRelevantNode) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, dAInfo) } val sort = v.symbolConverter.toSort(e.typ) val newVar = v.decider.fresh(sort, None) // just make sure the returned term typechecks @@ -141,7 +143,7 @@ object evaluator extends EvaluationRules { reserveHeaps = Nil, exhaleExt = false) - eval2(s1, e, pve, v)((s2, t, eNew, v1) => { + eval2(s1, e, pve, v, dAInfo)((s2, t, eNew, v1) => { val s3 = if (s2.recordPossibleTriggers) e match { @@ -159,7 +161,7 @@ object evaluator extends EvaluationRules { Q(s4, t, eNew, v1)}) } - protected def eval2(s: State, e: ast.Exp, pve: PartialVerificationError, v: Verifier) + protected def eval2(s: State, e: ast.Exp, pve: PartialVerificationError, v: Verifier, dAInfo: DependencyAnalysisInfo) (Q: (State, Term, Option[ast.Exp], Verifier) => VerificationResult) : VerificationResult = { val eOpt = Option.when(withExp)(e) @@ -170,9 +172,9 @@ object evaluator extends EvaluationRules { case _: ast.NullLit => Q(s, Null, eOpt, v) case ast.IntLit(bigval) => Q(s, IntLiteral(bigval), eOpt, v) - case ast.EqCmp(e0, e1) => evalBinOp(s, e0, e1, Equals, pve, v)((s1, t, e0New, e1New, v1) => + case ast.EqCmp(e0, e1) => evalBinOp(s, e0, e1, Equals, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, Option.when(withExp)(ast.EqCmp(e0New.get, e1New.get)(e.pos, e.info, e.errT)), v1)) - case ast.NeCmp(e0, e1) => evalBinOp(s, e0, e1, (p0: Term, p1: Term) => Not(Equals(p0, p1)), pve, v)((s1, t, e0New, e1New, v1) => + case ast.NeCmp(e0, e1) => evalBinOp(s, e0, e1, (p0: Term, p1: Term) => Not(Equals(p0, p1)), pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, Option.when(withExp)(ast.NeCmp(e0New.get, e1New.get)(e.pos, e.info, e.errT)), v1)) case x: ast.LocalVarWithVersion => @@ -187,8 +189,8 @@ object evaluator extends EvaluationRules { case ast.FractionalPerm(e0, e1) => var t1: Term = null - evalBinOp(s, e0, e1, (t0, _t1) => {t1 = _t1; FractionPerm(t0, t1)}, pve, v)((s1, tFP, e0New, e1New, v1) => - failIfDivByZero(s1, tFP, e1, e1New, t1, predef.Zero, pve, v1)((s2, t, v2) + evalBinOp(s, e0, e1, (t0, _t1) => {t1 = _t1; FractionPerm(t0, t1)}, pve, v, dAInfo)((s1, tFP, e0New, e1New, v1) => + failIfDivByZero(s1, tFP, e1, e1New, t1, predef.Zero, pve, v1, dAInfo)((s2, t, v2) => Q(s2, t, e0New.map(ast.FractionalPerm(_, e1New.get)(e.pos, e.info, e.errT)), v2))) case _: ast.WildcardPerm if s.assertReadAccessOnly => @@ -199,7 +201,7 @@ object evaluator extends EvaluationRules { case _: ast.WildcardPerm => val (tVar, tConstraints, eVar) = v.decider.freshARP() val constraintExp = Option.when(withExp)(DebugExp.createInstance(s"${eVar.get.toString} > none", true)) - v.decider.assumeDefinition(tConstraints, constraintExp, v.decider.analysisSourceInfoStack.getAssumptionType) + v.decider.assumeDefinition(tConstraints, constraintExp, dAInfo) /* TODO: Only record wildcards in State.constrainableARPs that are used in exhale * position. Currently, wildcards used in inhale position (only) may not be removed * from State.constrainableARPs (potentially inefficient, but should be sound). @@ -217,14 +219,14 @@ object evaluator extends EvaluationRules { Q(s1, tVar, eVar, v) case fa: ast.FieldAccess => - eval(s, fa.rcv, pve, v)((s1, tRcvr, eRcvr, v1) => { + eval(s, fa.rcv, pve, v, dAInfo)((s1, tRcvr, eRcvr, v1) => { val (debugHeapName, debugLabel) = v1.getDebugOldLabel(s1, fa.pos) val newFa = Option.when(withExp)({ if (s1.isEvalInOld) ast.FieldAccess(eRcvr.get, fa.field)(fa.pos, fa.info, fa.errT) else ast.DebugLabelledOld(ast.FieldAccess(eRcvr.get, fa.field)(), debugLabel)(fa.pos, fa.info, fa.errT) }) val ve = pve dueTo InsufficientPermission(fa) - v.heapSupporter.evalFieldAccess(s1, fa, tRcvr, eRcvr, ve, v1)((s2, snap, v2) => { + v.heapSupporter.evalFieldAccess(s1, fa, tRcvr, eRcvr, ve, v1, dAInfo)((s2, snap, v2) => { val s3 = if (Verifier.config.enableDebugging() && !s2.isEvalInOld) s2.copy(oldHeaps = s2.oldHeaps + (debugHeapName -> magicWandSupporter.getEvalHeap(s2))) else s2 @@ -233,15 +235,15 @@ object evaluator extends EvaluationRules { }) case ast.Not(e0) => - eval(s, e0, pve, v)((s1, t0, e0New, v1) => + eval(s, e0, pve, v, dAInfo)((s1, t0, e0New, v1) => Q(s1, Not(t0), e0New.map(ast.Not(_)(e.pos, e.info, e.errT)), v1)) case ast.Minus(e0) => - eval(s, e0, pve, v)((s1, t0, e0New, v1) => + eval(s, e0, pve, v, dAInfo)((s1, t0, e0New, v1) => Q(s1, Minus(0, t0), e0New.map(ast.Minus(_)(e.pos, e.info, e.errT)), v1)) case ast.Old(e0) => - evalInOldState(s, Verifier.PRE_STATE_LABEL, e0, pve, v)((s1, t0, e0New, v1) => + evalInOldState(s, Verifier.PRE_STATE_LABEL, e0, pve, v, dAInfo)((s1, t0, e0New, v1) => Q(s1, t0, e0New.map(ast.Old(_)(e.pos, e.info, e.errT)), v1)) case old@ast.DebugLabelledOld(e0, lbl) => @@ -252,11 +254,11 @@ object evaluator extends EvaluationRules { s.oldHeaps.get(heapName) match { case None => val failure = createFailure(pve dueTo LabelledStateNotReached(ast.LabelledOld(e0, heapName)(old.pos, old.info, old.errT)), v, s, "labelled state reached") - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, v.decider.analysisSourceInfoStack.getAssertionType, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, dAInfo, v.reportFurtherErrors()) val freshVar = v.decider.fresh(v.symbolConverter.toSort(old.typ), None) if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, freshVar, None, v) else failure case _ => - evalInOldState(s, heapName, e0, pve, v)((s1, t0, _, v1) => + evalInOldState(s, heapName, e0, pve, v, dAInfo)((s1, t0, _, v1) => Q(s1, t0, Some(old), v1)) } @@ -264,21 +266,21 @@ object evaluator extends EvaluationRules { s.oldHeaps.get(lbl) match { case None => val failure = createFailure(pve dueTo LabelledStateNotReached(old), v, s, "labelled state reached") - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, v.decider.analysisSourceInfoStack.getAssertionType, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, dAInfo, v.reportFurtherErrors()) val freshVar = v.decider.fresh(v.symbolConverter.toSort(old.typ), None) if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, freshVar, None, v) else failure case _ => - evalInOldState(s, lbl, e0, pve, v)((s1, t0, e0New, v1) => + evalInOldState(s, lbl, e0, pve, v, dAInfo)((s1, t0, e0New, v1) => Q(s1, t0, e0New.map(ast.LabelledOld(_, lbl)(old.pos, old.info, old.errT)), v1))} case l@ast.Let(x, e0, e1) => - eval(s, e0, pve, v)((s1, t0, e0New, v1) => { + eval(s, e0, pve, v, dAInfo)((s1, t0, e0New, v1) => { val t = v1.decider.appliedFresh("letvar", v1.symbolConverter.toSort(x.typ), s1.relevantQuantifiedVariables.map(_._1)) val debugExp = Option.when(withExp)(DebugExp.createInstance("letvar assignment", InsertionOrderedSet(DebugExp.createInstance(ast.EqCmp(x.localVar, e0)(), ast.EqCmp(x.localVar, e0New.get)())))) - v1.decider.assumeDefinition(BuiltinEquals(t, t0), debugExp, v1.decider.analysisSourceInfoStack.getAssumptionType) + v1.decider.assumeDefinition(BuiltinEquals(t, t0), debugExp, dAInfo) val newFuncRec = s1.functionRecorder.recordFreshSnapshot(t.applicable.asInstanceOf[Function]).enterLet(l) val possibleTriggersBefore = if (s1.recordPossibleTriggers) s1.possibleTriggers else Map.empty - eval(s1.copy(g = s1.g + (x.localVar, (t0, e0New)), functionRecorder = newFuncRec), e1, pve, v1)((s2, t2, e1New, v2) => { + eval(s1.copy(g = s1.g + (x.localVar, (t0, e0New)), functionRecorder = newFuncRec), e1, pve, v1, dAInfo)((s2, t2, e1New, v2) => { val newPossibleTriggers = if (s2.recordPossibleTriggers) { val addedTriggers = s2.possibleTriggers -- possibleTriggersBefore.keys val addedTriggersReplaced = addedTriggers.map(at => at._1.replace(x.localVar, e0) -> at._2) @@ -293,30 +295,30 @@ object evaluator extends EvaluationRules { /* Strict evaluation of AND */ case ast.And(e0, e1) if Verifier.config.disableShortCircuitingEvaluations() => - evalBinOp(s, e0, e1, (t1, t2) => And(t1, t2), pve, v)((s1, t, e0New, e1New, v1) => + evalBinOp(s, e0, e1, (t1, t2) => And(t1, t2), pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(ast.And(_, e1New.get)(e.pos, e.info, e.errT)), v1)) /* Short-circuiting evaluation of AND */ case ae @ ast.And(_, _) => val flattened = flattenOperator(ae, {case ast.And(e0, e1) => Seq(e0, e1)}) - evalSeqShortCircuit(And, s, flattened, pve, v)(Q) + evalSeqShortCircuit(And, s, flattened, pve, v, dAInfo)(Q) /* Strict evaluation of OR */ case ast.Or(e0, e1) if Verifier.config.disableShortCircuitingEvaluations() => - evalBinOp(s, e0, e1, (t1, t2) => Or(t1, t2), pve, v)((s1, t, e0New, e1New, v1) => + evalBinOp(s, e0, e1, (t1, t2) => Or(t1, t2), pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(ast.Or(_, e1New.get)(e.pos, e.info, e.errT)), v1)) /* Short-circuiting evaluation of OR */ case oe @ ast.Or(_, _) => val flattened = flattenOperator(oe, {case ast.Or(e0, e1) => Seq(e0, e1)}) - evalSeqShortCircuit(Or, s, flattened, pve, v)(Q) + evalSeqShortCircuit(Or, s, flattened, pve, v, dAInfo)(Q) case implies @ ast.Implies(e0, e1) => val impliesRecord = new ImpliesRecord(implies, s, v.decider.pcs, "Implies") val uidImplies = v.symbExLog.openScope(impliesRecord) - eval(s, e0, pve, v)((s1, t0, e0New, v1) => - evalImplies(s1, t0, (e0, e0New), e1, implies.info == FromShortCircuitingAnd, pve, v1)((s2, t1, e1New, v2) => { + eval(s, e0, pve, v, dAInfo)((s1, t0, e0New, v1) => + evalImplies(s1, t0, (e0, e0New), e1, implies.info == FromShortCircuitingAnd, pve, v1, dAInfo)((s2, t1, e1New, v2) => { v2.symbExLog.closeScope(uidImplies) val implExpP = e0New.map(ast.Implies(_, e1New.get)(e.pos, e.info, e.errT)) Q(s2, t1, implExpP, v2) @@ -325,11 +327,11 @@ object evaluator extends EvaluationRules { case condExp @ ast.CondExp(e0, e1, e2) => val condExpRecord = new CondExpRecord(condExp, s, v.decider.pcs, "CondExp") val uidCondExp = v.symbExLog.openScope(condExpRecord) - eval(s, e0, pve, v)((s1, t0, e0New, v1) => - joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s1, v1, v1.decider.analysisSourceInfoStack.getAssumptionType)((s2, v2, QB) => - brancher.branch(s2.copy(parallelizeBranches = false), t0, (e0, e0New), v2, AssumptionType.Internal)( - (s3, v3) => eval(s3.copy(parallelizeBranches = s2.parallelizeBranches), e1, pve, v3)((s4, t4, e4, v4) => QB(s4, (t4, e4), v4)), - (s3, v3) => eval(s3.copy(parallelizeBranches = s2.parallelizeBranches), e2, pve, v3)((s4, t4, e4, v4) => QB(s4, (t4, e4), v4))) + eval(s, e0, pve, v, dAInfo)((s1, t0, e0New, v1) => + joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s1, v1, dAInfo)((s2, v2, QB) => + brancher.branch(s2.copy(parallelizeBranches = false), t0, (e0, e0New), v2, dAInfo.withDependencyType(DependencyType.Internal))( + (s3, v3) => eval(s3.copy(parallelizeBranches = s2.parallelizeBranches), e1, pve, v3, dAInfo)((s4, t4, e4, v4) => QB(s4, (t4, e4), v4)), + (s3, v3) => eval(s3.copy(parallelizeBranches = s2.parallelizeBranches), e2, pve, v3, dAInfo)((s4, t4, e4, v4) => QB(s4, (t4, e4), v4))) )(entries => { /* TODO: If branch(...) took orElse-continuations that are executed if a branch is dead, then then comparisons with t0/Not(t0) wouldn't be necessary. */ @@ -351,88 +353,88 @@ object evaluator extends EvaluationRules { /* Integers */ case ast.Add(e0, e1) => - evalBinOp(s, e0, e1, Plus, pve, v)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.Add(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) + evalBinOp(s, e0, e1, Plus, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.Add(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case ast.Sub(e0, e1) => - evalBinOp(s, e0, e1, Minus, pve, v)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.Sub(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) + evalBinOp(s, e0, e1, Minus, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.Sub(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case ast.Mul(e0, e1) => - evalBinOp(s, e0, e1, Times, pve, v)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.Mul(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) + evalBinOp(s, e0, e1, Times, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.Mul(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case ast.Div(e0, e1) => - evalBinOp(s, e0, e1, Div, pve, v)((s1, tDiv, e0New, e1New, v1) => - failIfDivByZero(s1, tDiv, e1, e1New, tDiv.p1, 0, pve, v1)((s2, t, v2) + evalBinOp(s, e0, e1, Div, pve, v, dAInfo)((s1, tDiv, e0New, e1New, v1) => + failIfDivByZero(s1, tDiv, e1, e1New, tDiv.p1, 0, pve, v1, dAInfo)((s2, t, v2) => Q(s2, t, e0New.map(e0p => ast.Div(e0p, e1New.get)(e.pos, e.info, e.errT)), v2))) case ast.Mod(e0, e1) => - evalBinOp(s, e0, e1, Mod, pve, v)((s1, tMod, e0New, e1New, v1) => - failIfDivByZero(s1, tMod, e1, e1New, tMod.p1, 0, pve, v1)((s2, t, v2) + evalBinOp(s, e0, e1, Mod, pve, v, dAInfo)((s1, tMod, e0New, e1New, v1) => + failIfDivByZero(s1, tMod, e1, e1New, tMod.p1, 0, pve, v1, dAInfo)((s2, t, v2) => Q(s2, t, e0New.map(e0p => ast.Mod(e0p, e1New.get)(e.pos, e.info, e.errT)), v2))) case ast.LeCmp(e0, e1) => - evalBinOp(s, e0, e1, AtMost, pve, v)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.LeCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) + evalBinOp(s, e0, e1, AtMost, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.LeCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case ast.LtCmp(e0, e1) => - evalBinOp(s, e0, e1, Less, pve, v)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.LtCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) + evalBinOp(s, e0, e1, Less, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.LtCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case ast.GeCmp(e0, e1) => - evalBinOp(s, e0, e1, AtLeast, pve, v)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.GeCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) + evalBinOp(s, e0, e1, AtLeast, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.GeCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case ast.GtCmp(e0, e1) => - evalBinOp(s, e0, e1, Greater, pve, v)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.GtCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) + evalBinOp(s, e0, e1, Greater, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.GtCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) /* Permissions */ case ast.PermAdd(e0, e1) => - evalBinOp(s, e0, e1, PermPlus, pve, v)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.PermAdd(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) + evalBinOp(s, e0, e1, PermPlus, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.PermAdd(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case ast.PermSub(e0, e1) => - evalBinOp(s, e0, e1, PermMinus, pve, v)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.PermSub(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) + evalBinOp(s, e0, e1, PermMinus, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.PermSub(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case ast.PermMinus(e0) => - eval(s, e0, pve, v)((s1, t0, e0New, v1) => + eval(s, e0, pve, v, dAInfo)((s1, t0, e0New, v1) => Q(s1, PermMinus(NoPerm, t0), e0New.map(e0p => ast.PermMinus(e0p)(e.pos, e.info, e.errT)), v1)) case ast.PermMul(e0, e1) => - evalBinOp(s, e0, e1, PermTimes, pve, v)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.PermMul(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) + evalBinOp(s, e0, e1, PermTimes, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.PermMul(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case ast.DebugPermMin(e0, e1) => - evalBinOp(s, e0, e1, PermMin, pve, v)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.DebugPermMin(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) + evalBinOp(s, e0, e1, PermMin, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.DebugPermMin(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case ast.IntPermMul(e0, e1) => - eval(s, e0, pve, v)((s1, t0, e0New, v1) => - eval(s1, e1, pve, v1)((s2, t1, e1New, v2) => + eval(s, e0, pve, v, dAInfo)((s1, t0, e0New, v1) => + eval(s1, e1, pve, v1, dAInfo)((s2, t1, e1New, v2) => Q(s2, IntPermTimes(t0, t1), e0New.map(e0p => ast.IntPermMul(e0p, e1New.get)(e.pos, e.info, e.errT)), v2))) case ast.PermDiv(e0, e1) => - eval(s, e0, pve, v)((s1, t0, e0New, v1) => - eval(s1, e1, pve, v1)((s2, t1, e1New, v2) => - failIfDivByZero(s2, PermIntDiv(t0, t1), e1, e1New, t1, 0, pve, v2)((s3, t, v3) + eval(s, e0, pve, v, dAInfo)((s1, t0, e0New, v1) => + eval(s1, e1, pve, v1, dAInfo)((s2, t1, e1New, v2) => + failIfDivByZero(s2, PermIntDiv(t0, t1), e1, e1New, t1, 0, pve, v2, dAInfo)((s3, t, v3) => Q(s3, t, e0New.map(e0p => ast.PermDiv(e0p, e1New.get)(e.pos, e.info, e.errT)), v3)))) case ast.PermPermDiv(e0, e1) => - eval(s, e0, pve, v)((s1, t0, e0New, v1) => - eval(s1, e1, pve, v1)((s2, t1, e1New, v2) => - failIfDivByZero(s2, PermPermDiv(t0, t1), e1, e1New, t1, FractionPermLiteral(Rational(0, 1)), pve, v2)((s3, t, v3) => + eval(s, e0, pve, v, dAInfo)((s1, t0, e0New, v1) => + eval(s1, e1, pve, v1, dAInfo)((s2, t1, e1New, v2) => + failIfDivByZero(s2, PermPermDiv(t0, t1), e1, e1New, t1, FractionPermLiteral(Rational(0, 1)), pve, v2, dAInfo)((s3, t, v3) => Q(s3, t, e0New.map(e0p => ast.PermPermDiv(e0p, e1New.get)(e.pos, e.info, e.errT)), v3)))) case ast.PermLeCmp(e0, e1) => - evalBinOp(s, e0, e1, AtMost, pve, v)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.PermLeCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) + evalBinOp(s, e0, e1, AtMost, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.PermLeCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case ast.PermLtCmp(e0, e1) => - evalBinOp(s, e0, e1, Less, pve, v)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.PermLtCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) + evalBinOp(s, e0, e1, Less, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.PermLtCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case ast.PermGeCmp(e0, e1) => - evalBinOp(s, e0, e1, AtLeast, pve, v)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.PermGeCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) + evalBinOp(s, e0, e1, AtLeast, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.PermGeCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case ast.PermGtCmp(e0, e1) => - evalBinOp(s, e0, e1, Greater, pve, v)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.PermGtCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) + evalBinOp(s, e0, e1, Greater, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.PermGtCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) /* Others */ /* Domains not handled directly */ case dfa @ ast.DomainFuncApp(funcName, eArgs, m) => - evals(s, eArgs, _ => pve, v)((s1, tArgs, eArgsNew, v1) => { + evals(s, eArgs, _ => pve, v, dAInfo)((s1, tArgs, eArgsNew, v1) => { val inSorts = tArgs map (_.sort) val outSort = v1.symbolConverter.toSort(dfa.typ) val fi = v1.symbolConverter.toFunction(s.program.findDomainFunction(funcName), inSorts :+ outSort, s.program) @@ -440,7 +442,7 @@ object evaluator extends EvaluationRules { Q(s1, App(fi, tArgs), dfaP, v1)}) case bf @ ast.BackendFuncApp(funcName, eArgs) => - evals(s, eArgs, _ => pve, v)((s1, tArgs, eArgsNew, v1) => { + evals(s, eArgs, _ => pve, v, dAInfo)((s1, tArgs, eArgsNew, v1) => { val func = s.program.findDomainFunction(funcName) val fi = v1.symbolConverter.toFunction(func, s.program) val bfP = Option.when(withExp)(ast.BackendFuncApp(funcName, eArgsNew.get)(bf.pos, bf.info, bf.typ, bf.interpretation, bf.errT)) @@ -448,8 +450,8 @@ object evaluator extends EvaluationRules { case ast.CurrentPerm(resacc) => val h = s.partiallyConsumedHeap.getOrElse(s.h) - evalResourceAccess(s, resacc, pve, v)((s1, identifier, args, eArgsNew, v1) => { - v1.heapSupporter.evalCurrentPerm(s1, h, resacc, identifier, args, eArgsNew, v1)((s2, t, v2) => + evalResourceAccess(s, resacc, pve, v, dAInfo)((s1, identifier, args, eArgsNew, v1) => { + v1.heapSupporter.evalCurrentPerm(s1, h, resacc, identifier, args, eArgsNew, v1, dAInfo)((s2, t, v2) => Q(s2, t, Option.when(withExp)(e), v2)) }) @@ -470,7 +472,7 @@ object evaluator extends EvaluationRules { val s2 = s1.copy(s1.g + gVars, quantifiedVariables = varPairs.map(v => v._1 -> Option.when(withExp)(v._2)) ++ s1.quantifiedVariables) - evals(s2, args, _ => pve, v)((s3, ts, es, v3) => { + evals(s2, args, _ => pve, v, dAInfo)((s3, ts, es, v3) => { val possibleConds = v3.heapSupporter.collectForPermConditions(s3, resource, varPairs, ts, es) def evalOptions(s: State, @@ -483,7 +485,7 @@ object evaluator extends EvaluationRules { val impliesRecord = new ImpliesRecord(null, s, v.decider.pcs, "ForPerm") val uidImplies = v.symbExLog.openScope(impliesRecord) val (t, (e0, e1), qVars, defs, triggers) = conds.head - evalImplies(s.copy(g = s.g + defs), t, (e0, e1), body, false, pve, v)((sNext, tImplies, bodyNew, vNext) => { + evalImplies(s.copy(g = s.g + defs), t, (e0, e1), body, false, pve, v, dAInfo)((sNext, tImplies, bodyNew, vNext) => { val tQuant = SimplifyingForall(qVars, tImplies, triggers) val eQuantNew = Option.when(withExp)(ast.Forall(varsNew.get, Seq(), ast.Implies(e0, bodyNew.get)())()) v.symbExLog.closeScope(uidImplies) @@ -550,7 +552,7 @@ object evaluator extends EvaluationRules { } val name = s"prog.$posString" val s0 = s.copy(functionRecorder = s.functionRecorder.enterQuantifiedExp(sourceQuant)) - evalQuantified(s0, qantOp, eQuant.variables, Nil, Seq(body), Some(eTriggers), name, pve, v){ + evalQuantified(s0, qantOp, eQuant.variables, Nil, Seq(body), Some(eTriggers), name, pve, v, dAInfo){ case (s1, tVars, eVars, _, _, Some((Seq(tBody), bodyNew, tTriggers, (tAuxGlobal, tAux), auxExps)), v1) => val tAuxHeapIndep = tAux.flatMap(v.quantifierSupporter.makeTriggersHeapIndependent(_, v1.decider.fresh)) val auxGlobalsExp = auxExps.map(_._1) @@ -558,10 +560,10 @@ object evaluator extends EvaluationRules { val commentGlobal = "Nested auxiliary terms: globals (aux)" v1.decider.prover.comment(commentGlobal) v1.decider.dependencyAnalyzer.disableTransitiveEdges() - v1.decider.assume(tAuxGlobal, Option.when(withExp)(DebugExp.createInstance(description=commentGlobal, children=auxGlobalsExp.get)), enforceAssumption = false, assumptionType=AssumptionType.Internal) + v1.decider.assume(tAuxGlobal, Option.when(withExp)(DebugExp.createInstance(description=commentGlobal, children=auxGlobalsExp.get)), enforceAssumption = false, dAInfo.withDependencyType(DependencyType.Internal)) val commentNonGlobals = "Nested auxiliary terms: non-globals (aux)" v1.decider.prover.comment(commentNonGlobals) - v1.decider.assume(tAuxHeapIndep/*tAux*/, Option.when(withExp)(DebugExp.createInstance(description=commentNonGlobals, children=auxNonGlobalsExp.get)), enforceAssumption = false, assumptionType=AssumptionType.Internal) + v1.decider.assume(tAuxHeapIndep/*tAux*/, Option.when(withExp)(DebugExp.createInstance(description=commentNonGlobals, children=auxNonGlobalsExp.get)), enforceAssumption = false, dAInfo.withDependencyType(DependencyType.Internal)) v1.decider.dependencyAnalyzer.enableTransitiveEdges() if (qantOp == Exists) { // For universal quantification, the non-global auxiliary assumptions will contain the information that @@ -575,7 +577,7 @@ object evaluator extends EvaluationRules { val exp = ast.Forall(eQuant.variables, eTriggers, body)(sourceQuant.pos, sourceQuant.info, sourceQuant.errT) DebugExp.createInstance(exp, expNew) }) - v1.decider.assume(Quantification(Forall, tVars, FunctionPreconditionTransformer.transform(tBody, s1.program), tTriggers, name, quantWeight), debugExp, v1.decider.analysisSourceInfoStack.getAssumptionType) + v1.decider.assume(Quantification(Forall, tVars, FunctionPreconditionTransformer.transform(tBody, s1.program), tTriggers, name, quantWeight), debugExp, dAInfo) } val tQuant = Quantification(qantOp, tVars, tBody, tTriggers, name, quantWeight) @@ -584,11 +586,11 @@ object evaluator extends EvaluationRules { Q(s2, tQuant, eQuantNew, v1) case (s1, _, _, _, _, None, v1) => // This should not happen unless the current path is dead. - if (v1.decider.checkSmoke(isAssert = true)) { + if (v1.decider.checkSmoke(dAInfo, isAssert = true)) { Unreachable() } else { val failure = createFailure(pve.dueTo(InternalReason(sourceQuant, "Quantifier evaluation failed.")), v1, s1, "quantifier could be evaluated") - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(False, v1.decider.analysisSourceInfoStack.getAssertionType, v1.reportFurtherErrors()) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(False, dAInfo, v1.reportFurtherErrors()) val freshVar = v1.decider.fresh(v1.symbolConverter.toSort(sourceQuant.typ), None) if(s1.retryLevel == 0 && v1.reportFurtherErrors()) failure combine Q(s1, freshVar, None, v1) else failure } @@ -597,7 +599,7 @@ object evaluator extends EvaluationRules { case fapp @ ast.FuncApp(funcName, eArgs) => val func = s.program.findFunction(funcName) val dependencyType = v.decider.analysisSourceInfoStack.getDependencyType - evals2(s, eArgs, Nil, _ => pve, v)((s1, tArgs, eArgsNew, v1) => { + evals2(s, eArgs, Nil, _ => pve, v, dAInfo)((s1, tArgs, eArgsNew, v1) => { // bookkeeper.functionApplications += 1 val joinFunctionArgs = tArgs //++ c2a.quantifiedVariables.filterNot(tArgs.contains) val (debugHeapName, debugLabel) = v1.getDebugOldLabel(s1, fapp.pos) @@ -612,7 +614,7 @@ object evaluator extends EvaluationRules { * Hence, the joinedFApp will take two arguments, namely, i*i and i, * although the latter is not necessary. */ - joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s1a, v1, assumptionType=dependencyType.assumptionType)((s2, v2, QB) => { + joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s1a, v1, dAInfo)((s2, v2, QB) => { val pres = func.pres.map(_.transform { /* [Malte 2018-08-20] Two examples of the test suite, one of which is the regression * for Carbon issue #210, fail if the subsequent code that strips out triggers from @@ -672,14 +674,14 @@ object evaluator extends EvaluationRules { s2.assertReadAccessOnly /* should currently always be false */ else true) val oldIsRelevantJoinNode = v2.decider.analysisSourceInfoStack.isJoinRelevantNode v2.decider.analysisSourceInfoStack.isJoinRelevantNode = true - consumes(s3, pres, true, _ => pvePre, v2, v2.decider.analysisSourceInfoStack.getDependencyType)((s4, snap, v3) => { + consumes(s3, pres, true, _ => pvePre, v2, dAInfo)((s4, snap, v3) => { v3.decider.analysisSourceInfoStack.isJoinRelevantNode = oldIsRelevantJoinNode val snap1 = snap.get.convert(sorts.Snap) val preFApp = App(functionSupporter.preconditionVersion(v3.symbolConverter.toFunction(func)), snap1 :: tArgs) val preExp = Option.when(withExp)({ DebugExp.createInstance(Some(s"precondition of ${func.name}(${eArgsNew.get.mkString(", ")}) holds"), None, None, InsertionOrderedSet.empty) }) - v3.decider.assume(preFApp, preExp, assumptionType=dependencyType.assumptionType) + v3.decider.assume(preFApp, preExp, dAInfo) val funcAnn = func.info.getUniqueInfo[AnnotationInfo] val tFApp = funcAnn match { case Some(a) if a.values.contains("opaque") => @@ -709,7 +711,7 @@ object evaluator extends EvaluationRules { /* TODO: The join-function is heap-independent, and it is not obvious how a * joined snapshot could be defined and represented */ - })(join(func.typ, s"joined_${func.name}", joinFunctionArgs, Option.when(withExp)(eArgs), v1))((s6, r, v4) + })(join(func.typ, s"joined_${func.name}", joinFunctionArgs, Option.when(withExp)(eArgs), v1, dAInfo))((s6, r, v4) => Q(s6, r._1, r._2, v4))}) case ast.Unfolding( @@ -719,11 +721,11 @@ object evaluator extends EvaluationRules { val predicate = s.program.findPredicate(predicateName) if (s.cycles(predicate) < Verifier.config.recursivePredicateUnfoldings()) { v.decider.startDebugSubExp() - evals(s, eArgs, _ => pve, v)((s1, tArgs, eArgsNew, v1) => - eval(s1, ePerm.getOrElse(ast.FullPerm()()), pve, v1)((s2, tPerm, ePermNew, v2) => - v2.decider.assert(IsPositive(tPerm)) { // TODO: Replace with permissionSupporter.assertNotNegative + evals(s, eArgs, _ => pve, v, dAInfo)((s1, tArgs, eArgsNew, v1) => + eval(s1, ePerm.getOrElse(ast.FullPerm()()), pve, v1, dAInfo)((s2, tPerm, ePermNew, v2) => + v2.decider.assert(IsPositive(tPerm), dAInfo) { // TODO: Replace with permissionSupporter.assertNotNegative case true => - joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s2, v2, v2.decider.analysisSourceInfoStack.getAssumptionType)((s3, v3, QB) => { + joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s2, v2, dAInfo)((s3, v3, QB) => { val s4 = s3.incCycleCounter(predicate) .copy(recordVisited = true) /* [2014-12-10 Malte] The commented code should replace the code following @@ -735,7 +737,7 @@ object evaluator extends EvaluationRules { // val c4 = c3.decCycleCounter(predicate) // eval(σ1, eIn, pve, c4)((tIn, c5) => // QB(tIn, c5))}) - consume(s4, acc, true, pve, v3, DependencyAnalyzer.extractDependencyTypeFromInfo(acc.info).getOrElse(DependencyType.Internal))((s5, snap, v4) => { + consume(s4, acc, true, pve, v3, dAInfo)((s5, snap, v4) => { val fr6 = s5.functionRecorder.recordSnapshot(pa, v4.decider.pcs.branchConditions, snap.get) .changeDepthBy(+1) @@ -750,7 +752,7 @@ object evaluator extends EvaluationRules { if (!Verifier.config.disableFunctionUnfoldTrigger()) { val eArgsString = eArgsNew.mkString(", ") val debugExp = Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}($eArgsString))", isInternal_ = true)) - v4.decider.assume(App(s.predicateData(predicate.name).triggerFunction, snap.get.convert(terms.sorts.Snap) +: tArgs), debugExp, AssumptionType.Trigger) + v4.decider.assume(App(s.predicateData(predicate.name).triggerFunction, snap.get.convert(terms.sorts.Snap) +: tArgs), debugExp, dAInfo.withDependencyType(DependencyType.Trigger)) } val body = predicate.body.get /* Only non-abstract predicates can be unfolded */ val s7 = s6.scalePermissionFactor(tPerm, ePermNew) @@ -760,7 +762,7 @@ object evaluator extends EvaluationRules { if (s7a.predicateData(predicate.name).predContents.isDefined) { val toReplace: silicon.Map[Term, Term] = silicon.Map.from(s7a.predicateData(predicate.name).params.get.zip(Seq(snap.get) ++ tArgs)) - predicateSupporter.producePredicateContents(s7a, s7a.predicateData(predicate.name).predContents.get, toReplace, v4, DependencyType.Internal, true)((s8, v5) => { + predicateSupporter.producePredicateContents(s7a, s7a.predicateData(predicate.name).predContents.get, toReplace, v4, dAInfo.withDependencyType(DependencyType.Internal), true)((s8, v5) => { val s9 = s8.copy(g = s7.g, functionRecorder = s8.functionRecorder.changeDepthBy(-1), recordVisited = s3.recordVisited, @@ -769,10 +771,10 @@ object evaluator extends EvaluationRules { constrainableARPs = s1.constrainableARPs) .decCycleCounter(predicate) val s10 = v5.stateConsolidator(s9).consolidateOptionally(s9, v5) - eval(s10, eIn, pve, v5)((s9, t9, e9, v9) => QB(s9, (t9, e9), v9)) + eval(s10, eIn, pve, v5, dAInfo)((s9, t9, e9, v9) => QB(s9, (t9, e9), v9)) }) } else { - produce(s7a, toSf(snap.get), body, pve, v4, v4.decider.analysisSourceInfoStack.getAssumptionType)((s8, v5) => { + produce(s7a, toSf(snap.get), body, pve, v4, dAInfo)((s8, v5) => { val s9 = s8.copy(g = s7.g, functionRecorder = s8.functionRecorder.changeDepthBy(-1), recordVisited = s3.recordVisited, @@ -781,18 +783,18 @@ object evaluator extends EvaluationRules { constrainableARPs = s1.constrainableARPs) .decCycleCounter(predicate) val s10 = v5.stateConsolidator(s9).consolidateOptionally(s9, v5) - eval(s10, eIn, pve, v5)((s9, t9, e9, v9) => QB(s9, (t9, e9), v9))}) + eval(s10, eIn, pve, v5, dAInfo)((s9, t9, e9, v9) => QB(s9, (t9, e9), v9))}) } }) })(join(eIn.typ, "joined_unfolding", s2.relevantQuantifiedVariables.map(_._1), - Option.when(withExp)(s2.relevantQuantifiedVariables.map(_._2.get)), v2))((s12, r12, v7) + Option.when(withExp)(s2.relevantQuantifiedVariables.map(_._2.get)), v2, dAInfo))((s12, r12, v7) => { v7.decider.finishDebugSubExp(s"unfolded(${predicate.name})") Q(s12, r12._1, r12._2, v7)}) case false => v2.decider.finishDebugSubExp(s"unfolded(${predicate.name})") val failure = createFailure(pve dueTo NonPositivePermission(ePerm.get), v2, s2, IsPositive(tPerm), ePermNew.map(p => ast.PermGtCmp(p, ast.NoPerm()())(p.pos, p.info, p.errT))) - if(s2.retryLevel == 0) v2.decider.handleFailedAssertionForDependencyAnalysis(False, v2.decider.analysisSourceInfoStack.getAssertionType, v2.reportFurtherErrors()) + if(s2.retryLevel == 0) v2.decider.handleFailedAssertionForDependencyAnalysis(False, dAInfo, v2.reportFurtherErrors()) val freshVar = v2.decider.fresh(v2.symbolConverter.toSort(e.typ), None) // TODO ake: function recorder if(s2.retryLevel == 0 && v2.reportFurtherErrors() && Verifier.config.disableInfeasibilityChecks()) failure combine Q(s2, freshVar, None, v2) else failure })) @@ -803,123 +805,123 @@ object evaluator extends EvaluationRules { } case ast.Applying(wand, eIn) => - joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s, v, v.decider.analysisSourceInfoStack.getAssumptionType)((s1, v1, QB) => + joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s, v, dAInfo)((s1, v1, QB) => magicWandSupporter.applyWand(s1, wand, pve, v1)((s2, v2) => { - eval(s2, eIn, pve, v2)((s3, t, eInNew, v3) => QB(s3, (t, eInNew), v3)) + eval(s2, eIn, pve, v2, dAInfo)((s3, t, eInNew, v3) => QB(s3, (t, eInNew), v3)) }))(join(eIn.typ, "joined_applying", s.relevantQuantifiedVariables.map(_._1), - Option.when(withExp)(s.relevantQuantifiedVariables.map(_._2.get)), v))((s4, r4, v4) + Option.when(withExp)(s.relevantQuantifiedVariables.map(_._2.get)), v, dAInfo))((s4, r4, v4) => Q(s4, r4._1, r4._2, v4)) case ast.Asserting(eAss, eIn) => - consume(s, eAss, false, pve, v, DependencyAnalyzer.extractDependencyTypeFromInfo(eAss.info).getOrElse(DependencyType.ExplicitAssertion))((s2, _, v2) => { + consume(s, eAss, false, pve, v, dAInfo /* TODO ake: explicit assertion? */)((s2, _, v2) => { val s3 = s2.copy(g = s.g, h = s.h) - eval(s3, eIn, pve, v2)(Q) + eval(s3, eIn, pve, v2, dAInfo)(Q) }) /* Sequences */ - case ast.SeqContains(e0, e1) => evalBinOp(s, e1, e0, SeqIn, pve, v)((s1, t, e1New, e0New, v1) => + case ast.SeqContains(e0, e1) => evalBinOp(s, e1, e0, SeqIn, pve, v, dAInfo)((s1, t, e1New, e0New, v1) => Q(s1, t, e0New.map(e0p => ast.SeqContains(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) /* Note the reversed order of the arguments! */ case ast.SeqIndex(e0, e1) => - evals2(s, Seq(e0, e1), Nil, _ => pve, v)({case (s1, Seq(t0, t1), esNew, v1) => + evals2(s, Seq(e0, e1), Nil, _ => pve, v, dAInfo)({case (s1, Seq(t0, t1), esNew, v1) => val eNew = esNew.map(es => ast.SeqIndex(es.head, es(1))(e.pos, e.info, e.errT)) if (s1.triggerExp) { Q(s1, SeqAt(t0, t1), eNew, v1) } else { - v1.decider.assert(AtLeast(t1, IntLiteral(0))) { + v1.decider.assert(AtLeast(t1, IntLiteral(0)), dAInfo) { case true => - v1.decider.assert(Less(t1, SeqLength(t0))) { + v1.decider.assert(Less(t1, SeqLength(t0)), dAInfo) { case true => Q(s1, SeqAt(t0, t1), eNew, v1) case false => val assertExp2 = Option.when(withExp)(ast.LtCmp(e1, ast.SeqLength(e0)())(e1.pos, e1.info, e1.errT)) val failure = createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, Less(t1, SeqLength(t0)), assertExp2) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(Less(t1, SeqLength(t0)), v1.decider.analysisSourceInfoStack.getAssertionType, assumeFailedAssertion=false) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(Less(t1, SeqLength(t0)), dAInfo, assumeFailedAssertion=false) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { val assertExp2 = Option.when(withExp)(ast.LeCmp(e1, ast.SeqLength(e0)())()) val assertExp2New = esNew.map(es => ast.LeCmp(es(1), ast.SeqLength(es.head)())()) - v1.decider.assume(Less(t1, SeqLength(t0)), assertExp2, assertExp2New, assumptionType = AssumptionType.Explicit) + v1.decider.assume(Less(t1, SeqLength(t0)), assertExp2, assertExp2New, dAInfo.withDependencyType(DependencyType.Explicit)) failure combine Q(s1, SeqAt(t0, t1), eNew, v1) } else failure} case false => val assertExp1 = Option.when(withExp)(ast.GeCmp(e1, ast.IntLit(0)())(e1.pos, e1.info, e1.errT)) val assertExp1New = Option.when(withExp)(ast.GeCmp(esNew.get(1), ast.IntLit(0)())(e1.pos, e1.info, e1.errT)) val failure1 = createFailure(pve dueTo SeqIndexNegative(e0, e1), v1, s1, AtLeast(t1, IntLiteral(0)), assertExp1New) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(AtLeast(t1, IntLiteral(0)), v1.decider.analysisSourceInfoStack.getAssertionType, assumeFailedAssertion=false) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(AtLeast(t1, IntLiteral(0)), dAInfo, assumeFailedAssertion=false) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { - v1.decider.assume(AtLeast(t1, IntLiteral(0)), assertExp1, assertExp1New, AssumptionType.Explicit) + v1.decider.assume(AtLeast(t1, IntLiteral(0)), assertExp1, assertExp1New, dAInfo.withDependencyType(DependencyType.Explicit)) val assertExp2 = Option.when(withExp)(ast.LtCmp(e1, ast.SeqLength(e0)())(e1.pos, e1.info, e1.errT)) val assertExp2New = Option.when(withExp)(ast.LtCmp(esNew.get(1), ast.SeqLength(esNew.get(0))())(e1.pos, e1.info, e1.errT)) - v1.decider.assert(Less(t1, SeqLength(t0))) { + v1.decider.assert(Less(t1, SeqLength(t0)), dAInfo) { case true => failure1 combine Q(s1, SeqAt(t0, t1), eNew, v1) case false => val failure2 = failure1 combine createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, Less(t1, SeqLength(t0)), assertExp2New) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(Less(t1, SeqLength(t0)), v1.decider.analysisSourceInfoStack.getAssertionType, assumeFailedAssertion=false) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(Less(t1, SeqLength(t0)), dAInfo, assumeFailedAssertion=false) if (v1.reportFurtherErrors()) { - v1.decider.assume(Less(t1, SeqLength(t0)), assertExp2, assertExp2New, AssumptionType.Explicit) + v1.decider.assume(Less(t1, SeqLength(t0)), assertExp2, assertExp2New, dAInfo.withDependencyType(DependencyType.Explicit)) failure2 combine Q(s1, SeqAt(t0, t1), eNew, v1) } else failure2} } else failure1}}}) - case ast.SeqAppend(e0, e1) => evalBinOp(s, e0, e1, SeqAppend, pve, v)((s1, t, e0New, e1New, v1) => + case ast.SeqAppend(e0, e1) => evalBinOp(s, e0, e1, SeqAppend, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.SeqAppend(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) - case ast.SeqDrop(e0, e1) => evalBinOp(s, e0, e1, SeqDrop, pve, v)((s1, t, e0New, e1New, v1) => + case ast.SeqDrop(e0, e1) => evalBinOp(s, e0, e1, SeqDrop, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.SeqDrop(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) - case ast.SeqTake(e0, e1) => evalBinOp(s, e0, e1, SeqTake, pve, v)((s1, t, e0New, e1New, v1) => + case ast.SeqTake(e0, e1) => evalBinOp(s, e0, e1, SeqTake, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.SeqTake(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) - case ast.SeqLength(e0) => eval(s, e0, pve, v)((s1, t0, e0New, v1) => + case ast.SeqLength(e0) => eval(s, e0, pve, v, dAInfo)((s1, t0, e0New, v1) => Q(s1, SeqLength(t0), e0New.map(e0p => ast.SeqLength(e0p)(e.pos, e.info, e.errT)), v1)) case ast.EmptySeq(typ) => Q(s, SeqNil(v.symbolConverter.toSort(typ)), Option.when(withExp)(e), v) - case ast.RangeSeq(e0, e1) => evalBinOp(s, e0, e1, SeqRanged, pve, v)((s1, t, e0New, e1New, v1) => + case ast.RangeSeq(e0, e1) => evalBinOp(s, e0, e1, SeqRanged, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.RangeSeq(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case ast.SeqUpdate(e0, e1, e2) => - evals2(s, Seq(e0, e1, e2), Nil, _ => pve, v)({ case (s1, Seq(t0, t1, t2), esNew, v1) => + evals2(s, Seq(e0, e1, e2), Nil, _ => pve, v, dAInfo)({ case (s1, Seq(t0, t1, t2), esNew, v1) => val eNew = esNew.map(es => ast.SeqUpdate(es.head, es(1), es(2))(e.pos, e.info, e.errT)) if (s1.triggerExp) { Q(s1, SeqUpdate(t0, t1, t2), eNew, v1) } else { val assertExp = Option.when(withExp)(ast.GeCmp(e1, ast.IntLit(0)())(e1.pos, e1.info, e1.errT)) val assertExpNew = Option.when(withExp)(ast.GeCmp(esNew.get(1), ast.IntLit(0)())(e1.pos, e1.info, e1.errT)) - v1.decider.assert(AtLeast(t1, IntLiteral(0))) { + v1.decider.assert(AtLeast(t1, IntLiteral(0)), dAInfo) { case true => val assertExp2New = Option.when(withExp)(ast.LtCmp(esNew.get(1), ast.SeqLength(esNew.get(0))())(e1.pos, e1.info, e1.errT)) - v1.decider.assert(Less(t1, SeqLength(t0))) { + v1.decider.assert(Less(t1, SeqLength(t0)), dAInfo) { case true => Q(s1, SeqUpdate(t0, t1, t2), eNew, v1) case false => val failure = createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, Less(t1, SeqLength(t0)), assertExp2New) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(Less(t1, SeqLength(t0)), v1.decider.analysisSourceInfoStack.getAssertionType, assumeFailedAssertion=false) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(Less(t1, SeqLength(t0)), dAInfo, assumeFailedAssertion=false) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { val assertExp3 = Option.when(withExp)(ast.LeCmp(e1, ast.SeqLength(e0)())()) val assertExp3New = Option.when(withExp)(ast.LeCmp(esNew.get(1), ast.SeqLength(esNew.get(0))())()) - v1.decider.assume(Less(t1, SeqLength(t0)), assertExp3, assertExp3New, AssumptionType.Explicit) + v1.decider.assume(Less(t1, SeqLength(t0)), assertExp3, assertExp3New, dAInfo.withDependencyType(DependencyType.Explicit)) failure combine Q(s1, SeqUpdate(t0, t1, t2), eNew, v1)} else failure} case false => val failure1 = createFailure(pve dueTo SeqIndexNegative(e0, e1), v1, s1, AtLeast(t1, IntLiteral(0)), assertExpNew) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(AtLeast(t1, IntLiteral(0)), v1.decider.analysisSourceInfoStack.getAssertionType, assumeFailedAssertion=false) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(AtLeast(t1, IntLiteral(0)), dAInfo, assumeFailedAssertion=false) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { - v1.decider.assume(AtLeast(t1, IntLiteral(0)), assertExp, assertExpNew, AssumptionType.Explicit) + v1.decider.assume(AtLeast(t1, IntLiteral(0)), assertExp, assertExpNew, dAInfo.withDependencyType(DependencyType.Explicit)) val assertExp2 = Option.when(withExp)(ast.LtCmp(e1, ast.SeqLength(e0)())(e1.pos, e1.info, e1.errT)) val assertExp2New = Option.when(withExp)(ast.LtCmp(esNew.get(1), ast.SeqLength(esNew.get(0))())(e1.pos, e1.info, e1.errT)) - v1.decider.assert(Less(t1, SeqLength(t0))) { + v1.decider.assert(Less(t1, SeqLength(t0)), dAInfo) { case true => failure1 combine Q(s1, SeqUpdate(t0, t1, t2), eNew, v1) case false => val failure2 = failure1 combine createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, Less(t1, SeqLength(t0)), assertExp2New) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(Less(t1, SeqLength(t0)), v1.decider.analysisSourceInfoStack.getAssertionType, assumeFailedAssertion=false) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(Less(t1, SeqLength(t0)), dAInfo, assumeFailedAssertion=false) if (v1.reportFurtherErrors()) { - v1.decider.assume(Less(t1, SeqLength(t0)), assertExp2, assertExp2New, AssumptionType.Explicit) + v1.decider.assume(Less(t1, SeqLength(t0)), assertExp2, assertExp2New, dAInfo.withDependencyType(DependencyType.Explicit)) failure2 combine Q(s1, SeqUpdate(t0, t1, t2), eNew, v1) } else failure2} } else failure1}}}) case seq@ast.ExplicitSeq(es) => - evals2(s, es, Nil, _ => pve, v)((s1, tEs, esNew, v1) => { + evals2(s, es, Nil, _ => pve, v, dAInfo)((s1, tEs, esNew, v1) => { val tSeq = tEs.tail.foldLeft[SeqTerm](SeqSingleton(tEs.head))((tSeq, te) => SeqAppend(tSeq, SeqSingleton(te))) @@ -928,7 +930,7 @@ object evaluator extends EvaluationRules { val exp = ast.EqCmp(ast.SeqLength(seq)(), ast.IntLit(es.size)())(seq.pos, seq.info, seq.errT) DebugExp.createInstance(exp, expNew) }) - v1.decider.assume(SeqLength(tSeq) === IntLiteral(es.size), debugExp, v1.decider.analysisSourceInfoStack.getAssumptionType) + v1.decider.assume(SeqLength(tSeq) === IntLiteral(es.size), debugExp, dAInfo) Q(s1, tSeq, esNew.map(en => ast.ExplicitSeq(en)(e.pos, e.info, e.errT)), v1)}) /* Sets and multisets */ @@ -939,68 +941,68 @@ object evaluator extends EvaluationRules { Q(s, EmptyMultiset(v.symbolConverter.toSort(typ)), Option.when(withExp)(e), v) case ast.ExplicitSet(es) => - evals2(s, es, Nil, _ => pve, v)((s1, tEs, esNew, v1) => { + evals2(s, es, Nil, _ => pve, v, dAInfo)((s1, tEs, esNew, v1) => { val tSet = tEs.tail.foldLeft[SetTerm](SingletonSet(tEs.head))((tSet, te) => SetAdd(tSet, te)) Q(s1, tSet, esNew.map(es => ast.ExplicitSet(es)(e.pos, e.info, e.errT)), v1)}) case ast.ExplicitMultiset(es) => - evals2(s, es, Nil, _ => pve, v)((s1, tEs, esNew, v1) => { + evals2(s, es, Nil, _ => pve, v, dAInfo)((s1, tEs, esNew, v1) => { val tMultiset = tEs.tail.foldLeft[MultisetTerm](SingletonMultiset(tEs.head))((tMultiset, te) => MultisetAdd(tMultiset, te)) Q(s1, tMultiset, esNew.map(es => ast.ExplicitMultiset(es)(e.pos, e.info, e.errT)), v1)}) case ast.AnySetUnion(e0, e1) => e.typ match { - case _: ast.SetType => evalBinOp(s, e0, e1, SetUnion, pve, v)((s1, t, e0New, e1New, v1) + case _: ast.SetType => evalBinOp(s, e0, e1, SetUnion, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.AnySetUnion(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) - case _: ast.MultisetType => evalBinOp(s, e0, e1, MultisetUnion, pve, v)((s1, t, e0New, e1New, v1) + case _: ast.MultisetType => evalBinOp(s, e0, e1, MultisetUnion, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.AnySetUnion(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case _ => sys.error("Expected a (multi)set-typed expression but found %s (%s) of sort %s" .format(e, e.getClass.getName, e.typ)) } case ast.AnySetIntersection(e0, e1) => e.typ match { - case _: ast.SetType => evalBinOp(s, e0, e1, SetIntersection, pve, v)((s1, t, e0New, e1New, v1) + case _: ast.SetType => evalBinOp(s, e0, e1, SetIntersection, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.AnySetIntersection(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) - case _: ast.MultisetType => evalBinOp(s, e0, e1, MultisetIntersection, pve, v)((s1, t, e0New, e1New, v1) + case _: ast.MultisetType => evalBinOp(s, e0, e1, MultisetIntersection, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.AnySetIntersection(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case _ => sys.error("Expected a (multi)set-typed expression but found %s (%s) of sort %s" .format(e, e.getClass.getName, e.typ)) } case ast.AnySetSubset(e0, e1) => e0.typ match { - case _: ast.SetType => evalBinOp(s, e0, e1, SetSubset, pve, v)((s1, t, e0New, e1New, v1) + case _: ast.SetType => evalBinOp(s, e0, e1, SetSubset, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.AnySetSubset(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) - case _: ast.MultisetType => evalBinOp(s, e0, e1, MultisetSubset, pve, v)((s1, t, e0New, e1New, v1) + case _: ast.MultisetType => evalBinOp(s, e0, e1, MultisetSubset, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.AnySetSubset(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case _ => sys.error("Expected a (multi)set-typed expression but found %s (%s) of sort %s" .format(e, e.getClass.getName, e.typ)) } case ast.AnySetMinus(e0, e1) => e.typ match { - case _: ast.SetType => evalBinOp(s, e0, e1, SetDifference, pve, v)((s1, t, e0New, e1New, v1) + case _: ast.SetType => evalBinOp(s, e0, e1, SetDifference, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.AnySetMinus(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) - case _: ast.MultisetType => evalBinOp(s, e0, e1, MultisetDifference, pve, v)((s1, t, e0New, e1New, v1) + case _: ast.MultisetType => evalBinOp(s, e0, e1, MultisetDifference, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.AnySetMinus(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case _ => sys.error("Expected a (multi)set-typed expression but found %s (%s) of sort %s" .format(e, e.getClass.getName, e.typ)) } case ast.AnySetContains(e0, e1) => e1.typ match { - case _: ast.SetType => evalBinOp(s, e0, e1, SetIn, pve, v)((s1, t, e0New, e1New, v1) + case _: ast.SetType => evalBinOp(s, e0, e1, SetIn, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.AnySetContains(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) - case _: ast.MultisetType => evalBinOp(s, e0, e1, (t0, t1) => MultisetCount(t1, t0), pve, v)((s1, t, e0New, e1New, v1) + case _: ast.MultisetType => evalBinOp(s, e0, e1, (t0, t1) => MultisetCount(t1, t0), pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.AnySetContains(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case _ => sys.error("Expected a (multi)set-typed expression but found %s (%s) of sort %s" .format(e, e.getClass.getName, e.typ)) } case ast.AnySetCardinality(e0) => e0.typ match { - case _: ast.SetType => eval(s, e0, pve, v)((s1, t0, e0New, v1) + case _: ast.SetType => eval(s, e0, pve, v, dAInfo)((s1, t0, e0New, v1) => Q(s1, SetCardinality(t0), e0New.map(e0p => ast.AnySetCardinality(e0p)(e.pos, e.info, e.errT)), v1)) - case _: ast.MultisetType => eval(s, e0, pve, v)((s1, t0, e0New, v1) + case _: ast.MultisetType => eval(s, e0, pve, v, dAInfo)((s1, t0, e0New, v1) => Q(s1, MultisetCardinality(t0), e0New.map(e0p => ast.AnySetCardinality(e0p)(e.pos, e.info, e.errT)), v1)) case _ => sys.error("Expected a (multi)set-typed expression but found %s (%s) of type %s" .format(e0, e0.getClass.getName, e0.typ)) @@ -1011,29 +1013,29 @@ object evaluator extends EvaluationRules { case ast.EmptyMap(keyType, valueType) => Q(s, EmptyMap(v.symbolConverter.toSort(keyType), v.symbolConverter.toSort(valueType)), Option.when(withExp)(e), v) case em: ast.ExplicitMap => - eval(s, em.desugared, pve, v)((s1, t0, _, v1) => Q(s1, t0, Option.when(withExp)(em), v1)) + eval(s, em.desugared, pve, v, dAInfo)((s1, t0, _, v1) => Q(s1, t0, Option.when(withExp)(em), v1)) case ast.MapCardinality(base) => - eval(s, base, pve, v)((s1, t0, baseNew, v1) => Q(s1, MapCardinality(t0), baseNew.map(ast.MapCardinality(_)(e.pos, e.info, e.errT)), v1)) + eval(s, base, pve, v, dAInfo)((s1, t0, baseNew, v1) => Q(s1, MapCardinality(t0), baseNew.map(ast.MapCardinality(_)(e.pos, e.info, e.errT)), v1)) case ast.MapDomain(base) => - eval(s, base, pve, v)((s1, t0, baseNew, v1) => Q(s1, MapDomain(t0), baseNew.map(ast.MapDomain(_)(e.pos, e.info, e.errT)), v1)) + eval(s, base, pve, v, dAInfo)((s1, t0, baseNew, v1) => Q(s1, MapDomain(t0), baseNew.map(ast.MapDomain(_)(e.pos, e.info, e.errT)), v1)) case ast.MapRange(base) => - eval(s, base, pve, v)((s1, t0, baseNew, v1) => Q(s1, MapRange(t0), baseNew.map(ast.MapRange(_)(e.pos, e.info, e.errT)), v1)) + eval(s, base, pve, v, dAInfo)((s1, t0, baseNew, v1) => Q(s1, MapRange(t0), baseNew.map(ast.MapRange(_)(e.pos, e.info, e.errT)), v1)) case ml@ast.MapLookup(base, key) => - evals2(s, Seq(base, key), Nil, _ => pve, v)({ + evals2(s, Seq(base, key), Nil, _ => pve, v, dAInfo)({ case (s1, Seq(baseT, keyT), esNew, v1) if s1.triggerExp => Q(s1, MapLookup(baseT, keyT), esNew.map(es => ast.MapLookup(es(0), es(1))(e.pos, e.info, e.errT)), v1) case (s1, Seq(baseT, keyT), esNew, v1) => val eNew = esNew.map(es => ast.MapLookup(es(0), es(1))(e.pos, e.info, e.errT)) - v1.decider.assert(SetIn(keyT, MapDomain(baseT))) { + v1.decider.assert(SetIn(keyT, MapDomain(baseT)), dAInfo) { case true => Q(s1, MapLookup(baseT, keyT), eNew, v1) case false => val assertExp = Option.when(withExp)(ast.MapContains(key, base)(ml.pos, ml.info, ml.errT)) val assertExpNew = Option.when(withExp)(ast.MapContains(esNew.get(1), esNew.get(0))(ml.pos, ml.info, ml.errT)) val failure1 = createFailure(pve dueTo MapKeyNotContained(base, key), v1, s1, SetIn(keyT, MapDomain(baseT)), assertExpNew) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(SetIn(keyT, MapDomain(baseT)), v1.decider.analysisSourceInfoStack.getAssertionType, assumeFailedAssertion=false) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(SetIn(keyT, MapDomain(baseT)), dAInfo, assumeFailedAssertion=false) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { - v1.decider.assume(SetIn(keyT, MapDomain(baseT)), assertExp, assertExpNew, AssumptionType.Explicit) + v1.decider.assume(SetIn(keyT, MapDomain(baseT)), assertExp, assertExpNew, dAInfo.withDependencyType(DependencyType.Explicit)) failure1 combine Q(s1, MapLookup(baseT, keyT), eNew, v1) } else { failure1 @@ -1042,13 +1044,13 @@ object evaluator extends EvaluationRules { }) case ast.MapUpdate(base, key, value) => - evals2(s, Seq(base, key, value), Nil, _ => pve, v)({ + evals2(s, Seq(base, key, value), Nil, _ => pve, v, dAInfo)({ case (s1, Seq(baseT, keyT, valueT), esNew, v1) => Q(s1, MapUpdate(baseT, keyT, valueT), esNew.map(es => ast.MapUpdate(es(0), es(1), es(2))(e.pos, e.info, e.errT)), v1) }) case ast.MapContains(key, base) => - evals2(s, Seq(key, base), Nil, _ => pve, v)({ + evals2(s, Seq(key, base), Nil, _ => pve, v, dAInfo)({ case (s1, Seq(keyT, baseT), esNew, v1) => Q(s1, SetIn(keyT, MapDomain(baseT)), esNew.map(es => ast.MapContains(es(0), es(1))(e.pos, e.info, e.errT)), v1) }) @@ -1078,7 +1080,8 @@ object evaluator extends EvaluationRules { optTriggers: Option[Seq[ast.Trigger]], name: String, pve: PartialVerificationError, - v: Verifier) + v: Verifier, + dAInfo: DependencyAnalysisInfo) (Q: (State, Seq[Var], /* Variables from vars */ Option[Seq[ast.LocalVarDecl]], @@ -1106,18 +1109,18 @@ object evaluator extends EvaluationRules { type R = (State, Seq[Term], Option[Seq[ast.Exp]], Option[(Seq[Term], Option[Seq[ast.Exp]], Seq[Trigger], (Seq[Term], Seq[Quantification]), Option[(InsertionOrderedSet[DebugExp], InsertionOrderedSet[DebugExp])], Map[ast.Exp, Term])]) executionFlowController.locallyWithResult[R](s1, v)((s2, v1, QB) => { val preMark = v1.decider.setPathConditionMark() - evals(s2, es1, _ => pve, v1)((s3, ts1, es1New, v2) => { + evals(s2, es1, _ => pve, v1, dAInfo)((s3, ts1, es1New, v2) => { val bc = And(ts1) // ME: If bc is unsatisfiable, we are assuming false here. In that case, evaluating es2 and the triggers // may not return any value (e.g. if es2 contains a field read for which we don't have permission, a smoke // check succeeds, then the continuation for evals(es2) is never invoked). This caused issue #842. // In this case, we return None. val expPair = (viper.silicon.utils.ast.BigAnd(es1), es1New.map(viper.silicon.utils.ast.BigAnd(_))) - v2.decider.setCurrentBranchCondition(bc, expPair, v2.decider.analysisSourceInfoStack.getAssumptionType) + v2.decider.setCurrentBranchCondition(bc, expPair, dAInfo) var es2AndTriggerTerms: Option[(Seq[Term], Option[Seq[ast.Exp]], Seq[Trigger], (Seq[Term], Seq[Quantification]), Option[(InsertionOrderedSet[DebugExp], InsertionOrderedSet[DebugExp])], Map[ast.Exp, Term])] = None var finalState = s3 - val es2AndTriggerResult = evals(s3, es2, _ => pve, v2)((s4, ts2, es2New, v3) => { - evalTriggers(s4, optTriggers.getOrElse(Nil), pve, v3)((s5, tTriggers, _) => { // TODO: v4 isn't forward - problem? + val es2AndTriggerResult = evals(s3, es2, _ => pve, v2, dAInfo)((s4, ts2, es2New, v3) => { + evalTriggers(s4, optTriggers.getOrElse(Nil), pve, v3, dAInfo)((s5, tTriggers, _) => { // TODO: v4 isn't forward - problem? val (auxGlobals, auxNonGlobalQuants) = v3.decider.pcs.after(preMark).quantified(quant, tVars, tTriggers, s"$name-aux", isGlobal = false, bc) val auxExps = @@ -1146,13 +1149,14 @@ object evaluator extends EvaluationRules { eRhs: ast.Exp, fromShortCircuitingAnd: Boolean, pve: PartialVerificationError, - v: Verifier) + v: Verifier, + dAInfo: DependencyAnalysisInfo) (Q: (State, Term, Option[ast.Exp], Verifier) => VerificationResult) : VerificationResult = { - joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s, v, v.decider.analysisSourceInfoStack.getAssumptionType)((s1, v1, QB) => - brancher.branch(s1.copy(parallelizeBranches = false), tLhs, eLhs, v1, AssumptionType.Internal, fromShortCircuitingAnd = fromShortCircuitingAnd)( - (s2, v2) => eval(s2.copy(parallelizeBranches = s1.parallelizeBranches), eRhs, pve, v2)((s2, tRhs, eRhsNew, v2) => QB(s2, (tRhs, eRhsNew), v2)), + joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s, v, dAInfo)((s1, v1, QB) => + brancher.branch(s1.copy(parallelizeBranches = false), tLhs, eLhs, v1, dAInfo.withDependencyType(DependencyType.Internal), fromShortCircuitingAnd = fromShortCircuitingAnd)( + (s2, v2) => eval(s2.copy(parallelizeBranches = s1.parallelizeBranches), eRhs, pve, v2, dAInfo)((s2, tRhs, eRhsNew, v2) => QB(s2, (tRhs, eRhsNew), v2)), (s2, v2) => QB(s2.copy(parallelizeBranches = s1.parallelizeBranches), (True, Option.when(withExp)(ast.TrueLit()())), v2)) )(entries => { assert(entries.length <= 2) @@ -1170,7 +1174,8 @@ object evaluator extends EvaluationRules { label: String, e: ast.Exp, pve: PartialVerificationError, - v: Verifier) + v: Verifier, + dAInfo: DependencyAnalysisInfo) (Q: (State, Term, Option[ast.Exp], Verifier) => VerificationResult) : VerificationResult = { @@ -1179,7 +1184,7 @@ object evaluator extends EvaluationRules { val s2 = v.stateConsolidator(s1).consolidateOptionally(s1, v) val possibleTriggersBefore: Map[ast.Exp, Term] = if (s.recordPossibleTriggers) s.possibleTriggers else Map.empty - eval(s2, e, pve, v)((s3, t, eNew, v1) => { + eval(s2, e, pve, v, dAInfo)((s3, t, eNew, v1) => { val newPossibleTriggers = if (s.recordPossibleTriggers) { // For all new possible trigger expressions e and translated term t, // make sure we remember t as the term for old[label](e) instead. @@ -1209,10 +1214,10 @@ object evaluator extends EvaluationRules { Q(s4, t, eNew, v1)}) } - def evalResourceAccess(s: State, resacc: ast.ResourceAccess, pve: PartialVerificationError, v: Verifier) + def evalResourceAccess(s: State, resacc: ast.ResourceAccess, pve: PartialVerificationError, v: Verifier, dAInfo: DependencyAnalysisInfo) (Q: (State, ChunkIdentifer, Seq[Term], Option[Seq[ast.Exp]], Verifier) => VerificationResult) : VerificationResult = { - evals(s, resacc.args(s.program), _ => pve, v)((s1, tArgs, eArgsNew, v1) => + evals(s, resacc.args(s.program), _ => pve, v, dAInfo)((s1, tArgs, eArgsNew, v1) => Q(s1, ChunkIdentifier(resacc.res(s1.program), s1.program), tArgs, eArgsNew, v1)) } @@ -1221,11 +1226,12 @@ object evaluator extends EvaluationRules { e1: ast.Exp, termOp: ((Term, Term)) => T, pve: PartialVerificationError, - v: Verifier) + v: Verifier, + dAInfo: DependencyAnalysisInfo) (Q: (State, T, Option[ast.Exp], Option[ast.Exp], Verifier) => VerificationResult) : VerificationResult = { - evalBinOp(s, e0, e1, (t0, t1) => termOp((t0, t1)), pve, v)(Q) + evalBinOp(s, e0, e1, (t0, t1) => termOp((t0, t1)), pve, v, dAInfo)(Q) } private def evalBinOp[T <: Term] @@ -1234,12 +1240,13 @@ object evaluator extends EvaluationRules { e1: ast.Exp, termOp: (Term, Term) => T, pve: PartialVerificationError, - v: Verifier) + v: Verifier, + dAInfo: DependencyAnalysisInfo) (Q: (State, T, Option[ast.Exp], Option[ast.Exp], Verifier) => VerificationResult) : VerificationResult = { - eval(s, e0, pve, v)((s1, t0, e0New, v1) => - eval(s1, e1, pve, v1)((s2, t1, e1New, v2) => + eval(s, e0, pve, v, dAInfo)((s1, t0, e0New, v1) => + eval(s1, e1, pve, v1, dAInfo)((s2, t1, e1New, v2) => Q(s2, termOp(t0, t1), e0New, e1New, v2))) } @@ -1250,20 +1257,21 @@ object evaluator extends EvaluationRules { tDivisor: Term, tZero: Term, pve: PartialVerificationError, - v: Verifier) + v: Verifier, + dAInfo: DependencyAnalysisInfo) (Q: (State, Term, Verifier) => VerificationResult) : VerificationResult = { - v.decider.assert(tDivisor !== tZero){ + v.decider.assert(tDivisor !== tZero, dAInfo){ case true => Q(s, t, v) case false => val (notZeroExp, notZeroExpNew) = if (withExp) { (Some(ast.NeCmp(eDivisor, ast.IntLit(0)())(eDivisor.pos, eDivisor.info, eDivisor.errT)), Some(ast.NeCmp(eDivisorNew.get, ast.IntLit(0)())(eDivisor.pos, eDivisor.info, eDivisor.errT))) } else { (None, None) } val failure = createFailure(pve dueTo DivisionByZero(eDivisor), v, s, tDivisor !== tZero, notZeroExpNew) - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(tDivisor !== tZero, v.decider.analysisSourceInfoStack.getAssertionType, assumeFailedAssertion=false) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(tDivisor !== tZero, dAInfo, assumeFailedAssertion=false) if (s.retryLevel == 0 && v.reportFurtherErrors()) { - v.decider.assume(tDivisor !== tZero, notZeroExp, notZeroExpNew, AssumptionType.Explicit) + v.decider.assume(tDivisor !== tZero, notZeroExp, notZeroExpNew, dAInfo.withDependencyType(DependencyType.Explicit)) failure combine Q(s, t, v) } else failure } @@ -1272,11 +1280,12 @@ object evaluator extends EvaluationRules { def evalTriggers(s: State, silverTriggers: Seq[ast.Trigger], pve: PartialVerificationError, - v: Verifier) + v: Verifier, + dAInfo: DependencyAnalysisInfo) (Q: (State, Seq[Trigger], Verifier) => VerificationResult) : VerificationResult = { - evalTriggers(s, silverTriggers map (_.exps), Nil, pve, v)((s1, tTriggersSets, v1) => { + evalTriggers(s, silverTriggers map (_.exps), Nil, pve, v, dAInfo)((s1, tTriggersSets, v1) => { /* [2015-12-15 Malte] * Evaluating triggers that did not occur in the body (and whose corresponding term has * therefore not already been recorded in the context) might introduce new path conditions, @@ -1304,7 +1313,8 @@ object evaluator extends EvaluationRules { eTriggerSets: TriggerSets[ast.Exp], tTriggersSets: TriggerSets[Term], pve: PartialVerificationError, - v: Verifier) + v: Verifier, + dAInfo: DependencyAnalysisInfo) (Q: (State, TriggerSets[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -1312,15 +1322,15 @@ object evaluator extends EvaluationRules { Q(s, tTriggersSets, v) else { if (eTriggerSets.head.collect{case fa: ast.FieldAccess => fa; case pa: ast.PredicateAccess => pa; case wand: ast.MagicWand => wand }.nonEmpty ) { - evalHeapTrigger(s, eTriggerSets.head, pve, v)((s1, ts, v1) => - evalTriggers(s1, eTriggerSets.tail, tTriggersSets :+ ts, pve, v1)(Q)) + evalHeapTrigger(s, eTriggerSets.head, pve, v, dAInfo)((s1, ts, v1) => + evalTriggers(s1, eTriggerSets.tail, tTriggersSets :+ ts, pve, v1, dAInfo)(Q)) } else { - evalTrigger(s, eTriggerSets.head, pve, v)((s1, ts, v1) => - evalTriggers(s1, eTriggerSets.tail, tTriggersSets :+ ts, pve, v1)(Q)) + evalTrigger(s, eTriggerSets.head, pve, v, dAInfo)((s1, ts, v1) => + evalTriggers(s1, eTriggerSets.tail, tTriggersSets :+ ts, pve, v1, dAInfo)(Q)) }} } - private def evalTrigger(s: State, exps: Seq[ast.Exp], pve: PartialVerificationError, v: Verifier) + private def evalTrigger(s: State, exps: Seq[ast.Exp], pve: PartialVerificationError, v: Verifier, dAInfo: DependencyAnalysisInfo) (Q: (State, Seq[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -1392,7 +1402,7 @@ object evaluator extends EvaluationRules { */ val r = - evals(s.copy(triggerExp = true), remainingTriggerExpressions, _ => pve, v)((_, remainingTriggerTerms, _, v1) => { + evals(s.copy(triggerExp = true), remainingTriggerExpressions, _ => pve, v, dAInfo)((_, remainingTriggerTerms, _, v1) => { optRemainingTriggerTerms = Some(remainingTriggerTerms) pcDelta = v1.decider.pcs.after(preMark).assumptions //decider.π -- πPre pcDeltaExp = v1.decider.pcs.after(preMark).assumptionExps @@ -1408,7 +1418,7 @@ object evaluator extends EvaluationRules { (r, optRemainingTriggerTerms) match { case (Success(), Some(remainingTriggerTerms)) => // TODO ake: wrap pcDelta with labels? - v.decider.assume(pcDelta, Option.when(withExp)(DebugExp.createInstance("pcDeltaExp", children = pcDeltaExp)), enforceAssumption = false, assumptionType=AssumptionType.Internal) + v.decider.assume(pcDelta, Option.when(withExp)(DebugExp.createInstance("pcDeltaExp", children = pcDeltaExp)), enforceAssumption = false, dAInfo.withDependencyType(DependencyType.Internal)) Q(s, cachedTriggerTerms ++ remainingTriggerTerms, v) case _ => for (e <- remainingTriggerExpressions) @@ -1422,7 +1432,8 @@ object evaluator extends EvaluationRules { joinFunctionName: String, joinFunctionArgs: Seq[Term], joinFunctionArgsExp: Option[Seq[ast.Exp]], - v: Verifier) + v: Verifier, + dAInfo: DependencyAnalysisInfo) (entries: Seq[JoinDataEntry[(Term, Option[ast.Exp])]]) : (State, (Term, Option[ast.Exp])) = { @@ -1457,13 +1468,13 @@ object evaluator extends EvaluationRules { var sJoined = entries.tail.foldLeft(entries.head.s)((sAcc, entry) => sAcc.merge(entry.s)) sJoined = sJoined.copy(functionRecorder = sJoined.functionRecorder.recordPathSymbol(joinSymbol)) - joinDefEqs foreach { case (t, exp, expNew) => v.decider.assume(t, exp, expNew, AssumptionType.Internal)} + joinDefEqs foreach { case (t, exp, expNew) => v.decider.assume(t, exp, expNew, dAInfo.withDependencyType(DependencyType.Internal))} (sJoined, (joinTerm, joinExp)) } } - def evalHeapTrigger(s: State, exps: Seq[ast.Exp], pve: PartialVerificationError, v: Verifier) + def evalHeapTrigger(s: State, exps: Seq[ast.Exp], pve: PartialVerificationError, v: Verifier, dAInfo: DependencyAnalysisInfo) (Q: (State, Seq[Term], Verifier) => VerificationResult) : VerificationResult = { var triggers: Seq[Term] = Seq() var triggerAxioms: Seq[Term] = Seq() @@ -1471,18 +1482,18 @@ object evaluator extends EvaluationRules { exps foreach { case ra: ast.ResourceAccess if s.isUsedAsTrigger(ra.res(s.program)) => - val (axioms, trigs, _, smDef) = generateResourceTrigger(ra, s, pve, v) + val (axioms, trigs, _, smDef) = generateResourceTrigger(ra, s, pve, v, dAInfo) triggers = triggers ++ trigs triggerAxioms = triggerAxioms ++ axioms smDefs = smDefs ++ smDef - case e => evalTrigger(s.copy(triggerExp = true), Seq(e), pve, v)((_, t, _) => { + case e => evalTrigger(s.copy(triggerExp = true), Seq(e), pve, v, dAInfo)((_, t, _) => { triggers = triggers ++ t Success() }) } val triggerString = exps.mkString(", ") - v.decider.assume(triggerAxioms, Option.when(withExp)(DebugExp.createInstance(s"Heap Triggers ($triggerString)")), enforceAssumption = false, assumptionType=AssumptionType.Trigger) + v.decider.assume(triggerAxioms, Option.when(withExp)(DebugExp.createInstance(s"Heap Triggers ($triggerString)")), enforceAssumption = false, dAInfo.withDependencyType(DependencyType.Trigger)) var fr = s.functionRecorder for (smDef <- smDefs){ fr = fr.recordFvfAndDomain(smDef) @@ -1493,7 +1504,8 @@ object evaluator extends EvaluationRules { private def generateResourceTrigger(ra: ast.ResourceAccess, s: State, pve: PartialVerificationError, - v: Verifier) + v: Verifier, + dAInfo: DependencyAnalysisInfo) : (Seq[Term], Seq[Term], Term, Seq[SnapshotMapDefinition]) = { var axioms = Seq.empty[Term] var triggers = Seq.empty[Term] @@ -1514,7 +1526,7 @@ object evaluator extends EvaluationRules { s, resource, codomainQVars, relevantChunks, v, optSmDomainDefinitionCondition) val s1 = s.copy(smCache = smCache1) - evals(s1.copy(triggerExp = true), eArgs, _ => pve, v)((_, tArgs, _, _) => { + evals(s1.copy(triggerExp = true), eArgs, _ => pve, v, dAInfo)((_, tArgs, _, _) => { axioms = axioms ++ smDef1.valueDefinitions mostRecentTrig = ResourceTriggerFunction(resource, smDef1.sm, tArgs, s.program) triggers = triggers :+ mostRecentTrig @@ -1532,7 +1544,8 @@ object evaluator extends EvaluationRules { s: State, exps: Seq[ast.Exp], pve: PartialVerificationError, - v: Verifier) + v: Verifier, + dAInfo: DependencyAnalysisInfo) (Q: (State, Term, Option[ast.Exp], Verifier) => VerificationResult) : VerificationResult = { assert( @@ -1543,16 +1556,16 @@ object evaluator extends EvaluationRules { val stop = if (constructor == Or) True else False - eval(s, exps.head, pve, v)((s1, t0, e0New, v1) => { + eval(s, exps.head, pve, v, dAInfo)((s1, t0, e0New, v1) => { t0 match { case _ if exps.tail.isEmpty => Q(s1, t0, e0New, v1) // Done, if no expressions left (necessary) case `stop` => Q(s1, t0, e0New, v1) // Done, if last expression was true/false for or/and (optimisation) case _ => val expPair = if (constructor == Or) (exps.head, e0New) else (ast.Not(exps.head)(), e0New.map(ast.Not(_)())) - joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s1, v1, v1.decider.analysisSourceInfoStack.getAssumptionType)((s2, v2, QB) => - brancher.branch(s2.copy(parallelizeBranches = false), if (constructor == Or) t0 else Not(t0), expPair, v2, AssumptionType.Internal, fromShortCircuitingAnd = true)( + joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s1, v1, dAInfo)((s2, v2, QB) => + brancher.branch(s2.copy(parallelizeBranches = false), if (constructor == Or) t0 else Not(t0), expPair, v2, dAInfo.withDependencyType(DependencyType.Internal), fromShortCircuitingAnd = true)( (s3, v3) => QB(s3.copy(parallelizeBranches = s2.parallelizeBranches), (t0, e0New), v3), - (s3, v3) => evalSeqShortCircuit(constructor, s3.copy(parallelizeBranches = s2.parallelizeBranches), exps.tail, pve, v3)((s2, t2, e2, v2) => QB(s2, (t2, e2), v2))) + (s3, v3) => evalSeqShortCircuit(constructor, s3.copy(parallelizeBranches = s2.parallelizeBranches), exps.tail, pve, v3, dAInfo)((s2, t2, e2, v2) => QB(s2, (t2, e2), v2))) ){case Seq(ent) => (ent.s, ent.data) case Seq(ent1, ent2) => diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 3e46277ec..cd36ec2ce 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -11,7 +11,7 @@ import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.decider.RecordedPathConditions import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType -import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, CompositeAnalysisSourceInfo, DependencyAnalyzer, DependencyType, SimpleAssumptionNode} +import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, CompositeAnalysisSourceInfo, DependencyAnalysisInfo, DependencyAnalysisInfoWithoutSource, DependencyAnalyzer, DependencyType, FullDependencyAnalysisInfo, NoDependencyAnalysisInfo, SimpleAssumptionNode} import viper.silicon.interfaces._ import viper.silicon.interfaces.state.{NonQuantifiedChunk, QuantifiedChunk} import viper.silicon.logger.records.data.{CommentRecord, ConditionalEdgeRecord, ExecuteRecord, MethodCallRecord} @@ -68,12 +68,12 @@ object executor extends ExecutionRules { val condEdgeRecord = new ConditionalEdgeRecord(ce.condition, s, v.decider.pcs) val sepIdentifier = v.symbExLog.openScope(condEdgeRecord) val s1 = handleOutEdge(s, edge, v) - val dependencyType = DependencyAnalyzer.extractDependencyTypeFromInfo(ce.condition.info).getOrElse(DependencyType.PathCondition) - eval(s1, ce.condition, IfFailed(ce.condition), v, Some(dependencyType))((s2, tCond, condNew, v1) => + val dAInfo = FullDependencyAnalysisInfo.create(ce.condition) + eval(s1, ce.condition, IfFailed(ce.condition), v, dAInfo)((s2, tCond, condNew, v1) => /* Using branch(...) here ensures that the edge condition is recorded * as a branch condition on the pathcondition stack. */ - brancher.branch(s2.copy(parallelizeBranches = false), tCond, (ce.condition, condNew), v1, dependencyType.assumptionType)( + brancher.branch(s2.copy(parallelizeBranches = false), tCond, (ce.condition, condNew), v1, dAInfo)( (s3, v3) => exec(s3.copy(parallelizeBranches = s2.parallelizeBranches), ce.target, ce.kind, v3, joinPoint)((s4, v4) => { v4.symbExLog.closeScope(sepIdentifier) @@ -94,7 +94,7 @@ object executor extends ExecutionRules { def handleOutEdge(s: State, edge: SilverEdge, v: Verifier): State = { edge.kind match { case cfg.Kind.Out if !v.decider.isPathInfeasible() => - val (fr1, h1) = v.stateConsolidator(s).merge(s.functionRecorder, s, s.h, s.invariantContexts.head, v) + val (fr1, h1) = v.stateConsolidator(s).merge(s.functionRecorder, s, s.h, s.invariantContexts.head, v, dAInfo) val s1 = s.copy(functionRecorder = fr1, h = h1, invariantContexts = s.invariantContexts.tail) s1 @@ -147,12 +147,12 @@ object executor extends ExecutionRules { case _ => false }) - val dependencyType = DependencyAnalyzer.extractDependencyTypeFromInfo(cedge1.condition.info).getOrElse(DependencyType.PathCondition) + val dAInfo = FullDependencyAnalysisInfo.create(cedge1.condition) - eval(s, cedge1.condition, pvef(cedge1.condition), v, Some(dependencyType))((s1, t0, condNew, v1) => + eval(s, cedge1.condition, pvef(cedge1.condition), v, dAInfo)((s1, t0, condNew, v1) => // The type arguments here are Null because there is no need to pass any join data. - joiner.join[scala.Null, scala.Null](s1, v1, dependencyType.assumptionType, resetState = false)((s2, v2, QB) => { - brancher.branch(s2, t0, (cedge1.condition, condNew), v2, dependencyType.assumptionType)( + joiner.join[scala.Null, scala.Null](s1, v1, dAInfo, resetState = false)((s2, v2, QB) => { + brancher.branch(s2, t0, (cedge1.condition, condNew), v2, dAInfo)( // Follow only until join point. (s3, v3) => follow(s3, edge1, v3, Some(newJoinPoint))((s, v) => QB(s, null, v)), (s3, v3) => follow(s3, edge2, v3, Some(newJoinPoint))((s, v) => QB(s, null, v)) @@ -183,9 +183,9 @@ object executor extends ExecutionRules { if Verifier.config.parallelizeBranches() && cond2 == ast.Not(cond1)() => val condEdgeRecord = new ConditionalEdgeRecord(thenEdge.condition, s, v.decider.pcs) val sepIdentifier = v.symbExLog.openScope(condEdgeRecord) - val dependencyType = DependencyAnalyzer.extractDependencyTypeFromInfo(thenEdge.condition.info).getOrElse(DependencyType.PathCondition) - val res = eval(s, thenEdge.condition, IfFailed(thenEdge.condition), v, Some(dependencyType))((s2, tCond, eCondNew, v1) => - brancher.branch(s2, tCond, (thenEdge.condition, eCondNew), v1, dependencyType.assumptionType)( + val dAInfo = FullDependencyAnalysisInfo.create(thenEdge.condition) + val res = eval(s, thenEdge.condition, IfFailed(thenEdge.condition), v, dAInfo)((s2, tCond, eCondNew, v1) => + brancher.branch(s2, tCond, (thenEdge.condition, eCondNew), v1, dAInfo)( (s3, v3) => { follow(s3, thenEdge, v3, joinPoint)(Q) }, @@ -279,7 +279,8 @@ object executor extends ExecutionRules { })}) combine executionFlowController.locally(s, v)((s0, v0) => { v0.decider.prover.comment("Loop head block: Establish invariant") - consumes(s0, invs, false, LoopInvariantNotEstablished, v0, DependencyType.Invariant)((sLeftover, _, v1) => { + val dAInfoInv = DependencyAnalysisInfoWithoutSource(DependencyType.Invariant) // FIXME ake: NoInfo? + consumes(s0, invs, false, LoopInvariantNotEstablished, v0, dAInfoInv)((sLeftover, _, v1) => { v1.decider.prover.comment("Loop head block: Execute statements of loop head block (in invariant state)") phase1data.foldLeft(Success(): VerificationResult) { case (result, _) if !result.continueVerification => result @@ -315,7 +316,8 @@ object executor extends ExecutionRules { * attempting to re-establish the invariant. */ v.decider.prover.comment("Loop head block: Re-establish invariant") - consumes(s, invs, false, e => LoopInvariantNotPreserved(e), v, DependencyType.Invariant)((_, _, _) => + val dAInfoInv = DependencyAnalysisInfoWithoutSource(DependencyType.Invariant) // FIXME ake: NoInfo? + consumes(s, invs, false, e => LoopInvariantNotPreserved(e), v, dAInfoInv)((_, _, _) => Success()) } } @@ -335,27 +337,27 @@ object executor extends ExecutionRules { (Q: (State, Verifier) => VerificationResult) : VerificationResult = { val sepIdentifier = v.symbExLog.openScope(new ExecuteRecord(stmt, s, v.decider.pcs)) - val sourceInfo = v.decider.pushAndGetAnalysisSourceInfo(stmt, Some(DependencyType.get(stmt))) - exec2(s, stmt, v)((s1, v1) => { - v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) + val dAInfo = FullDependencyAnalysisInfo.create(stmt) + exec2(s, stmt, v, dAInfo)((s1, v1) => { v1.symbExLog.closeScope(sepIdentifier) Q(s1, v1) }) } - def exec2(state: State, stmt: ast.Stmt, v: Verifier) + def exec2(state: State, stmt: ast.Stmt, v: Verifier, dAInfo: DependencyAnalysisInfo) (continuation: (State, Verifier) => VerificationResult) : VerificationResult = { if(v.decider.isPathInfeasible()){ - val assertionNodesForJoin = DependencyAnalyzer.extractAssertionsForJoin(stmt, state.program) - assertionNodesForJoin.foreach(n => v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, CompositeAnalysisSourceInfo(v.decider.analysisSourceInfoStack.getFullSourceInfo.getTopLevelSource, AnalysisSourceInfo.createAnalysisSourceInfo(n)), v.decider.analysisSourceInfoStack.getDependencyType, isJoinNode=true)) + // FIXME ake: infeasbile path +// val assertionNodesForJoin = DependencyAnalyzer.extractAssertionsForJoin(stmt, state.program) +// assertionNodesForJoin.foreach(n => v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, CompositeAnalysisSourceInfo(v.decider.analysisSourceInfoStack.getFullSourceInfo.getTopLevelSource, AnalysisSourceInfo.createAnalysisSourceInfo(n)), v.decider.analysisSourceInfoStack.getDependencyType, isJoinNode=true)) if(Statements.hasProofObligations(stmt, state.program)){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType, isJoinNode=false) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, dAInfo) } if(Statements.introducesSmtAssumptions(stmt)){ - v.decider.dependencyAnalyzer.addAssumption(True, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getAssumptionType, isJoinNode=false) + v.decider.dependencyAnalyzer.addAssumption(True, dAInfo) } return continuation(state, v) } @@ -376,14 +378,6 @@ object executor extends ExecutionRules { v.decider.prover.comment(stmt.toString()) } - val annotatedDependencyTypeOpt = DependencyAnalyzer.extractDependencyTypeFromInfo(stmt.info) - - def getDependencyType(defaultType: DependencyType): DependencyType = annotatedDependencyTypeOpt.getOrElse(defaultType) - - def getAssumptionType(defaultType: AssumptionType): AssumptionType = annotatedDependencyTypeOpt.map(_.assumptionType).getOrElse(defaultType) - - def getAssertionType(defaultType: AssumptionType): AssumptionType = annotatedDependencyTypeOpt.map(_.assertionType).getOrElse(defaultType) - val executed = stmt match { case ast.Seqn(stmts, _) => execs(s, stmts, v)(Q) @@ -398,8 +392,8 @@ object executor extends ExecutionRules { Q(s.copy(g = s.g + (x -> (t, newExp))), v) case ass @ ast.LocalVarAssign(x, rhs) => - eval(s, rhs, AssignmentFailed(ass), v)((s1, tRhs, rhsNew, v1) => { - val (t, e) = ssaifyRhs(tRhs, rhs, rhsNew, x.name, x.typ, v, s1, getAssumptionType(AssumptionType.SourceCode)) + eval(s, rhs, AssignmentFailed(ass), v, dAInfo)((s1, tRhs, rhsNew, v1) => { + val (t, e) = ssaifyRhs(tRhs, rhs, rhsNew, x.name, x.typ, v, s1, dAInfo) Q(s1.copy(g = s1.g + (x, (t, e))), v1)}) /* TODO: Encode assignments e1.f := e2 as @@ -411,10 +405,10 @@ object executor extends ExecutionRules { case ass @ ast.FieldAssign(ast.FieldAccess(eRcvr, field), rhs) => assert(!s.exhaleExt) val pve = AssignmentFailed(ass) - eval(s, eRcvr, pve, v)((s1, tRcvr, eRcvrNew, v1) => { - eval(s1, rhs, pve, v1)((s2, tRhs, eRhsNew, v2) => { - val (tSnap, _) = ssaifyRhs(tRhs, rhs, eRhsNew, field.name, field.typ, v2, s2, getAssumptionType(AssumptionType.SourceCode)) - v2.heapSupporter.execFieldAssign(s2, ass, tRcvr, eRcvrNew, tSnap, eRhsNew, pve, v2, getDependencyType(DependencyType.SourceCode))(Q) + eval(s, eRcvr, pve, v, dAInfo)((s1, tRcvr, eRcvrNew, v1) => { + eval(s1, rhs, pve, v1, dAInfo)((s2, tRhs, eRhsNew, v2) => { + val (tSnap, _) = ssaifyRhs(tRhs, rhs, eRhsNew, field.name, field.typ, v2, s2, dAInfo) + v2.heapSupporter.execFieldAssign(s2, ass, tRcvr, eRcvrNew, tSnap, eRhsNew, pve, v2, dAInfo)(Q) }) }) @@ -423,7 +417,7 @@ object executor extends ExecutionRules { val debugExp = Option.when(withExp)(ast.NeCmp(x, ast.NullLit()())()) val debugExpSubst = Option.when(withExp)(ast.NeCmp(eRcvrNew.get, ast.NullLit()())()) val (debugHeapName, debugLabel) = v.getDebugOldLabel(s, stmt.pos) - v.decider.assume(tRcvr !== Null, debugExp, debugExpSubst, getAssumptionType(AssumptionType.SourceCode)) + v.decider.assume(tRcvr !== Null, debugExp, debugExpSubst, dAInfo) val eRcvr = Option.when(withExp)(Seq(x)) val p = FullPerm @@ -437,7 +431,7 @@ object executor extends ExecutionRules { val fld = flds.head val snap = v.decider.fresh(fld.name, v.symbolConverter.toSort(fld.typ), Option.when(withExp)(extractPTypeFromExp(x))) val snapExp = Option.when(withExp)(ast.DebugLabelledOld(ast.FieldAccess(eRcvrNew.get, fld)(), debugLabel)(stmt.pos, stmt.info, stmt.errT)) - v.heapSupporter.produceSingle(s, fld, Seq(tRcvr), eRcvr, snap, snapExp, p, pExp, NullPartialVerificationError, false, v, getDependencyType(DependencyType.SourceCode))((s1, v1) => { + v.heapSupporter.produceSingle(s, fld, Seq(tRcvr), eRcvr, snap, snapExp, p, pExp, NullPartialVerificationError, false, v, dAInfo)((s1, v1) => { addFieldPerms(s1, flds.tail, v1)(QB) }) } @@ -447,7 +441,7 @@ object executor extends ExecutionRules { addFieldPerms(s, fields, v)((s0, v0) => { val s1 = s0.copy(g = s0.g + (x, (tRcvr, eRcvrNew))) val s2 = if (withExp) s1.copy(oldHeaps = s1.oldHeaps + (debugHeapName -> magicWandSupporter.getEvalHeap(s1))) else s1 - v0.decider.assume(ts, Option.when(withExp)(DebugExp.createInstance(Some("Reference Disjointness"), esNew, esNew, InsertionOrderedSet.empty)), enforceAssumption = false, getAssumptionType(AssumptionType.SourceCode)) + v0.decider.assume(ts, Option.when(withExp)(DebugExp.createInstance(Some("Reference Disjointness"), esNew, esNew, InsertionOrderedSet.empty)), enforceAssumption = false, dAInfo) Q(s2, v0) }) @@ -456,25 +450,25 @@ object executor extends ExecutionRules { /* We're done */ Success() case _ => - produce(s, freshSnap, a, InhaleFailed(inhale), v, getAssumptionType(AssumptionType.Explicit))((s1, v1) => { + produce(s, freshSnap, a, InhaleFailed(inhale), v, dAInfo)((s1, v1) => { v1.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterInhale) - if(v1.decider.isDependencyAnalysisEnabled && a.isInstanceOf[ast.FalseLit]) v1.decider.checkSmokeAndSetInfeasibilityNode() + if(v1.decider.isDependencyAnalysisEnabled && a.isInstanceOf[ast.FalseLit]) v1.decider.checkSmokeAndSetInfeasibilityNode(dAInfo) Q(s1, v1)}) } case exhale @ ast.Exhale(a) => val pve = ExhaleFailed(exhale) - consume(s, a, false, pve, v, getDependencyType(DependencyType.ExplicitAssertion))((s1, _, v1) => + consume(s, a, false, pve, v, dAInfo)((s1, _, v1) => Q(s1, v1)) case assert @ ast.Assert(a: ast.FalseLit) if !s.isInPackage => /* "assert false" triggers a smoke check. If successful, we backtrack. */ executionFlowController.tryOrFail0(s.copy(h = magicWandSupporter.getEvalHeap(s)), v)((s1, v1, QS) => { - if (v1.decider.checkSmoke(getAssertionType(AssumptionType.Explicit), isAssert = true)) + if (v1.decider.checkSmoke(dAInfo, isAssert = true)) QS(s1.copy(h = s.h), v1) else { val failure = createFailure(AssertFailed(assert) dueTo AssertionFalse(a), v1, s1, False, true, Option.when(withExp)(a)) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(False, getAssertionType(AssumptionType.Explicit), v1.reportFurtherErrors()) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(False, dAInfo, v1.reportFurtherErrors()) if(s1.retryLevel == 0 && v1.reportFurtherErrors()) failure combine QS(s1, v1) else failure } })((s2, v2) => @@ -486,7 +480,7 @@ object executor extends ExecutionRules { case assert @ ast.Assert(a) if Verifier.config.disableSubsumption() => val r = - consume(s, a, false, AssertFailed(assert), v, getDependencyType(DependencyType.ExplicitAssertion))((_, _, _) => + consume(s, a, false, AssertFailed(assert), v, dAInfo)((_, _, _) => Success()) r combine Q(s, v) @@ -502,11 +496,11 @@ object executor extends ExecutionRules { * hUsed (reserveHeaps.head) instead of consuming them. hUsed is later discarded and replaced * by s.h. By copying hUsed to s.h the contained permissions remain available inside the wand. */ - consume(s, a, false, pve, v, getDependencyType(DependencyType.ExplicitAssertion))((s2, _, v1) => { + consume(s, a, false, pve, v, dAInfo)((s2, _, v1) => { Q(s2.copy(h = s2.reserveHeaps.head), v1) }) } else - consume(s, a, false, pve, v, getDependencyType(DependencyType.ExplicitAssertion))((s1, _, v1) => { + consume(s, a, false, pve, v, dAInfo)((s1, _, v1) => { val s2 = s1.copy(h = s.h, reserveHeaps = s.reserveHeaps) Q(s2, v1)}) @@ -515,7 +509,7 @@ object executor extends ExecutionRules { case ast.MethodCall(methodName, _, _) if !Verifier.config.disableHavocHack407() && methodName.startsWith(hack407_method_name_prefix) => - val analysisInfo = v.decider.getAnalysisInfo(getAssumptionType(AssumptionType.Explicit)) + val analysisInfo = v.decider.getAnalysisInfo(dAInfo) val resourceName = methodName.stripPrefix(hack407_method_name_prefix) val member = s.program.collectFirst { case m: ast.Field if m.name == resourceName => m @@ -546,14 +540,15 @@ object executor extends ExecutionRules { val pveCall = CallFailed(call) val pveCallTransformed = pveCall.withReasonNodeTransformed(reasonTransformer) - v.decider.dependencyAnalyzer.addAssumption(True, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getAssumptionType, - isJoinNode=false, None) + // FIXME ake: why was this added? +// v.decider.dependencyAnalyzer.addAssumption(True, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getAssumptionType, +// isJoinNode=false, None) val mcLog = new MethodCallRecord(call, s, v.decider.pcs) val sepIdentifier = v.symbExLog.openScope(mcLog) val paramLog = new CommentRecord("Parameters", s, v.decider.pcs) val paramId = v.symbExLog.openScope(paramLog) - evals(s, eArgs, _ => pveCall, v)((s1, tArgs, eArgsNew, v1) => { + evals(s, eArgs, _ => pveCall, v, dAInfo)((s1, tArgs, eArgsNew, v1) => { v1.symbExLog.closeScope(paramId) val exampleTrafo = CounterexampleTransformer({ case ce: SiliconCounterexample => ce.withStore(s1.g) @@ -570,14 +565,14 @@ object executor extends ExecutionRules { recordVisited = true) v1.decider.analysisSourceInfoStack.isJoinRelevantNode = true - consumes(s2, meth.pres, false, _ => pvePre, v1, getDependencyType(DependencyType.MethodCall))((s3, _, v2) => { + consumes(s2, meth.pres, false, _ => pvePre, v1, dAInfo)((s3, _, v2) => { v2.symbExLog.closeScope(preCondId) val postCondLog = new CommentRecord("Postcondition", s3, v2.decider.pcs) val postCondId = v2.symbExLog.openScope(postCondLog) val outs = meth.formalReturns.map(_.localVar) val gOuts = Store(outs.map(x => (x, v2.decider.fresh(x))).toMap) val s4 = s3.copy(g = s3.g + gOuts, oldHeaps = s3.oldHeaps + (Verifier.PRE_STATE_LABEL -> magicWandSupporter.getEvalHeap(s1))) - produces(s4, freshSnap, meth.posts, _ => pveCallTransformed, v2, getAssumptionType(AssumptionType.MethodCall))((s5, v3) => { + produces(s4, freshSnap, meth.posts, _ => pveCallTransformed, v2, dAInfo)((s5, v3) => { v3.decider.analysisSourceInfoStack.isJoinRelevantNode = false v3.symbExLog.closeScope(postCondId) v3.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) @@ -594,14 +589,12 @@ object executor extends ExecutionRules { v.decider.startDebugSubExp() val ePerm = pap.perm val predicate = s.program.findPredicate(predicateName) - val predicateAnnotatedAssumptionType = DependencyAnalyzer.extractAssumptionTypeFromInfo(predicate.info).map(DependencyType.make) - val finalDependencyType = getDependencyType(predicateAnnotatedAssumptionType.getOrElse(DependencyType.Rewrite)) val pve = FoldFailed(fold) - evals(s, eArgs, _ => pve, v)((s1, tArgs, eArgsNew, v1) => - eval(s1, ePerm, pve, v1)((s2, tPerm, ePermNew, v2) => - permissionSupporter.assertPositive(s2, tPerm, if (withExp) ePermNew.get else ePerm, pve, v2)((s3, v3) => { + evals(s, eArgs, _ => pve, v, dAInfo)((s1, tArgs, eArgsNew, v1) => + eval(s1, ePerm, pve, v1, dAInfo)((s2, tPerm, ePermNew, v2) => + permissionSupporter.assertPositive(s2, tPerm, if (withExp) ePermNew.get else ePerm, pve, v2, dAInfo)((s3, v3) => { val wildcards = s3.constrainableARPs -- s1.constrainableARPs - predicateSupporter.fold(s3, predAcc, tArgs, eArgsNew, tPerm, ePermNew, wildcards, pve, v3, finalDependencyType)((s4, v4) => { + predicateSupporter.fold(s3, predAcc, tArgs, eArgsNew, tPerm, ePermNew, wildcards, pve, v3, dAInfo)((s4, v4) => { v3.decider.finishDebugSubExp(s"folded ${predAcc.toString}") Q(s4, v4) } @@ -612,16 +605,14 @@ object executor extends ExecutionRules { v.decider.startDebugSubExp() val ePerm = pap.perm val predicate = s.program.findPredicate(predicateName) - val predicateAnnotatedAssumptionType = DependencyAnalyzer.extractAssumptionTypeFromInfo(predicate.info).map(DependencyType.make) - val finalDependencyType = getDependencyType(predicateAnnotatedAssumptionType.getOrElse(DependencyType.Rewrite)) val pve = UnfoldFailed(unfold) - evals(s, eArgs, _ => pve, v)((s1, tArgs, eArgsNew, v1) => - eval(s1, ePerm, pve, v1)((s2, tPerm, ePermNew, v2) => { - val s2a = v2.heapSupporter.triggerResourceIfNeeded(s2, pa, tArgs, eArgsNew, v2) + evals(s, eArgs, _ => pve, v, dAInfo)((s1, tArgs, eArgsNew, v1) => + eval(s1, ePerm, pve, v1, dAInfo)((s2, tPerm, ePermNew, v2) => { + val s2a = v2.heapSupporter.triggerResourceIfNeeded(s2, pa, tArgs, eArgsNew, v2, dAInfo) - permissionSupporter.assertPositive(s2a, tPerm, if (withExp) ePermNew.get else ePerm, pve, v2)((s3, v3) => { + permissionSupporter.assertPositive(s2a, tPerm, if (withExp) ePermNew.get else ePerm, pve, v2, dAInfo)((s3, v3) => { val wildcards = s3.constrainableARPs -- s1.constrainableARPs - predicateSupporter.unfold(s3, predicate, tArgs, eArgsNew, tPerm, ePermNew, wildcards, pve, v3, pa, finalDependencyType)( + predicateSupporter.unfold(s3, predicate, tArgs, eArgsNew, tPerm, ePermNew, wildcards, pve, v3, pa, dAInfo)( (s4, v4) => { v2.decider.finishDebugSubExp(s"unfolded ${pa.toString}") Q(s4, v4) @@ -631,7 +622,7 @@ object executor extends ExecutionRules { case pckg @ ast.Package(wand, proofScript) => val pve = PackageFailed(pckg) - magicWandSupporter.packageWand(s.copy(isInPackage = true), wand, proofScript, pve, v, getDependencyType(DependencyType.Rewrite))((s1, chWand, v1) => { + magicWandSupporter.packageWand(s.copy(isInPackage = true), wand, proofScript, pve, v, dAInfo)((s1, chWand, v1) => { val hOps = s1.reserveHeaps.head + chWand assert(s.exhaleExt || s1.reserveHeaps.length == 1) @@ -657,7 +648,7 @@ object executor extends ExecutionRules { val s3 = chWand match { case ch: QuantifiedMagicWandChunk => - v1.heapSupporter.triggerResourceIfNeeded(s2, wand, ch.singletonArgs.get, ch.singletonArgExps, v1) + v1.heapSupporter.triggerResourceIfNeeded(s2, wand, ch.singletonArgs.get, ch.singletonArgExps, v1, dAInfo) case _ => s2 } @@ -666,13 +657,13 @@ object executor extends ExecutionRules { case apply @ ast.Apply(e) => val pve = ApplyFailed(apply) - magicWandSupporter.applyWand(s, e, pve, v, getDependencyType(DependencyType.Rewrite))(Q) + magicWandSupporter.applyWand(s, e, pve, v, dAInfo)(Q) case havoc: ast.Quasihavoc => - havocSupporter.execHavoc(havoc, v, s, getAssumptionType(AssumptionType.Explicit))(Q) + havocSupporter.execHavoc(havoc, v, s, dAInfo)(Q) case havocall: ast.Quasihavocall => - havocSupporter.execHavocall(havocall, v, s, getAssumptionType(AssumptionType.Explicit))(Q) + havocSupporter.execHavocall(havocall, v, s, dAInfo)(Q) case viper.silicon.extensions.TryBlock(body) => var bodySucceeded = false @@ -696,7 +687,7 @@ object executor extends ExecutionRules { executed } - private def ssaifyRhs(rhs: Term, rhsExp: ast.Exp, rhsExpNew: Option[ast.Exp], name: String, typ: ast.Type, v: Verifier, s : State, assumptionType: AssumptionType): (Term, Option[ast.Exp]) = { + private def ssaifyRhs(rhs: Term, rhsExp: ast.Exp, rhsExpNew: Option[ast.Exp], name: String, typ: ast.Type, v: Verifier, s : State, dAInfo: DependencyAnalysisInfo): (Term, Option[ast.Exp]) = { rhs match { case _: Var | _: Literal if !v.decider.isDependencyAnalysisEnabled => (rhs, rhsExpNew) @@ -723,7 +714,7 @@ object executor extends ExecutionRules { } else { (None, None) } - v.decider.assumeDefinition(BuiltinEquals(t, rhs), debugExp, assumptionType) + v.decider.assumeDefinition(BuiltinEquals(t, rhs), debugExp, dAInfo) (t, eNew) } } diff --git a/src/main/scala/rules/HavocSupporter.scala b/src/main/scala/rules/HavocSupporter.scala index 720eea50f..d422bc02a 100644 --- a/src/main/scala/rules/HavocSupporter.scala +++ b/src/main/scala/rules/HavocSupporter.scala @@ -7,11 +7,8 @@ package viper.silicon.rules import viper.silicon.debugger.DebugExp -import viper.silicon.Map -import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType -import viper.silicon.dependencyAnalysis.{AnalysisInfo, AssumptionType, StmtAnalysisSourceInfo} +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfo import viper.silicon.interfaces.VerificationResult -import viper.silicon.interfaces.state.{Chunk, NonQuantifiedChunk, QuantifiedChunk} import viper.silicon.rules.evaluator.{eval, evalQuantified, evals} import viper.silicon.state._ import viper.silicon.state.terms._ @@ -38,7 +35,7 @@ object havocSupporter extends SymbolicExecutionRules { def execHavoc(havoc: ast.Quasihavoc, v: Verifier, s: State, - assumptionType: AssumptionType) + dAInfo: DependencyAnalysisInfo) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -47,15 +44,15 @@ object havocSupporter extends SymbolicExecutionRules { // If there is no havoc condition, use True as the condition val lhsExpr = havoc.lhs.getOrElse(ast.TrueLit()(havoc.pos)) - eval(s, lhsExpr, pve, v)((s0, lhsTerm, _, v0) => { - evals(s0, havoc.exp.args(s0.program), _ => pve, v0)((s1, tRcvrs, _, v1) => { + eval(s, lhsExpr, pve, v, dAInfo)((s0, lhsTerm, _, v0) => { + evals(s0, havoc.exp.args(s0.program), _ => pve, v0, dAInfo)((s1, tRcvrs, _, v1) => { val resource = havoc.exp.res(s1.program) // Call the havoc helper function, which returns a new heap, which is // partially havocked. Since we are executing a Havoc statement, we wrap // the HavocHelperData inside of a HavocOneData case (as opposed to HavocAllData). val condInfo = HavocOneData(tRcvrs) - val newHeap = v1.heapSupporter.havocResource(s1, lhsTerm, resource, condInfo, v1, assumptionType) + val newHeap = v1.heapSupporter.havocResource(s1, lhsTerm, resource, condInfo, v1, dAInfo) Q(s1.copy(h = newHeap), v1) }) @@ -76,7 +73,7 @@ object havocSupporter extends SymbolicExecutionRules { def execHavocall(havocall: ast.Quasihavocall, v: Verifier, s: State, - assumptionType: AssumptionType) + dAInfo: DependencyAnalysisInfo) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -101,7 +98,8 @@ object havocSupporter extends SymbolicExecutionRules { optTriggers = None, // Triggers: none needed for Havocall name = qid, pve = pve, - v = v) + v = v, + dAInfo = dAInfo) { case (s1, tVars, eVars, Seq(tCond), _, Some((tArgs, eArgs, Seq(), _, _)), v1) => // Seq() represents an empty list of Triggers @@ -125,11 +123,11 @@ object havocSupporter extends SymbolicExecutionRules { val notInjectiveReason = QuasihavocallNotInjective(havocall) val comment = "QP receiver injectivity check is well-defined" val injectivityDebugExp = Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)) - v.decider.assume(FunctionPreconditionTransformer.transform(receiverInjectivityCheck, s.program), injectivityDebugExp, assumptionType) - v.decider.assert(receiverInjectivityCheck) { + v.decider.assume(FunctionPreconditionTransformer.transform(receiverInjectivityCheck, s.program), injectivityDebugExp, dAInfo) + v.decider.assert(receiverInjectivityCheck, dAInfo) { case false => val failure = createFailure(pve dueTo notInjectiveReason, v, s1, receiverInjectivityCheck, "QP receiver injective") - if(s1.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(receiverInjectivityCheck, v.decider.analysisSourceInfoStack.getAssertionType, v.reportFurtherErrors()) + if(s1.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(receiverInjectivityCheck, dAInfo, v.reportFurtherErrors()) if(s1.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s1, v1) else failure case true => // Generate the inverse axioms @@ -150,13 +148,13 @@ object havocSupporter extends SymbolicExecutionRules { ) val comment = "Definitional axioms for havocall inverse functions" v.decider.prover.comment(comment) - v.decider.assume(inverseFunctions.definitionalAxioms, Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, assumptionType=assumptionType) + v.decider.assume(inverseFunctions.definitionalAxioms, Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, dAInfo=dAInfo) // Call the havoc helper function, which returns a new heap, which is // partially havocked. Since we are executing a Havocall statement, we wrap // the HavocHelperData inside of a HavocAllData case. val condInfo = HavocallData(inverseFunctions, codomainQVars, imagesOfCodomain) - val newHeap = v1.heapSupporter.havocResource(s1, tCond, resource, condInfo, v1, assumptionType) + val newHeap = v1.heapSupporter.havocResource(s1, tCond, resource, condInfo, v1, dAInfo) Q(s1.copy(h = newHeap), v1) } diff --git a/src/main/scala/rules/HeapSupporter.scala b/src/main/scala/rules/HeapSupporter.scala index a06f2835a..9b5fd09db 100644 --- a/src/main/scala/rules/HeapSupporter.scala +++ b/src/main/scala/rules/HeapSupporter.scala @@ -9,17 +9,16 @@ package viper.silicon.rules import viper.silicon import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp -import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType -import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyType, TransitivityAnalysisSourceInfo} +import viper.silicon.dependencyAnalysis._ import viper.silicon.interfaces.VerificationResult import viper.silicon.interfaces.state.{ChunkIdentifer, NonQuantifiedChunk, QuantifiedChunk} import viper.silicon.resources.{FieldID, PredicateID} import viper.silicon.rules.havocSupporter.{HavocHelperData, HavocOneData, HavocallData} import viper.silicon.rules.quantifiedChunkSupporter.freshSnapshotMap +import viper.silicon.state._ import viper.silicon.state.terms._ import viper.silicon.state.terms.perms.IsPositive import viper.silicon.state.terms.predef.{`?r`, `?s`} -import viper.silicon.state._ import viper.silicon.supporters.functions.NoopFunctionRecorder import viper.silicon.utils.ast.{BigAnd, replaceVarsInExp} import viper.silicon.utils.freshSnap @@ -37,7 +36,8 @@ trait HeapSupportRules extends SymbolicExecutionRules { tRcvr: Term, eRcvr: Option[ast.Exp], ve: VerificationError, - v: Verifier) + v: Verifier, + dAInfo: DependencyAnalysisInfo) (Q: (State, Term, Verifier) => VerificationResult) : VerificationResult @@ -47,7 +47,8 @@ trait HeapSupportRules extends SymbolicExecutionRules { identifier: ChunkIdentifer, tArgs: Seq[Term], eArgs: Option[Seq[ast.Exp]], - v: Verifier) + v: Verifier, + dAInfo: DependencyAnalysisInfo) (Q: (State, Term, Verifier) => VerificationResult): VerificationResult def isPossibleTrigger(s: State, fa: ast.FieldAccess): Boolean @@ -60,7 +61,7 @@ trait HeapSupportRules extends SymbolicExecutionRules { eRhsNew: Option[ast.Exp], pve: PartialVerificationError, v: Verifier, - dependencyType: DependencyType) + dAInfo: DependencyAnalysisInfo) (Q: (State, Verifier) => VerificationResult) : VerificationResult @@ -68,7 +69,8 @@ trait HeapSupportRules extends SymbolicExecutionRules { resAcc: ast.ResourceAccess, tArgs: Seq[Term], eArgs: Option[Seq[ast.Exp]], - v: Verifier): State + v: Verifier, + dAInfo: DependencyAnalysisInfo): State def consumeSingle(s: State, h: Heap, @@ -80,7 +82,7 @@ trait HeapSupportRules extends SymbolicExecutionRules { returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, - dependencyType: DependencyType) + dAInfo: DependencyAnalysisInfo) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult): VerificationResult def consumeQuantified(s: State, @@ -109,7 +111,7 @@ trait HeapSupportRules extends SymbolicExecutionRules { notInjectiveReason: => ErrorReason, insufficientPermissionReason: => ErrorReason, v: Verifier, - dependencyType: DependencyType) + dAInfo: DependencyAnalysisInfo) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult): VerificationResult def produceSingle(s: State, @@ -123,7 +125,7 @@ trait HeapSupportRules extends SymbolicExecutionRules { pve: PartialVerificationError, mergeAndTrigger: Boolean, v: Verifier, - dependencyType: DependencyType) + dAInfo: DependencyAnalysisInfo) (Q: (State, Verifier) => VerificationResult): VerificationResult def produceQuantified(s: State, @@ -151,7 +153,7 @@ trait HeapSupportRules extends SymbolicExecutionRules { negativePermissionReason: => ErrorReason, notInjectiveReason: => ErrorReason, v: Verifier, - assumptionType: AssumptionType) + dAInfo: DependencyAnalysisInfo) (Q: (State, Verifier) => VerificationResult): VerificationResult def havocResource(s: State, @@ -159,7 +161,7 @@ trait HeapSupportRules extends SymbolicExecutionRules { resource: ast.Resource, condInfo: HavocHelperData, v: Verifier, - assumptionType: AssumptionType): Heap + dAInfo: DependencyAnalysisInfo): Heap def collectForPermConditions(s: State, resource: ast.Resource, @@ -184,12 +186,12 @@ class DefaultHeapSupportRules extends HeapSupportRules { eRhsNew: Option[ast.Exp], pve: PartialVerificationError, v: Verifier, - dependencyType: DependencyType) + dAInfo: DependencyAnalysisInfo) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { if(v.decider.isPathInfeasible()){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType, v.decider.analysisSourceInfoStack.isJoinRelevantNode) - v.decider.dependencyAnalyzer.addAssumption(False, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getAssumptionType, isJoinNode=false) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, dAInfo) + v.decider.dependencyAnalyzer.addAssumption(False, dAInfo) return Q(s, v) } @@ -199,8 +201,8 @@ class DefaultHeapSupportRules extends HeapSupportRules { val (relevantChunks, otherChunks) = quantifiedChunkSupporter.splitHeap[QuantifiedFieldChunk](s.h, BasicChunkIdentifier(field.name)) val hints = quantifiedChunkSupporter.extractHints(None, Seq(tRcvr)) - val chunkOrderHeuristics = quantifiedChunkSupporter.singleReceiverChunkOrderHeuristic(Seq(tRcvr), hints, v) - val s2 = triggerResourceIfNeeded(s, ass.lhs, Seq(tRcvr), eRcvrNew.map(Seq(_)), v) + val chunkOrderHeuristics = quantifiedChunkSupporter.singleReceiverChunkOrderHeuristic(Seq(tRcvr), hints, v, dAInfo) + val s2 = triggerResourceIfNeeded(s, ass.lhs, Seq(tRcvr), eRcvrNew.map(Seq(_)), v, dAInfo) v.decider.clearModel() val lhsSourceInfo = TransitivityAnalysisSourceInfo(v.decider.analysisSourceInfoStack.getFullSourceInfo, AnalysisSourceInfo.createAnalysisSourceInfo(ass.lhs)) v.decider.analysisSourceInfoStack.setForcedSource(lhsSourceInfo,v.decider.analysisSourceInfoStack.getDependencyType) // splitting lhs and rhs to make permission flow analysis more precise @@ -217,7 +219,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { Option.when(withExp)(ast.FullPerm()()), chunkOrderHeuristics, v, - dependencyType + dAInfo ) v.decider.analysisSourceInfoStack.removeForcedSource() result match { @@ -226,13 +228,15 @@ class DefaultHeapSupportRules extends HeapSupportRules { val (sm, smValueDef) = quantifiedChunkSupporter.singletonSnapshotMap(s3, field, Seq(tRcvr), tRhs, v) v.decider.prover.comment("Definitional axioms for singleton-FVF's value") val debugExp = Option.when(withExp)(DebugExp.createInstance("Definitional axioms for singleton-FVF's value", isInternal_ = true)) - v.decider.assumeDefinition(smValueDef, debugExp, dependencyType.assumptionType) + v.decider.assumeDefinition(smValueDef, debugExp, dAInfo) + // FIXME ake: Field assignment optimization + val dAInfoInt = FullDependencyAnalysisInfo(lhsSourceInfo, dAInfo.getDependencyType NoDependencyAnalysisMerge(), NoDependencyAnalysisJoin()) // TODO ake: review v.decider.analysisSourceInfoStack.setForcedSource(lhsSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType) // splitting lhs and rhs to make permission flow analysis more precise val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(Seq(`?r`), Option.when(withExp)(Seq(ast.LocalVarDecl("r", ast.Ref)(ass.pos, ass.info, ass.errT))), field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), sm, s.program, v, AssumptionType.Internal, isExhale=false) if (s3.heapDependentTriggers.contains(field)) { val debugExp2 = Option.when(withExp)(DebugExp.createInstance(s"FieldTrigger(${eRcvrNew.toString}.${field.name})")) - v.decider.assume(FieldTrigger(field.name, sm, tRcvr), debugExp2, AssumptionType.Trigger) + v.decider.assume(FieldTrigger(field.name, sm, tRcvr), debugExp2, dAInfo.withDependencyType(DependencyType.Trigger)) } v.decider.analysisSourceInfoStack.removeForcedSource() v.decider.dependencyAnalyzer.addCustomTransitiveDependency(lhsSourceInfo, v.decider.analysisSourceInfoStack.getFullSourceInfo) @@ -242,16 +246,16 @@ class DefaultHeapSupportRules extends HeapSupportRules { Q(s5, v) case (Incomplete(_, _), s3, _) => val failure = createFailure(ve, v, s3, "sufficient permission") - if(s3.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, dependencyType.assertionType, v.reportFurtherErrors()) + if(s3.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, dAInfo, v.reportFurtherErrors()) if(s3.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s3, v) else failure } } else { val description = s"consume ${ass.pos}: $ass" val lhsSourceInfo = TransitivityAnalysisSourceInfo(v.decider.analysisSourceInfoStack.getFullSourceInfo, AnalysisSourceInfo.createAnalysisSourceInfo(ass.lhs)) v.decider.analysisSourceInfoStack.setForcedSource(lhsSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType) // splitting lhs and rhs to make permission flow analysis more precise - chunkSupporter.consume(s, s.h, field, Seq(tRcvr), eRcvrNew.map(Seq(_)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), false, ve, v, description, dependencyType)((s3, h3, _, v3) => { + chunkSupporter.consume(s, s.h, field, Seq(tRcvr), eRcvrNew.map(Seq(_)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), false, ve, v, description, dAInfo)((s3, h3, _, v3) => { val id = BasicChunkIdentifier(field.name) - val newChunk = BasicChunk.apply(FieldID, id, Seq(tRcvr), eRcvrNew.map(Seq(_)), tRhs, eRhsNew, FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), v3.decider.getAnalysisInfo(AssumptionType.Internal)) + val newChunk = BasicChunk.apply(FieldID, id, Seq(tRcvr), eRcvrNew.map(Seq(_)), tRhs, eRhsNew, FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), v3.decider.getAnalysisInfo(dAInfo.withDependencyType(DependencyType.Internal))) v.decider.analysisSourceInfoStack.removeForcedSource() chunkSupporter.produce(s3, h3, newChunk, v3)((s4, h4, v4) => { val s5 = s4.copy(h = h4) @@ -270,11 +274,12 @@ class DefaultHeapSupportRules extends HeapSupportRules { identifier: ChunkIdentifer, tArgs: Seq[Term], eArgs: Option[Seq[ast.Exp]], - v: Verifier) + v: Verifier, + dAInfo: DependencyAnalysisInfo) (Q: (State, Term, Verifier) => VerificationResult): VerificationResult = { if(v.decider.isPathInfeasible()){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType, v.decider.analysisSourceInfoStack.isJoinRelevantNode) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, dAInfo) return Q(s, NoPerm, v) } @@ -303,7 +308,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { case w: ast.MagicWand => MagicWandIdentifier(w, s2.program).toString } DebugExp.createInstance(s"Resource trigger(${name}($argsString))", isInternal_ = true) - }), AssumptionType.Trigger) + }), dAInfo.withDependencyType(DependencyType.Trigger)) } val currentPermAmount = ResourcePermissionLookup(res, pmDef.pm, tArgs, s2.program) @@ -313,7 +318,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { v.decider.prover.comment(s"perm($resAcc) ~~> assume upper permission bound") val (debugHeapName, debugLabel) = v.getDebugOldLabel(s2, resAcc.pos, Some(h)) val exp = Option.when(withExp)(ast.PermLeCmp(ast.DebugLabelledOld(ast.CurrentPerm(resAcc)(), debugLabel)(), ast.FullPerm()())()) - v.decider.assume(PermAtMost(currentPermAmount, FullPerm), exp, exp.map(s2.substituteVarsInExp(_)), AssumptionType.Internal) + v.decider.assume(PermAtMost(currentPermAmount, FullPerm), exp, exp.map(s2.substituteVarsInExp(_)), dAInfo.withDependencyType(DependencyType.Internal)) val s3 = if (Verifier.config.enableDebugging()) s2.copy(oldHeaps = s2.oldHeaps + (debugHeapName -> h)) else s2 s3 case _ => s2 @@ -337,11 +342,12 @@ class DefaultHeapSupportRules extends HeapSupportRules { tRcvr: Term, eRcvr: Option[ast.Exp], ve: VerificationError, - v: Verifier) + v: Verifier, + dAInfo: DependencyAnalysisInfo) (Q: (State, Term, Verifier) => VerificationResult) : VerificationResult = { if(v.decider.isPathInfeasible()){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType, v.decider.analysisSourceInfoStack.isJoinRelevantNode) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, dAInfo) val sort = v.symbolConverter.toSort(fa.field.typ) val newVar = v.decider.fresh(sort, None) // just make sure the returned term typechecks @@ -361,11 +367,11 @@ class DefaultHeapSupportRules extends HeapSupportRules { * quantifier in whose body field 'fa.field' was accessed) * which is protected by a trigger term that we currently don't have. */ - v.decider.assume(And(fvfDef.valueDefinitions), Option.when(withExp)(DebugExp.createInstance("Value definitions", isInternal_ = true)), AssumptionType.Internal) + v.decider.assume(And(fvfDef.valueDefinitions), Option.when(withExp)(DebugExp.createInstance("Value definitions", isInternal_ = true)), dAInfo.withDependencyType(DependencyType.Internal)) if (s.heapDependentTriggers.contains(fa.field)) { val trigger = FieldTrigger(fa.field.name, fvfDef.sm, tRcvr) val triggerExp = Option.when(withExp)(DebugExp.createInstance(s"FieldTrigger(${eRcvr.toString()}.${fa.field.name})")) - v.decider.assume(trigger, triggerExp, AssumptionType.Trigger) + v.decider.assume(trigger, triggerExp, dAInfo.withDependencyType(DependencyType.Trigger)) } if (s.triggerExp) { val fvfLookup = Lookup(fa.field.name, fvfDef.sm, tRcvr) @@ -374,10 +380,10 @@ class DefaultHeapSupportRules extends HeapSupportRules { Q(s2, fvfLookup, v) } else { val toAssert = IsPositive(totalPermissions.replace(`?r`, tRcvr)) - v.decider.assert(toAssert) { + v.decider.assert(toAssert, dAInfo) { case false => val failure = createFailure(ve, v, s, toAssert, Option.when(withExp)(perms.IsPositive(ast.CurrentPerm(fa)())())) - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(toAssert, v.decider.analysisSourceInfoStack.getAssertionType, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(toAssert, dAInfo, v.reportFurtherErrors()) val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(fa.field, s, v), Option.when(withExp)(PUnknown())) if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, snap, v) else failure case true => @@ -412,7 +418,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { if (s2.heapDependentTriggers.contains(fa.field)) { val trigger = FieldTrigger(fa.field.name, sm, tRcvr) val triggerExp = Option.when(withExp)(DebugExp.createInstance(s"FieldTrigger(${eRcvr.toString()}.${fa.field.name})")) - v.decider.assume(trigger, triggerExp, AssumptionType.Trigger) + v.decider.assume(trigger, triggerExp, dAInfo.withDependencyType(DependencyType.Trigger)) } val (permCheck, permCheckExp, s3) = if (s2.triggerExp) { @@ -428,10 +434,10 @@ class DefaultHeapSupportRules extends HeapSupportRules { } (Implies(lhs, IsPositive(totalPerms)), Option.when(withExp)(perms.IsPositive(ast.CurrentPerm(fa)(fa.pos, fa.info, fa.errT))(fa.pos, fa.info, fa.errT)), s3) } - v.decider.assert(permCheck) { + v.decider.assert(permCheck,dAInfo) { case false => val failure = createFailure(ve, v, s3, permCheck, permCheckExp) - if(s3.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(permCheck, v.decider.analysisSourceInfoStack.getAssertionType, v.reportFurtherErrors()) + if(s3.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(permCheck, dAInfo, v.reportFurtherErrors()) val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(fa.field, s3, v), Option.when(withExp)(PUnknown())) if(s3.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s3, snap, v) else failure case true => @@ -444,7 +450,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { } } else { val resource = fa.res(s.program) - chunkSupporter.lookup(s, s.h, resource, Seq(tRcvr), Option.when(withExp)(Seq(eRcvr.get)), ve, v, v.decider.analysisSourceInfoStack.getAssumptionType)((s2, h2, tSnap, v2) => { + chunkSupporter.lookup(s, s.h, resource, Seq(tRcvr), Option.when(withExp)(Seq(eRcvr.get)), ve, v, dAInfo)((s2, h2, tSnap, v2) => { val fr = s2.functionRecorder.recordSnapshot(fa, v2.decider.pcs.branchConditions, tSnap) val s3 = s2.copy(h = h2, functionRecorder = fr) Q(s3, tSnap, v) @@ -456,7 +462,8 @@ class DefaultHeapSupportRules extends HeapSupportRules { resAcc: ast.ResourceAccess, tArgs: Seq[Term], eArgs: Option[Seq[ast.Exp]], - v: Verifier): State = { + v: Verifier, + dAInfo: DependencyAnalysisInfo): State = { if (s.isUsedAsTrigger(resAcc.res(s.program))) { val resource = resAcc.res(s.program) val chunkId = ChunkIdentifier(resource, s.program) @@ -478,7 +485,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { val eArgsStr = eArgs.mkString(", ") val debugExp = Option.when(withExp)(DebugExp.createInstance(Some(s"Resource trigger(${name}($eArgsStr))"), Some(resAcc), Some(resAcc), None, isInternal_ = true, InsertionOrderedSet.empty)) - v.decider.assume(trigger(smDef1.sm), debugExp, AssumptionType.Trigger) + v.decider.assume(trigger(smDef1.sm), debugExp, dAInfo.withDependencyType(DependencyType.Trigger)) s.copy(smCache = smCache1, functionRecorder = s.functionRecorder.recordFvfAndDomain(smDef1)) } else { s @@ -496,7 +503,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { pve: PartialVerificationError, mergeAndTrigger: Boolean, v: Verifier, - dependencyType: DependencyType) + dAInfo: DependencyAnalysisInfo) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { val useQPs = s.isQuantifiedResource(resource) if (useQPs) { @@ -504,17 +511,17 @@ class DefaultHeapSupportRules extends HeapSupportRules { val tFormalArgs = s.getFormalArgVars(resource, v) val eFormalArgs = Option.when(withExp)(s.getFormalArgDecls(resource)) quantifiedChunkSupporter.produceSingleLocation( - s, resource, tFormalArgs, eFormalArgs, tArgs, eArgs, tSnap, tPerm, ePerm, trigger, mergeAndTrigger, v, dependencyType.assumptionType)(Q) + s, resource, tFormalArgs, eFormalArgs, tArgs, eArgs, tSnap, tPerm, ePerm, trigger, mergeAndTrigger, v, dAInfo)(Q) } else { resource match { case w: ast.MagicWand => - magicWandSupporter.createChunk(s, w, MagicWandSnapshot(tSnap), pve, v, dependencyType.assumptionType)((s2, chWand, v2) => + magicWandSupporter.createChunk(s, w, MagicWandSnapshot(tSnap), pve, v, dAInfo)((s2, chWand, v2) => chunkSupporter.produce(s2, s2.h, chWand, v2)((s3, h3, v3) => Q(s3.copy(h = h3), v3))) case _ => val chunkId = ChunkIdentifier(resource, s.program) val (resId, snap1) = if (resource.isInstanceOf[ast.Field]) (FieldID, tSnap) else (PredicateID, tSnap.convert(sorts.Snap)) - val ch = BasicChunk.apply(resId, chunkId.asInstanceOf[BasicChunkIdentifier], tArgs, eArgs, snap1, eSnap, tPerm, ePerm, v.decider.getAnalysisInfo(dependencyType.assumptionType)) + val ch = BasicChunk.apply(resId, chunkId.asInstanceOf[BasicChunkIdentifier], tArgs, eArgs, snap1, eSnap, tPerm, ePerm, v.decider.getAnalysisInfo(dAInfo)) if (mergeAndTrigger) { chunkSupporter.produce(s, s.h, ch, v)((s2, h2, v2) => { if (resource.isInstanceOf[ast.Predicate] && Verifier.config.enablePredicateTriggersOnInhale() && s2.functionRecorder == NoopFunctionRecorder @@ -522,7 +529,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { val predicate = resource.asInstanceOf[ast.Predicate] val argsString = eArgs.mkString(", ") val debugExp = Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}($argsString))", isInternal_ = true)) - v2.decider.assume(App(s2.predicateData(predicate.name).triggerFunction, snap1 +: tArgs), debugExp, AssumptionType.Trigger) + v2.decider.assume(App(s2.predicateData(predicate.name).triggerFunction, snap1 +: tArgs), debugExp, dAInfo.withDependencyType(DependencyType.Trigger)) } Q(s2.copy(h = h2), v2) }) @@ -543,10 +550,10 @@ class DefaultHeapSupportRules extends HeapSupportRules { returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, - dependencyType: DependencyType) + dAInfo: DependencyAnalysisInfo) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult): VerificationResult = { if(v.decider.isPathInfeasible()){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType, v.decider.analysisSourceInfoStack.isJoinRelevantNode) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, dAInfo) return Q(s, h, Some(Unit), v) } @@ -556,14 +563,14 @@ class DefaultHeapSupportRules extends HeapSupportRules { val tFormalArgs = s.getFormalArgVars(resource, v) val eFormalArgs = Option.when(withExp)(s.getFormalArgDecls(resource)) quantifiedChunkSupporter.consumeSingleLocation( - s, h, tFormalArgs, eFormalArgs, tArgs, eArgs, resAcc, tPerm, ePerm, returnSnap, None, pve, v, dependencyType)(Q) + s, h, tFormalArgs, eFormalArgs, tArgs, eArgs, resAcc, tPerm, ePerm, returnSnap, None, pve, v, dAInfo)(Q) } else { val ve = resAcc match { case l: ast.LocationAccess => pve dueTo InsufficientPermission(l) case w: ast.MagicWand => pve dueTo MagicWandChunkNotFound(w) } val description = s"consume ${resAcc.pos}: $resAcc" - chunkSupporter.consume(s, h, resource, tArgs, eArgs, tPerm, ePerm, returnSnap, ve, v, description, dependencyType)(Q) + chunkSupporter.consume(s, h, resource, tArgs, eArgs, tPerm, ePerm, returnSnap, ve, v, description, dAInfo)(Q) } } @@ -592,7 +599,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { negativePermissionReason: => ErrorReason, notInjectiveReason: => ErrorReason, v: Verifier, - assumptionType: AssumptionType) + dAInfo: DependencyAnalysisInfo) (Q: (State, Verifier) => VerificationResult): VerificationResult = { val tSnap = resource match { case f: ast.Field => @@ -629,7 +636,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { negativePermissionReason, notInjectiveReason, v, - assumptionType + dAInfo )(Q) } @@ -659,10 +666,10 @@ class DefaultHeapSupportRules extends HeapSupportRules { notInjectiveReason: => ErrorReason, insufficientPermissionReason: => ErrorReason, v: Verifier, - dependencyType: DependencyType) + dAInfo: DependencyAnalysisInfo) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult): VerificationResult = { if(v.decider.isPathInfeasible()){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType, v.decider.analysisSourceInfoStack.isJoinRelevantNode) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, dAInfo) return Q(s, h, Some(Unit), v) } @@ -693,7 +700,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { notInjectiveReason, insufficientPermissionReason, v, - dependencyType + dAInfo )(Q) } @@ -702,11 +709,11 @@ class DefaultHeapSupportRules extends HeapSupportRules { resource: ast.Resource, condInfo: HavocHelperData, v: Verifier, - assumptionType: AssumptionType): Heap = { + dAInfo: DependencyAnalysisInfo): Heap = { if (s.isQuantifiedResource(resource)) { - havocQuantifiedResource(s, lhs, resource, condInfo, v, assumptionType) + havocQuantifiedResource(s, lhs, resource, condInfo, v, dAInfo) } else { - havocNonQuantifiedResource(s, lhs, resource, condInfo, v, assumptionType) + havocNonQuantifiedResource(s, lhs, resource, condInfo, v, dAInfo) } } @@ -728,7 +735,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { resource: ast.Resource, condInfo: HavocHelperData, v: Verifier, - assumptionType: AssumptionType) + dAInfo: DependencyAnalysisInfo) : Heap = { val id = ChunkIdentifier(resource, s.program) @@ -739,12 +746,12 @@ class DefaultHeapSupportRules extends HeapSupportRules { val havockedSnap = v.decider.fresh("mwsf", sorts.MagicWandSnapFunction, Option.when(withExp)(PUnknown())) val cond = replacementCond(lhs, ch.args, condInfo) val magicWandSnapshot = MagicWandSnapshot(Ite(cond, havockedSnap, ch.snap.mwsf)) - NonQuantifiedChunk.withSnap(ch, magicWandSnapshot, None, v.decider.getAnalysisInfo(assumptionType)) + NonQuantifiedChunk.withSnap(ch, magicWandSnapshot, None, v.decider.getAnalysisInfo(dAInfo)) case ch => val havockedSnap = freshSnap(ch.snap.sort, v) val cond = replacementCond(lhs, ch.args, condInfo) - NonQuantifiedChunk.withSnap(ch, Ite(cond, havockedSnap, ch.snap), None, v.decider.getAnalysisInfo(assumptionType)) + NonQuantifiedChunk.withSnap(ch, Ite(cond, havockedSnap, ch.snap), None, v.decider.getAnalysisInfo(dAInfo)) } Heap(otherChunks ++ newChunks) } @@ -771,7 +778,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { resource: ast.Resource, condInfo: HavocHelperData, v: Verifier, - assumptionType: AssumptionType) : Heap = { + dAInfo: DependencyAnalysisInfo) : Heap = { // Quantified field chunks are of the form R(r; sm, pm). // Conceptually, quantified predicate/wand chunks look like R(r1, ..., rn; sm, pm). @@ -824,9 +831,9 @@ class DefaultHeapSupportRules extends HeapSupportRules { v.decider.prover.comment("axiomatized snapshot map after havoc") val debugExp = Option.when(withExp)(DebugExp.createInstance("havoc new axiom", isInternal_ = true)) - v.decider.assume(newAxiom, debugExp, assumptionType) + v.decider.assume(newAxiom, debugExp, dAInfo) - QuantifiedChunk.withSnapshotMap(ch, newSm, v.decider.getAnalysisInfo(assumptionType)) + QuantifiedChunk.withSnapshotMap(ch, newSm, v.decider.getAnalysisInfo(dAInfo)) } Heap(newChunks ++ otherChunks) } diff --git a/src/main/scala/rules/Joiner.scala b/src/main/scala/rules/Joiner.scala index f507f780a..fa7be35c0 100644 --- a/src/main/scala/rules/Joiner.scala +++ b/src/main/scala/rules/Joiner.scala @@ -6,11 +6,10 @@ package viper.silicon.rules -import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType -import viper.silicon.dependencyAnalysis.{AnalysisInfo, AssumptionType, StringAnalysisSourceInfo} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.decider.RecordedPathConditions +import viper.silicon.dependencyAnalysis.{AnalysisInfo, AssumptionType, DependencyAnalysisInfo, StringAnalysisSourceInfo} import viper.silicon.interfaces.{Success, VerificationResult} import viper.silicon.logger.records.structural.JoiningRecord import viper.silicon.state.State @@ -36,7 +35,7 @@ case class JoinDataEntry[D](s: State, data: D, pathConditions: RecordedPathCondi trait JoiningRules extends SymbolicExecutionRules { - def join[D, JD](s: State, v: Verifier, assumptionType: AssumptionType, resetState: Boolean = true) + def join[D, JD](s: State, v: Verifier, dAInfo: DependencyAnalysisInfo, resetState: Boolean = true) (block: (State, Verifier, (State, D, Verifier) => VerificationResult) => VerificationResult) (merge: Seq[JoinDataEntry[D]] => (State, JD)) (Q: (State, JD, Verifier) => VerificationResult) @@ -44,7 +43,7 @@ trait JoiningRules extends SymbolicExecutionRules { } object joiner extends JoiningRules { - def join[D, JD](s: State, v: Verifier, assumptionType: AssumptionType, resetState: Boolean = true) + def join[D, JD](s: State, v: Verifier, dAInfo: DependencyAnalysisInfo, resetState: Boolean = true) (block: (State, Verifier, (State, D, Verifier) => VerificationResult) => VerificationResult) (merge: Seq[JoinDataEntry[D]] => (State, JD)) (Q: (State, JD, Verifier) => VerificationResult) @@ -105,13 +104,13 @@ object joiner extends JoiningRules { val pcsExp = Option.when(withExp)(entry.pathConditions.conditionalizedExp) val comment = "Joined path conditions" v.decider.prover.comment(comment) - v.decider.assume(pcs, Option.when(withExp)(DebugExp.createInstance(comment, InsertionOrderedSet(pcsExp.get))), enforceAssumption = false, assumptionType) + v.decider.assume(pcs, Option.when(withExp)(DebugExp.createInstance(comment, InsertionOrderedSet(pcsExp.get))), enforceAssumption = false, dAInfo) feasibleBranches = And(entry.pathConditions.branchConditions) :: feasibleBranches feasibleBranchesExp = feasibleBranchesExp.map(fbe => BigAnd(entry.pathConditions.branchConditionExps.map(_._1)) :: fbe) feasibleBranchesExpNew = feasibleBranchesExpNew.map(fbe => BigAnd(entry.pathConditions.branchConditionExps.map(_._2.get)) :: fbe) }) // Assume we are in a feasible branch - v.decider.assume(Or(feasibleBranches), Option.when(withExp)(DebugExp.createInstance(Some("Feasible Branches"), feasibleBranchesExp.map(BigOr(_)), feasibleBranchesExpNew.map(BigOr(_)), InsertionOrderedSet.empty)), assumptionType) + v.decider.assume(Or(feasibleBranches), Option.when(withExp)(DebugExp.createInstance(Some("Feasible Branches"), feasibleBranchesExp.map(BigOr(_)), feasibleBranchesExpNew.map(BigOr(_)), InsertionOrderedSet.empty)), dAInfo) Q(sJoined, dataJoined, v) } } diff --git a/src/main/scala/rules/LetSupporter.scala b/src/main/scala/rules/LetSupporter.scala index 8707efd9e..577971960 100644 --- a/src/main/scala/rules/LetSupporter.scala +++ b/src/main/scala/rules/LetSupporter.scala @@ -6,6 +6,7 @@ package viper.silicon.rules +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfo import viper.silver.ast import viper.silver.verifier.PartialVerificationError import viper.silicon.interfaces.VerificationResult @@ -15,12 +16,14 @@ import viper.silicon.verifier.Verifier trait LetSupportRules extends SymbolicExecutionRules { def handle[E <: ast.Exp] - (s: State, e: ast.Exp, pve: PartialVerificationError, v: Verifier) + (s: State, e: ast.Exp, pve: PartialVerificationError, v: Verifier, + dAInfo: DependencyAnalysisInfo) (Q: (State, Store, E, Verifier) => VerificationResult) : VerificationResult def handle[E <: ast.Exp] - (s: State, let: ast.Let, pve: PartialVerificationError, v: Verifier) + (s: State, let: ast.Let, pve: PartialVerificationError, v: Verifier, + dAInfo: DependencyAnalysisInfo) (Q: (State, Store, E, Verifier) => VerificationResult) : VerificationResult } @@ -29,22 +32,24 @@ object letSupporter extends LetSupportRules { import evaluator._ def handle[E <: ast.Exp] - (s: State, e: ast.Exp, pve: PartialVerificationError, v: Verifier) + (s: State, e: ast.Exp, pve: PartialVerificationError, v: Verifier, + dAInfo: DependencyAnalysisInfo) (Q: (State, Store, E, Verifier) => VerificationResult) : VerificationResult = { e match { - case let: ast.Let => handle(s, Nil, let, pve, v)(Q) + case let: ast.Let => handle(s, Nil, let, pve, v, dAInfo)(Q) case _ => Q(s, Store(), e.asInstanceOf[E], v) } } def handle[E <: ast.Exp] - (s: State, let: ast.Let, pve: PartialVerificationError, v: Verifier) + (s: State, let: ast.Let, pve: PartialVerificationError, v: Verifier, + dAInfo: DependencyAnalysisInfo) (Q: (State, Store, E, Verifier) => VerificationResult) : VerificationResult = { - handle(s, Nil, let, pve, v)(Q) + handle(s, Nil, let, pve, v, dAInfo)(Q) } private def handle[E <: ast.Exp] @@ -52,17 +57,18 @@ object letSupporter extends LetSupportRules { bindings: Seq[(ast.AbstractLocalVar, (Term, Option[ast.Exp]))], let: ast.Let, pve: PartialVerificationError, - v: Verifier) + v: Verifier, + dAInfo: DependencyAnalysisInfo) (Q: (State, Store, E, Verifier) => VerificationResult) : VerificationResult = { val ast.Let(x, exp, body) = let - eval(s, exp, pve, v)((s1, t, expNew, v1) => { + eval(s, exp, pve, v, dAInfo)((s1, t, expNew, v1) => { val bindings1 = bindings :+ (x.localVar, (t, expNew)) val s2 = s1.copy(s1.g + (x.localVar, (t, expNew))) body match { - case nestedLet: ast.Let => handle(s2, bindings1, nestedLet, pve, v1)(Q) + case nestedLet: ast.Let => handle(s2, bindings1, nestedLet, pve, v1, dAInfo)(Q) case _ => Q(s2, Store(bindings1), body.asInstanceOf[E], v1)}}) } } diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 7fb554f27..481f2ce40 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -10,8 +10,7 @@ import viper.silicon._ import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.decider.RecordedPathConditions -import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType -import viper.silicon.dependencyAnalysis.{AssumptionType, DependencyType, ExpAnalysisSourceInfo} +import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfo, DependencyType, ExpAnalysisSourceInfo} import viper.silicon.interfaces._ import viper.silicon.interfaces.state._ import viper.silicon.state._ @@ -92,12 +91,12 @@ object magicWandSupporter extends SymbolicExecutionRules { snap: MagicWandSnapshot, pve: PartialVerificationError, v: Verifier, - assumptionType: AssumptionType) + dAInfo: DependencyAnalysisInfo) (Q: (State, MagicWandChunk, Verifier) => VerificationResult) : VerificationResult = { - evaluateWandArguments(s, wand, pve, v)((s1, ts, esNew, v1) => { + evaluateWandArguments(s, wand, pve, v, dAInfo)((s1, ts, esNew, v1) => { val newChunk = MagicWandChunk(MagicWandIdentifier(wand, s.program), s1.g.values, ts, esNew, snap, FullPerm, - Option.when(withExp)(ast.FullPerm()(wand.pos, wand.info, wand.errT)), v.decider.getAnalysisInfo(assumptionType)) + Option.when(withExp)(ast.FullPerm()(wand.pos, wand.info, wand.errT)), v.decider.getAnalysisInfo(dAInfo)) Q(s1, newChunk, v1) }) } @@ -115,14 +114,14 @@ object magicWandSupporter extends SymbolicExecutionRules { * @param v Verifier instance * @return Fresh instance of [[viper.silicon.state.terms.MagicWandSnapshot]] */ - def createMagicWandSnapshot(abstractLhs: Var, rhsSnapshot: Term, v: Verifier, assumptionType: AssumptionType): MagicWandSnapshot = { + def createMagicWandSnapshot(abstractLhs: Var, rhsSnapshot: Term, v: Verifier, dAInfo: DependencyAnalysisInfo): MagicWandSnapshot = { val mwsf = v.decider.fresh("mwsf", sorts.MagicWandSnapFunction, Option.when(withExp)(PUnknown())) val magicWandSnapshot = MagicWandSnapshot(mwsf) v.decider.assumeDefinition(Forall( abstractLhs, MWSFLookup(mwsf, abstractLhs) === rhsSnapshot, Trigger(MWSFLookup(mwsf, abstractLhs)) - ), Option.when(withExp)(DebugExp.createInstance("Magic wand snapshot definition", isInternal_ = true)), assumptionType) + ), Option.when(withExp)(DebugExp.createInstance("Magic wand snapshot definition", isInternal_ = true)), dAInfo) magicWandSnapshot } @@ -136,13 +135,14 @@ object magicWandSupporter extends SymbolicExecutionRules { def evaluateWandArguments(s: State, wand: ast.MagicWand, pve: PartialVerificationError, - v: Verifier) + v: Verifier, + dAInfo: DependencyAnalysisInfo) (Q: (State, Seq[Term], Option[Seq[ast.Exp]], Verifier) => VerificationResult) : VerificationResult = { val s1 = s.copy(exhaleExt = false) val es = wand.subexpressionsToEvaluate(s.program) - evals(s1, es, _ => pve, v)((s2, ts, esNew, v1) => { + evals(s1, es, _ => pve, v, dAInfo)((s2, ts, esNew, v1) => { Q(s2.copy(exhaleExt = s.exhaleExt), ts, esNew, v1) }) } @@ -154,7 +154,8 @@ object magicWandSupporter extends SymbolicExecutionRules { pLossExp: Option[ast.Exp], failure: Failure, qvars: Seq[Var], - v: Verifier) + v: Verifier, + dAInfo: DependencyAnalysisInfo) (consumeFunction: (State, Heap, Term, Option[ast.Exp], Verifier) => (ConsumptionResult, State, Heap, Option[CH])) (Q: (State, Stack[Heap], Stack[Option[CH]], Verifier) => VerificationResult) : VerificationResult = { @@ -185,7 +186,7 @@ object magicWandSupporter extends SymbolicExecutionRules { case (Some(ch1: QuantifiedBasicChunk), Some(ch2: QuantifiedBasicChunk)) => ch1.snapshotMap === ch2.snapshotMap case _ => True } - v.decider.assume(tEq, Option.when(withExp)(DebugExp.createInstance("Snapshots", isInternal_ = true)), AssumptionType.Internal) + v.decider.assume(tEq, Option.when(withExp)(DebugExp.createInstance("Snapshots", isInternal_ = true)), dAInfo) /* In the future it might be worth to recheck whether the permissions needed, in the case of * success being an instance of Incomplete, are zero. @@ -200,7 +201,7 @@ object magicWandSupporter extends SymbolicExecutionRules { * from heap, i.e. that tEq does not result in already having the required permissions before * consuming from heap. */ - if (v.decider.checkSmoke()) { + if (v.decider.checkSmoke(dAInfo)) { (Complete(), sOut, h +: hps, cch +: cchs) } else { (success, sOut, h +: hps, cch +: cchs) @@ -212,7 +213,7 @@ object magicWandSupporter extends SymbolicExecutionRules { assert(consumedChunks.length == hs.length) Q(s1, heaps.reverse, consumedChunks.reverse, v) case Incomplete(_, _) => - if(s1.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, v.decider.analysisSourceInfoStack.getAssertionType, v.reportFurtherErrors()) + if(s1.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, dAInfo, v.reportFurtherErrors()) if(s1.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s1, heaps.reverse, consumedChunks.reverse, v) else failure } } @@ -241,7 +242,7 @@ object magicWandSupporter extends SymbolicExecutionRules { proofScript: ast.Seqn, pve: PartialVerificationError, v: Verifier, - dependencyType: DependencyType = DependencyType.Rewrite) + dAInfo: DependencyAnalysisInfo) (Q: (State, Chunk, Verifier) => VerificationResult) : VerificationResult = { @@ -314,16 +315,16 @@ object magicWandSupporter extends SymbolicExecutionRules { freshSnapRoot: Var, snapRhs: Term, v: Verifier, - assumptionType: AssumptionType) + dAInfo: DependencyAnalysisInfo) : VerificationResult = { val preMark = v.decider.setPathConditionMark() v.decider.prover.comment(s"Create MagicWandSnapFunction for wand $wand") - val wandSnapshot = this.createMagicWandSnapshot(freshSnapRoot, snapRhs, v, assumptionType) + val wandSnapshot = this.createMagicWandSnapshot(freshSnapRoot, snapRhs, v, dAInfo) val bodyVars = wand.subexpressionsToEvaluate(s.program) - evals(s, bodyVars, _ => pve, v)((s2, tArgs, eArgsNew, v2) => { + evals(s, bodyVars, _ => pve, v, dAInfo)((s2, tArgs, eArgsNew, v2) => { // Currently, the snapshot of a wand differs depending on whether it is a quantified magic wand or not. // Therefore, we have to keep the case distinction here and cannot leave everything but the chunk creation // to the HeapSupporter. @@ -334,14 +335,14 @@ object magicWandSupporter extends SymbolicExecutionRules { val (sm, smValueDef) = quantifiedChunkSupporter.singletonSnapshotMap(s2, wand, tArgs, snapshotTerm, v2) v2.decider.prover.comment("Definitional axioms for singleton-SM's value") val debugExp = Option.when(withExp)(DebugExp.createInstance("Definitional axioms for singleton-SM's value", true)) - v2.decider.assumeDefinition(smValueDef, debugExp, assumptionType) + v2.decider.assumeDefinition(smValueDef, debugExp, dAInfo) val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(formalVars, formalVarExps, wand, tArgs, - eArgsNew, FullPerm, Option.when(withExp)(ast.FullPerm()()), sm, s.program, v, assumptionType, isExhale=false) + eArgsNew, FullPerm, Option.when(withExp)(ast.FullPerm()()), sm, s.program, v, dAInfo, isExhale=false) val conservedPcs = s2.conservedPcs.head :+ v2.decider.pcs.after(preMark).definitionsOnly (s2, ch, conservedPcs.flatMap(_.conditionalized), Option.when(withExp)(conservedPcs.flatMap(_.conditionalizedExp)), v2) } else { val ch = MagicWandChunk.apply(MagicWandIdentifier(wand, s.program), s2.g.values, tArgs, eArgsNew, wandSnapshot, FullPerm, - Option.when(withExp)(ast.FullPerm()(wand.pos, wand.info, wand.errT)), v.decider.getAnalysisInfo(assumptionType)) + Option.when(withExp)(ast.FullPerm()(wand.pos, wand.info, wand.errT)), v.decider.getAnalysisInfo(dAInfo)) val conservedPcs = s2.conservedPcs.head :+ v2.decider.pcs.after(preMark).definitionsOnly // Partition path conditions into a set which include the freshSnapRoot and those which do not val (pcsWithFreshSnapRoot, pcsWithoutFreshSnapRoot) = conservedPcs.flatMap(pcs => pcs.conditionalized).partition(_.contains(freshSnapRoot)) @@ -375,10 +376,8 @@ object magicWandSupporter extends SymbolicExecutionRules { val freshSnapRoot = freshSnap(sorts.Snap, v1) // Produce the wand's LHS. - val prevForcedSource = v.decider.analysisSourceInfoStack.getForcedSource - v.decider.analysisSourceInfoStack.setForcedSource(ExpAnalysisSourceInfo(wand.left, wand.left.pos), dependencyType) - produce(s1.copy(conservingSnapshotGeneration = true), toSf(freshSnapRoot), wand.left, pve, v1, dependencyType.assumptionType)((sLhs, v2) => { - v.decider.analysisSourceInfoStack.setForcedSource(prevForcedSource) + val dAInfoLeft = dAInfo.withSource(ExpAnalysisSourceInfo(wand.left, wand.left.pos)) + produce(s1.copy(conservingSnapshotGeneration = true), toSf(freshSnapRoot), wand.left, pve, v1, dAInfoLeft)((sLhs, v2) => { val proofScriptCfg = proofScript.toCfg() val emptyHeap = v2.heapSupporter.getEmptyHeap(sLhs.program) @@ -409,18 +408,15 @@ object magicWandSupporter extends SymbolicExecutionRules { // Execute proof script, i.e. the part written after the magic wand wrapped by curly braces. // The proof script should transform the current state such that we can consume the wand's RHS. - val prevSourceInfo = v2.decider.analysisSourceInfoStack.getAnalysisSourceInfos - v2.decider.analysisSourceInfoStack.setAnalysisSourceInfo(List.empty) executor.exec(s2, proofScriptCfg, v2)((proofScriptState, proofScriptVerifier) => { - v2.decider.analysisSourceInfoStack.setAnalysisSourceInfo(prevSourceInfo) // Consume the wand's RHS and produce a snapshot which records all the values of variables on the RHS. // This part indirectly calls the methods `this.transfer` and `this.consumeFromMultipleHeaps`. consume( proofScriptState.copy(oldHeaps = s2.oldHeaps, reserveCfgs = proofScriptState.reserveCfgs.tail), - wand.right, true, pve, proofScriptVerifier, dependencyType + wand.right, true, pve, proofScriptVerifier, dAInfo )((s3, snapRhs, v3) => { analysisLabels = v.decider.pcs.after(prePackageMark).analysisLabels - createWandChunkAndRecordResults(s3.copy(exhaleExt = false, oldHeaps = s.oldHeaps), freshSnapRoot, snapRhs.get, v3, dependencyType.assumptionType) + createWandChunkAndRecordResults(s3.copy(exhaleExt = false, oldHeaps = s.oldHeaps), freshSnapRoot, snapRhs.get, v3, dAInfo) }) }) }) @@ -432,11 +428,11 @@ object magicWandSupporter extends SymbolicExecutionRules { // Moreover, we need to set reserveHeaps to structurally match [State RHS] below. val emptyHeap = v.heapSupporter.getEmptyHeap(sEmp.program) val s1 = sEmp.copy(reserveHeaps = emptyHeap +: emptyHeap +: emptyHeap +: s.reserveHeaps.tail) - createWandChunkAndRecordResults(s1, freshSnap(sorts.Snap, v), freshSnap(sorts.Snap, v), v, dependencyType.assumptionType) + createWandChunkAndRecordResults(s1, freshSnap(sorts.Snap, v), freshSnap(sorts.Snap, v), v, dAInfo) } // some of the analysis labels, introduced while verifying the package statement, might be needed later on -> reassume them - analysisLabels foreach (l => v.decider.assume(v.decider.wrapWithDependencyAnalysisLabel(l, Set.empty, Set(l)), None, AssumptionType.Internal)) + analysisLabels foreach (l => v.decider.assume(v.decider.wrapWithDependencyAnalysisLabel(l, Set.empty, Set(l)), None, dAInfo.withDependencyType(DependencyType.Internal))) recordedBranches.foldLeft(tempResult)((prevRes, recordedState) => { prevRes && { @@ -452,10 +448,10 @@ object magicWandSupporter extends SymbolicExecutionRules { val exp = viper.silicon.utils.ast.BigAnd(branchConditionsExp.map(_._1)) val expNew = Option.when(withExp)(viper.silicon.utils.ast.BigAnd(branchConditionsExp.map(_._2.get))) // Set the branch conditions - v1.decider.setCurrentBranchCondition(And(branchConditions map (t => v1.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t)))), (exp, expNew), dependencyType.assumptionType) + v1.decider.setCurrentBranchCondition(And(branchConditions map (t => v1.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t)))), (exp, expNew), dAInfo) // Recreate all path conditions in the Z3 proof script that we recorded for that branch - v1.decider.assume(conservedPcs._1 map (t => v1.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t))), conservedPcs._2, AssumptionType.Internal) + v1.decider.assume(conservedPcs._1 map (t => v1.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t))), conservedPcs._2, dAInfo.withDependencyType(DependencyType.Internal)) // Execute the continuation Q Q(s2, magicWandChunk, v1) @@ -478,13 +474,13 @@ object magicWandSupporter extends SymbolicExecutionRules { wand: ast.MagicWand, pve: PartialVerificationError, v: Verifier, - dependencyType: DependencyType = DependencyType.Rewrite) + dAInfo: DependencyAnalysisInfo) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { // Consume the magic wand instance "A --* B". - consume(s, wand, true, pve, v, dependencyType)((s1, snapWand, v1) => { + consume(s, wand, true, pve, v, dAInfo)((s1, snapWand, v1) => { // Consume the wand's LHS "A". - consume(s1, wand.left, true, pve, v1, dependencyType)((s2, snapLhs, v2) => { + consume(s1, wand.left, true, pve, v1, dAInfo)((s2, snapLhs, v2) => { /* It is assumed that snap and MagicWandSnapshot.abstractLhs are structurally the same. * Equating the two snapshots is sound iff a wand is applied only once. * The old solution in this case did use this assumption: @@ -501,13 +497,13 @@ object magicWandSupporter extends SymbolicExecutionRules { case SortWrapper(snapshot: MagicWandSnapshot, _) => snapshot.applyToMWSF(snapLhs.get) // Fallback solution for quantified magic wands case predicateLookup: PredicateLookup => - v2.decider.assume(snapLhs.get === First(snapWand.get), Option.when(withExp)(DebugExp.createInstance("Magic wand snapshot", isInternal_ = true)), dependencyType.assumptionType) + v2.decider.assume(snapLhs.get === First(snapWand.get), Option.when(withExp)(DebugExp.createInstance("Magic wand snapshot", isInternal_ = true)), dAInfo) Second(predicateLookup) case _ => snapWand.get } // Produce the wand's RHS. - produce(s3.copy(conservingSnapshotGeneration = true), toSf(magicWandSnapshotLookup), wand.right, pve, v2, dependencyType.assumptionType)((s4, v3) => { + produce(s3.copy(conservingSnapshotGeneration = true), toSf(magicWandSnapshotLookup), wand.right, pve, v2, dAInfo)((s4, v3) => { // Recreate old state without the magic wand, and the state with the oldHeap called lhs. val s5 = s4.copy(g = s1.g, conservingSnapshotGeneration = s3.conservingSnapshotGeneration) @@ -526,7 +522,8 @@ object magicWandSupporter extends SymbolicExecutionRules { permsExp: Option[ast.Exp], failure: Failure, qvars: Seq[Var], - v: Verifier) + v: Verifier, + dAInfo: DependencyAnalysisInfo) (consumeFunction: (State, Heap, Term, Option[ast.Exp], Verifier) => (ConsumptionResult, State, Heap, Option[CH])) (Q: (State, Option[CH], Verifier) => VerificationResult) : VerificationResult = { @@ -543,13 +540,13 @@ object magicWandSupporter extends SymbolicExecutionRules { */ val preMark = v.decider.setPathConditionMark() executionFlowController.tryOrFail2[Stack[Heap], Stack[Option[CH]]](s, v)((s1, v1, QS) => - this.consumeFromMultipleHeaps(s1, s1.reserveHeaps.tail, perms, permsExp, failure, qvars, v1)(consumeFunction)(QS) + this.consumeFromMultipleHeaps(s1, s1.reserveHeaps.tail, perms, permsExp, failure, qvars, v1, dAInfo)(consumeFunction)(QS) )((s2, hs2, chs2, v2) => { val conservedPcs = s2.conservedPcs.head :+ v2.decider.pcs.after(preMark) val s3 = s2.copy(conservedPcs = conservedPcs +: s2.conservedPcs.tail, reserveHeaps = s.reserveHeaps.head +: hs2) val usedChunks = chs2.flatten - val (fr4, hUsed) = v2.stateConsolidator(s2).merge(s3.functionRecorder, s2, s2.reserveHeaps.head, Heap(usedChunks), v2) + val (fr4, hUsed) = v2.stateConsolidator(s2).merge(s3.functionRecorder, s2, s2.reserveHeaps.head, Heap(usedChunks), v2, dAInfo) val s4 = s3.copy(functionRecorder = fr4, reserveHeaps = hUsed +: s3.reserveHeaps.tail) @@ -595,7 +592,7 @@ object magicWandSupporter extends SymbolicExecutionRules { * heap. After a statement is executed those permissions are transferred to hOps. */ val emptyHeap = v.heapSupporter.getEmptyHeap(newState.program) - val (fr, hOpsJoinUsed) = v.stateConsolidator(newState).merge(newState.functionRecorder, newState, newState.reserveHeaps(1), newState.h, v) + val (fr, hOpsJoinUsed) = v.stateConsolidator(newState).merge(newState.functionRecorder, newState, newState.reserveHeaps(1), newState.h, v, dAInfo) newState.copy(functionRecorder = fr, h = emptyHeap, reserveHeaps = emptyHeap +: hOpsJoinUsed +: newState.reserveHeaps.drop(2)) } else newState diff --git a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala index 524916978..93c357412 100644 --- a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala +++ b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala @@ -7,8 +7,7 @@ package viper.silicon.rules import viper.silicon.debugger.DebugExp -import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType -import viper.silicon.dependencyAnalysis.{AssumptionType, DependencyType} +import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfo, DependencyType} import viper.silicon.interfaces.state._ import viper.silicon.interfaces.{Success, VerificationResult} import viper.silicon.resources.{FieldID, NonQuantifiedPropertyInterpreter, Resources} @@ -123,8 +122,8 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { case None => // We have not yet checked for a definite alias val id = ChunkIdentifier(resource, s.program) - val potentialAlias = chunkSupporter.findChunk[NonQuantifiedChunk](relevantChunks, id, args, v) - potentialAlias.filter(c => v.decider.check(IsPositive(c.perm), Verifier.config.checkTimeout())).map(_.snap) + val potentialAlias = chunkSupporter.findChunk[NonQuantifiedChunk](relevantChunks, id, args, v, dAInfo) + potentialAlias.filter(c => v.decider.check(IsPositive(c.perm), Verifier.config.checkTimeout(), dAInfo)).map(_.snap) case Some(v) => // We have checked for a definite alias and may or may not have found one. v @@ -169,13 +168,13 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { if (relevantChunks.size == 1 && !Verifier.config.counterexample.isDefined) { val chunk = relevantChunks.head val argsEqual = And(chunk.args.zip(args).map { case (t1, t2) => t1 === t2 }) - if (v.decider.check(argsEqual, Verifier.config.checkTimeout())) { + if (v.decider.check(argsEqual, Verifier.config.checkTimeout(), dAInfo)) { return Q(s, chunk.snap, chunk.perm, chunk.permExp, v) } } val (s1, taggedSnap, snapDefs, permSum, permSumExp) = summariseOnly(s, relevantChunks, resource, args, argsExp, knownValue, v) - v.decider.assumeDefinition(And(snapDefs), Option.when(withExp)(DebugExp.createInstance("Snapshot", isInternal_ = true)), AssumptionType.Internal) + v.decider.assumeDefinition(And(snapDefs), Option.when(withExp)(DebugExp.createInstance("Snapshot", isInternal_ = true)), dAInfo.withDependencyType(DependencyType.Internal)) // v.decider.assume(PermAtMost(permSum, FullPerm())) /* Done in StateConsolidator instead */ val s2 = @@ -199,7 +198,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { argsExp: Option[Seq[ast.Exp]], ve: VerificationError, v: Verifier, - assumptionType: AssumptionType) + dAInfo: DependencyAnalysisInfo) (Q: (State, Term, Verifier) => VerificationResult) : VerificationResult = { @@ -207,7 +206,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { val relevantChunks = findChunksWithID[NonQuantifiedChunk](h.values, id).toSeq if (relevantChunks.isEmpty) { - if (v.decider.checkSmoke(isAssert = true)) { + if (v.decider.checkSmoke(dAInfo, isAssert = true)) { if (s.isInPackage || Verifier.config.disableInfeasibilityChecks()) { val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) Q(s, snap, v) @@ -216,7 +215,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { } } else { val failure = createFailure(ve, v, s, False, "branch is dead") - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, v.decider.analysisSourceInfoStack.getAssertionType, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, dAInfo, v.reportFurtherErrors()) if(s.retryLevel == 0 && v.reportFurtherErrors() && Verifier.config.disableInfeasibilityChecks()){ val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) failure combine Q(s, snap, v) @@ -226,12 +225,12 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { } } else { summarise(s, relevantChunks, resource, args, argsExp, None, v)((s1, snap, permSum, permSumExp, v1) => - v.decider.assert(IsPositive(permSum), assumptionType) { + v.decider.assert(IsPositive(permSum), dAInfo) { case true => Q(s1, snap, v1) case false => val failure = createFailure(ve, v, s1, IsPositive(permSum), permSumExp.map(IsPositive(_)())) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(IsPositive(permSum), assumptionType, v1.reportFurtherErrors()) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(IsPositive(permSum), dAInfo, v1.reportFurtherErrors()) if(s1.retryLevel == 0 && v1.reportFurtherErrors()) failure combine Q(s1, snap, v1) else failure }) } @@ -247,14 +246,14 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { returnSnap: Boolean, ve: VerificationError, v: Verifier, - dependencyType: DependencyType) + dAInfo: DependencyAnalysisInfo) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { if (!s.assertReadAccessOnly) - actualConsumeComplete(s, h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, dependencyType)(Q) + actualConsumeComplete(s, h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, dAInfo)(Q) else - summariseHeapAndAssertReadAccess(s, h, resource, perms, args, argsExp, returnSnap, ve, v, dependencyType.assertionType)(Q) + summariseHeapAndAssertReadAccess(s, h, resource, perms, args, argsExp, returnSnap, ve, v, dAInfo)(Q) } private def summariseHeapAndAssertReadAccess(s: State, @@ -266,7 +265,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { returnSnap: Boolean, ve: VerificationError, v: Verifier, - assertionType: AssumptionType) + dAInfo: DependencyAnalysisInfo) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -275,22 +274,22 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { if (returnSnap) { summarise(s, relevantChunks, resource, args, argsExp, None, v)((s1, snap, permSum, permSumExp, v1) => - v.decider.assert(Implies(IsPositive(perm), IsPositive(permSum)), assertionType) { + v.decider.assert(Implies(IsPositive(perm), IsPositive(permSum)), dAInfo) { case true => Q(s1, h, Some(snap), v1) case false => val failure = createFailure(ve, v, s1, IsPositive(permSum), permSumExp.map(IsPositive(_)())) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(Implies(IsPositive(perm), IsPositive(permSum)), assertionType, v1.reportFurtherErrors()) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(Implies(IsPositive(perm), IsPositive(permSum)), dAInfo, v1.reportFurtherErrors()) if(s1.retryLevel == 0 && v1.reportFurtherErrors()) failure combine Q(s1, h, Some(snap), v1) else failure }) } else { val (s1, permSum, permSumExp) = permSummariseOnly(s, relevantChunks, resource, args, argsExp) - v.decider.assert(Implies(IsPositive(perm), IsPositive(permSum)), assertionType) { + v.decider.assert(Implies(IsPositive(perm), IsPositive(permSum)), dAInfo) { case true => Q(s1, h, None, v) case false => val failure = createFailure(ve, v, s1, IsPositive(permSum), permSumExp.map(IsPositive(_)())) - if(s1.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(Implies(IsPositive(perm), IsPositive(permSum)), assertionType, v.reportFurtherErrors()) + if(s1.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(Implies(IsPositive(perm), IsPositive(permSum)), dAInfo, v.reportFurtherErrors()) if(s1.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s1, h, None, v) else failure } } @@ -306,7 +305,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { returnSnap: Boolean, ve: VerificationError, v: Verifier, - dependencyType: DependencyType) + dAInfo: DependencyAnalysisInfo) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -320,16 +319,16 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { if (relevantChunks.isEmpty) { // if no permission is exhaled, return none - v.decider.assert(perms === NoPerm, dependencyType.assertionType) { + v.decider.assert(perms === NoPerm, dAInfo) { case true => Q(s, h, None, v) case false => val failure = createFailure(ve, v, s, perms === NoPerm, permsExp.map(pe => ast.EqCmp(pe, ast.NoPerm()())(pe.pos, pe.info, pe.errT))) - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(perms === NoPerm, dependencyType.assertionType, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(perms === NoPerm, dAInfo, v.reportFurtherErrors()) if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, h, None, v) else failure } } else { if (!terms.utils.consumeExactRead(perms, s.constrainableARPs)) { - actualConsumeCompleteConstrainable(s, relevantChunks, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, dependencyType)((s1, updatedChunks, optSnap, v2) => { + actualConsumeCompleteConstrainable(s, relevantChunks, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, dAInfo)((s1, updatedChunks, optSnap, v2) => { Q(s1, Heap(updatedChunks ++ otherChunks), optSnap, v2) }) } else { @@ -340,8 +339,8 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { val newChunks = ListBuffer[NonQuantifiedChunk]() var moreNeeded = true - val definiteAlias = chunkSupporter.findChunk[NonQuantifiedChunk](relevantChunks, id, args, v).filter(c => - v.decider.check(IsPositive(c.perm), Verifier.config.checkTimeout()) + val definiteAlias = chunkSupporter.findChunk[NonQuantifiedChunk](relevantChunks, id, args, v, dAInfo).filter(c => + v.decider.check(IsPositive(c.perm), Verifier.config.checkTimeout(), dAInfo) ) val sortFunction: (NonQuantifiedChunk, NonQuantifiedChunk) => Boolean = (ch1, ch2) => { @@ -382,16 +381,16 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { pSum = PermPlus(pSum, Ite(eq, ch.perm, NoPerm)) pSumExp = eqExp.map(eq => ast.PermAdd(pSumExp.get, ast.CondExp(eq, ch.permExp.get, ast.NoPerm()())(eq.pos, eq.info, eq.errT))()) - val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, pTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, pTakenExp.get)(pe.pos, pe.info, pe.errT)), v.decider.getAnalysisInfo(dependencyType.assumptionType)).asInstanceOf[NonQuantifiedChunk] - val _ = GeneralChunk.withPerm(ch, pTaken, None, v.decider.getAnalysisInfo(dependencyType.assertionType), isExhale=true) + val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, pTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, pTakenExp.get)(pe.pos, pe.info, pe.errT)), v.decider.getAnalysisInfo(dAInfo)).asInstanceOf[NonQuantifiedChunk] + val _ = GeneralChunk.withPerm(ch, pTaken, None, v.decider.getAnalysisInfo(dAInfo), isExhale=true) pNeeded = PermMinus(pNeeded, pTaken) pNeededExp = permsExp.map(pe => ast.PermSub(pNeededExp.get, pTakenExp.get)(pe.pos, pe.info, pe.errT)) - if (!v.decider.check(IsNonPositive(newChunk.perm), Verifier.config.splitTimeout(), AssumptionType.Internal)) { + if (!v.decider.check(IsNonPositive(newChunk.perm), Verifier.config.splitTimeout(), dAInfo.withDependencyType(DependencyType.Internal))) { newChunks.append(newChunk) } - moreNeeded = !v.decider.check(pNeeded === NoPerm, Verifier.config.splitTimeout(), dependencyType.assumptionType) + moreNeeded = !v.decider.check(pNeeded === NoPerm, Verifier.config.splitTimeout(), dAInfo) } else { newChunks.append(ch) } @@ -404,7 +403,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { newChunks foreach { ch => val resource = Resources.resourceDescriptions(ch.resourceID) val pathCond = interpreter.buildPathConditionsForChunk(ch, resource.instanceProperties(s.mayAssumeUpperBounds)) - pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), dependencyType.assumptionType)) + pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), dAInfo)) } val newHeap = Heap(allChunks) @@ -414,7 +413,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { if (returnSnap) { summarise(s0, relevantChunks.toSeq, resource, args, argsExp, Some(definiteAlias.map(_.snap)), v)((s1, snap, _, _, v1) => { - val condSnap = Some(if (v1.decider.check(IsPositive(perms), Verifier.config.checkTimeout(), AssumptionType.Internal)) { + val condSnap = Some(if (v1.decider.check(IsPositive(perms), Verifier.config.checkTimeout(), dAInfo.withDependencyType(DependencyType.Internal))) { snap } else { Ite(IsPositive(perms), snap.convert(sorts.Snap), Unit) @@ -422,12 +421,12 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { if (!moreNeeded) { Q(s1, newHeap, condSnap, v1) } else { - v1.decider.assert(pNeeded === NoPerm, dependencyType.assertionType) { + v1.decider.assert(pNeeded === NoPerm, dAInfo) { case true => Q(s1, newHeap, condSnap, v1) case false => val failure = createFailure(ve, v1, s1, pNeeded === NoPerm, pNeededExp.map(pn => ast.EqCmp(pn, ast.NoPerm()())(pn.pos, pn.info, pn.errT))) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(pNeeded === NoPerm, dependencyType.assertionType, v1.reportFurtherErrors()) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(pNeeded === NoPerm, dAInfo, v1.reportFurtherErrors()) if(s1.retryLevel == 0 && v1.reportFurtherErrors()) failure combine Q(s1, newHeap, condSnap, v1) else failure } } @@ -436,12 +435,12 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { if (!moreNeeded) { Q(s0, newHeap, None, v) } else { - v.decider.assert(pNeeded === NoPerm, dependencyType.assertionType) { + v.decider.assert(pNeeded === NoPerm, dAInfo) { case true => Q(s0, newHeap, None, v) case false => val failure = createFailure(ve, v, s0, pNeeded === NoPerm, pNeededExp.map(pn => ast.EqCmp(pn, ast.NoPerm()())(pn.pos, pn.info, pn.errT))) - if(s0.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(pNeeded === NoPerm, dependencyType.assertionType, v.reportFurtherErrors()) + if(s0.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(pNeeded === NoPerm, dAInfo, v.reportFurtherErrors()) if(s0.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s0, newHeap, None, v) else failure } } @@ -460,7 +459,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { returnSnap: Boolean, ve: VerificationError, v: Verifier, - dependencyType: DependencyType) + dAInfo: DependencyAnalysisInfo) (Q: (State, ListBuffer[NonQuantifiedChunk], Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -497,13 +496,13 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { ast.Implies(ast.Not(eqExp.get)(), ast.EqCmp(permTakenExp.get, ast.NoPerm()())())(pe.pos, pe.info, pe.errT)))) - v.decider.assume(constraint, Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp)), dependencyType.assumptionType) + v.decider.assume(constraint, Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp)), dAInfo) newFr = newFr.recordPathSymbol(permTaken.applicable.asInstanceOf[Function]).recordConstraint(constraint) @unused // required in order to ensure a sound dependency analysis - val _ = GeneralChunk.withPerm(ch, permTaken, None, v.decider.getAnalysisInfo(dependencyType.assertionType), isExhale=true) - NonQuantifiedChunk.withPerm(ch, PermMinus(ch.perm, permTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, permTakenExp.get)(pe.pos, pe.info, pe.errT)), v.decider.getAnalysisInfo(dependencyType.assumptionType)) + val _ = GeneralChunk.withPerm(ch, permTaken, None, v.decider.getAnalysisInfo(dAInfo), isExhale=true) + NonQuantifiedChunk.withPerm(ch, PermMinus(ch.perm, permTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, permTakenExp.get)(pe.pos, pe.info, pe.errT)), v.decider.getAnalysisInfo(dAInfo)) }) val totalTakenBounds = @@ -515,16 +514,16 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { val constraintExp = permsExp.map(pe => ast.Implies(ast.NeCmp(totalPermSumExp.get, ast.NoPerm()())(), ast.And(ast.PermLeCmp(ast.NoPerm()(), totalPermTakenExp.get)(), ast.PermLeCmp(totalPermTakenExp.get, totalPermSumExp.get)())(pe.pos, pe.info, pe.errT))()) - v.decider.assume(totalTakenBounds, constraintExp, constraintExp, dependencyType.assumptionType) + v.decider.assume(totalTakenBounds, constraintExp, constraintExp, dAInfo) newFr = newFr.recordConstraint(totalTakenBounds) val s1 = s.copy(functionRecorder = newFr) - v.decider.assert(Implies(PermLess(NoPerm, perms), totalPermTaken !== NoPerm), dependencyType.assertionType) { + v.decider.assert(Implies(PermLess(NoPerm, perms), totalPermTaken !== NoPerm), dAInfo) { case true => val constraintExp = permsExp.map(pe => ast.EqCmp(pe, totalPermTakenExp.get)()) - v.decider.assume(perms === totalPermTaken, Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp)), dependencyType.assumptionType) + v.decider.assume(perms === totalPermTaken, Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp)), dAInfo) if (returnSnap) { summarise(s1, relevantChunks.toSeq, resource, args, argsExp, None, v)((s2, snap, _, _, v1) => Q(s2, updatedChunks, Some(snap), v1)) @@ -534,7 +533,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { case false => v.decider.finishDebugSubExp(s"consume permissions for ${resource.toString()}") val failure = createFailure(ve, v, s, totalPermTaken !== NoPerm, totalPermTakenExp.map(tpt => ast.NeCmp(tpt, ast.NoPerm()())())) - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(Implies(PermLess(NoPerm, perms), totalPermTaken !== NoPerm), dependencyType.assertionType, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(Implies(PermLess(NoPerm, perms), totalPermTaken !== NoPerm), dAInfo, v.reportFurtherErrors()) if(s.retryLevel == 0 && v.reportFurtherErrors()){ val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) // TODO ake: function recorder failure combine Q(s1, updatedChunks, if(returnSnap) Some(snap) else None, v) @@ -547,7 +546,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { private val freeReceiver = Var(Identifier("?rcvr"), sorts.Ref, false) private val freeReceiverExp = ast.LocalVar("?rcvr", ast.Ref)() - def assumeFieldPermissionUpperBounds(h: Heap, v: Verifier): Unit = { + def assumeFieldPermissionUpperBounds(h: Heap, v: Verifier, dAInfo: DependencyAnalysisInfo): Unit = { // TODO: Instead of "manually" assuming such upper bounds, appropriate PropertyInterpreters // should be used, see StateConsolidator val relevantChunksPerField = MMap.empty[String, MList[BasicChunk]] @@ -575,7 +574,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { relevantChunks foreach (chunk => { val instantiatedPermSum = permissionSum.replace(freeReceiver, chunk.args.head) val exp = permissionSumExp.map(pse => ast.PermLeCmp(replaceVarsInExp(pse, Seq(freeReceiverExp.name), Seq(chunk.argsExp.get.head)), ast.FullPerm()())()) - v.decider.assume(PermAtMost(instantiatedPermSum, FullPerm), exp, exp, AssumptionType.Internal) + v.decider.assume(PermAtMost(instantiatedPermSum, FullPerm), exp, exp, dAInfo.withDependencyType(DependencyType.Internal)) }) } } diff --git a/src/main/scala/rules/PermissionSupporter.scala b/src/main/scala/rules/PermissionSupporter.scala index 347290f29..5be402858 100644 --- a/src/main/scala/rules/PermissionSupporter.scala +++ b/src/main/scala/rules/PermissionSupporter.scala @@ -6,16 +6,17 @@ package viper.silicon.rules -import viper.silver.ast -import viper.silver.verifier.PartialVerificationError +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfo import viper.silicon.interfaces.VerificationResult import viper.silicon.state.State import viper.silicon.state.terms.{Term, Var, perms} import viper.silicon.verifier.Verifier +import viper.silver.ast +import viper.silver.verifier.PartialVerificationError import viper.silver.verifier.reasons.{NegativePermission, NonPositivePermission} object permissionSupporter extends SymbolicExecutionRules { - def assertNotNegative(s: State, tPerm: Term, ePerm: ast.Exp, ePermNew: Option[ast.Exp], pve: PartialVerificationError, v: Verifier) + def assertNotNegative(s: State, tPerm: Term, ePerm: ast.Exp, ePermNew: Option[ast.Exp], pve: PartialVerificationError, v: Verifier, dAInfo: DependencyAnalysisInfo) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -23,18 +24,18 @@ object permissionSupporter extends SymbolicExecutionRules { case k: Var if s.constrainableARPs.contains(k) => Q(s, v) case _ => - v.decider.assert(perms.IsNonNegative(tPerm)) { + v.decider.assert(perms.IsNonNegative(tPerm), dAInfo) { case true => Q(s, v) case false => val assertExp = ePermNew.map(ep => perms.IsNonNegative(ep)(ep.pos, ep.info, ep.errT)) val failure = createFailure(pve dueTo NegativePermission(ePerm), v, s, perms.IsNonNegative(tPerm), assertExp) - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(perms.IsNonNegative(tPerm), v.decider.analysisSourceInfoStack.getAssertionType, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(perms.IsNonNegative(tPerm), dAInfo, v.reportFurtherErrors()) if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, v) else failure } } } - def assertPositive(s: State, tPerm: Term, ePerm: ast.Exp, pve: PartialVerificationError, v: Verifier) + def assertPositive(s: State, tPerm: Term, ePerm: ast.Exp, pve: PartialVerificationError, v: Verifier, dAInfo: DependencyAnalysisInfo) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -42,11 +43,11 @@ object permissionSupporter extends SymbolicExecutionRules { case k: Var if s.constrainableARPs.contains(k) => Q(s, v) case _ => - v.decider.assert(perms.IsPositive(tPerm)) { + v.decider.assert(perms.IsPositive(tPerm), dAInfo) { case true => Q(s, v) case false => val failure = createFailure(pve dueTo NonPositivePermission(ePerm), v, s, perms.IsPositive(tPerm), Option.when(withExp)(perms.IsPositive(ePerm)())) - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(perms.IsPositive(tPerm), v.decider.analysisSourceInfoStack.getAssertionType, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(perms.IsPositive(tPerm), dAInfo, v.reportFurtherErrors()) if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, v) else failure } } diff --git a/src/main/scala/rules/PredicateSupporter.scala b/src/main/scala/rules/PredicateSupporter.scala index e7d31266c..a94819679 100644 --- a/src/main/scala/rules/PredicateSupporter.scala +++ b/src/main/scala/rules/PredicateSupporter.scala @@ -8,17 +8,16 @@ package viper.silicon.rules import viper.silicon import viper.silicon.Config.JoinMode -import viper.silicon.debugger.DebugExp import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp -import viper.silicon.dependencyAnalysis.{AssumptionType, DependencyType} +import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfo, DependencyType} import viper.silicon.interfaces.VerificationResult import viper.silicon.interfaces.state.{ChunkIdentifer, GeneralChunk, NonQuantifiedChunk} import viper.silicon.resources.FieldID import viper.silicon.state._ import viper.silicon.state.terms._ import viper.silicon.state.terms.predef.`?r` -import viper.silicon.supporters.{PredicateBranchNode, PredicateLeafNode, PredicateContentsTree} +import viper.silicon.supporters.{PredicateBranchNode, PredicateContentsTree, PredicateLeafNode} import viper.silicon.utils.toSf import viper.silicon.verifier.Verifier import viper.silver.ast @@ -34,7 +33,7 @@ trait PredicateSupportRules extends SymbolicExecutionRules { constrainableWildcards: InsertionOrderedSet[Var], pve: PartialVerificationError, v: Verifier, - dependencyType: DependencyType = DependencyType.Rewrite) + dAInfo: DependencyAnalysisInfo) (Q: (State, Verifier) => VerificationResult) : VerificationResult @@ -48,7 +47,7 @@ trait PredicateSupportRules extends SymbolicExecutionRules { pve: PartialVerificationError, v: Verifier, pa: ast.PredicateAccess, - dependencyType: DependencyType = DependencyType.Rewrite) + dAInfo: DependencyAnalysisInfo) (Q: (State, Verifier) => VerificationResult) : VerificationResult } @@ -66,7 +65,7 @@ object predicateSupporter extends PredicateSupportRules { constrainableWildcards: InsertionOrderedSet[Var], pve: PartialVerificationError, v: Verifier, - dependencyType: DependencyType = DependencyType.Rewrite) + dAInfo: DependencyAnalysisInfo) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -80,33 +79,33 @@ object predicateSupporter extends PredicateSupportRules { val s1 = s.copy(g = gIns, smDomainNeeded = true) .scalePermissionFactor(tPerm, ePerm) - consume(s1, body, true, pve, v, dependencyType)((s1a, snap, v1) => { + consume(s1, body, true, pve, v, dAInfo)((s1a, snap, v1) => { if (!Verifier.config.disableFunctionUnfoldTrigger()) { val predTrigger = App(s1a.predicateData(predicate.name).triggerFunction, snap.get.convert(terms.sorts.Snap) +: tArgs) val eArgsString = eArgs.mkString(", ") - v1.decider.assume(predTrigger, Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}($eArgsString))")), AssumptionType.Trigger) + v1.decider.assume(predTrigger, Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}($eArgsString))")), dAInfo) } val s2 = s1a.copy(g = s.g, smDomainNeeded = s.smDomainNeeded, permissionScalingFactor = s.permissionScalingFactor, permissionScalingFactorExp = s.permissionScalingFactorExp).setConstrainable(constrainableWildcards, false) - v1.heapSupporter.produceSingle(s2, predicate, tArgs, eArgs, snap.get.convert(s2.predicateSnapMap(predicate.name)), None, tPerm, ePerm, pve, true, v1, dependencyType)((s3, v3) => { - val s4 = v3.heapSupporter.triggerResourceIfNeeded(s3, pa, tArgs, eArgs, v3) + v1.heapSupporter.produceSingle(s2, predicate, tArgs, eArgs, snap.get.convert(s2.predicateSnapMap(predicate.name)), None, tPerm, ePerm, pve, true, v1, dAInfo)((s3, v3) => { + val s4 = v3.heapSupporter.triggerResourceIfNeeded(s3, pa, tArgs, eArgs, v3, dAInfo) Q(s4, v3) }) }) } - def producePredicateContents(s: State, tree: PredicateContentsTree, toReplace: silicon.Map[Term, Term], v: Verifier, dependencyType: DependencyType, isUnfolding: Boolean = false) + def producePredicateContents(s: State, tree: PredicateContentsTree, toReplace: silicon.Map[Term, Term], v: Verifier, dAInfo: DependencyAnalysisInfo, isUnfolding: Boolean = false) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { tree match { case PredicateLeafNode(h, assumptions) => val debugExp = Option.when(withExp)(DebugExp.createInstance("Assumption from unfolded predicate body")) - assumptions.foreach(a => v.decider.assume(a.replace(toReplace), debugExp, dependencyType.assumptionType)) - val substChunks = h.values.map(chunk => GeneralChunk.permScale(GeneralChunk.substitute(chunk.asInstanceOf[GeneralChunk], toReplace, v.decider.getAnalysisInfo(dependencyType.assumptionType)), s.permissionScalingFactor, s.permissionScalingFactorExp, v.decider.getAnalysisInfo(dependencyType.assumptionType))) + assumptions.foreach(a => v.decider.assume(a.replace(toReplace), debugExp, dAInfo)) + val substChunks = h.values.map(chunk => GeneralChunk.permScale(GeneralChunk.substitute(chunk.asInstanceOf[GeneralChunk], toReplace, v.decider.getAnalysisInfo(dAInfo)), s.permissionScalingFactor, s.permissionScalingFactorExp, v.decider.getAnalysisInfo(dAInfo))) val quantifiedResourceIdentifiers: Set[ChunkIdentifer] = s.qpPredicates.map(p => BasicChunkIdentifier(p.name)) ++ s.qpFields.map(f => BasicChunkIdentifier(f.name)) ++ s.qpMagicWands @@ -120,28 +119,28 @@ object predicateSupporter extends PredicateSupportRules { case _ => s.program.findPredicate(bc.id.name) } val (sm, smValueDef) = quantifiedChunkSupporter.singletonSnapshotMap(s, resource, bc.args, bc.snap, v) - v.decider.assumeDefinition(smValueDef, None, dependencyType.assumptionType) + v.decider.assumeDefinition(smValueDef, None, dAInfo) val codQvars = bc.resourceID match { case FieldID => Seq(`?r`) case _ => s.predicateFormalVarMap(resource.asInstanceOf[ast.Predicate].name) } newFr = newFr.recordFvfAndDomain(SnapshotMapDefinition(resource, sm, Seq(smValueDef), Seq())) - quantifiedChunkSupporter.createSingletonQuantifiedChunk(codQvars, None, resource, bc.args, None, bc.perm, None, sm, s.program, v, dependencyType.assumptionType, isExhale=false) + quantifiedChunkSupporter.createSingletonQuantifiedChunk(codQvars, None, resource, bc.args, None, bc.perm, None, sm, s.program, v, dAInfo, isExhale=false) case mwc: MagicWandChunk => val wand = mwc.id.ghostFreeWand val bodyVars = wand.subexpressionsToEvaluate(s.program) val codQvars = bodyVars.indices.toList.map(i => Var(Identifier(s"x$i"), v.symbolConverter.toSort(bodyVars(i).typ), false)) val (sm, smValueDef) = quantifiedChunkSupporter.singletonSnapshotMap(s, wand, mwc.args, mwc.snap, v) - v.decider.assumeDefinition(smValueDef, None, dependencyType.assumptionType) + v.decider.assumeDefinition(smValueDef, None, dAInfo) newFr = newFr.recordFvfAndDomain(SnapshotMapDefinition(wand, sm, Seq(smValueDef), Seq())) - quantifiedChunkSupporter.createSingletonQuantifiedChunk(codQvars, None, wand, mwc.args, None, mwc.perm, None, sm, s.program, v, dependencyType.assumptionType, isExhale=false) + quantifiedChunkSupporter.createSingletonQuantifiedChunk(codQvars, None, wand, mwc.args, None, mwc.perm, None, sm, s.program, v, dAInfo, isExhale=false) } } else { c } }) val substHeap = Heap(substChunksOptQps) - val (fr1, h1) = v.stateConsolidator(s).merge(newFr, s, s.h, substHeap, v) + val (fr1, h1) = v.stateConsolidator(s).merge(newFr, s, s.h, substHeap, v, dAInfo) val s1 = s.copy(h = h1, functionRecorder = fr1) Q(s1, v) @@ -149,13 +148,13 @@ object predicateSupporter extends PredicateSupportRules { val substCond = cond.replace(toReplace) if (!isUnfolding && s.moreJoins.id >= JoinMode.Impure.id) { - joiner.join[scala.Null, scala.Null](s, v, dependencyType.assumptionType, resetState = false)((s1, v1, QB) => { - brancher.branch(s1, substCond, condExp, v1, dependencyType.assumptionType)( + joiner.join[scala.Null, scala.Null](s, v, dAInfo, resetState = false)((s1, v1, QB) => { + brancher.branch(s1, substCond, condExp, v1, dAInfo)( (s2, v2) => { - producePredicateContents(s2, left, toReplace, v2, dependencyType, isUnfolding)((s3, v3) => QB(s3, null, v3)) + producePredicateContents(s2, left, toReplace, v2, dAInfo, isUnfolding)((s3, v3) => QB(s3, null, v3)) }, (s2, v2) => { - producePredicateContents(s2, right, toReplace, v2, dependencyType, isUnfolding)((s3, v3) => QB(s3, null, v3)) + producePredicateContents(s2, right, toReplace, v2, dAInfo, isUnfolding)((s3, v3) => QB(s3, null, v3)) } ) }) (entries => { @@ -170,12 +169,12 @@ object predicateSupporter extends PredicateSupportRules { (s2, null) }) ((sp, _, vp) => Q(sp, vp)) } else { - brancher.branch(s, substCond, condExp, v, dependencyType.assumptionType)( + brancher.branch(s, substCond, condExp, v, dAInfo)( (s1, v1) => { - producePredicateContents(s1, left, toReplace, v1, dependencyType, isUnfolding)(Q) + producePredicateContents(s1, left, toReplace, v1, dAInfo, isUnfolding)(Q) }, (s2, v2) => { - producePredicateContents(s2, right, toReplace, v2, dependencyType, isUnfolding)(Q) + producePredicateContents(s2, right, toReplace, v2, dAInfo, isUnfolding)(Q) } ) } @@ -192,7 +191,7 @@ object predicateSupporter extends PredicateSupportRules { pve: PartialVerificationError, v: Verifier, pa: ast.PredicateAccess, - dependencyType: DependencyType = DependencyType.Rewrite) + dAInfo: DependencyAnalysisInfo) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -204,19 +203,19 @@ object predicateSupporter extends PredicateSupportRules { val body = predicate.body.get /* Only non-abstract predicates can be unfolded */ val s1 = s.scalePermissionFactor(tPerm, ePerm) - v.heapSupporter.consumeSingle(s1, s1.h, pa, tArgs, eArgs, tPerm, ePerm, true, pve, v, dependencyType)((s2, h2, snap, v1) => { + v.heapSupporter.consumeSingle(s1, s1.h, pa, tArgs, eArgs, tPerm, ePerm, true, pve, v, dAInfo)((s2, h2, snap, v1) => { val s3 = s2.copy(g = gIns, h = h2) .setConstrainable(constrainableWildcards, false) if (s3.predicateData(predicate.name).predContents.isDefined) { val toReplace: silicon.Map[Term, Term] = silicon.Map.from(s3.predicateData(predicate.name).params.get.zip(Seq(snap.get) ++ tArgs)) - producePredicateContents(s3, s3.predicateData(predicate.name).predContents.get, toReplace, v1, dependencyType, false)((s4, v4) => { + producePredicateContents(s3, s3.predicateData(predicate.name).predContents.get, toReplace, v1, dAInfo, false)((s4, v4) => { v4.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterUnfold) if (!Verifier.config.disableFunctionUnfoldTrigger()) { val predicateTrigger = App(s4.predicateData(predicate.name).triggerFunction, snap.get.convert(terms.sorts.Snap) +: tArgs) val eargs = eArgs.mkString(", ") - v4.decider.assume(predicateTrigger, Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}($eargs))")), AssumptionType.Trigger) + v4.decider.assume(predicateTrigger, Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}($eargs))")), dAInfo.withDependencyType(DependencyType.Trigger)) } Q(s4.copy(g = s.g, permissionScalingFactor = s.permissionScalingFactor, @@ -224,14 +223,14 @@ object predicateSupporter extends PredicateSupportRules { v4) }) } else { - produce(s3, toSf(snap.get), body, pve, v1, dependencyType.assumptionType)((s4, v2) => { + produce(s3, toSf(snap.get), body, pve, v1, dAInfo)((s4, v2) => { v2.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterUnfold) if (!Verifier.config.disableFunctionUnfoldTrigger()) { val predicateTrigger = App(s4.predicateData(predicate.name).triggerFunction, snap.get.convert(terms.sorts.Snap) +: tArgs) val eargs = eArgs.mkString(", ") - v2.decider.assume(predicateTrigger, Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}($eargs))")), AssumptionType.Trigger) + v2.decider.assume(predicateTrigger, Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}($eargs))")), dAInfo.withDependencyType(DependencyType.Trigger)) } Q(s4.copy(g = s.g, permissionScalingFactor = s.permissionScalingFactor, diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index 932b06535..6787c1747 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -7,9 +7,8 @@ package viper.silicon.rules import viper.silicon.Config.JoinMode -import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyAnalyzer, DependencyType, ExpAnalysisSourceInfo} -import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType import viper.silicon.debugger.DebugExp +import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfo, DependencyType} import viper.silicon.interfaces.{Unreachable, VerificationResult} import viper.silicon.logger.records.data.{CondExpRecord, ImpliesRecord, ProduceRecord} import viper.silicon.state._ @@ -42,7 +41,7 @@ trait ProductionRules extends SymbolicExecutionRules { a: ast.Exp, pve: PartialVerificationError, v: Verifier, - assumptionType: AssumptionType) + dAInfo: DependencyAnalysisInfo) (Q: (State, Verifier) => VerificationResult) : VerificationResult @@ -67,7 +66,7 @@ trait ProductionRules extends SymbolicExecutionRules { as: Seq[ast.Exp], pvef: ast.Exp => PartialVerificationError, v: Verifier, - assumptionType: AssumptionType) + dAInfo: DependencyAnalysisInfo) (Q: (State, Verifier) => VerificationResult) : VerificationResult } @@ -107,11 +106,11 @@ object producer extends ProductionRules { a: ast.Exp, pve: PartialVerificationError, v: Verifier, - assumptionType: AssumptionType) + dAInfo: DependencyAnalysisInfo) (Q: (State, Verifier) => VerificationResult) : VerificationResult = - produceR(s, sf, a.whenInhaling, pve, v, assumptionType)(Q) + produceR(s, sf, a.whenInhaling, pve, v, dAInfo)(Q) /** @inheritdoc */ def produces(s: State, @@ -119,7 +118,7 @@ object producer extends ProductionRules { as: Seq[ast.Exp], pvef: ast.Exp => PartialVerificationError, v: Verifier, - assumptionType: AssumptionType) + dAInfo: DependencyAnalysisInfo) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -134,7 +133,7 @@ object producer extends ProductionRules { allPves ++= pves }) - produceTlcs(s, sf, allTlcs.result(), allPves.result(), v, assumptionType)(Q) + produceTlcs(s, sf, allTlcs.result(), allPves.result(), v, dAInfo)(Q) } private def produceTlcs(s: State, @@ -142,7 +141,7 @@ object producer extends ProductionRules { as: Seq[ast.Exp], pves: Seq[PartialVerificationError], v: Verifier, - assumptionType: AssumptionType) + dAInfo: DependencyAnalysisInfo) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -152,31 +151,31 @@ object producer extends ProductionRules { val a = as.head.whenInhaling val pve = pves.head - val sourceInfo = v.decider.pushAndGetAnalysisSourceInfo(a, Some(DependencyType.get(a, DependencyType.make(assumptionType)))) + val sourceInfo = v.decider.pushAndGetAnalysisSourceInfo(a, Some(DependencyType.get(a, DependencyType.make(dAInfo)))) if (as.tail.isEmpty) - wrappedProduceTlc(s, sf, a, pve, v, assumptionType)((s1, v1) => { + wrappedProduceTlc(s, sf, a, pve, v, dAInfo)((s1, v1) => { v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) Q(s1, v1) }) else { try { val (sf0, sf1) = - v.snapshotSupporter.createSnapshotPair(s, sf, a, viper.silicon.utils.ast.BigAnd(as.tail), v, assumptionType) + v.snapshotSupporter.createSnapshotPair(s, sf, a, viper.silicon.utils.ast.BigAnd(as.tail), v, dAInfo) /* TODO: Refactor createSnapshotPair s.t. it can be used with Seq[Exp], * then remove use of BigAnd; for one it is not efficient since * the tail of the (decreasing list parameter as) is BigAnd-ed * over and over again. */ - wrappedProduceTlc(s, sf0, a, pve, v, assumptionType)((s1, v1) => { + wrappedProduceTlc(s, sf0, a, pve, v, dAInfo)((s1, v1) => { v1.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) - produceTlcs(s1, sf1, as.tail, pves.tail, v1, assumptionType)(Q) + produceTlcs(s1, sf1, as.tail, pves.tail, v1, dAInfo)(Q) }) } catch { // We will get an IllegalArgumentException from createSnapshotPair if sf(...) returns Unit. // This should never happen if we're in a reachable state, so here we check for that // (without timeout, since there is no fallback) and stop verifying the current branch. - case _: IllegalArgumentException if v.decider.check(False, Verifier.config.assertTimeout.getOrElse(0)) => + case _: IllegalArgumentException if v.decider.check(False, Verifier.config.assertTimeout.getOrElse(0), dAInfo) => Unreachable() } @@ -189,14 +188,14 @@ object producer extends ProductionRules { a: ast.Exp, pve: PartialVerificationError, v: Verifier, - assumptionType: AssumptionType) + dAInfo: DependencyAnalysisInfo) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { val tlcs = a.topLevelConjuncts val pves = Seq.fill(tlcs.length)(pve) - produceTlcs(s, sf, tlcs, pves, v, assumptionType)(Q) + produceTlcs(s, sf, tlcs, pves, v, dAInfo)(Q) } /** Wrapper/decorator for consume that injects the following operations: @@ -207,21 +206,21 @@ object producer extends ProductionRules { a: ast.Exp, pve: PartialVerificationError, v: Verifier, - assumptionType: AssumptionType) + dAInfo: DependencyAnalysisInfo) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { if(v.decider.isPathInfeasible()){ if(!Expressions.isKnownWellDefined(a, Some(s.program))){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType, v.decider.analysisSourceInfoStack.isJoinRelevantNode) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, dAInfo) } - v.decider.dependencyAnalyzer.addAssumption(True, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getAssumptionType, isJoinNode=false) + v.decider.dependencyAnalyzer.addAssumption(True, dAInfo) return Q(s, v) } val sepIdentifier = v.symbExLog.openScope(new ProduceRecord(a, s, v.decider.pcs)) - produceTlc(s, sf, a, pve, v, assumptionType)((s1, v1) => { + produceTlc(s, sf, a, pve, v, dAInfo)((s1, v1) => { v1.symbExLog.closeScope(sepIdentifier) Q(s1, v1)}) } @@ -231,10 +230,9 @@ object producer extends ProductionRules { a: ast.Exp, pve: PartialVerificationError, v: Verifier, - _assumptionType: AssumptionType) + dAInfo: DependencyAnalysisInfo) (continuation: (State, Verifier) => VerificationResult) : VerificationResult = { - val assumptionType = _assumptionType v.logger.debug(s"\nPRODUCE ${viper.silicon.utils.ast.sourceLineColumn(a)}: $a") v.logger.debug(v.stateFormatter.format(s, v.decider.pcs)) @@ -246,16 +244,16 @@ object producer extends ProductionRules { val impliesRecord = new ImpliesRecord(imp, s, v.decider.pcs, "produce") val uidImplies = v.symbExLog.openScope(impliesRecord) - eval(s, e0, pve, v)((s1, t0, e0New, v1) => + eval(s, e0, pve, v, dAInfo)((s1, t0, e0New, v1) => // The type arguments here are Null because there is no need to pass any join data. - joiner.join[scala.Null, scala.Null](s1, v1, assumptionType, resetState = false)((s1, v1, QB) => - branch(s1.copy(parallelizeBranches = false), t0, (e0, e0New), v1, assumptionType)( - (s2, v2) => produceR(s2.copy(parallelizeBranches = s1.parallelizeBranches), sf, a0, pve, v2, assumptionType)((s3, v3) => { + joiner.join[scala.Null, scala.Null](s1, v1, dAInfo, resetState = false)((s1, v1, QB) => + branch(s1.copy(parallelizeBranches = false), t0, (e0, e0New), v1, dAInfo)( + (s2, v2) => produceR(s2.copy(parallelizeBranches = s1.parallelizeBranches), sf, a0, pve, v2, dAInfo)((s3, v3) => { v3.symbExLog.closeScope(uidImplies) QB(s3, null, v3) }), (s2, v2) => { - v2.decider.assume(sf(sorts.Snap, v2) === Unit, Option.when(withExp)(DebugExp.createInstance("Empty snapshot", true)), assumptionType) + v2.decider.assume(sf(sorts.Snap, v2) === Unit, Option.when(withExp)(DebugExp.createInstance("Empty snapshot", true)), dAInfo) /* TODO: Avoid creating a fresh var (by invoking) `sf` that is not used * otherwise. In order words, only make this assumption if `sf` has * already been used, e.g. in a snapshot equality such as `s0 == (s1, s2)`. @@ -279,14 +277,14 @@ object producer extends ProductionRules { val impliesRecord = new ImpliesRecord(imp, s, v.decider.pcs, "produce") val uidImplies = v.symbExLog.openScope(impliesRecord) - eval(s, e0, pve, v)((s1, t0, e0New, v1) => - branch(s1, t0, (e0, e0New), v1, assumptionType)( - (s2, v2) => produceR(s2, sf, a0, pve, v2, assumptionType)((s3, v3) => { + eval(s, e0, pve, v, dAInfo)((s1, t0, e0New, v1) => + branch(s1, t0, (e0, e0New), v1, dAInfo)( + (s2, v2) => produceR(s2, sf, a0, pve, v2, dAInfo)((s3, v3) => { v3.symbExLog.closeScope(uidImplies) Q(s3, v3) }), (s2, v2) => { - v2.decider.assume(sf(sorts.Snap, v2) === Unit, Option.when(withExp)(DebugExp.createInstance("Empty snapshot", isInternal_ = true)), assumptionType) + v2.decider.assume(sf(sorts.Snap, v2) === Unit, Option.when(withExp)(DebugExp.createInstance("Empty snapshot", isInternal_ = true)), dAInfo) /* TODO: Avoid creating a fresh var (by invoking) `sf` that is not used * otherwise. In order words, only make this assumption if `sf` has * already been used, e.g. in a snapshot equality such as `s0 == (s1, s2)`. @@ -299,15 +297,15 @@ object producer extends ProductionRules { val condExpRecord = new CondExpRecord(ite, s, v.decider.pcs, "produce") val uidCondExp = v.symbExLog.openScope(condExpRecord) - eval(s, e0, pve, v)((s1, t0, e0New, v1) => + eval(s, e0, pve, v, dAInfo)((s1, t0, e0New, v1) => // The type arguments here are Null because there is no need to pass any join data. - joiner.join[scala.Null, scala.Null](s1, v1, assumptionType, resetState = false)((s1, v1, QB) => - branch(s1.copy(parallelizeBranches = false), t0, (e0, e0New), v1, assumptionType)( - (s2, v2) => produceR(s2.copy(parallelizeBranches = s1.parallelizeBranches), sf, a1, pve, v2, assumptionType)((s3, v3) => { + joiner.join[scala.Null, scala.Null](s1, v1, dAInfo, resetState = false)((s1, v1, QB) => + branch(s1.copy(parallelizeBranches = false), t0, (e0, e0New), v1, dAInfo)( + (s2, v2) => produceR(s2.copy(parallelizeBranches = s1.parallelizeBranches), sf, a1, pve, v2, dAInfo)((s3, v3) => { v3.symbExLog.closeScope(uidCondExp) QB(s3, null, v3) }), - (s2, v2) => produceR(s2.copy(parallelizeBranches = s1.parallelizeBranches), sf, a2, pve, v2, assumptionType)((s3, v3) => { + (s2, v2) => produceR(s2.copy(parallelizeBranches = s1.parallelizeBranches), sf, a2, pve, v2, dAInfo)((s3, v3) => { v3.symbExLog.closeScope(uidCondExp) QB(s3, null, v3) })) @@ -327,29 +325,29 @@ object producer extends ProductionRules { val condExpRecord = new CondExpRecord(ite, s, v.decider.pcs, "produce") val uidCondExp = v.symbExLog.openScope(condExpRecord) - eval(s, e0, pve, v)((s1, t0, e0New, v1) => - branch(s1, t0, (e0, e0New), v1, assumptionType)( - (s2, v2) => produceR(s2, sf, a1, pve, v2, assumptionType)((s3, v3) => { + eval(s, e0, pve, v, dAInfo)((s1, t0, e0New, v1) => + branch(s1, t0, (e0, e0New), v1, dAInfo)( + (s2, v2) => produceR(s2, sf, a1, pve, v2, dAInfo)((s3, v3) => { v3.symbExLog.closeScope(uidCondExp) Q(s3, v3) }), - (s2, v2) => produceR(s2, sf, a2, pve, v2, assumptionType)((s3, v3) => { + (s2, v2) => produceR(s2, sf, a2, pve, v2, dAInfo)((s3, v3) => { v3.symbExLog.closeScope(uidCondExp) Q(s3, v3) }))) case let: ast.Let if !let.isPure => - letSupporter.handle[ast.Exp](s, let, pve, v)((s1, g1, body, v1) => - produceR(s1.copy(g = s1.g + g1), sf, body, pve, v1, assumptionType)(Q)) + letSupporter.handle[ast.Exp](s, let, pve, v, dAInfo)((s1, g1, body, v1) => + produceR(s1.copy(g = s1.g + g1), sf, body, pve, v1, dAInfo)(Q)) case accPred: ast.AccessPredicate => val eArgs = accPred.loc.args(s.program) val ePerm = accPred.perm val resource = accPred.res(s.program) - evals(s, eArgs, _ => pve, v)((s1, tArgs, eArgsNew, v1) => - eval(s1, ePerm, pve, v1)((s1a, tPerm, ePermNew, v1a) => - permissionSupporter.assertNotNegative(s1a, tPerm, ePerm, ePermNew, pve, v1a)((s1b, v2) => { + evals(s, eArgs, _ => pve, v, dAInfo)((s1, tArgs, eArgsNew, v1) => + eval(s1, ePerm, pve, v1, dAInfo)((s1a, tPerm, ePermNew, v1a) => + permissionSupporter.assertNotNegative(s1a, tPerm, ePerm, ePermNew, pve, v1a, dAInfo)((s1b, v2) => { val s2 = s1b.copy(constrainableARPs = s.constrainableARPs) val snap = sf(v2.snapshotSupporter.optimalSnapshotSort(resource, s2, v2), v2) val gain = if (!Verifier.config.unsafeWildcardOptimization() || @@ -358,7 +356,7 @@ object producer extends ProductionRules { else WildcardSimplifyingPermTimes(tPerm, s2.permissionScalingFactor) val gainExp = ePermNew.map(p => ast.PermMul(p, s2.permissionScalingFactorExp.get)(p.pos, p.info, p.errT)) - v2.heapSupporter.produceSingle(s2, resource, tArgs, eArgsNew, snap, None, gain, gainExp, pve, true, v2, DependencyType.make(assumptionType))(Q) + v2.heapSupporter.produceSingle(s2, resource, tArgs, eArgsNew, snap, None, gain, gainExp, pve, true, v2, dAInfo)(Q) }))) @@ -382,12 +380,12 @@ object producer extends ProductionRules { if (forall.triggers.isEmpty) None else Some(forall.triggers) val s0 = s.copy(functionRecorder = s.functionRecorder.enterQuantifiedExp(qpa)) - evalQuantified(s0, Forall, forall.variables, Seq(cond), ePerm +: eArgs, optTrigger, qid, pve, v) { + evalQuantified(s0, Forall, forall.variables, Seq(cond), ePerm +: eArgs, optTrigger, qid, pve, v, dAInfo) { case (s1, qvars, qvarExps, Seq(tCond), eCondNew, Some((Seq(tPerm, tArgs@_*), permArgs, tTriggers, (auxGlobals, auxNonGlobals), auxExps)), v1) => val s1a = s1.copy(constrainableARPs = s.constrainableARPs) v1.heapSupporter.produceQuantified(s1a, sf, forall, resource, qvars, qvarExps, tFormalArgs, eFormalArgs, qid, optTrigger, tTriggers, auxGlobals, auxNonGlobals, auxExps.map(_._1), auxExps.map(_._2), tCond, eCondNew.map(_.head), tArgs, permArgs.map(_.tail), tPerm, permArgs.map(_.head), pve, NegativePermission(ePerm), - QPAssertionNotInjective(resAcc), v1, assumptionType)((s2, v2) => { + QPAssertionNotInjective(resAcc), v1, dAInfo)((s2, v2) => { Q(s2.copy(functionRecorder = s2.functionRecorder.leaveQuantifiedExp(qpa)), v2) }) case (s1, _, _, _, _, None, v1) => Q(s1.copy(constrainableARPs = s.constrainableARPs), v1) @@ -399,9 +397,9 @@ object producer extends ProductionRules { /* Any regular expressions, i.e. boolean and arithmetic. */ case _ => v.decider.assume(sf(sorts.Snap, v) === Unit, - Option.when(withExp)(DebugExp.createInstance("Empty snapshot", true)), assumptionType) /* TODO: See comment for case ast.Implies above */ - eval(s, a, pve, v)((s1, t, aNew, v1) => { - v1.decider.assume(t, Option.when(withExp)(a), aNew, assumptionType) + Option.when(withExp)(DebugExp.createInstance("Empty snapshot", true)), dAInfo) /* TODO: See comment for case ast.Implies above */ + eval(s, a, pve, v, dAInfo)((s1, t, aNew, v1) => { + v1.decider.assume(t, Option.when(withExp)(a), aNew, dAInfo) Q(s1, v1)}) } diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index f2723c6a6..41d26e6b9 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -10,8 +10,7 @@ import viper.silicon import viper.silicon.Map import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp -import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType -import viper.silicon.dependencyAnalysis.{AssumptionType, DependencyType} +import viper.silicon.dependencyAnalysis._ import viper.silicon.interfaces.VerificationResult import viper.silicon.interfaces.state._ import viper.silicon.logger.records.data.CommentRecord @@ -191,7 +190,7 @@ trait QuantifiedChunkSupport extends SymbolicExecutionRules { sm: Term, program: ast.Program, v: Verifier, - assumptionType: AssumptionType, + dAInfo: DependencyAnalysisInfo, isExhale: Boolean) : QuantifiedBasicChunk @@ -233,7 +232,7 @@ trait QuantifiedChunkSupport extends SymbolicExecutionRules { qidPrefix: String, v: Verifier, program: ast.Program, - assumptionType: AssumptionType, + dAInfo: DependencyAnalysisInfo, isExhale: Boolean) : (QuantifiedBasicChunk, InverseFunctions) @@ -246,7 +245,7 @@ trait QuantifiedChunkSupport extends SymbolicExecutionRules { def hintBasedChunkOrderHeuristic(hints: Seq[Term]) : Seq[QuantifiedBasicChunk] => Seq[QuantifiedBasicChunk] - def findChunk(chunks: Iterable[Chunk], chunk: QuantifiedChunk, v: Verifier): Option[QuantifiedChunk] + def findChunk(chunks: Iterable[Chunk], chunk: QuantifiedChunk, v: Verifier, dAInfo: DependencyAnalysisInfo): Option[QuantifiedChunk] /** Merge the snapshots of two quantified heap chunks that denote the same field locations * @@ -293,7 +292,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { sm: Term, program: ast.Program, v: Verifier, - assumptionType: AssumptionType, + dAInfo: DependencyAnalysisInfo, isExhale: Boolean) : QuantifiedBasicChunk = { @@ -324,7 +323,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { hints, program, v, - assumptionType, + dAInfo, isExhale) } @@ -348,7 +347,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { qidPrefix: String, v: Verifier, program: ast.Program, - assumptionType: AssumptionType, + dAInfo: DependencyAnalysisInfo, isExhale: Boolean) : (QuantifiedBasicChunk, InverseFunctions) = { @@ -392,7 +391,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { hints, program, v, - assumptionType, + dAInfo, isExhale) (ch, inverseFunctions) @@ -437,7 +436,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { hints: Seq[Term], program: ast.Program, v: Verifier, - assumptionType: AssumptionType, + dAInfo: DependencyAnalysisInfo, isExhale: Boolean) : QuantifiedBasicChunk = { @@ -458,7 +457,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optSingletonArguments.map(_.head), optSingletonArgumentsExp.map(_.head), hints, - v.decider.getAnalysisInfo(assumptionType), + v.decider.getAnalysisInfo(dAInfo), isExhale) case predicate: ast.Predicate => @@ -475,7 +474,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optSingletonArguments, optSingletonArgumentsExp, hints, - v.decider.getAnalysisInfo(assumptionType), + v.decider.getAnalysisInfo(dAInfo), isExhale) case wand: ast.MagicWand => @@ -492,7 +491,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optSingletonArguments, optSingletonArgumentsExp, hints, - v.decider.getAnalysisInfo(assumptionType), + v.decider.getAnalysisInfo(dAInfo), isExhale) case other => @@ -672,21 +671,18 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { smDef: SnapshotMapDefinition, v: Verifier) : (PermMapDefinition, PmCache) = { - v.decider.analysisSourceInfoStack.setForcedSource("summarizing heap") - v.decider.dependencyAnalyzer.disableTransitiveEdges() + val dAInfo = FullDependencyAnalysisInfo.create("summarizing heap", DependencyType.Internal, NoDependencyAnalysisMerge(), NoDependencyAnalysisJoin()) val res = Verifier.config.mapCache(s.pmCache.get(resource, relevantChunks)) match { case Some(pmDef) => - v.decider.assume(pmDef.valueDefinitions, Option.when(withExp)(DebugExp.createInstance("value definitions", isInternal_ = true)), enforceAssumption = false, assumptionType=AssumptionType.Internal) + v.decider.assume(pmDef.valueDefinitions, Option.when(withExp)(DebugExp.createInstance("value definitions", isInternal_ = true)), enforceAssumption = false, dAInfo=dAInfo) (pmDef, s.pmCache) case _ => val (pm, valueDef) = quantifiedChunkSupporter.summarisePerm(s, relevantChunks, formalQVars, resource, smDef, v) val pmDef = PermMapDefinition(resource, pm, valueDef) - v.decider.assume(valueDef, Option.when(withExp)(DebugExp.createInstance("value definitions", isInternal_ = true)), enforceAssumption = false, assumptionType=AssumptionType.Internal) + v.decider.assume(valueDef, Option.when(withExp)(DebugExp.createInstance("value definitions", isInternal_ = true)), enforceAssumption = false, dAInfo=dAInfo) (pmDef, s.pmCache + ((resource, relevantChunks) -> pmDef)) } - v.decider.analysisSourceInfoStack.removeForcedSource() - v.decider.dependencyAnalyzer.enableTransitiveEdges() res } @@ -714,8 +710,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optSmDomainDefinitionCondition: Option[Term] = None, optQVarsInstantiations: Option[Seq[Term]] = None) : (SnapshotMapDefinition, SnapshotMapCache) = { - v.decider.analysisSourceInfoStack.setForcedSource("summarizing heap") - v.decider.dependencyAnalyzer.disableTransitiveEdges() + val dAInfo = FullDependencyAnalysisInfo.create("summarizing heap", DependencyType.Internal, NoDependencyAnalysisMerge(), NoDependencyAnalysisJoin()) def emitSnapshotMapDefinition(s: State, smDef: SnapshotMapDefinition, @@ -728,7 +723,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { case None => val comment = "Definitional axioms for snapshot map domain" v.decider.prover.comment(comment) - v.decider.assume(smDef.domainDefinitions, Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, assumptionType=AssumptionType.Internal) + v.decider.assume(smDef.domainDefinitions, Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, dAInfo=dAInfo) case Some(_instantiations) => // TODO: Avoid pattern matching on resource val instantiations = resource match { @@ -740,7 +735,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.prover.comment(comment) // TODO: Avoid cast to Quantification v.decider.assume(smDef.domainDefinitions.map(_.asInstanceOf[Quantification].instantiate(instantiations)), - Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, assumptionType=AssumptionType.Internal) + Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, dAInfo=dAInfo) } } @@ -748,7 +743,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { case None => val comment = "Definitional axioms for snapshot map values" v.decider.prover.comment(comment) - v.decider.assume(smDef.valueDefinitions, Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, assumptionType=AssumptionType.Internal) + v.decider.assume(smDef.valueDefinitions, Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, dAInfo=dAInfo) case Some(_instantiations) => // TODO: Avoid pattern matching on resource val instantiations = resource match { @@ -760,7 +755,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.prover.comment(comment) // TODO: Avoid cast to Quantification v.decider.assume(smDef.valueDefinitions.map(_.asInstanceOf[Quantification].instantiate(instantiations)), - Option.when(withExp)(DebugExp.createInstance(comment, true)), enforceAssumption = false, assumptionType=AssumptionType.Internal) + Option.when(withExp)(DebugExp.createInstance(comment, true)), enforceAssumption = false, dAInfo=dAInfo) } } @@ -786,8 +781,6 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { } emitSnapshotMapDefinition(s, smDef, v, optQVarsInstantiations) - v.decider.analysisSourceInfoStack.removeForcedSource() - v.decider.dependencyAnalyzer.enableTransitiveEdges() (smDef, smCache) } @@ -868,7 +861,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { negativePermissionReason: => ErrorReason, notInjectiveReason: => ErrorReason, v: Verifier, - assumptionType: AssumptionType) + dAInfo: DependencyAnalysisInfo) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -899,7 +892,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { qidPrefix = qid, v = v, program = s.program, - assumptionType = assumptionType, + dAInfo = dAInfo, isExhale = false) val (effectiveTriggers, effectiveTriggersQVars, effectiveTriggersQVarExps) = optTrigger match { @@ -945,10 +938,9 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val commentGlobals = "Nested auxiliary terms: globals" v.decider.prover.comment(commentGlobals) - v.decider.analysisSourceInfoStack.setForcedSource(commentGlobals) + val dAInfoGlobals = FullDependencyAnalysisInfo.create(commentGlobals, DependencyType.Internal, NoDependencyAnalysisMerge(), NoDependencyAnalysisJoin()) v.decider.assume(auxGlobals, Option.when(withExp)(DebugExp.createInstance(description=commentGlobals, children=auxGlobalsExp.get)), - enforceAssumption = false, assumptionType=AssumptionType.Internal) - v.decider.analysisSourceInfoStack.removeForcedSource() + enforceAssumption = false, dAInfo=dAInfoGlobals) val commentNonGlobals = "Nested auxiliary terms: non-globals" v.decider.prover.comment(commentNonGlobals) @@ -956,13 +948,13 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { auxNonGlobals.map(_.copy( vars = effectiveTriggersQVars, triggers = effectiveTriggers)), - Option.when(withExp)(DebugExp.createInstance(description=commentNonGlobals, children=auxNonGlobalsExp.get)), enforceAssumption = false, assumptionType=AssumptionType.Internal) + Option.when(withExp)(DebugExp.createInstance(description=commentNonGlobals, children=auxNonGlobalsExp.get)), enforceAssumption = false, dAInfo=dAInfo.withDependencyType(DependencyType.Internal)) val nonNegImplication = Implies(tCond, perms.IsNonNegative(tPerm)) val nonNegImplicationExp = eCond.map(c => ast.Implies(c, ast.PermGeCmp(ePerm.get, ast.NoPerm()())())(c.pos, c.info, c.errT)) val nonNegTerm = Forall(qvars, Implies(FunctionPreconditionTransformer.transform(nonNegImplication, s.program), nonNegImplication), Nil) // TODO: Replace by QP-analogue of permissionSupporter.assertNotNegative - v.decider.assert(nonNegTerm) { + v.decider.assert(nonNegTerm, dAInfo) { case true => /* TODO: Can we omit/simplify the injectivity check in certain situations? */ @@ -984,7 +976,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.prover.comment(comment) val completeReceiverInjectivityCheck = Implies(FunctionPreconditionTransformer.transform(receiverInjectivityCheck, s.program), receiverInjectivityCheck) - v.decider.assert(completeReceiverInjectivityCheck) { + v.decider.assert(completeReceiverInjectivityCheck, dAInfo) { case true => val ax = inverseFunctions.axiomInversesOfInvertibles val inv = inverseFunctions.copy(axiomInversesOfInvertibles = Forall(ax.vars, ax.body, effectiveTriggers)) @@ -993,8 +985,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.prover.comment(comment) val definitionalAxiomMark = v.decider.setPathConditionMark() v.decider.assume(inv.definitionalAxioms.map(a => FunctionPreconditionTransformer.transform(a, s.program)), - Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, assumptionType=assumptionType) - v.decider.assume(inv.definitionalAxioms, Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, assumptionType=assumptionType) + Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, dAInfo=dAInfo) + v.decider.assume(inv.definitionalAxioms, Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, dAInfo=dAInfo) val conservedPcs = if (s.recordPcs) (s.conservedPcs.head :+ v.decider.pcs.after(definitionalAxiomMark)) +: s.conservedPcs.tail else s.conservedPcs @@ -1017,7 +1009,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { triggers = effectiveTriggers, qidPrefix = qid ) - v.decider.assume(pcsForChunk, pcsForChunkExp, pcsForChunkExp, AssumptionType.Internal) + v.decider.assume(pcsForChunk, pcsForChunkExp, pcsForChunkExp, dAInfo.withDependencyType(DependencyType.Internal)) }) val (fr1, h1) = v.stateConsolidator(s).merge(s.functionRecorder, s, s.h, Heap(Seq(ch)), v) @@ -1041,7 +1033,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val qvarsToInv = inv.qvarsToInversesOf(codomainVars) val condOfInv = tCond.replace(qvarsToInv) v.decider.assume(Forall(codomainVars, Implies(condOfInv, trigger), Trigger(inv.inversesOf(codomainVars))), - Option.when(withExp)(DebugExp.createInstance("Inverse Trigger", true)), AssumptionType.Trigger) + Option.when(withExp)(DebugExp.createInstance("Inverse Trigger", true)), dAInfo.withDependencyType(DependencyType.Trigger)) val newFuncRec = fr1.recordFvfAndDomain(smDef1) (smCache1, newFuncRec) } else { @@ -1055,12 +1047,12 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { Q(s1, v) case false => val failure = createFailure(pve dueTo notInjectiveReason, v, s, receiverInjectivityCheck, "QP receiver is injective") - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(completeReceiverInjectivityCheck, v.decider.analysisSourceInfoStack.getAssertionType, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(completeReceiverInjectivityCheck, dAInfo, v.reportFurtherErrors()) if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, v) else failure } case false => val failure = createFailure(pve dueTo negativePermissionReason, v, s, nonNegImplication, nonNegImplicationExp) - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(nonNegTerm, v.decider.analysisSourceInfoStack.getAssertionType, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(nonNegTerm, dAInfo, v.reportFurtherErrors()) if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, v) else failure } @@ -1078,7 +1070,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { resourceTriggerFactory: Term => Term, /* Trigger with some snapshot */ mergeAndTrigger: Boolean, v: Verifier, - assumptionType: AssumptionType) + dAInfo: DependencyAnalysisInfo) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -1087,11 +1079,11 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val comment = "Definitional axioms for singleton-SM's value" v.decider.prover.comment(comment) val definitionalAxiomMark = v.decider.setPathConditionMark() - v.decider.assumeDefinition(smValueDef, Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), assumptionType) + v.decider.assumeDefinition(smValueDef, Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), dAInfo) val conservedPcs = if (s.recordPcs) (s.conservedPcs.head :+ v.decider.pcs.after(definitionalAxiomMark)) +: s.conservedPcs.tail else s.conservedPcs - val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(formalQVars, formalQVarsExp, resource, tArgs, eArgs, tPerm, ePerm, sm, s.program, v, assumptionType, isExhale=false) + val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(formalQVars, formalQVarsExp, resource, tArgs, eArgs, tPerm, ePerm, sm, s.program, v, dAInfo, isExhale=false) val s1 = if (mergeAndTrigger) { val (fr1, h1) = v.stateConsolidator(s).merge(s.functionRecorder, s, s.h, Heap(Seq(ch)), v) @@ -1099,7 +1091,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val interpreter = new NonQuantifiedPropertyInterpreter(h1.values, v) val resourceDescription = Resources.resourceDescriptions(ch.resourceID) val pcs = interpreter.buildPathConditionsForChunk(ch, resourceDescription.instanceProperties(s.mayAssumeUpperBounds)) - pcs.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), assumptionType)) + pcs.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), dAInfo)) val smCache1 = if (s.isUsedAsTrigger(resource)) { val (relevantChunks, _) = @@ -1107,7 +1099,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val (smDef1, smCache1) = quantifiedChunkSupporter.summarisingSnapshotMap( s, resource, formalQVars, relevantChunks, v) - v.decider.assume(resourceTriggerFactory(smDef1.sm), Option.when(withExp)(DebugExp.createInstance("Resource Trigger", true)), AssumptionType.Trigger) + v.decider.assume(resourceTriggerFactory(smDef1.sm), Option.when(withExp)(DebugExp.createInstance("Resource Trigger", true)), dAInfo.withDependencyType(DependencyType.Trigger)) smCache1 } else { s.smCache @@ -1151,7 +1143,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { notInjectiveReason: => ErrorReason, insufficientPermissionReason: => ErrorReason, v: Verifier, - dependencyType: DependencyType) + dAInfo: DependencyAnalysisInfo) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -1188,7 +1180,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val comment = "Nested auxiliary terms: globals" v.decider.prover.comment(comment) v.decider.analysisSourceInfoStack.setForcedSource(comment) - v.decider.assume(auxGlobals, Option.when(withExp)(DebugExp.createInstance(description=comment, children=auxGlobalsExp.get)), enforceAssumption = false, assumptionType=AssumptionType.Internal) + v.decider.assume(auxGlobals, Option.when(withExp)(DebugExp.createInstance(description=comment, children=auxGlobalsExp.get)), enforceAssumption = false, dAInfo=dAInfo.withDependencyType(DependencyType.Internal)) v.decider.analysisSourceInfoStack.removeForcedSource() val comment2 = "Nested auxiliary terms: non-globals" @@ -1199,10 +1191,10 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.assume( auxNonGlobals.map(_.copy( vars = effectiveTriggersQVars, - triggers = effectiveTriggers)), Option.when(withExp)(DebugExp.createInstance(description=comment2, children=auxNonGlobalsExp.get)), enforceAssumption = false, assumptionType=AssumptionType.Internal) + triggers = effectiveTriggers)), Option.when(withExp)(DebugExp.createInstance(description=comment2, children=auxNonGlobalsExp.get)), enforceAssumption = false, dAInfo=dAInfo.withDependencyType(DependencyType.Internal)) case Some(_) => /* Explicit triggers were provided. */ - v.decider.assume(auxNonGlobals, Option.when(withExp)(DebugExp.createInstance(description=comment2, children=auxNonGlobalsExp.get)), enforceAssumption = false, assumptionType=AssumptionType.Internal) + v.decider.assume(auxNonGlobals, Option.when(withExp)(DebugExp.createInstance(description=comment2, children=auxNonGlobalsExp.get)), enforceAssumption = false, dAInfo=dAInfo.withDependencyType(DependencyType.Internal)) } val nonNegImplication = Implies(tCond, perms.IsNonNegative(tPerm)) @@ -1210,11 +1202,11 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val nonNegTerm = Forall(qvars, Implies(FunctionPreconditionTransformer.transform(nonNegImplication, s.program), nonNegImplication), Nil) val nonNegExp = qvarExps.map(qv => ast.Forall(qv, Nil, nonNegImplicationExp.get)()) // TODO: Replace by QP-analogue of permissionSupporter.assertNotNegative - v.decider.assert(nonNegTerm, dependencyType.assertionType) { + v.decider.assert(nonNegTerm, dAInfo) { case true => val hints = quantifiedChunkSupporter.extractHints(Some(tCond), tArgs) val chunkOrderHeuristics = - qpAppChunkOrderHeuristics(inverseFunctions.invertibles, qvars, hints, v) + qpAppChunkOrderHeuristics(inverseFunctions.invertibles, qvars, hints, v, dAInfo) val loss = if (!Verifier.config.unsafeWildcardOptimization() || (resource.isInstanceOf[ast.Location] && s.permLocations.contains(resource.asInstanceOf[ast.Location]))) PermTimes(tPerm, s.permissionScalingFactor) @@ -1246,7 +1238,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { program = s.program) v.decider.prover.comment("Check receiver injectivity") val completeReceiverInjectivityCheck = Implies(FunctionPreconditionTransformer.transform(receiverInjectivityCheck, s.program), receiverInjectivityCheck) - v.decider.assert(completeReceiverInjectivityCheck, dependencyType.assertionType) { + v.decider.assert(completeReceiverInjectivityCheck, dAInfo) { case true => val qvarsToInvOfLoc = inverseFunctions.qvarsToInversesOf(formalQVars) val condOfInvOfLoc = tCond.replace(qvarsToInvOfLoc) @@ -1260,8 +1252,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.prover.comment("Definitional axioms for inverse functions") v.decider.assume(inverseFunctions.definitionalAxioms.map(a => FunctionPreconditionTransformer.transform(a, s.program)), - Option.when(withExp)(DebugExp.createInstance("Inverse Function Axioms", isInternal_ = true)), enforceAssumption = false, assumptionType=dependencyType.assumptionType) - v.decider.assume(inverseFunctions.definitionalAxioms, Option.when(withExp)(DebugExp.createInstance("Inverse function axiom", isInternal_ = true)), enforceAssumption = false, assumptionType=dependencyType.assumptionType) + Option.when(withExp)(DebugExp.createInstance("Inverse Function Axioms", isInternal_ = true)), enforceAssumption = false, dAInfo = dAInfo) + v.decider.assume(inverseFunctions.definitionalAxioms, Option.when(withExp)(DebugExp.createInstance("Inverse function axiom", isInternal_ = true)), enforceAssumption = false, dAInfo=dAInfo) if (s.isUsedAsTrigger(resource)){ v.decider.assume( @@ -1269,7 +1261,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { formalQVars, Implies(condOfInvOfLoc, ResourceTriggerFunction(resource, smDef1.get.sm, formalQVars, s.program)), Trigger(inverseFunctions.inversesOf(formalQVars)))), - Option.when(withExp)(DebugExp.createInstance("Inverse Function", isInternal_ = true)), enforceAssumption = false, assumptionType=dependencyType.assumptionType) + Option.when(withExp)(DebugExp.createInstance("Inverse Function", isInternal_ = true)), enforceAssumption = false, dAInfo=dAInfo) } @@ -1282,7 +1274,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { lossExp, createFailure(pve dueTo insufficientPermissionReason/*InsufficientPermission(acc.loc)*/, v, s, "consuming QP"), formalQVars, - v)((s2, heap, rPerm, rPermExp, v2) => { + v, + dAInfo)((s2, heap, rPerm, rPermExp, v2) => { val (relevantChunks, otherChunks) = quantifiedChunkSupporter.splitHeap[QuantifiedBasicChunk]( heap, ChunkIdentifier(resource, s.program)) @@ -1300,7 +1293,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { rPermExp, chunkOrderHeuristics, v2, - dependencyType) + dAInfo) val optSmDomainDefinitionCondition2 = if (s3.smDomainNeeded) Some(And(condOfInvOfLoc, IsPositive(lossOfInvOfLoc), And(imagesOfFormalQVars))) else None @@ -1332,15 +1325,15 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { qid, v2, s.program, - dependencyType.assertionType, + dAInfo, isExhale = true ) val debugExp = Option.when(withExp)(DebugExp.createInstance("Inverse functions for quantified permission", isInternal_ = true)) - v.decider.assume(FunctionPreconditionTransformer.transform(inverseFunctions.axiomInvertiblesOfInverses, s3.program), debugExp, dependencyType.assumptionType) - v.decider.assume(inverseFunctions.axiomInvertiblesOfInverses, debugExp, dependencyType.assumptionType) + v.decider.assume(FunctionPreconditionTransformer.transform(inverseFunctions.axiomInvertiblesOfInverses, s3.program), debugExp, dAInfo) + v.decider.assume(inverseFunctions.axiomInvertiblesOfInverses, debugExp, dAInfo) val substitutedAxiomInversesOfInvertibles = inverseFunctions.axiomInversesOfInvertibles.replace(formalQVars, tArgs) - v.decider.assume(FunctionPreconditionTransformer.transform(substitutedAxiomInversesOfInvertibles, s3.program), debugExp, dependencyType.assumptionType) - v.decider.assume(substitutedAxiomInversesOfInvertibles, debugExp, dependencyType.assumptionType) + v.decider.assume(FunctionPreconditionTransformer.transform(substitutedAxiomInversesOfInvertibles, s3.program), debugExp, dAInfo) + v.decider.assume(substitutedAxiomInversesOfInvertibles, debugExp, dAInfo) val h2 = Heap(remainingChunks ++ otherChunks) val s4 = s3.copy(smCache = smCache2, constrainableARPs = s.constrainableARPs) @@ -1369,7 +1362,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { lossExp, chunkOrderHeuristics, v, - dependencyType + dAInfo ) permissionRemovalResult match { case (Complete(), s2, remainingChunks) => @@ -1396,13 +1389,13 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { } case (Incomplete(_, _), s2, _) => val failure = createFailure(pve dueTo insufficientPermissionReason, v, s2, "QP consume") - if(s2.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, dependencyType.assertionType, v.reportFurtherErrors()) + if(s2.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, dAInfo, v.reportFurtherErrors()) if(s2.retryLevel == 0 && v.reportFurtherErrors() && Verifier.config.disableInfeasibilityChecks()) failure combine Q(s2, s2.h, None, v) else failure } } case false => val failure = createFailure(pve dueTo notInjectiveReason, v, s, receiverInjectivityCheck, "QP receiver injective") - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(receiverInjectivityCheck, dependencyType.assertionType, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(receiverInjectivityCheck, dAInfo, v.reportFurtherErrors()) if(s.retryLevel == 0 && v.reportFurtherErrors()){ val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) // TODO ake: function recorder failure combine Q(s, s.h, if(returnSnap) Some(snap) else None, v) @@ -1412,7 +1405,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { } case false => val failure = createFailure(pve dueTo negativePermissionReason, v, s, nonNegTerm, nonNegExp) - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(nonNegTerm, dependencyType.assertionType, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(nonNegTerm, dAInfo, v.reportFurtherErrors()) if(s.retryLevel == 0 && v.reportFurtherErrors()){ val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) failure combine Q(s, s.h, if(returnSnap) Some(snap) else None, v) @@ -1435,7 +1428,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optChunkOrderHeuristic: Option[Seq[QuantifiedBasicChunk] => Seq[QuantifiedBasicChunk]], pve: PartialVerificationError, v: Verifier, - dependencyType: DependencyType) + dAInfo: DependencyAnalysisInfo) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -1447,7 +1440,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { heuristics case None => quantifiedChunkSupporter.singleReceiverChunkOrderHeuristic(arguments, - quantifiedChunkSupporter.extractHints(None, arguments), v) + quantifiedChunkSupporter.extractHints(None, arguments), v, dAInfo) } if (s.exhaleExt) { @@ -1456,7 +1449,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { case wand: ast.MagicWand => createFailure(pve dueTo MagicWandChunkNotFound(wand), v, s, "single QP consume inside package") case _ => sys.error(s"Found resource $resourceAccess, which is not yet supported as a quantified resource.") } - magicWandSupporter.transfer(s, permissions, permissionsExp, failure, Seq(), v)((s1, h1, rPerm, rPermExp, v1) => { + magicWandSupporter.transfer(s, permissions, permissionsExp, failure, Seq(), v, dAInfo)((s1, h1, rPerm, rPermExp, v1) => { val (relevantChunks, otherChunks) = quantifiedChunkSupporter.splitHeap[QuantifiedBasicChunk](h1, chunkIdentifier) val (result, s2, remainingChunks) = quantifiedChunkSupporter.removePermissions( @@ -1472,7 +1465,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { rPermExp, chunkOrderHeuristics, v, - dependencyType + dAInfo ) val h2 = Heap(remainingChunks ++ otherChunks) val (smDef1, smCache1) = @@ -1491,7 +1484,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { } val consumedChunk = quantifiedChunkSupporter.createSingletonQuantifiedChunk( - codomainQVars, codomainQVarsExp, resource, arguments, argumentsExp, permsTaken, permsTakenExp, smDef1.sm, s.program, v1, dependencyType.assertionType, isExhale=true) + codomainQVars, codomainQVarsExp, resource, arguments, argumentsExp, permsTaken, permsTakenExp, smDef1.sm, s.program, v1, dAInfo, isExhale=true) val s3 = s2.copy(functionRecorder = s2.functionRecorder.recordFvfAndDomain(smDef1), smCache = smCache1) (result, s3, h2, Some(consumedChunk)) @@ -1522,7 +1515,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { permissionsExp, chunkOrderHeuristics, v, - dependencyType + dAInfo ) result match { case (Complete(), s1, remainingChunks) => @@ -1550,7 +1543,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { case wand: ast.MagicWand => createFailure(pve dueTo MagicWandChunkNotFound(wand), v, s, "single QP consume") case _ => sys.error(s"Found resource $resourceAccess, which is not yet supported as a quantified resource.") } - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, dependencyType.assertionType, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, dAInfo, v.reportFurtherErrors()) if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, s.h, None, v) else failure } } @@ -1563,7 +1556,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { perms: Term, permsExp: Option[ast.Exp], v: Verifier, - assertionType: AssumptionType) + dAInfo: DependencyAnalysisInfo) : ConsumptionResult = { var permsAvailable: Term = NoPerm @@ -1580,7 +1573,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { // final check val result = - if (v.decider.check(tookEnoughCheck, Verifier.config.assertTimeout.getOrElse(0), assertionType) /* This check is a must-check, i.e. an assert */ ) + if (v.decider.check(tookEnoughCheck, Verifier.config.assertTimeout.getOrElse(0), dAInfo) /* This check is a must-check, i.e. an assert */ ) Complete() else Incomplete(PermMinus(permsAvailable, perms), permsAvailableExp.map(pa => ast.PermSub(pa, permsExp.get)())) @@ -1604,7 +1597,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { permsExp: Option[ast.Exp], // p(rs) chunkOrderHeuristic: Seq[QuantifiedBasicChunk] => Seq[QuantifiedBasicChunk], v: Verifier, - dependencyType: DependencyType) + dAInfo: DependencyAnalysisInfo) : (ConsumptionResult, State, Seq[QuantifiedBasicChunk]) = { val rmPermRecord = new CommentRecord("removePermissions", s, v.decider.pcs) @@ -1621,7 +1614,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val constrainPermissions = !consumeExactRead(perms, s.constrainableARPs) if (s.assertReadAccessOnly) { - val result = assertReadPermission(s, candidates, codomainQVars, condition, perms, permsExp, v, dependencyType.assertionType) + val result = assertReadPermission(s, candidates, codomainQVars, condition, perms, permsExp, v, dAInfo) return (result, s, relevantChunks) } @@ -1685,23 +1678,23 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { if (constrainPermissions) { v.decider.prover.comment(s"Constrain original permissions $perms") - v.decider.assume(permissionConstraint, permissionConstraintExp, permissionConstraintExp, dependencyType.assumptionType) + v.decider.assume(permissionConstraint, permissionConstraintExp, permissionConstraintExp, dAInfo) remainingChunks = - remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.getAnalysisInfo(dependencyType.assumptionType)) + remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.getAnalysisInfo(dAInfo)) } else { v.decider.prover.comment(s"Chunk depleted?") - val chunkDepleted = v.decider.check(depletedCheck, Verifier.config.splitTimeout(), AssumptionType.Internal) + val chunkDepleted = v.decider.check(depletedCheck, Verifier.config.splitTimeout(), dAInfo.withDependencyType(DependencyType.Internal)) if (!chunkDepleted) { val unusedCheck = Forall(codomainQVars, ithPTaken === NoPerm, Nil) - val chunkUnused = v.decider.check(unusedCheck, Verifier.config.checkTimeout(), AssumptionType.Internal) + val chunkUnused = v.decider.check(unusedCheck, Verifier.config.checkTimeout(), dAInfo.withDependencyType(DependencyType.Internal)) if (chunkUnused) { remainingChunks = remainingChunks :+ ithChunk } else { remainingChunks = - remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.getAnalysisInfo(dependencyType.assumptionType)) + remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.getAnalysisInfo(dAInfo)) } }else{ - val _ = GeneralChunk.withPerm(ithChunk, ithPTaken, None, v.decider.getAnalysisInfo(dependencyType.assertionType), isExhale=true) + val _ = GeneralChunk.withPerm(ithChunk, ithPTaken, None, v.decider.getAnalysisInfo(dAInfo), isExhale=true) } } @@ -1714,7 +1707,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { Forall(codomainQVars, Implies(condition, ithPNeeded === NoPerm), Nil) v.decider.prover.comment(s"Intermediate check if already taken enough permissions") - success = if (v.decider.check(tookEnoughCheck, Verifier.config.splitTimeout(), dependencyType.assertionType)) { + success = if (v.decider.check(tookEnoughCheck, Verifier.config.splitTimeout(), dAInfo)) { Complete() } else { Incomplete(ithPNeeded, ithPNeededExp) @@ -1724,7 +1717,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.prover.comment("Final check if taken enough permissions") success = - if (success.isComplete || v.decider.check(tookEnoughCheck, Verifier.config.assertTimeout.getOrElse(0), dependencyType.assertionType) /* This check is a must-check, i.e. an assert */) + if (success.isComplete || v.decider.check(tookEnoughCheck, Verifier.config.assertTimeout.getOrElse(0), dAInfo) /* This check is a must-check, i.e. an assert */) Complete() else success @@ -2043,7 +2036,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { matchingChunks ++ otherChunks } - override def findChunk(chunks: Iterable[Chunk], chunk: QuantifiedChunk, v: Verifier): Option[QuantifiedChunk] = { + override def findChunk(chunks: Iterable[Chunk], chunk: QuantifiedChunk, v: Verifier, dAInfo: DependencyAnalysisInfo): Option[QuantifiedChunk] = { val lr = chunk match { case qfc: QuantifiedFieldChunk if qfc.invs.isDefined => val qvarsAndInverses = qfc.invs.get.qvarsToInverses.map(qvi => (qvi._1, App(qvi._2, qfc.invs.get.additionalArguments.toSeq ++ qfc.quantifiedVars))) @@ -2084,11 +2077,11 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { // Hence, we need to compare the conditions for equality in addition to verifying that the receivers match. val equalityCond = And(cond.replace(chunk.quantifiedVars, singletonArguments), cCond.replace(ch.quantifiedVars, cSingletonArguments)) - val result = v.decider.check(And(equalityCond, equalityTerm), Verifier.config.checkTimeout()) + val result = v.decider.check(And(equalityCond, equalityTerm), Verifier.config.checkTimeout(), dAInfo) if (result) { // Learn the equality val debugExp = Option.when(withExp)(DebugExp.createInstance("Chunks alias", isInternal_ = true)) - v.decider.assume(equalityTerm, debugExp, AssumptionType.Internal) + v.decider.assume(equalityTerm, debugExp, dAInfo.withDependencyType(DependencyType.Internal)) } result case _ => false @@ -2114,11 +2107,11 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val condReplaced = cCond.replace(cQvars, quantVars) val secondReplaced = p._2.replace(cQvars, quantVars) val equalityTerm = SimplifyingForall(quantVars, And(Seq(p._1 === secondReplaced, cond === condReplaced)), Seq()) - val result = v.decider.check(equalityTerm, Verifier.config.checkTimeout()) + val result = v.decider.check(equalityTerm, Verifier.config.checkTimeout(), dAInfo) if (result) { // Learn the equality val debugExp = Option.when(withExp)(DebugExp.createInstance("Chunks alias", isInternal_ = true)) - v.decider.assume(equalityTerm, debugExp, AssumptionType.Internal) + v.decider.assume(equalityTerm, debugExp, dAInfo.withDependencyType(DependencyType.Internal)) } result } else { @@ -2180,7 +2173,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { (fr2, sm, Forall(qVars, smDef, triggers)) } - def qpAppChunkOrderHeuristics(receiverTerms: Seq[Term], quantVars: Seq[Var], hints: Seq[Term], v: Verifier) + def qpAppChunkOrderHeuristics(receiverTerms: Seq[Term], quantVars: Seq[Var], hints: Seq[Term], v: Verifier, dAInfo: DependencyAnalysisInfo) : Seq[QuantifiedBasicChunk] => Seq[QuantifiedBasicChunk] = { // Heuristics that looks for quantified chunks that have the same shape (as in, the same number and types of // quantified variables) and identical receiver terms. @@ -2206,7 +2199,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { receiverTerms.zip(cInvertibles).forall(p => { if (cQvars.length == quantVars.length && cQvars.zip(quantVars).forall(vars => vars._1.sort == vars._2.sort)) { val secondReplaced = p._2.replace(cQvars, quantVars) - v.decider.check(SimplifyingForall(quantVars, p._1 === secondReplaced, Seq()), Verifier.config.checkTimeout()) + v.decider.check(SimplifyingForall(quantVars, p._1 === secondReplaced, Seq()), Verifier.config.checkTimeout(), dAInfo) } else { false } @@ -2222,7 +2215,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { } } - def singleReceiverChunkOrderHeuristic(receiver: Seq[Term], hints: Seq[Term], v: Verifier) + def singleReceiverChunkOrderHeuristic(receiver: Seq[Term], hints: Seq[Term], v: Verifier, dAInfo: DependencyAnalysisInfo) : Seq[QuantifiedBasicChunk] => Seq[QuantifiedBasicChunk] = { // Heuristic that emulates greedy Silicon behavior for consuming single-receiver permissions. // First: Find singleton chunks that have the same receiver syntactically. @@ -2239,7 +2232,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { } else { val greedyMatch = chunks.find(c => c.singletonArguments match { case Some(args) if args.length == receiver.length => - args.zip(receiver).forall(ts => v.decider.check(ts._1 === ts._2, Verifier.config.checkTimeout())) + args.zip(receiver).forall(ts => v.decider.check(ts._1 === ts._2, Verifier.config.checkTimeout(), dAInfo)) case _ => false }).toSeq diff --git a/src/main/scala/rules/StateConsolidator.scala b/src/main/scala/rules/StateConsolidator.scala index 3fa83e6c4..535e57cd9 100644 --- a/src/main/scala/rules/StateConsolidator.scala +++ b/src/main/scala/rules/StateConsolidator.scala @@ -9,7 +9,7 @@ package viper.silicon.rules import viper.silicon.Config import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp -import viper.silicon.dependencyAnalysis.AssumptionType +import viper.silicon.dependencyAnalysis._ import viper.silicon.interfaces.state._ import viper.silicon.logger.records.data.{CommentRecord, SingleMergeRecord} import viper.silicon.resources.{NonQuantifiedPropertyInterpreter, Resources} @@ -26,11 +26,11 @@ import scala.annotation.unused trait StateConsolidationRules extends SymbolicExecutionRules { def consolidate(s: State, v: Verifier): State def consolidateOptionally(s: State, v: Verifier): State - def merge(fr: FunctionRecorder, s: State, h: Heap, newH: Heap, v: Verifier): (FunctionRecorder, Heap) - def merge(fr: FunctionRecorder, s: State, h: Heap, ch: NonQuantifiedChunk, v: Verifier): (FunctionRecorder, Heap) + def merge(fr: FunctionRecorder, s: State, h: Heap, newH: Heap, v: Verifier, dAInfo: DependencyAnalysisInfo): (FunctionRecorder, Heap) + def merge(fr: FunctionRecorder, s: State, h: Heap, ch: NonQuantifiedChunk, v: Verifier, dAInfo: DependencyAnalysisInfo): (FunctionRecorder, Heap) - protected def assumeUpperPermissionBoundForQPFields(s: State, v: Verifier): State - protected def assumeUpperPermissionBoundForQPFields(s: State, heaps: Seq[Heap], v: Verifier): State + protected def assumeUpperPermissionBoundForQPFields(s: State, v: Verifier, dAInfo: DependencyAnalysisInfo): State + protected def assumeUpperPermissionBoundForQPFields(s: State, heaps: Seq[Heap], v: Verifier, dAInfo: DependencyAnalysisInfo): State } /** Performs the minimal work necessary for any consolidator: merging two heaps combines the chunk @@ -42,15 +42,15 @@ class MinimalStateConsolidator extends StateConsolidationRules { def consolidate(s: State, @unused v: Verifier): State = s def consolidateOptionally(s: State, @unused v: Verifier): State = s - def merge(fr: FunctionRecorder, s: State, h: Heap, newH: Heap, v: Verifier): (FunctionRecorder, Heap) = + def merge(fr: FunctionRecorder, s: State, h: Heap, newH: Heap, v: Verifier, dAInfo: DependencyAnalysisInfo): (FunctionRecorder, Heap) = (fr, Heap(h.values ++ newH.values)) - def merge(fr: FunctionRecorder, s: State, h: Heap, ch: NonQuantifiedChunk, v: Verifier): (FunctionRecorder, Heap) = + def merge(fr: FunctionRecorder, s: State, h: Heap, ch: NonQuantifiedChunk, v: Verifier, dAInfo: DependencyAnalysisInfo): (FunctionRecorder, Heap) = (fr, h + ch) - protected def assumeUpperPermissionBoundForQPFields(s: State, @unused v: Verifier): State = s + protected def assumeUpperPermissionBoundForQPFields(s: State, @unused v: Verifier, @unused dAInfo: DependencyAnalysisInfo): State = s - protected def assumeUpperPermissionBoundForQPFields(s: State, @unused heaps: Seq[Heap], @unused v: Verifier): State = s + protected def assumeUpperPermissionBoundForQPFields(s: State, @unused heaps: Seq[Heap], @unused v: Verifier, @unused dAInfo: DependencyAnalysisInfo): State = s } /** Default implementation that merges as many known-alias chunks as possible, and deduces various @@ -62,8 +62,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol val comLog = new CommentRecord("state consolidation", s, v.decider.pcs) val sepIdentifier = v.symbExLog.openScope(comLog) - val prevForcedSource = v.decider.analysisSourceInfoStack.getForcedSource - v.decider.analysisSourceInfoStack.setForcedSource("state consolidation") + val dAInfo = FullDependencyAnalysisInfo.create("state consolidation", DependencyType.Internal, NoDependencyAnalysisMerge(), NoDependencyAnalysisJoin()) // TODO ake: review v.decider.prover.comment("[state consolidation]") v.decider.prover.saturate(config.proverSaturationTimeouts.beforeIteration) @@ -83,9 +82,9 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol val roundLog = new CommentRecord("Round " + fixedPointRound, s, v.decider.pcs) val roundSepIdentifier = v.symbExLog.openScope(roundLog) - val (_functionRecorder, _mergedChunks, _newChunks, snapEqs) = singleMerge(functionRecorder, destChunks, newChunks, s.functionRecorderQuantifiedVariables().map(_._1), v) + val (_functionRecorder, _mergedChunks, _newChunks, snapEqs) = singleMerge(functionRecorder, destChunks, newChunks, s.functionRecorderQuantifiedVariables().map(_._1), v, dAInfo) - snapEqs foreach (t => v.decider.assume(t, Option.when(withExp)(DebugExp.createInstance("Snapshot Equations", true)), AssumptionType.Internal)) + snapEqs foreach (t => v.decider.assume(t, Option.when(withExp)(DebugExp.createInstance("Snapshot Equations", true)), dAInfo)) functionRecorder = _functionRecorder mergedChunks = _mergedChunks @@ -103,16 +102,12 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol mergedChunks.filter(_.isInstanceOf[BasicChunk]) foreach { case ch: BasicChunk => val resource = Resources.resourceDescriptions(ch.resourceID) val pathCond = interpreter.buildPathConditionsForChunk(ch, resource.instanceProperties(s.mayAssumeUpperBounds)) - v.decider.dependencyAnalyzer.disableTransitiveEdges() - pathCond.foreach(p => v.decider.assume(v.decider.wrapWithDependencyAnalysisLabel(p._1, Set(ch)), Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), AssumptionType.Internal)) - v.decider.dependencyAnalyzer.enableTransitiveEdges() + pathCond.foreach(p => v.decider.assume(v.decider.wrapWithDependencyAnalysisLabel(p._1, Set(ch)), Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), dAInfo.withMergeInfo(NoDependencyAnalysisMerge()))) } Resources.resourceDescriptions foreach { case (id, desc) => val pathCond = interpreter.buildPathConditionsForResource(id, desc.delayedProperties(s.mayAssumeUpperBounds)) - v.decider.dependencyAnalyzer.disableTransitiveEdges() - pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), AssumptionType.Internal)) - v.decider.dependencyAnalyzer.enableTransitiveEdges() + pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), dAInfo.withMergeInfo(NoDependencyAnalysisMerge()))) } v.symbExLog.closeScope(sepIdentifier) @@ -123,8 +118,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol h = mergedHeaps.head, reserveHeaps = mergedHeaps.tail) - val s2 = assumeUpperPermissionBoundForQPFields(s1, v) - v.decider.analysisSourceInfoStack.setForcedSource(prevForcedSource) + val s2 = assumeUpperPermissionBoundForQPFields(s1, v, dAInfo) s2 } @@ -132,24 +126,24 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol if (s.retrying) consolidate(s, v) else s - def merge(fr: FunctionRecorder, s: State, h: Heap, ch: NonQuantifiedChunk, v: Verifier): (FunctionRecorder, Heap) = { - merge(fr, s, h, Heap(Seq(ch)), v) + def merge(fr: FunctionRecorder, s: State, h: Heap, ch: NonQuantifiedChunk, v: Verifier, dAInfo: DependencyAnalysisInfo): (FunctionRecorder, Heap) = { + merge(fr, s, h, Heap(Seq(ch)), v, dAInfo) } - def merge(fr1: FunctionRecorder, s: State, h: Heap, newH: Heap, v: Verifier): (FunctionRecorder, Heap) = { + def merge(fr1: FunctionRecorder, s: State, h: Heap, newH: Heap, v: Verifier, dAInfo: DependencyAnalysisInfo): (FunctionRecorder, Heap) = { if(v.decider.isPathInfeasible()) return (fr1, h) val mergeLog = new CommentRecord("Merge", null, v.decider.pcs) val sepIdentifier = v.symbExLog.openScope(mergeLog) - val (fr2, mergedChunks, newlyAddedChunks, snapEqs) = singleMerge(fr1, h.values.toSeq, newH.values.toSeq, s.functionRecorderQuantifiedVariables().map(_._1), v) + val (fr2, mergedChunks, newlyAddedChunks, snapEqs) = singleMerge(fr1, h.values.toSeq, newH.values.toSeq, s.functionRecorderQuantifiedVariables().map(_._1), v, dAInfo) - v.decider.assume(snapEqs, Option.when(withExp)(DebugExp.createInstance("Snapshot", isInternal_ = true)), enforceAssumption = false, assumptionType=AssumptionType.Internal) + v.decider.assume(snapEqs, Option.when(withExp)(DebugExp.createInstance("Snapshot", isInternal_ = true)), enforceAssumption = false, dAInfo.withDependencyType(DependencyType.Internal)) val interpreter = new NonQuantifiedPropertyInterpreter(mergedChunks, v) newlyAddedChunks.filter(_.isInstanceOf[BasicChunk]) foreach { case ch: BasicChunk => val resource = Resources.resourceDescriptions(ch.resourceID) val pathCond = interpreter.buildPathConditionsForChunk(ch, resource.instanceProperties(s.mayAssumeUpperBounds)) - pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), AssumptionType.Internal)) + pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), dAInfo.withDependencyType(DependencyType.Internal))) } v.symbExLog.closeScope(sepIdentifier) @@ -160,7 +154,8 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol destChunks: Seq[Chunk], newChunks: Seq[Chunk], qvars: Seq[Var], - v: Verifier) + v: Verifier, + dAInfo: DependencyAnalysisInfo) : (FunctionRecorder, Seq[Chunk], Seq[Chunk], @@ -179,11 +174,10 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol * nextChunk: current chunk from the sequence of new chunks/of chunks to merge into the * sequence of destination chunks */ - val prevSource = v.decider.analysisSourceInfoStack.getForcedSource - v.decider.analysisSourceInfoStack.setUniqueForcedSource("state_consolidation") - val res = findMatchingChunk(accMergedChunks, nextChunk, v) match { + val dAInfo = FullDependencyAnalysisInfo.create("stat_consolidation", DependencyType.Internal, NoDependencyAnalysisMerge(), NoDependencyAnalysisJoin()) // TODO ake: review + val res = findMatchingChunk(accMergedChunks, nextChunk, v, dAInfo) match { case Some(ch) => - val resMerge = mergeChunks(fr1, ch, nextChunk, qvars, v) + val resMerge = mergeChunks(fr1, ch, nextChunk, qvars, v, dAInfo) resMerge match { case Some((fr2, newChunk, snapEq)) => @@ -194,36 +188,34 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol case None => (fr1, nextChunk +: accMergedChunks, nextChunk +: accNewChunks, accSnapEqs) } - v.decider.analysisSourceInfoStack.setForcedSource(prevSource) res } v.symbExLog.closeScope(sepIdentifier) result } - private def findMatchingChunk(chunks: Iterable[Chunk], chunk: Chunk, v: Verifier): Option[Chunk] = { + private def findMatchingChunk(chunks: Iterable[Chunk], chunk: Chunk, v: Verifier, dAInfo: DependencyAnalysisInfo): Option[Chunk] = { chunk match { case chunk: BasicChunk => - chunkSupporter.findChunk[BasicChunk](chunks, chunk.id, chunk.args, v) - case chunk: QuantifiedChunk => quantifiedChunkSupporter.findChunk(chunks, chunk, v) + chunkSupporter.findChunk[BasicChunk](chunks, chunk.id, chunk.args, v, dAInfo) + case chunk: QuantifiedChunk => quantifiedChunkSupporter.findChunk(chunks, chunk, v, dAInfo) case _ => None } } // Merges two chunks that are aliases (i.e. that have the same id and the args are proven to be equal) // and returns the merged chunk or None, if the chunks could not be merged - private def mergeChunks(fr1: FunctionRecorder, chunk1: Chunk, chunk2: Chunk, qvars: Seq[Var], v: Verifier): Option[(FunctionRecorder, Chunk, Term)] = { - val result = mergeChunks1(fr1, chunk1, chunk2, qvars, v) + private def mergeChunks(fr1: FunctionRecorder, chunk1: Chunk, chunk2: Chunk, qvars: Seq[Var], v: Verifier, dAInfo: DependencyAnalysisInfo): Option[(FunctionRecorder, Chunk, Term)] = { + val result = mergeChunks1(fr1, chunk1, chunk2, qvars, v, dAInfo) result.map({case (fRec, ch, snapEq) => // v.decider.dependencyAnalyzer.addPermissionDependencies(Set(chunk1, chunk2), Set(), ch) (fRec, ch, v.decider.wrapWithDependencyAnalysisLabel(snapEq, Set(chunk1, chunk2)))}) } - private def mergeChunks1(fr1: FunctionRecorder, chunk1: Chunk, chunk2: Chunk, qvars: Seq[Var], v: Verifier): Option[(FunctionRecorder, Chunk, Term)] = (chunk1, chunk2) match { + private def mergeChunks1(fr1: FunctionRecorder, chunk1: Chunk, chunk2: Chunk, qvars: Seq[Var], v: Verifier, dAInfo: DependencyAnalysisInfo): Option[(FunctionRecorder, Chunk, Term)] = (chunk1, chunk2) match { case (BasicChunk(rid1, id1, args1, args1Exp, snap1, snap1Exp, perm1, perm1Exp), BasicChunk(_, _, _, _, snap2, _, perm2, perm2Exp)) => val (fr2, combinedSnap, snapEq) = combineSnapshots(fr1, snap1, snap2, perm1, perm2, qvars, v) - - Some(fr2, BasicChunk(rid1, id1, args1, args1Exp, combinedSnap, snap1Exp, PermPlus(perm1, perm2), perm1Exp.map(p1 => ast.PermAdd(p1, perm2Exp.get)()), v.decider.getAnalysisInfo(AssumptionType.Internal)), snapEq) + Some(fr2, BasicChunk(rid1, id1, args1, args1Exp, combinedSnap, snap1Exp, PermPlus(perm1, perm2), perm1Exp.map(p1 => ast.PermAdd(p1, perm2Exp.get)()), v.decider.getAnalysisInfo(dAInfo.withDependencyType(DependencyType.Internal))), snapEq) case (l@QuantifiedFieldChunk(id1, fvf1, condition1, condition1Exp, perm1, perm1Exp, invs1, singletonRcvr1, singletonRcvr1Exp, hints1), r@QuantifiedFieldChunk(_, fvf2, _, _, perm2, perm2Exp, _, _, _, hints2)) => assert(l.quantifiedVars == Seq(`?r`)) @@ -233,14 +225,14 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol val permSum = PermPlus(perm1, perm2) val permSumExp = perm1Exp.map(p1 => ast.PermAdd(p1, perm2Exp.get)()) val bestHints = if (hints1.nonEmpty) hints1 else hints2 - Some(fr2, QuantifiedFieldChunk(id1, combinedSnap, condition1, condition1Exp, permSum, permSumExp, invs1, singletonRcvr1, singletonRcvr1Exp, bestHints, v.decider.getAnalysisInfo(AssumptionType.Internal)), snapEq) + Some(fr2, QuantifiedFieldChunk(id1, combinedSnap, condition1, condition1Exp, permSum, permSumExp, invs1, singletonRcvr1, singletonRcvr1Exp, bestHints, v.decider.getAnalysisInfo(dAInfo.withDependencyType(DependencyType.Internal))), snapEq) case (l@QuantifiedPredicateChunk(id1, qVars1, qVars1Exp, psf1, _, _, perm1, perm1Exp, _, _, _, _), r@QuantifiedPredicateChunk(_, qVars2, qVars2Exp, psf2, condition2, condition2Exp, perm2, perm2Exp, invs2, singletonArgs2, singletonArgs2Exp, hints2)) => val (fr2, combinedSnap, snapEq) = quantifiedChunkSupporter.combinePredicateSnapshotMaps(fr1, id1.name, qVars2, qvars, psf1, psf2, l.perm.replace(qVars1, qVars2), r.perm, v) val permSum = PermPlus(perm1.replace(qVars1, qVars2), perm2) val permSumExp = perm1Exp.map(p1 => ast.PermAdd(p1.replace(qVars1Exp.get.zip(qVars2Exp.get).toMap), perm2Exp.get)()) - Some(fr2, QuantifiedPredicateChunk(id1, qVars2, qVars2Exp, combinedSnap, condition2, condition2Exp, permSum, permSumExp, invs2, singletonArgs2, singletonArgs2Exp, hints2, v.decider.getAnalysisInfo(AssumptionType.Internal)), snapEq) + Some(fr2, QuantifiedPredicateChunk(id1, qVars2, qVars2Exp, combinedSnap, condition2, condition2Exp, permSum, permSumExp, invs2, singletonArgs2, singletonArgs2Exp, hints2, v.decider.getAnalysisInfo(dAInfo.withDependencyType(DependencyType.Internal))), snapEq) case _ => None } @@ -273,10 +265,10 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol } } - protected def assumeUpperPermissionBoundForQPFields(s: State, v: Verifier): State = - assumeUpperPermissionBoundForQPFields(s, s.h +: s.reserveHeaps, v) + protected def assumeUpperPermissionBoundForQPFields(s: State, v: Verifier, dAInfo: DependencyAnalysisInfo): State = + assumeUpperPermissionBoundForQPFields(s, s.h +: s.reserveHeaps, v, dAInfo) - protected def assumeUpperPermissionBoundForQPFields(s: State, heaps: Seq[Heap], v: Verifier): State = { + protected def assumeUpperPermissionBoundForQPFields(s: State, heaps: Seq[Heap], v: Verifier, dAInfo: DependencyAnalysisInfo): State = { heaps.foldLeft(s) { case (si, heap) => val chunks: Seq[QuantifiedFieldChunk] = heap.values.collect({ case ch: QuantifiedFieldChunk => ch }).to(Seq) @@ -308,7 +300,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol Some(DebugExp.createInstance(exp, exp)) } else { None } v.decider.assume( - Forall(receiver, PermAtMost(currentPermAmount, FullPerm), Trigger(trigger), "qp-fld-prm-bnd"), debugExp, AssumptionType.Internal) + Forall(receiver, PermAtMost(currentPermAmount, FullPerm), Trigger(trigger), "qp-fld-prm-bnd"), debugExp, dAInfo.withDependencyType(DependencyType.Internal)) } else { /* If we don't use heap-dependent triggers, the trigger x.f does not work. Instead, we assume the permission @@ -325,7 +317,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol val exp = ast.PermLeCmp(permExp, ast.FullPerm()())() Some(DebugExp.createInstance(exp, exp)) } else { None } - v.decider.assume(PermAtMost(PermLookup(field.name, pmDef.pm, chunk.singletonRcvr.get), FullPerm), debugExp, AssumptionType.Internal) + v.decider.assume(PermAtMost(PermLookup(field.name, pmDef.pm, chunk.singletonRcvr.get), FullPerm), debugExp, dAInfo.withDependencyType(DependencyType.Internal)) } else { val chunkReceivers = chunk.invs.get.inverses.map(i => App(i, chunk.invs.get.additionalArguments ++ chunk.quantifiedVars)) val triggers = chunkReceivers.map(r => Trigger(r)).toSeq @@ -341,7 +333,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol Some(DebugExp.createInstance(exp, exp)) } else { None } v.decider.assume( - Forall(chunk.quantifiedVars, PermAtMost(currentPermAmount, FullPerm), triggers, "qp-fld-prm-bnd"), debugExp, AssumptionType.Internal) + Forall(chunk.quantifiedVars, PermAtMost(currentPermAmount, FullPerm), triggers, "qp-fld-prm-bnd"), debugExp, dAInfo.withDependencyType(DependencyType.Internal)) } } @@ -413,15 +405,15 @@ class LastRetryFailOnlyStateConsolidator(config: Config) extends LastRetryStateC * - Merging heaps and assuming QP permission bounds is equivalent to [[MinimalStateConsolidator]] */ class MinimalRetryingStateConsolidator(config: Config) extends RetryingStateConsolidator(config) { - override def merge(fr: FunctionRecorder, s: State, h: Heap, newH: Heap, v: Verifier): (FunctionRecorder, Heap) = + override def merge(fr: FunctionRecorder, s: State, h: Heap, newH: Heap, v: Verifier, dAInfo: DependencyAnalysisInfo): (FunctionRecorder, Heap) = (fr, Heap(h.values ++ newH.values)) - override def merge(fr: FunctionRecorder, s: State, h: Heap, ch: NonQuantifiedChunk, v: Verifier): (FunctionRecorder, Heap) = + override def merge(fr: FunctionRecorder, s: State, h: Heap, ch: NonQuantifiedChunk, v: Verifier, dAInfo: DependencyAnalysisInfo): (FunctionRecorder, Heap) = (fr, h + ch) - override protected def assumeUpperPermissionBoundForQPFields(s: State, @unused v: Verifier): State = s + override protected def assumeUpperPermissionBoundForQPFields(s: State, @unused v: Verifier, dAInfo: DependencyAnalysisInfo): State = s - override protected def assumeUpperPermissionBoundForQPFields(s: State, @unused heaps: Seq[Heap], @unused v: Verifier): State = s + override protected def assumeUpperPermissionBoundForQPFields(s: State, @unused heaps: Seq[Heap], @unused v: Verifier, dAInfo: DependencyAnalysisInfo): State = s } /** A variant of [[DefaultStateConsolidator]] that aims to work best when Silicon is run in @@ -441,10 +433,12 @@ class MoreComplexExhaleStateConsolidator(config: Config) extends DefaultStateCon // silver\src\test\resources\quantifiedpermissions\sets\generalised_shape.sil // to fail. + val dAInfo = FullDependencyAnalysisInfo.create("state consolidation", DependencyType.Internal, NoDependencyAnalysisMerge(), NoDependencyAnalysisJoin()) // TODO ake: review + if (s.retrying) { // TODO: apply to all heaps (s.h +: s.reserveHeaps, as done below) // NOTE: Doing this regardless of s.retrying might improve completeness in certain (rare) cases - moreCompleteExhaleSupporter.assumeFieldPermissionUpperBounds(s.h, v) + moreCompleteExhaleSupporter.assumeFieldPermissionUpperBounds(s.h, v, dAInfo) } s diff --git a/src/main/scala/supporters/PredicateVerificationUnit.scala b/src/main/scala/supporters/PredicateVerificationUnit.scala index 85d886cf8..a5ff5489c 100644 --- a/src/main/scala/supporters/PredicateVerificationUnit.scala +++ b/src/main/scala/supporters/PredicateVerificationUnit.scala @@ -8,7 +8,7 @@ package viper.silicon.supporters import com.typesafe.scalalogging.Logger import viper.silicon.common.collections.immutable.InsertionOrderedSet -import viper.silicon.dependencyAnalysis.{DependencyAnalyzer, AssumptionType} +import viper.silicon.dependencyAnalysis.{AssumptionType, DependencyAnalyzer, NoDependencyAnalysisInfo} import viper.silver.ast import viper.silver.ast.Program import viper.silver.components.StatefulComponent @@ -116,8 +116,6 @@ trait DefaultPredicateVerificationUnitProvider extends VerifierComponent { v: Ve var branchResults: Seq[(Seq[Term], Seq[(ast.Exp, Option[ast.Exp])], Heap, InsertionOrderedSet[Term])] = Seq() - val assumptionType = DependencyAnalyzer.extractAssumptionTypeFromInfo(predicate.info).getOrElse(AssumptionType.Rewrite) - val result = predicate.body match { case None => Success() @@ -125,7 +123,7 @@ trait DefaultPredicateVerificationUnitProvider extends VerifierComponent { v: Ve /* locallyXXX { magicWandSupporter.checkWandsAreSelfFraming(σ.γ, σ.h, predicate, c)} &&*/ executionFlowController.locally(s, v)((s1, _) => { - produce(s1, toSf(snap), body, err, v, assumptionType)((s2, v2) => { + produce(s1, toSf(snap), body, err, v, NoDependencyAnalysisInfo())((s2, v2) => { val branchConds = v2.decider.pcs.branchConditions.reverse val branchCondExps = v2.decider.pcs.branchConditionExps.reverse assert(branchConds.length == branchCondExps.length) diff --git a/src/main/scala/supporters/SnapshotSupporter.scala b/src/main/scala/supporters/SnapshotSupporter.scala index 5f33a594c..46e17d531 100644 --- a/src/main/scala/supporters/SnapshotSupporter.scala +++ b/src/main/scala/supporters/SnapshotSupporter.scala @@ -6,9 +6,8 @@ package viper.silicon.supporters -import viper.silicon.dependencyAnalysis.AssumptionType -import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType import viper.silicon.debugger.DebugExp +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfo import viper.silicon.state.terms.{Combine, First, Second, Sort, Term, Unit, sorts} import viper.silicon.state.{MagicWandIdentifier, State, SymbolConverter} import viper.silicon.utils.toSf @@ -29,7 +28,7 @@ trait SnapshotSupporter { a0: ast.Exp, a1: ast.Exp, v: Verifier, - assumptionType: AssumptionType) + dAInfo: DependencyAnalysisInfo) : ((Sort, Verifier) => Term, (Sort, Verifier) => Term) } @@ -121,10 +120,10 @@ class DefaultSnapshotSupporter(symbolConverter: SymbolConverter) extends Snapsho a0: ast.Exp, a1: ast.Exp, v: Verifier, - assumptionType: AssumptionType) + dAInfo: DependencyAnalysisInfo) : ((Sort, Verifier) => Term, (Sort, Verifier) => Term) = { - val (snap0, snap1) = createSnapshotPair(s, sf(sorts.Snap, v), a0, a1, v, assumptionType) + val (snap0, snap1) = createSnapshotPair(s, sf(sorts.Snap, v), a0, a1, v, dAInfo) val sf0 = toSf(snap0) val sf1 = toSf(snap1) @@ -132,7 +131,7 @@ class DefaultSnapshotSupporter(symbolConverter: SymbolConverter) extends Snapsho (sf0, sf1) } - private def createSnapshotPair(@unused s: State, snap: Term, @unused a0: ast.Exp, @unused a1: ast.Exp, v: Verifier, assumptionType: AssumptionType): (Term, Term) = { + private def createSnapshotPair(@unused s: State, snap: Term, @unused a0: ast.Exp, @unused a1: ast.Exp, v: Verifier, dAInfo: DependencyAnalysisInfo): (Term, Term) = { /* [2015-11-17 Malte] If both fresh snapshot terms and first/second datatypes * are used, then the overall test suite verifies in 2min 10sec, whereas * it takes 2min 20sec when only first/second datatypes are used. Might be @@ -164,7 +163,7 @@ class DefaultSnapshotSupporter(symbolConverter: SymbolConverter) extends Snapsho (snap0, snap1, snap === Combine(snap0, snap1)) } - v.decider.assume(snapshotEq, Option.when(Verifier.config.enableDebugging())(DebugExp.createInstance("Snapshot", true)), assumptionType) + v.decider.assume(snapshotEq, Option.when(Verifier.config.enableDebugging())(DebugExp.createInstance("Snapshot", true)), dAInfo) (snap0, snap1) } From 21658db0d1687385e327489646ed8023fcaec965 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 30 Mar 2026 16:23:59 +0200 Subject: [PATCH 401/474] create DependencyAnalysisInfoes --- src/main/scala/debugger/SiliconDebugger.scala | 5 +- src/main/scala/decider/Decider.scala | 106 ++--- .../dependencyAnalysis/AnalysisInfo.scala | 4 +- .../AnalysisSourceInfo.scala | 6 +- .../DependencyAnalysisInfo.scala | 144 +++--- .../DependencyAnalyzer.scala | 78 ++-- .../DependencyGraphImporter.scala | 4 +- .../FrontendDependencyAnalysisInfo.scala | 6 +- .../scala/interfaces/decider/Prover.scala | 3 +- src/main/scala/interfaces/state/Chunks.scala | 4 +- .../NonQuantifiedPropertyInterpreter.scala | 3 +- src/main/scala/rules/Brancher.scala | 22 +- src/main/scala/rules/ChunkSupporter.scala | 82 ++-- src/main/scala/rules/Consumer.scala | 101 +++-- src/main/scala/rules/ConsumptionResult.scala | 5 +- src/main/scala/rules/Evaluator.scala | 409 +++++++++--------- src/main/scala/rules/Executor.scala | 130 +++--- src/main/scala/rules/HavocSupporter.scala | 24 +- src/main/scala/rules/HeapSupporter.scala | 138 +++--- src/main/scala/rules/Joiner.scala | 17 +- src/main/scala/rules/LetSupporter.scala | 26 +- src/main/scala/rules/MagicWandSupporter.scala | 78 ++-- .../rules/MoreCompleteExhaleSupporter.scala | 92 ++-- .../scala/rules/PermissionSupporter.scala | 14 +- src/main/scala/rules/PredicateSupporter.scala | 58 +-- src/main/scala/rules/Producer.scala | 102 +++-- .../scala/rules/QuantifiedChunkSupport.scala | 178 ++++---- src/main/scala/rules/StateConsolidator.scala | 93 ++-- .../scala/supporters/MethodSupporter.scala | 18 +- .../PredicateVerificationUnit.scala | 22 +- .../scala/supporters/SnapshotSupporter.scala | 12 +- .../functions/FunctionVerificationUnit.scala | 33 +- 32 files changed, 992 insertions(+), 1025 deletions(-) diff --git a/src/main/scala/debugger/SiliconDebugger.scala b/src/main/scala/debugger/SiliconDebugger.scala index 93f7b807a..a3b30f122 100644 --- a/src/main/scala/debugger/SiliconDebugger.scala +++ b/src/main/scala/debugger/SiliconDebugger.scala @@ -2,6 +2,7 @@ package viper.silicon.debugger import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.decider.{Cvc5ProverStdIO, RecordedPathConditions, Z3ProverStdIO} +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfoes import viper.silicon.interfaces.state.Chunk import viper.silicon.interfaces.{Failure, SiliconDebuggingFailureContext, Success, VerificationResult} import viper.silicon.resources.{FieldID, PredicateID} @@ -421,7 +422,7 @@ class SiliconDebugger(verificationResults: List[VerificationResult], var resE: ast.Exp = null var resV: Verifier = null val pve: PartialVerificationError = PartialVerificationError(r => ContractNotWellformed(assertionE, r)) - val verificationResult = evaluator.eval3(obl.s, assertionE, pve, obl.v)((_, t, newE, newV) => { + val verificationResult = evaluator.eval3(obl.s, assertionE, pve, obl.v, DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes)((_, t, newE, newV) => { resT = t resE = newE.get resV = newV @@ -488,7 +489,7 @@ class SiliconDebugger(verificationResults: List[VerificationResult], var evalPcs: RecordedPathConditions = null val pve: PartialVerificationError = PartialVerificationError(r => ContractNotWellformed(e, r)) val beforeEval = v.decider.setPathConditionMark() - val verificationResult = evaluator.eval3(obl.s, e, pve, v)((newS, t, newE, newV) => { + val verificationResult = evaluator.eval3(obl.s, e, pve, v, DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes)((newS, t, newE, newV) => { resS = newS resT = t resE = newE.get diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 362370163..9229fbb55 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -52,10 +52,10 @@ trait Decider { def pushScope(): Unit def popScope(): Unit - def checkSmoke(dAInfo: DependencyAnalysisInfo): Boolean - def checkSmoke(dAInfo: DependencyAnalysisInfo, isAssert: Boolean = false): Boolean + def checkSmoke(analysisInfoes: DependencyAnalysisInfoes): Boolean + def checkSmoke(analysisInfoes: DependencyAnalysisInfoes, isAssert: Boolean = false): Boolean - def setCurrentBranchCondition(t: Term, te: (ast.Exp, Option[ast.Exp]), dAInfo: DependencyAnalysisInfo): Unit + def setCurrentBranchCondition(t: Term, te: (ast.Exp, Option[ast.Exp]), analysisInfoes: DependencyAnalysisInfoes): Unit def setPathConditionMark(): Mark def finishDebugSubExp(description : String): Unit @@ -69,24 +69,24 @@ trait Decider { def pushAndGetAnalysisSourceInfo(stmt: ast.Stmt, dependencyType: Option[DependencyType]): AnalysisSourceInfo def isPathInfeasible(): Boolean - def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp], dAInfo: DependencyAnalysisInfo): Unit - def assume(t: Term, debugExp: Option[DebugExp], dAInfo: DependencyAnalysisInfo): Unit - def assume(assumptions: Iterable[Term], debugExps: Option[Iterable[DebugExp]], description: String, enforceAssumption: Boolean, dAInfo: DependencyAnalysisInfo): Unit - def assume(terms: Seq[Term], debugExps: Option[Seq[DebugExp]], dAInfo: DependencyAnalysisInfo): Unit - def assumeDefinition(t: Term, debugExp: Option[DebugExp], dAInfo: DependencyAnalysisInfo): Unit - def assume(terms: Iterable[Term], debugExp: Option[DebugExp], enforceAssumption: Boolean, dAInfo: DependencyAnalysisInfo): Unit + def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp], analysisInfoes: DependencyAnalysisInfoes): Unit + def assume(t: Term, debugExp: Option[DebugExp], analysisInfoes: DependencyAnalysisInfoes): Unit + def assume(assumptions: Iterable[Term], debugExps: Option[Iterable[DebugExp]], description: String, enforceAssumption: Boolean, analysisInfoes: DependencyAnalysisInfoes): Unit + def assume(terms: Seq[Term], debugExps: Option[Seq[DebugExp]], analysisInfoes: DependencyAnalysisInfoes): Unit + def assumeDefinition(t: Term, debugExp: Option[DebugExp], analysisInfoes: DependencyAnalysisInfoes): Unit + def assume(terms: Iterable[Term], debugExp: Option[DebugExp], enforceAssumption: Boolean, analysisInfoes: DependencyAnalysisInfoes): Unit def assumeLabel(term: Term, assumptionLabel: String): Unit - def check(t: Term, timeout: Int, dAInfo: DependencyAnalysisInfo): Boolean - def checkSmokeAndSetInfeasibilityNode(dAInfo: DependencyAnalysisInfo): Unit + def check(t: Term, timeout: Int, analysisInfoes: DependencyAnalysisInfoes): Boolean + def checkSmokeAndSetInfeasibilityNode(analysisInfoes: DependencyAnalysisInfoes): Unit /* TODO: Consider changing assert such that * 1. It passes State and Operations to the continuation * 2. The implementation reacts to a failing assertion by e.g. a state consolidation */ - def assert(t: Term, dAInfo: DependencyAnalysisInfo)(Q: Boolean => VerificationResult): VerificationResult - def assert(t: Term, dAInfo: DependencyAnalysisInfo, timeout: Option[Int])(Q: Boolean => VerificationResult): VerificationResult + def assert(t: Term, analysisInfoes: DependencyAnalysisInfoes)(Q: Boolean => VerificationResult): VerificationResult + def assert(t: Term, analysisInfoes: DependencyAnalysisInfoes, timeout: Option[Int])(Q: Boolean => VerificationResult): VerificationResult def fresh(id: String, sort: Sort, ptype: Option[PType]): Var def fresh(id: String, argSorts: Seq[Sort], resultSort: Sort): Function @@ -119,9 +119,9 @@ trait Decider { var analysisSourceInfoStack: AnalysisSourceInfoStack def initDependencyAnalyzer(member: Member, preambleNodes: Iterable[DependencyAnalysisNode]): Unit def removeDependencyAnalyzer(): Unit - def getAnalysisInfo(daInfo: DependencyAnalysisInfo): AnalysisInfo + def getAnalysisInfo(daInfoes: DependencyAnalysisInfoes): AnalysisInfo def isDependencyAnalysisEnabled: Boolean - def handleFailedAssertionForDependencyAnalysis(failedAssertion: Term, dAInfo: DependencyAnalysisInfo, assumeFailedAssertion: Boolean): Unit + def handleFailedAssertionForDependencyAnalysis(failedAssertion: Term, analysisInfoes: DependencyAnalysisInfoes, assumeFailedAssertion: Boolean): Unit } /* @@ -172,7 +172,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => analysisSourceInfoStack = AnalysisSourceInfoStack() } - def getAnalysisInfo(dAInfo: DependencyAnalysisInfo): AnalysisInfo = AnalysisInfo(this, dependencyAnalyzer, dAInfo) + def getAnalysisInfo(analysisInfoes: DependencyAnalysisInfoes): AnalysisInfo = AnalysisInfo(this, dependencyAnalyzer, analysisInfoes) def functionDecls: Set[FunctionDecl] = _declaredFreshFunctions def macroDecls: Vector[MacroDecl] = _declaredFreshMacros @@ -293,11 +293,11 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => //symbExLog.closeScope(sepIdentifier) } - def setCurrentBranchCondition(t: Term, te: (ast.Exp, Option[ast.Exp]), dAInfo: DependencyAnalysisInfo): Unit = { + def setCurrentBranchCondition(t: Term, te: (ast.Exp, Option[ast.Exp]), analysisInfoes: DependencyAnalysisInfoes): Unit = { if(isPathInfeasible()) return pathConditions.setCurrentBranchCondition(t, te) - assume(t, Option.when(te._2.isDefined)(te._1), te._2, dAInfo) + assume(t, Option.when(te._2.isDefined)(te._1), te._2, analysisInfoes) } def setPathConditionMark(): Mark = pathConditions.mark() @@ -392,23 +392,23 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => - def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp], dAInfo: DependencyAnalysisInfo): Unit = { + def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp], analysisInfoes: DependencyAnalysisInfoes): Unit = { if (finalExp.isDefined) { - assume(assumptions=InsertionOrderedSet((t, Some(DebugExp.createInstance(e.get, finalExp.get)))), analysisSourceInfoStack.getFullSourceInfo, enforceAssumption = false, isDefinition = false, dAInfo) + assume(assumptions=InsertionOrderedSet((t, Some(DebugExp.createInstance(e.get, finalExp.get)))), analysisSourceInfoStack.getFullSourceInfo, enforceAssumption = false, isDefinition = false, analysisInfoes) } else { - assume(assumptions=InsertionOrderedSet((t, None)), analysisSourceInfoStack.getFullSourceInfo, enforceAssumption = false, isDefinition = false, dAInfo) + assume(assumptions=InsertionOrderedSet((t, None)), analysisSourceInfoStack.getFullSourceInfo, enforceAssumption = false, isDefinition = false, analysisInfoes) } } - def assume(t: Term, debugExp: Option[DebugExp], dAInfo: DependencyAnalysisInfo): Unit = { - assume(InsertionOrderedSet(Seq((t, debugExp))), analysisSourceInfoStack.getFullSourceInfo, enforceAssumption = false, isDefinition = false, dAInfo) + def assume(t: Term, debugExp: Option[DebugExp], analysisInfoes: DependencyAnalysisInfoes): Unit = { + assume(InsertionOrderedSet(Seq((t, debugExp))), analysisSourceInfoStack.getFullSourceInfo, enforceAssumption = false, isDefinition = false, analysisInfoes) } - def assumeDefinition(t: Term, debugExp: Option[DebugExp], dAInfo: DependencyAnalysisInfo): Unit = { - assume(InsertionOrderedSet(Seq((t, debugExp))), analysisSourceInfoStack.getFullSourceInfo, enforceAssumption=false, isDefinition=true, dAInfo) + def assumeDefinition(t: Term, debugExp: Option[DebugExp], analysisInfoes: DependencyAnalysisInfoes): Unit = { + assume(InsertionOrderedSet(Seq((t, debugExp))), analysisSourceInfoStack.getFullSourceInfo, enforceAssumption=false, isDefinition=true, analysisInfoes) } - def assume(assumptions: InsertionOrderedSet[(Term, Option[DebugExp])], analysisSourceInfo: AnalysisSourceInfo, enforceAssumption: Boolean, isDefinition: Boolean, dAInfo: DependencyAnalysisInfo): Unit = { + def assume(assumptions: InsertionOrderedSet[(Term, Option[DebugExp])], analysisSourceInfo: AnalysisSourceInfo, enforceAssumption: Boolean, isDefinition: Boolean, analysisInfoes: DependencyAnalysisInfoes): Unit = { val filteredAssumptions = if (enforceAssumption) assumptions else assumptions filterNot (a => isKnownToBeTrue(a._1)) @@ -419,15 +419,15 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } val filteredAssumptionsWithLabels = filteredAssumptions map{case (t, _) => - val assumptionId: Option[Int] = dependencyAnalyzer.addAssumption(t, dAInfo) + val assumptionId: Option[Int] = dependencyAnalyzer.addAssumption(t, analysisInfoes) (t, DependencyAnalyzer.createAssumptionLabel(assumptionId)) } if (filteredAssumptions.nonEmpty) assumeWithoutSmokeChecks(filteredAssumptionsWithLabels, isDefinition=isDefinition) } - def assume(assumptions: Seq[Term], debugExps: Option[Seq[DebugExp]], dAInfo: DependencyAnalysisInfo): Unit = { - val assumptionsWithLabels = addAssumptionLabels(assumptions, dAInfo) + def assume(assumptions: Seq[Term], debugExps: Option[Seq[DebugExp]], analysisInfoes: DependencyAnalysisInfoes): Unit = { + val assumptionsWithLabels = addAssumptionLabels(assumptions, analysisInfoes) assumeWithoutSmokeChecks(InsertionOrderedSet(assumptionsWithLabels)) if (debugMode) { @@ -435,7 +435,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } } - def assume(assumptions: Iterable[Term], debugExps: Option[Iterable[DebugExp]], description: String, enforceAssumption: Boolean, dAInfo: DependencyAnalysisInfo): Unit = { + def assume(assumptions: Iterable[Term], debugExps: Option[Iterable[DebugExp]], description: String, enforceAssumption: Boolean, analysisInfoes: DependencyAnalysisInfoes): Unit = { val debugExp = Option.when(debugExps.isDefined)(DebugExp.createInstance(description, InsertionOrderedSet(debugExps.get))) val filteredTerms = @@ -444,7 +444,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => if(filteredTerms.isEmpty) return - val assumptionsWithLabels = addAssumptionLabels(filteredTerms, dAInfo) + val assumptionsWithLabels = addAssumptionLabels(filteredTerms, analysisInfoes) assumeWithoutSmokeChecks(InsertionOrderedSet(assumptionsWithLabels)) @@ -453,14 +453,14 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } } - private def addAssumptionLabels(filteredTerms: Iterable[Term], dAInfo: DependencyAnalysisInfo) = { + private def addAssumptionLabels(filteredTerms: Iterable[Term], analysisInfoes: DependencyAnalysisInfoes) = { filteredTerms map (t => { - val assumptionIds = dependencyAnalyzer.addAssumption(t, dAInfo) + val assumptionIds = dependencyAnalyzer.addAssumption(t, analysisInfoes) (t, DependencyAnalyzer.createAssumptionLabel(assumptionIds)) }) } - def assume(terms: Iterable[Term], debugExp: Option[DebugExp], enforceAssumption: Boolean, dAInfo: DependencyAnalysisInfo): Unit = { + def assume(terms: Iterable[Term], debugExp: Option[DebugExp], enforceAssumption: Boolean, analysisInfoes: DependencyAnalysisInfoes): Unit = { val filteredTerms = if (enforceAssumption) terms else terms filterNot isKnownToBeTrue @@ -470,7 +470,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => if (debugMode) { addDebugExp(debugExp.get.withTerm(And(filteredTerms))) } - val termsWithLabel = addAssumptionLabels(filteredTerms, dAInfo) + val termsWithLabel = addAssumptionLabels(filteredTerms, analysisInfoes) assumeWithoutSmokeChecks(InsertionOrderedSet(termsWithLabel)) } @@ -512,10 +512,10 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => /* Asserting facts */ - def checkSmoke(dAInfo: DependencyAnalysisInfo): Boolean = checkSmoke(dAInfo, isAssert = false) + def checkSmoke(analysisInfoes: DependencyAnalysisInfoes): Boolean = checkSmoke(analysisInfoes, isAssert = false) - def checkSmoke(dAInfo: DependencyAnalysisInfo, isAssert: Boolean=false): Boolean = { - val checkNode = dependencyAnalyzer.createAssertOrCheckNode(False, dAInfo, !isAssert) + def checkSmoke(analysisInfoes: DependencyAnalysisInfoes, isAssert: Boolean=false): Boolean = { + val checkNode = dependencyAnalyzer.createAssertOrCheckNode(False, analysisInfoes, !isAssert) val label = DependencyAnalyzer.createAssertionLabel(checkNode.map(_.id)) val timeout = if (isAssert) Verifier.config.assertTimeout.toOption else Verifier.config.checkTimeout.toOption @@ -527,7 +527,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => }else if(result){ checkNode foreach dependencyAnalyzer.addAssertionNode dependencyAnalyzer.processUnsatCoreAndAddDependencies(prover.getLastUnsatCore, label) - val infeasibleNodeId = dependencyAnalyzer.addInfeasibilityNode(!isAssert, dAInfo) + val infeasibleNodeId = dependencyAnalyzer.addInfeasibilityNode(!isAssert, analysisInfoes) // THIS WOULD BE UNSOUND! Unsoundness is introduced when infeasibility is introduced while executing a package statements and pontentially in other cases as well. // assumeWithoutSmokeChecks(InsertionOrderedSet((False, DependencyAnalyzer.createAssumptionLabel(infeasibleNodeId)))) dependencyAnalyzer.addDependency(checkNode.map(_.id), infeasibleNodeId) @@ -536,38 +536,38 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => result } - def checkSmokeAndSetInfeasibilityNode(dAInfo: DependencyAnalysisInfo): Unit = { + def checkSmokeAndSetInfeasibilityNode(analysisInfoes: DependencyAnalysisInfoes): Unit = { var infeasibilityNodeId: Option[Int] = pcs.getCurrentInfeasibilityNode if(infeasibilityNodeId.isDefined) return - val (success, checkNode) = deciderAssert(False, dAInfo, Some(Verifier.config.checkTimeout()), isCheck=true) + val (success, checkNode) = deciderAssert(False, analysisInfoes, Some(Verifier.config.checkTimeout()), isCheck=true) if(success){ - infeasibilityNodeId = dependencyAnalyzer.addInfeasibilityNode(isCheck = true, dAInfo) + infeasibilityNodeId = dependencyAnalyzer.addInfeasibilityNode(isCheck = true, analysisInfoes) dependencyAnalyzer.addDependency(checkNode.map(_.id), infeasibilityNodeId) pcs.setCurrentInfeasibilityNode(infeasibilityNodeId) } } - override def handleFailedAssertionForDependencyAnalysis(failedAssertion: Term, dAInfo: DependencyAnalysisInfo, assumeFailedAssertion: Boolean): Unit = { - dependencyAnalyzer.addAssertionFailedNode(failedAssertion, dAInfo) + override def handleFailedAssertionForDependencyAnalysis(failedAssertion: Term, analysisInfoes: DependencyAnalysisInfoes, assumeFailedAssertion: Boolean): Unit = { + dependencyAnalyzer.addAssertionFailedNode(failedAssertion, analysisInfoes) if(assumeFailedAssertion){ - assume(failedAssertion, None, None, dAInfo) + assume(failedAssertion, None, None, analysisInfoes) failedAssertion match { - case False => checkSmokeAndSetInfeasibilityNode(dAInfo) + case False => checkSmokeAndSetInfeasibilityNode(analysisInfoes) case _ => } } } - def check(t: Term, timeout: Int, dAInfo: DependencyAnalysisInfo): Boolean = { - deciderAssert(t, dAInfo, Some(timeout), isCheck=true)._1 + def check(t: Term, timeout: Int, analysisInfoes: DependencyAnalysisInfoes): Boolean = { + deciderAssert(t, analysisInfoes, Some(timeout), isCheck=true)._1 } - def assert(t: Term, dAInfo: DependencyAnalysisInfo)(Q: Boolean => VerificationResult): VerificationResult = assert(t, dAInfo, timeout=Verifier.config.assertTimeout.toOption)(Q) + def assert(t: Term, analysisInfoes: DependencyAnalysisInfoes)(Q: Boolean => VerificationResult): VerificationResult = assert(t, analysisInfoes, timeout=Verifier.config.assertTimeout.toOption)(Q) - def assert(t: Term, dAInfo: DependencyAnalysisInfo, timeout: Option[Int])(Q: Boolean => VerificationResult): VerificationResult = { - val (success, _) = deciderAssert(t, dAInfo, timeout) + def assert(t: Term, analysisInfoes: DependencyAnalysisInfoes, timeout: Option[Int])(Q: Boolean => VerificationResult): VerificationResult = { + val (success, _) = deciderAssert(t, analysisInfoes, timeout) // If the SMT query was not successful, store it (possibly "overwriting" // any previously saved query), otherwise discard any query we had saved @@ -581,13 +581,13 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => Q(success) } - private def deciderAssert(t: Term, dAInfo: DependencyAnalysisInfo, timeout: Option[Int], isCheck: Boolean=false) = { + private def deciderAssert(t: Term, analysisInfoes: DependencyAnalysisInfoes, timeout: Option[Int], isCheck: Boolean=false) = { val assertRecord = new DeciderAssertRecord(t, timeout) val sepIdentifier = symbExLog.openScope(assertRecord) val asserted = if(isDependencyAnalysisEnabled) t.equals(True) else isKnownToBeTrue(t) - val assertNode = if(!asserted) dependencyAnalyzer.createAssertOrCheckNode(t, dAInfo, isCheck) else None + val assertNode = if(!asserted) dependencyAnalyzer.createAssertOrCheckNode(t, analysisInfoes, isCheck) else None val result = asserted || proverAssert(t, timeout, DependencyAnalyzer.createAssertionLabel(assertNode map (_.id))) diff --git a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala index 087de4c24..0403a5b40 100644 --- a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala +++ b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala @@ -88,5 +88,7 @@ object DependencyType { case class DependencyType(assumptionType: AssumptionType, assertionType: AssumptionType) -case class AnalysisInfo(decider: Decider, dependencyAnalyzer: DependencyAnalyzer, dAInfo: DependencyAnalysisInfo) +case class AnalysisInfo(decider: Decider, dependencyAnalyzer: DependencyAnalyzer, analysisInfoes: DependencyAnalysisInfoes) { + def withDependencyType(dependencyType: DependencyType) = this.copy(analysisInfoes=analysisInfoes.withDependencyType(dependencyType)) +} diff --git a/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala b/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala index f2dcc450c..8fc4cf0ee 100644 --- a/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala +++ b/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala @@ -31,7 +31,7 @@ object AnalysisSourceInfo { } -abstract class AnalysisSourceInfo { +trait AnalysisSourceInfo extends ast.Info { override def toString: String = getPositionString def getDescription: String @@ -64,6 +64,10 @@ abstract class AnalysisSourceInfo { def getFineGrainedSource: AnalysisSourceInfo = this def isAnalysisEnabled: Boolean = true + + + override def comment: Seq[String] = Nil + override def isCached: Boolean = false } case class NoAnalysisSourceInfo() extends AnalysisSourceInfo { diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala index fbb2c7c27..a4a869034 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala @@ -1,116 +1,74 @@ package viper.silicon.dependencyAnalysis -import jdk.jshell.spi.ExecutionControl.NotImplementedException import viper.silicon.dependencyAnalysis.JoinType.JoinType import viper.silver.ast +import viper.silver.ast.NoPosition -trait DependencyAnalysisInfo { - def getSourceInfo: AnalysisSourceInfo - def getDependencyType: DependencyType - def getMergeInfo: DependencyAnalysisMergeInfo - def getJoinInfo: DependencyAnalysisJoinInfo - def withDependencyType(newDependencyType: DependencyType): DependencyAnalysisInfo - - def withSource(sourceInfo: AnalysisSourceInfo): DependencyAnalysisInfo - - def withAdditionalEvalNode(analysisSourceInfo: AnalysisSourceInfo): DependencyAnalysisInfo +trait AnalysisInfoes { } +case class DependencyAnalysisInfoes(sourceInfoes: List[AnalysisSourceInfo], dependencyTypes: List[DependencyTypeInfo], mergeInfoes: List[DependencyAnalysisMergeInfo], joinInfoes: List[DependencyAnalysisJoinInfo], nodes: List[ast.Node]) extends AnalysisInfoes { -case class NoDependencyAnalysisInfo() extends DependencyAnalysisInfo { - override def getSourceInfo: AnalysisSourceInfo = throw new NotImplementedException("NoDependencyAnalysisInfo.getSourceInfo") - - override def getDependencyType: DependencyType = throw new NotImplementedException("NoDependencyAnalysisInfo.getDependencyType") - - override def getMergeInfo: DependencyAnalysisMergeInfo = throw new NotImplementedException("NoDependencyAnalysisInfo.getMergeInfo") - - override def getJoinInfo: DependencyAnalysisJoinInfo = throw new NotImplementedException("NoDependencyAnalysisInfo.getJoinInfo") - - override def withDependencyType(newDependencyType: DependencyType): NoDependencyAnalysisInfo = this - - override def withSource(sourceInfo: AnalysisSourceInfo): DependencyAnalysisInfo = this - - override def withAdditionalEvalNode(analysisSourceInfo: AnalysisSourceInfo): NoDependencyAnalysisInfo = this -} - -case class DependencyAnalysisInfoWithoutSource( dependencyType: DependencyType, - mergeInfo: Option[DependencyAnalysisMergeInfo]=None, - joinInfo: DependencyAnalysisJoinInfo=NoDependencyAnalysisJoin(), - // additionalNodes: Set[CustomDependencyAnalysisNode]=Set.empty - ) extends DependencyAnalysisInfo { + def addInfo(info: ast.Info, node: ast.Node): DependencyAnalysisInfoes = { + val newSourceInfoes = sourceInfoes ++ info.getUniqueInfo[AnalysisSourceInfo].toList + val newDependencyInfoes = dependencyTypes ++ info.getUniqueInfo[DependencyTypeInfo].toList + val newMergeInfoes = mergeInfoes ++ info.getUniqueInfo[DependencyAnalysisMergeInfo].toList + val newJoinInfoes = joinInfoes ++ info.getUniqueInfo[DependencyAnalysisJoinInfo].toList + DependencyAnalysisInfoes(newSourceInfoes, newDependencyInfoes, newMergeInfoes, newJoinInfoes, nodes ++ List(node)) + } - override def getSourceInfo: AnalysisSourceInfo = throw new NotImplementedException("NoDependencyAnalysisInfo.getSourceInfo") + def addInfo(info: ast.Info): DependencyAnalysisInfoes = { + val newSourceInfoes = sourceInfoes ++ info.getUniqueInfo[AnalysisSourceInfo].toList + val newDependencyInfoes = dependencyTypes ++ info.getUniqueInfo[DependencyTypeInfo].toList + val newMergeInfoes = mergeInfoes ++ info.getUniqueInfo[DependencyAnalysisMergeInfo].toList + val newJoinInfoes = joinInfoes ++ info.getUniqueInfo[DependencyAnalysisJoinInfo].toList + DependencyAnalysisInfoes(newSourceInfoes, newDependencyInfoes, newMergeInfoes, newJoinInfoes, nodes) + } - override def getDependencyType: DependencyType = dependencyType + def addInfo(infoString: String, pos: ast.Position, dependencyType: DependencyType): DependencyAnalysisInfoes = + this.copy(sourceInfoes = sourceInfoes ++ List(StringAnalysisSourceInfo(infoString, pos)), dependencyTypes = dependencyTypes ++ List(DependencyTypeInfo(dependencyType))) - override def getMergeInfo: DependencyAnalysisMergeInfo = mergeInfo.get + def withDependencyType(dependencyType: DependencyType): DependencyAnalysisInfoes = { + this.copy(dependencyTypes = DependencyTypeInfo(dependencyType) +: dependencyTypes) + } - override def getJoinInfo: DependencyAnalysisJoinInfo = joinInfo + def withSource(source: AnalysisSourceInfo): DependencyAnalysisInfoes = { + this.copy(sourceInfoes = source +: sourceInfoes) + } - override def withDependencyType(newDependencyType: DependencyType): DependencyAnalysisInfoWithoutSource = - this.copy(dependencyType = newDependencyType) + def getSourceInfo: AnalysisSourceInfo = sourceInfoes.head - def withIsJoinNode(newJoinInfo: DependencyAnalysisJoinInfo): DependencyAnalysisInfoWithoutSource = - this.copy(joinInfo = newJoinInfo) + def getDependencyType: DependencyType = dependencyTypes.head.dependencyType - override def withAdditionalEvalNode(analysisSourceInfo: AnalysisSourceInfo): FullDependencyAnalysisInfo = - FullDependencyAnalysisInfo(analysisSourceInfo, List(analysisSourceInfo), dependencyType, mergeInfo.getOrElse(SimpleDependencyAnalysisMerge(analysisSourceInfo)), joinInfo) + def getMergeInfo: DependencyAnalysisMergeInfo = mergeInfoes.head // TODO - override def withSource(sourceInfo: AnalysisSourceInfo): DependencyAnalysisInfo = withAdditionalEvalNode(sourceInfo) + def getJoinInfo: List[DependencyAnalysisJoinInfo] = joinInfoes + def withMergeInfo(mergeInfo: DependencyAnalysisMergeInfo): DependencyAnalysisInfoes = + this.copy(mergeInfoes = mergeInfo +: mergeInfoes) } +object DependencyAnalysisInfoes { + val DefaultDependencyAnalysisInfoes = DependencyAnalysisInfoes(List.empty, List.empty, List.empty, List.empty, List.empty) + def create(sourceInfo: AnalysisSourceInfo, dependencyType: DependencyType, mergeInfo: DependencyAnalysisMergeInfo): DependencyAnalysisInfoes = + DependencyAnalysisInfoes(List(sourceInfo), List(DependencyTypeInfo(dependencyType)), List(mergeInfo), List.empty, List.empty) -case class FullDependencyAnalysisInfo(sourceInfo: AnalysisSourceInfo, - evaluationStack: List[AnalysisSourceInfo], /* TODO ake: do we need this? */ - dependencyType: DependencyType, - mergeInfo: DependencyAnalysisMergeInfo, /* required for lifting the low-level graph */ - joinInfo: DependencyAnalysisJoinInfo=NoDependencyAnalysisJoin(), /* required for interprocedural edges */ - //additionalNodes: Set[CustomDependencyAnalysisNode]=Set.empty, /* TODO ake: should be part of the AST node info but does not need to be propagated */ - ) extends DependencyAnalysisInfo { + def create(sourceInfo: AnalysisSourceInfo, dependencyType: DependencyType): DependencyAnalysisInfoes = + DependencyAnalysisInfoes(List(sourceInfo), List(DependencyTypeInfo(dependencyType)), List.empty, List.empty, List.empty) - override def getSourceInfo: AnalysisSourceInfo = sourceInfo - - override def getDependencyType: DependencyType = dependencyType - - override def getMergeInfo: DependencyAnalysisMergeInfo = mergeInfo - - override def getJoinInfo: DependencyAnalysisJoinInfo = joinInfo - - override def withAdditionalEvalNode(analysisSourceInfo: AnalysisSourceInfo): FullDependencyAnalysisInfo = - this.copy(evaluationStack=analysisSourceInfo+:evaluationStack) - override def withDependencyType(newDependencyType: DependencyType): FullDependencyAnalysisInfo = - this.copy(dependencyType=newDependencyType) - def withMergeInfo(newMergeInfo: DependencyAnalysisMergeInfo): FullDependencyAnalysisInfo = - this.copy(mergeInfo=newMergeInfo) - def withJoinInfo(newJoinInfo: DependencyAnalysisJoinInfo): FullDependencyAnalysisInfo = - this.copy(joinInfo=newJoinInfo) - - - override def withSource(sourceInfo: AnalysisSourceInfo): DependencyAnalysisInfo = - this.copy(sourceInfo=sourceInfo) + def create(infoString: String, dependencyType: DependencyType, mergeInfo: DependencyAnalysisMergeInfo): DependencyAnalysisInfoes = + create(StringAnalysisSourceInfo(infoString, NoPosition), dependencyType, mergeInfo) + def create(infoString: String, dependencyType: DependencyType): DependencyAnalysisInfoes = + create(StringAnalysisSourceInfo(infoString, NoPosition), dependencyType) } -object FullDependencyAnalysisInfo { - def create(sourceString: String, dependencyType: DependencyType, mergeInfo: DependencyAnalysisMergeInfo, joinNodeInfo: DependencyAnalysisJoinInfo) = { - val source = AnalysisSourceInfo.createAnalysisSourceInfo(sourceString, ast.NoPosition) - FullDependencyAnalysisInfo(source, List(source), dependencyType, mergeInfo, joinNodeInfo) - } - - def create(exp: ast.Exp) = { - val source = AnalysisSourceInfo.createAnalysisSourceInfo(exp) - val dependencyType = DependencyType.Implicit // FIXME ake: extract info from exp and initialize accordingly - FullDependencyAnalysisInfo(source, List(source), dependencyType, SimpleDependencyAnalysisMerge(source), NoDependencyAnalysisJoin()) - } +case class DependencyTypeInfo(dependencyType: DependencyType) extends ast.Info { - def create(stmt: ast.Stmt) = { - val source = AnalysisSourceInfo.createAnalysisSourceInfo(stmt) - val dependencyType = DependencyType.get(stmt) // FIXME ake: extract info from exp and initialize accordingly - FullDependencyAnalysisInfo(source, List(source), dependencyType, SimpleDependencyAnalysisMerge(source), NoDependencyAnalysisJoin()) - } + override def comment: Seq[String] = Nil + override def isCached: Boolean = false } @@ -119,14 +77,13 @@ object JoinType extends Enumeration { val Source, Sink = Value } -trait DependencyAnalysisJoinInfo { - def isJoin: Boolean = true +trait DependencyAnalysisJoinInfo extends ast.Info { -} + def isJoin: Boolean = true -case class NoDependencyAnalysisJoin() extends DependencyAnalysisJoinInfo { - override def isJoin: Boolean = false + override def comment: Seq[String] = Nil + override def isCached: Boolean = false } case class EvalStackDependencyAnalysisJoin(joinType: JoinType) extends DependencyAnalysisJoinInfo @@ -134,9 +91,12 @@ case class EvalStackDependencyAnalysisJoin(joinType: JoinType) extends Dependenc case class SimpleDependencyAnalysisJoin(sourceInfo: AnalysisSourceInfo, joinType: JoinType) extends DependencyAnalysisJoinInfo -trait DependencyAnalysisMergeInfo { +trait DependencyAnalysisMergeInfo extends ast.Info { def isMerge: Boolean = true + + override def comment: Seq[String] = Nil + override def isCached: Boolean = false } case class NoDependencyAnalysisMerge() extends DependencyAnalysisMergeInfo { diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index 35c7a671c..3e237fc0d 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -34,15 +34,15 @@ trait DependencyAnalyzer { def addNodes(nodes: Iterable[DependencyAnalysisNode]): Unit def addAssertionNode(node: GeneralAssertionNode): Unit def addAssumptionNode(node: GeneralAssumptionNode): Unit - def addAssumption(assumption: Term, dAInfo: DependencyAnalysisInfo, description: Option[String] = None): Option[Int] - def addAxiom(assumption: Term, dAInfo: DependencyAnalysisInfo, description: Option[String] = None): Option[Int] + def addAssumption(assumption: Term, analysisInfoes: DependencyAnalysisInfoes, description: Option[String] = None): Option[Int] + def addAxiom(assumption: Term, analysisInfoes: DependencyAnalysisInfoes, description: Option[String] = None): Option[Int] def registerInhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, labelNode: Option[LabelNode], analysisInfo: AnalysisInfo): CH = buildChunk(perm) def registerExhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, labelNodeOpt: Option[LabelNode], analysisInfo: AnalysisInfo): CH = buildChunk(perm) def createLabelNode(label: Var, sourceChunks: Iterable[Chunk], sourceTerms: Iterable[Term]): Option[LabelNode] - def createAssertOrCheckNode(term: Term, dAInfo: DependencyAnalysisInfo, isCheck: Boolean): Option[GeneralAssertionNode] - def addAssertFalseNode(isCheck: Boolean, dAInfo: DependencyAnalysisInfo): Option[Int] - def addInfeasibilityNode(isCheck: Boolean, dAInfo: DependencyAnalysisInfo): Option[Int] + def createAssertOrCheckNode(term: Term, analysisInfoes: DependencyAnalysisInfoes, isCheck: Boolean): Option[GeneralAssertionNode] + def addAssertFalseNode(isCheck: Boolean, analysisInfoes: DependencyAnalysisInfoes): Option[Int] + def addInfeasibilityNode(isCheck: Boolean, analysisInfoes: DependencyAnalysisInfoes): Option[Int] def addDependency(source: Option[Int], dest: Option[Int]): Unit def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit @@ -53,7 +53,7 @@ trait DependencyAnalyzer { * Adds dependencies between all pairs of sourceExps and targetExps, where sourceExps should be preconditions and * targetExps should be postconditions of an abstract function or method. */ - def addDependenciesForAbstractMembers(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp], dAInfo: DependencyAnalysisInfo): Unit + def addDependenciesForAbstractMembers(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp], analysisInfoes: DependencyAnalysisInfoes): Unit /** * Adds edges connecting nodes representing function postconditions with the corresponding axiom nodes. @@ -63,14 +63,14 @@ trait DependencyAnalyzer { /** * Adds an assertion and assumption node with the given analysis source info and dependencies to the current infeasibility node. */ - def addAssertionWithDepToInfeasNode(infeasNodeId: Option[Int], dAInfo: DependencyAnalysisInfo): Unit = {} + def addAssertionWithDepToInfeasNode(infeasNodeId: Option[Int], analysisInfoes: DependencyAnalysisInfoes): Unit = {} /** * @return the final dependency graph representing all direct and transitive dependencies */ def buildFinalGraph(): Option[DependencyGraph] - def addAssertionFailedNode(failedAssertion: Term, dAInfo: DependencyAnalysisInfo): Option[Int] + def addAssertionFailedNode(failedAssertion: Term, analysisInfoes: DependencyAnalysisInfoes): Option[Int] } object DependencyAnalyzer { @@ -323,14 +323,14 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { override def addAssertionNode(node: GeneralAssertionNode): Unit = dependencyGraph.addAssertionNode(node) // adding assumption nodes - override def addAssumption(assumption: Term, dAInfo: DependencyAnalysisInfo, description: Option[String]): Option[Int] = { - val node = SimpleAssumptionNode(assumption, description, dAInfo.getSourceInfo, dAInfo.getDependencyType.assumptionType, dAInfo.getMergeInfo.isMerge, dAInfo.getJoinInfo.isJoin) + override def addAssumption(assumption: Term, analysisInfoes: DependencyAnalysisInfoes, description: Option[String]): Option[Int] = { + val node = SimpleAssumptionNode(assumption, description, analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assumptionType, analysisInfoes.getMergeInfo.isMerge, analysisInfoes.getJoinInfo.exists(_.isJoin)) addAssumptionNode(node) Some(node.id) } - override def addAxiom(assumption: Term, dAInfo: DependencyAnalysisInfo, description: Option[String]): Option[Int] = { - val node = AxiomAssumptionNode(assumption, description, dAInfo.getSourceInfo, dAInfo.getDependencyType.assumptionType, dAInfo.getMergeInfo.isMerge, dAInfo.getJoinInfo.isJoin) + override def addAxiom(assumption: Term, analysisInfoes: DependencyAnalysisInfoes, description: Option[String]): Option[Int] = { + val node = AxiomAssumptionNode(assumption, description, analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assumptionType, analysisInfoes.getMergeInfo.isMerge, analysisInfoes.getJoinInfo.exists(_.isJoin)) addAssumptionNode(node) Some(node.id) } @@ -339,7 +339,7 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { val startTime = startTimeMeasurement() val labelNode = labelNodeOpt.get val chunk = buildChunk(Ite(labelNode.term, perm, NoPerm)) - val chunkNode = addPermissionExhaleNode(chunk, chunk.perm, analysisInfo.sourceInfo, analysisInfo.assumptionType, labelNode) + val chunkNode = addPermissionExhaleNode(chunk, chunk.perm, analysisInfo.analysisInfoes.getSourceInfo, analysisInfo.analysisInfoes.getDependencyType.assertionType, labelNode) if(chunkNode.isDefined) addDependency(chunkNode, Some(labelNode.id)) // addPermissionDependencies(sourceChunks, Set(), chunkNode) TODO ake: can be removed @@ -351,7 +351,7 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { val startTime = startTimeMeasurement() val labelNode = labelNodeOpt.get val chunk = buildChunk(Ite(labelNode.term, perm, NoPerm)) - val chunkNode = addPermissionInhaleNode(chunk, chunk.perm, analysisInfo.sourceInfo, analysisInfo.assumptionType, labelNode, isJoinNode=analysisInfo.isJoinNode) + val chunkNode = addPermissionInhaleNode(chunk, chunk.perm, analysisInfo.analysisInfoes.getSourceInfo, analysisInfo.analysisInfoes.getDependencyType.assumptionType, labelNode, isJoinNode=analysisInfo.analysisInfoes.getJoinInfo.exists(_.isJoin)) if(chunkNode.isDefined) addDependency(chunkNode, Some(labelNode.id)) // addPermissionDependencies(sourceChunks, Set(), chunkNode) TODO ake: can be removed @@ -380,35 +380,35 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { } // adding assertion nodes - override def createAssertOrCheckNode(term: Term, dAInfo: DependencyAnalysisInfo, isCheck: Boolean): Option[GeneralAssertionNode] = { + override def createAssertOrCheckNode(term: Term, analysisInfoes: DependencyAnalysisInfoes, isCheck: Boolean): Option[GeneralAssertionNode] = { if(isCheck) - Some(SimpleCheckNode(term, dAInfo.getSourceInfo, dAInfo.getDependencyType.assumptionType, dAInfo.getMergeInfo.isMerge, dAInfo.getJoinInfo.isJoin)) + Some(SimpleCheckNode(term, analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assumptionType, analysisInfoes.getMergeInfo.isMerge, analysisInfoes.getJoinInfo.exists(_.isJoin))) else - Some(SimpleAssertionNode(term, dAInfo.getSourceInfo, dAInfo.getDependencyType.assumptionType, dAInfo.getMergeInfo.isMerge, dAInfo.getJoinInfo.isJoin)) + Some(SimpleAssertionNode(term, analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assumptionType, analysisInfoes.getMergeInfo.isMerge, analysisInfoes.getJoinInfo.exists(_.isJoin))) } - def addAssertNode(term: Term, dAInfo: DependencyAnalysisInfo): Option[Int] = { - val node = createAssertOrCheckNode(term, dAInfo, isCheck=false) + def addAssertNode(term: Term, analysisInfoes: DependencyAnalysisInfoes): Option[Int] = { + val node = createAssertOrCheckNode(term, analysisInfoes, isCheck=false) node foreach addAssertionNode node map (_.id) } - override def addAssertFalseNode(isCheck: Boolean, dAInfo: DependencyAnalysisInfo): Option[Int] = { - val node = createAssertOrCheckNode(False, dAInfo, isCheck) + override def addAssertFalseNode(isCheck: Boolean, analysisInfoes: DependencyAnalysisInfoes): Option[Int] = { + val node = createAssertOrCheckNode(False, analysisInfoes, isCheck) addAssertionNode(node.get) node.map(_.id) } - override def addInfeasibilityNode(isCheck: Boolean, dAInfo: DependencyAnalysisInfo): Option[Int] = { - val node = InfeasibilityNode(dAInfo.getSourceInfo, dAInfo.getDependencyType.assumptionType) + override def addInfeasibilityNode(isCheck: Boolean, analysisInfoes: DependencyAnalysisInfoes): Option[Int] = { + val node = InfeasibilityNode(analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assumptionType) addAssumptionNode(node) Some(node.id) } - override def addAssertionFailedNode(failedAssertion: Term, dAInfo: DependencyAnalysisInfo): Option[Int] = { - val assumptionType = if(AssumptionType.postconditionTypes.contains(dAInfo.getDependencyType.assumptionType)) AssumptionType.ExplicitPostcondition else AssumptionType.Explicit - val assumeNode = SimpleAssumptionNode(failedAssertion, None, dAInfo.getSourceInfo, dAInfo.getDependencyType.assumptionType, dAInfo.getMergeInfo.isMerge, dAInfo.getJoinInfo.isJoin) - val assertFailedNode = SimpleAssertionNode(failedAssertion, dAInfo.getSourceInfo, dAInfo.getDependencyType.assumptionType, dAInfo.getMergeInfo.isMerge, dAInfo.getJoinInfo.isJoin, hasFailed=true) + override def addAssertionFailedNode(failedAssertion: Term, analysisInfoes: DependencyAnalysisInfoes): Option[Int] = { + val assumptionType = if(AssumptionType.postconditionTypes.contains(analysisInfoes.getDependencyType.assumptionType)) AssumptionType.ExplicitPostcondition else AssumptionType.Explicit + val assumeNode = SimpleAssumptionNode(failedAssertion, None, analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assumptionType, analysisInfoes.getMergeInfo.isMerge, analysisInfoes.getJoinInfo.exists(_.isJoin)) + val assertFailedNode = SimpleAssertionNode(failedAssertion, analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assumptionType, analysisInfoes.getMergeInfo.isMerge, hasFailed=true, isJoinNode=analysisInfoes.getJoinInfo.exists(_.isJoin)) dependencyGraph.addNode(assumeNode) dependencyGraph.addNode(assertFailedNode) dependencyGraph.addEdges(Set(assumeNode.id), assertFailedNode.id) @@ -458,9 +458,9 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { dependencyGraph.addEdges(sourceNodes map (_.id), targetNodes map (_.id)) } - override def addDependenciesForAbstractMembers(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp], dAInfo: DependencyAnalysisInfo): Unit = { - val sourceNodeIds = sourceExps.flatMap(e => addAssumption(True, dAInfo)) - val targetNodes = targetExps.flatMap(e => addAssertNode(True, dAInfo)) + override def addDependenciesForAbstractMembers(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp], analysisInfoes: DependencyAnalysisInfoes): Unit = { + val sourceNodeIds = sourceExps.flatMap(e => addAssumption(True, analysisInfoes)) + val targetNodes = targetExps.flatMap(e => addAssertNode(True, analysisInfoes)) dependencyGraph.addEdges(sourceNodeIds, targetNodes) } @@ -547,8 +547,8 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { * Adds an assertion node with the given analysis source info and dependencies to the current infeasibility node. * The resulting assertion node is required to detect dependencies of the source statement/expression on infeasible paths. */ - override def addAssertionWithDepToInfeasNode(infeasNodeId: Option[Int], dAInfo: DependencyAnalysisInfo): Unit = { - val newAssertionNodeId = addAssertNode(False, dAInfo) + override def addAssertionWithDepToInfeasNode(infeasNodeId: Option[Int], analysisInfoes: DependencyAnalysisInfoes): Unit = { + val newAssertionNodeId = addAssertNode(False, analysisInfoes) addDependency(infeasNodeId, newAssertionNodeId) } @@ -566,19 +566,19 @@ class NoDependencyAnalyzer extends DependencyAnalyzer { override def addNodes(nodes: Iterable[DependencyAnalysisNode]): Unit = {} override def addAssertionNode(node: GeneralAssertionNode): Unit = {} override def addAssumptionNode(node: GeneralAssumptionNode): Unit = {} - override def addAssumption(assumption: Term, dAInfo: DependencyAnalysisInfo, description: Option[String] = None): Option[Int] = None - override def addAxiom(assumption: Term, dAInfo: DependencyAnalysisInfo, description: Option[String]): Option[Int] = None + override def addAssumption(assumption: Term, analysisInfoes: DependencyAnalysisInfoes, description: Option[String] = None): Option[Int] = None + override def addAxiom(assumption: Term, analysisInfoes: DependencyAnalysisInfoes, description: Option[String]): Option[Int] = None override def createLabelNode(labelTerm: Var, sourceChunks: Iterable[Chunk], sourceTerms: Iterable[Term]): Option[LabelNode] = None - override def createAssertOrCheckNode(term: Term, dAInfo: DependencyAnalysisInfo, isCheck: Boolean): Option[GeneralAssertionNode] = None - override def addAssertFalseNode(isCheck: Boolean, dAInfo: DependencyAnalysisInfo): Option[Int] = None - override def addInfeasibilityNode(isCheck: Boolean, dAInfo: DependencyAnalysisInfo): Option[Int] = None - override def addAssertionFailedNode(failedAssertion: Term, dAInfo: DependencyAnalysisInfo): Option[Int] = None + override def createAssertOrCheckNode(term: Term, analysisInfoes: DependencyAnalysisInfoes, isCheck: Boolean): Option[GeneralAssertionNode] = None + override def addAssertFalseNode(isCheck: Boolean, analysisInfoes: DependencyAnalysisInfoes): Option[Int] = None + override def addInfeasibilityNode(isCheck: Boolean, analysisInfoes: DependencyAnalysisInfoes): Option[Int] = None + override def addAssertionFailedNode(failedAssertion: Term, analysisInfoes: DependencyAnalysisInfoes): Option[Int] = None override def addDependency(source: Option[Int], dest: Option[Int]): Unit = {} override def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit = {} override def addCustomTransitiveDependency(sourceSourceInfo: AnalysisSourceInfo, targetSourceInfo: AnalysisSourceInfo): Unit = {} - override def addDependenciesForAbstractMembers(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp], dAInfo: DependencyAnalysisInfo): Unit = {} + override def addDependenciesForAbstractMembers(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp], analysisInfoes: DependencyAnalysisInfoes): Unit = {} override def addFunctionAxiomEdges(): Unit = {} override def buildFinalGraph(): Option[DependencyGraph] = None diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala index 7660db674..e6e02fa6b 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala @@ -98,8 +98,8 @@ object DependencyGraphImporter { val node = nodeType match { case "Assumption" => SimpleAssumptionNode(term, description, sourceInfo, assumptionType, isClosed, isJoinNode, _id=nodeId) case "Axiom" => AxiomAssumptionNode(term, description, sourceInfo, assumptionType, isClosed, isJoinNode, _id=nodeId) - case "Assertion" => SimpleAssertionNode(term, assumptionType, sourceInfo, isClosed, hasFailed = false, isJoinNode, _id=nodeId) - case "Check" => SimpleCheckNode(term, assumptionType, sourceInfo, isClosed, hasFailed = false, isJoinNode, _id=nodeId) + case "Assertion" => SimpleAssertionNode(term, sourceInfo, assumptionType, isClosed, hasFailed = false, isJoinNode, _id=nodeId) + case "Check" => SimpleCheckNode(term, sourceInfo, assumptionType, isClosed, hasFailed = false, isJoinNode, _id=nodeId) case "Inhale" => PermissionInhaleNode(chunk, term, sourceInfo, assumptionType, isClosed, labelNode, isJoinNode, _id=nodeId) case "Exhale" => PermissionExhaleNode(chunk, term, sourceInfo, assumptionType, isClosed, labelNode, hasFailed = false, isJoinNode, _id=nodeId) case "Label" => LabelNode(dummyVar, _id=nodeId) diff --git a/src/main/scala/dependencyAnalysis/FrontendDependencyAnalysisInfo.scala b/src/main/scala/dependencyAnalysis/FrontendDependencyAnalysisInfo.scala index 036d3d47c..e501a079b 100644 --- a/src/main/scala/dependencyAnalysis/FrontendDependencyAnalysisInfo.scala +++ b/src/main/scala/dependencyAnalysis/FrontendDependencyAnalysisInfo.scala @@ -32,13 +32,13 @@ case class SimpleFrontendDependencyAnalysisInfo(sourceInfo: AnalysisSourceInfo, */ case class DependencyAnalysisJoinNodeInfo(sourceInfo: AnalysisSourceInfo, _dependencyType: DependencyType) extends FrontendDependencyAnalysisInfo { def getAssertionNode: GeneralAssertionNode = - SimpleAssertionNode(True, AssumptionType.CustomInternal, sourceInfo, isClosed=false, isJoinNode=true) + SimpleAssertionNode(True, sourceInfo, AssumptionType.CustomInternal, isClosed=false, isJoinNode=true) def getAssertionNode(outerSourceInfo: AnalysisSourceInfo): GeneralAssertionNode = - SimpleAssertionNode(True, AssumptionType.CustomInternal, CompositeAnalysisSourceInfo(outerSourceInfo, sourceInfo), isClosed=false, isJoinNode=true) + SimpleAssertionNode(True, CompositeAnalysisSourceInfo(outerSourceInfo, sourceInfo), AssumptionType.CustomInternal, isClosed=false, isJoinNode=true) def getAssertionNode(outerSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): GeneralAssertionNode = - SimpleAssertionNode(True, assumptionType, CompositeAnalysisSourceInfo(outerSourceInfo, sourceInfo), isClosed=false, isJoinNode=true) + SimpleAssertionNode(True, CompositeAnalysisSourceInfo(outerSourceInfo, sourceInfo), assumptionType, isClosed=false, isJoinNode=true) def getAssumptionNode(outerSourceInfo: AnalysisSourceInfo): GeneralAssumptionNode = SimpleAssumptionNode(True, None, CompositeAnalysisSourceInfo(outerSourceInfo, sourceInfo), AssumptionType.CustomInternal, isClosed=false, isJoinNode=true) diff --git a/src/main/scala/interfaces/decider/Prover.scala b/src/main/scala/interfaces/decider/Prover.scala index 87da9e24c..985bd63f7 100644 --- a/src/main/scala/interfaces/decider/Prover.scala +++ b/src/main/scala/interfaces/decider/Prover.scala @@ -46,7 +46,8 @@ trait ProverLike { if(Verifier.config.enableDependencyAnalysis()){ axioms.foreach(axiom => { val axiomInfo = axiom._2 - val id = if(axiomInfo.isDefined) preambleDependencyAnalyzer.addAxiom(axiom._1, axiomInfo.get._1, axiomInfo.get._2) else None + val analysisInfoes = axiomInfo.map(ai => DependencyAnalysisInfoes.create(ai._1, DependencyType.make(ai._2))) + val id = if(analysisInfoes.isDefined) preambleDependencyAnalyzer.addAxiom(axiom._1, analysisInfoes.get) else None assume(axiom._1, DependencyAnalyzer.createAxiomLabel(id)) }) } else{ diff --git a/src/main/scala/interfaces/state/Chunks.scala b/src/main/scala/interfaces/state/Chunks.scala index 865c7ea03..d3fb81a6e 100644 --- a/src/main/scala/interfaces/state/Chunks.scala +++ b/src/main/scala/interfaces/state/Chunks.scala @@ -6,7 +6,7 @@ package viper.silicon.interfaces.state -import viper.silicon.dependencyAnalysis.{AnalysisInfo, AssumptionType} +import viper.silicon.dependencyAnalysis.{AnalysisInfo, AssumptionType, DependencyType} import viper.silicon import viper.silicon.resources.ResourceID import viper.silicon.state.terms.{Term, Var} @@ -49,7 +49,7 @@ object GeneralChunk { def permMinus(chunk: GeneralChunk, newPerm: Term, newPermExp: Option[ast.Exp], analysisInfo: AnalysisInfo): GeneralChunk = { val newChunk = analysisInfo.decider.registerDerivedChunk[GeneralChunk](Set(chunk), {finalPerm => chunk.permMinus(finalPerm, newPermExp)}, - newPerm, analysisInfo.withAssumptionType(AssumptionType.Internal), isExhale=false, createLabel=false) // TODO ake: assumption type? maybe for exhale we want to have Implicit? + newPerm, analysisInfo.withDependencyType(DependencyType.Internal), isExhale=false, createLabel=false) // TODO ake: assumption type? maybe for exhale we want to have Implicit? @unused // we need to register the chunk to have a sound analysis val exhaledChunk = analysisInfo.decider.registerDerivedChunk[GeneralChunk](Set(chunk), { finalPerm => chunk.withPerm(finalPerm, newPermExp)}, diff --git a/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala b/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala index db638f8e3..96d0640f2 100644 --- a/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala +++ b/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala @@ -7,6 +7,7 @@ package viper.silicon.resources import viper.silicon.Map +import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfoes, DependencyType} import viper.silicon.interfaces.state._ import viper.silicon.state.terms.Term import viper.silicon.state.{QuantifiedBasicChunk, terms} @@ -120,7 +121,7 @@ class NonQuantifiedPropertyInterpreter(heap: Iterable[Chunk], verifier: Verifier otherwise: PropertyExpression[K], info: Info): (Term, Option[ast.Exp]) = { val conditionTerm = buildPathCondition(condition, info)._1 - if (verifier.decider.check(conditionTerm, Verifier.config.checkTimeout())) { + if (verifier.decider.check(conditionTerm, Verifier.config.checkTimeout(), DependencyAnalysisInfoes.create("property interpreter", DependencyType.Internal) /* TODO ake */)) { val (t, e) = buildPathCondition(thenDo, info) (verifier.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(conditionTerm)), e) // TODO ake: causes imprecision! } else { diff --git a/src/main/scala/rules/Brancher.scala b/src/main/scala/rules/Brancher.scala index ec68ea485..d4b0f0565 100644 --- a/src/main/scala/rules/Brancher.scala +++ b/src/main/scala/rules/Brancher.scala @@ -8,7 +8,7 @@ package viper.silicon.rules import viper.silicon.common.concurrency._ import viper.silicon.decider.PathConditionStack -import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfo, DependencyAnalyzer, DependencyType} +import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfoes, DependencyAnalyzer, DependencyType} import viper.silicon.interfaces.{Unreachable, VerificationResult} import viper.silicon.reporting.condenseToViperResult import viper.silicon.state.State @@ -27,7 +27,7 @@ trait BranchingRules extends SymbolicExecutionRules { condition: Term, conditionExp: (ast.Exp, Option[ast.Exp]), v: Verifier, - dAInfo: DependencyAnalysisInfo, + analysisInfoes: DependencyAnalysisInfoes, fromShortCircuitingAnd: Boolean = false) (fTrue: (State, Verifier) => VerificationResult, fFalse: (State, Verifier) => VerificationResult) @@ -39,7 +39,7 @@ object brancher extends BranchingRules { condition: Term, conditionExp: (ast.Exp, Option[ast.Exp]), v: Verifier, - dAInfo: DependencyAnalysisInfo, + analysisInfoes: DependencyAnalysisInfoes, fromShortCircuitingAnd: Boolean = false) (fThen: (State, Verifier) => VerificationResult, fElse: (State, Verifier) => VerificationResult) @@ -51,9 +51,9 @@ object brancher extends BranchingRules { // val assertionNodesForJoin = DependencyAnalyzer.extractAssertionsForJoin(conditionExp._1, s.program) // assertionNodesForJoin.foreach(n => v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, CompositeAnalysisSourceInfo(v.decider.analysisSourceInfoStack.getFullSourceInfo.getTopLevelSource, AnalysisSourceInfo.createAnalysisSourceInfo(n)), v.decider.analysisSourceInfoStack.getDependencyType, isJoinNode=true)) if(!Expressions.isKnownWellDefined(conditionExp._1, Some(s.program))){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, dAInfo) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfoes) } - v.decider.dependencyAnalyzer.addAssumption(condition, dAInfo) + v.decider.dependencyAnalyzer.addAssumption(condition, analysisInfoes) v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(analysisSourceInfo) return fThen(s, v).combine(fElse(s, v)) } @@ -77,13 +77,13 @@ object brancher extends BranchingRules { /* True if the then-branch is to be explored */ val executeThenBranch = ( skipPathFeasibilityCheck - || !v.decider.check(negatedCondition, Verifier.config.checkTimeout(), dAInfo.withDependencyType(DependencyType.Internal))) + || !v.decider.check(negatedCondition, Verifier.config.checkTimeout(), analysisInfoes.withDependencyType(DependencyType.Internal))) /* False if the then-branch is to be explored */ val executeElseBranch = ( !executeThenBranch /* Assumes that ast least one branch is feasible */ || skipPathFeasibilityCheck - || !v.decider.check(condition, Verifier.config.checkTimeout(), dAInfo.withDependencyType(DependencyType.Internal))) + || !v.decider.check(condition, Verifier.config.checkTimeout(), analysisInfoes.withDependencyType(DependencyType.Internal))) v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) @@ -166,8 +166,8 @@ object brancher extends BranchingRules { executionFlowController.locally(s, v0)((s1, v1) => { v1.decider.prover.comment(s"[else-branch: $cnt | $negatedCondition]") val sourceInfo = v.decider.pushAndGetAnalysisSourceInfo(conditionExp._1, Some(DependencyType.get(conditionExp._1, DependencyType.PathCondition))) - v1.decider.setCurrentBranchCondition(negatedCondition, (negatedConditionExp, negatedConditionExpNew), dAInfo) - if(v.decider.isDependencyAnalysisEnabled && !executeElseBranch) v.decider.checkSmokeAndSetInfeasibilityNode(dAInfo) + v1.decider.setCurrentBranchCondition(negatedCondition, (negatedConditionExp, negatedConditionExpNew), analysisInfoes) + if(v.decider.isDependencyAnalysisEnabled && !executeElseBranch) v.decider.checkSmokeAndSetInfeasibilityNode(analysisInfoes) v1.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) var functionsOfElseBranchdDeciderBefore: Set[FunctionDecl] = null @@ -220,8 +220,8 @@ object brancher extends BranchingRules { executionFlowController.locally(s, v)((s1, v1) => { v1.decider.prover.comment(s"[then-branch: $cnt | $condition]") val sourceInfo = v.decider.pushAndGetAnalysisSourceInfo(conditionExp._1, Some(DependencyType.get(conditionExp._1, DependencyType.PathCondition))) - v1.decider.setCurrentBranchCondition(condition, conditionExp, dAInfo) - if(v.decider.isDependencyAnalysisEnabled && !executeThenBranch) v.decider.checkSmokeAndSetInfeasibilityNode(dAInfo) + v1.decider.setCurrentBranchCondition(condition, conditionExp, analysisInfoes) + if(v.decider.isDependencyAnalysisEnabled && !executeThenBranch) v.decider.checkSmokeAndSetInfeasibilityNode(analysisInfoes) v1.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) fThen(v1.stateConsolidator(s1).consolidateOptionally(s1, v1), v1) diff --git a/src/main/scala/rules/ChunkSupporter.scala b/src/main/scala/rules/ChunkSupporter.scala index 4c8bde746..2fa35c20b 100644 --- a/src/main/scala/rules/ChunkSupporter.scala +++ b/src/main/scala/rules/ChunkSupporter.scala @@ -7,7 +7,7 @@ package viper.silicon.rules import viper.silicon.debugger.DebugExp -import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfo, DependencyType} +import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfoes, DependencyType, StringAnalysisSourceInfo} import viper.silicon.interfaces.state._ import viper.silicon.interfaces.{Success, VerificationResult} import viper.silicon.resources.{NonQuantifiedPropertyInterpreter, Resources} @@ -34,7 +34,7 @@ trait ChunkSupportRules extends SymbolicExecutionRules { ve: VerificationError, v: Verifier, description: String, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult @@ -49,7 +49,7 @@ trait ChunkSupportRules extends SymbolicExecutionRules { argsExp: Option[Seq[ast.Exp]], ve: VerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Heap, Term, Verifier) => VerificationResult) : VerificationResult @@ -58,7 +58,7 @@ trait ChunkSupportRules extends SymbolicExecutionRules { id: ChunkIdentifer, args: Iterable[Term], v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) : Option[CH] def findChunksWithID[CH <: NonQuantifiedChunk: ClassTag] @@ -80,15 +80,15 @@ object chunkSupporter extends ChunkSupportRules { ve: VerificationError, v: Verifier, description: String, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { if(v.decider.isPathInfeasible()){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, dAInfo) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfoes) return Q(s, h, Option.when(returnSnap)(Unit), v) } - consume2(s, h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, dAInfo)((s2, h2, optSnap, v2) => + consume2(s, h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, analysisInfoes)((s2, h2, optSnap, v2) => optSnap match { case Some(snap) => Q(s2, h2, Some(snap.convert(sorts.Snap)), v2) @@ -117,14 +117,14 @@ object chunkSupporter extends ChunkSupportRules { returnSnap: Boolean, ve: VerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { val id = ChunkIdentifier(resource, s.program) if (s.exhaleExt) { val failure = createFailure(ve, v, s, "chunk consume in package") - magicWandSupporter.transfer(s, perms, permsExp, failure, Seq(), v, dAInfo)(consumeGreedy(_, _, id, args, _, _, _, dAInfo))((s1, optCh, v1) => + magicWandSupporter.transfer(s, perms, permsExp, failure, Seq(), v, analysisInfoes)(consumeGreedy(_, _, id, args, _, _, _, analysisInfoes))((s1, optCh, v1) => if (returnSnap){ Q(s1, h, optCh.flatMap(ch => Some(ch.snap)), v1) } else { @@ -133,15 +133,15 @@ object chunkSupporter extends ChunkSupportRules { } else { executionFlowController.tryOrFail2[Heap, Option[Term]](s.copy(h = h), v)((s1, v1, QS) => if (s1.moreCompleteExhale) { - moreCompleteExhaleSupporter.consumeComplete(s1, s1.h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v1, dAInfo)((s2, h2, snap2, v2) => { + moreCompleteExhaleSupporter.consumeComplete(s1, s1.h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v1, analysisInfoes)((s2, h2, snap2, v2) => { QS(s2.copy(h = s.h), h2, snap2, v2) }) } else { - consumeGreedy(s1, s1.h, id, args, perms, permsExp, v1, dAInfo) match { + consumeGreedy(s1, s1.h, id, args, perms, permsExp, v1, analysisInfoes) match { case (Complete(), s2, h2, optCh2) => val snap = optCh2 match { case Some(ch) if returnSnap => - if (v1.decider.check(IsPositive(perms), Verifier.config.checkTimeout(), dAInfo)) { + if (v1.decider.check(IsPositive(perms), Verifier.config.checkTimeout(), analysisInfoes)) { Some(ch.snap) } else { Some(Ite(IsPositive(perms), ch.snap.convert(sorts.Snap), Unit)) @@ -149,14 +149,14 @@ object chunkSupporter extends ChunkSupportRules { case _ => None } QS(s2.copy(h = s.h), h2, snap, v1) - case (_, s2, h2, _) if v1.decider.checkSmoke(dAInfo, isAssert = true) => + case (_, s2, h2, _) if v1.decider.checkSmoke(analysisInfoes, isAssert = true) => if(Verifier.config.disableInfeasibilityChecks()) QS(s2.copy(h = s.h), h2, None, v1) else Success() // TODO: Mark branch as dead? case _ => val failure = createFailure(ve, v1, s1, "consuming chunk", true) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(False, dAInfo, v1.reportFurtherErrors()) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfoes, v1.reportFurtherErrors()) if(s1.retryLevel == 0 && v1.reportFurtherErrors() && Verifier.config.disableInfeasibilityChecks()){ failure combine QS(s1.copy(h = s.h), s1.h, None, v1) }else{ @@ -175,7 +175,7 @@ object chunkSupporter extends ChunkSupportRules { perms: Term, permsExp: Option[ast.Exp], v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) : (ConsumptionResult, State, Heap, Option[NonQuantifiedChunk]) = { val consumeExact = terms.utils.consumeExactRead(perms, s.constrainableARPs) @@ -184,13 +184,13 @@ object chunkSupporter extends ChunkSupportRules { val interpreter = new NonQuantifiedPropertyInterpreter(heap.values, v) val resource = Resources.resourceDescriptions(chunk.resourceID) val pathCond = interpreter.buildPathConditionsForChunk(chunk, resource.instanceProperties(s.mayAssumeUpperBounds)) - pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), dAInfo)) + pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), analysisInfoes)) } - findChunk[NonQuantifiedChunk](h.values, id, args, v, dAInfo) match { + findChunk[NonQuantifiedChunk](h.values, id, args, v, analysisInfoes) match { case Some(ch) => if (s.assertReadAccessOnly) { - if (v.decider.check(Implies(IsPositive(perms), IsPositive(ch.perm)), Verifier.config.assertTimeout.getOrElse(0), dAInfo)) { + if (v.decider.check(Implies(IsPositive(perms), IsPositive(ch.perm)), Verifier.config.assertTimeout.getOrElse(0), analysisInfoes)) { (Complete(), s, h, Some(ch)) } else { (Incomplete(perms, permsExp), s, h, None) @@ -199,22 +199,22 @@ object chunkSupporter extends ChunkSupportRules { val toTake = PermMin(ch.perm, perms) val toTakeExp = permsExp.map(pe => buildMinExp(Seq(ch.permExp.get, pe), ast.Perm)) val newPermExp = permsExp.map(pe => ast.PermSub(ch.permExp.get, toTakeExp.get)(pe.pos, pe.info, pe.errT)) - val newChunk = NonQuantifiedChunk.withPerm(ch, PermMinus(ch.perm, toTake), newPermExp, v.decider.getAnalysisInfo(dAInfo)) - val takenChunk = Some(NonQuantifiedChunk.withPerm(ch, toTake, toTakeExp, v.decider.getAnalysisInfo(dAInfo), isExhale=true)) + val newChunk = NonQuantifiedChunk.withPerm(ch, PermMinus(ch.perm, toTake), newPermExp, v.decider.getAnalysisInfo(analysisInfoes)) + val takenChunk = Some(NonQuantifiedChunk.withPerm(ch, toTake, toTakeExp, v.decider.getAnalysisInfo(analysisInfoes), isExhale=true)) var newHeap = h - ch - if (!v.decider.check(newChunk.perm === NoPerm, Verifier.config.checkTimeout(), dAInfo.withDependencyType(DependencyType.Internal))) { + if (!v.decider.check(newChunk.perm === NoPerm, Verifier.config.checkTimeout(), analysisInfoes.withDependencyType(DependencyType.Internal))) { newHeap = newHeap + newChunk assumeProperties(newChunk, newHeap) } val remainingExp = permsExp.map(pe => ast.PermSub(pe, toTakeExp.get)(pe.pos, pe.info, pe.errT)) - (ConsumptionResult(PermMinus(perms, toTake), remainingExp, Seq(), v, 0), s, newHeap, takenChunk) + (ConsumptionResult(PermMinus(perms, toTake), remainingExp, Seq(), v, 0, analysisInfoes), s, newHeap, takenChunk) } else { - if (v.decider.check(ch.perm !== NoPerm, Verifier.config.checkTimeout(), dAInfo)) { + if (v.decider.check(ch.perm !== NoPerm, Verifier.config.checkTimeout(), analysisInfoes)) { val constraintExp = permsExp.map(pe => ast.PermLtCmp(pe, ch.permExp.get)(pe.pos, pe.info, pe.errT)) - v.decider.assume(PermLess(perms, ch.perm), Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp)), dAInfo) + v.decider.assume(PermLess(perms, ch.perm), Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp)), analysisInfoes) val newPermExp = permsExp.map(pe => ast.PermSub(ch.permExp.get, pe)(pe.pos, pe.info, pe.errT)) - val newChunk = NonQuantifiedChunk.withPerm(ch, PermMinus(ch.perm, perms), newPermExp, v.decider.getAnalysisInfo(dAInfo)) - val takenChunk = NonQuantifiedChunk.withPerm(ch, perms, permsExp, v.decider.getAnalysisInfo(dAInfo), isExhale=true) + val newChunk = NonQuantifiedChunk.withPerm(ch, PermMinus(ch.perm, perms), newPermExp, v.decider.getAnalysisInfo(analysisInfoes)) + val takenChunk = NonQuantifiedChunk.withPerm(ch, perms, permsExp, v.decider.getAnalysisInfo(analysisInfoes), isExhale=true) val newHeap = h - ch + newChunk assumeProperties(newChunk, newHeap) (Complete(), s, newHeap, Some(takenChunk)) @@ -223,7 +223,7 @@ object chunkSupporter extends ChunkSupportRules { } } case None => - if (consumeExact && s.retrying && v.decider.check(perms === NoPerm, Verifier.config.checkTimeout(), dAInfo)) { + if (consumeExact && s.retrying && v.decider.check(perms === NoPerm, Verifier.config.checkTimeout(), analysisInfoes)) { (Complete(), s, h, None) } else { (Incomplete(perms, permsExp), s, h, None) @@ -235,9 +235,11 @@ object chunkSupporter extends ChunkSupportRules { (Q: (State, Heap, Verifier) => VerificationResult) : VerificationResult = { + // TODO ake + val analysisInfoes = DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes.withSource(StringAnalysisSourceInfo("produce", ast.NoPosition)).withDependencyType(DependencyType.Internal) // Try to merge the chunk into the heap by finding an alias. // In any case, property assumptions are added after the merge step. - val (fr1, h1) = v.stateConsolidator(s).merge(s.functionRecorder, s, h, ch, v, dAInfo) + val (fr1, h1) = v.stateConsolidator(s).merge(s.functionRecorder, s, h, ch, v, analysisInfoes) Q(s.copy(functionRecorder = fr1), h1, v) } @@ -248,11 +250,11 @@ object chunkSupporter extends ChunkSupportRules { argsExp: Option[Seq[ast.Exp]], ve: VerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Heap, Term, Verifier) => VerificationResult) : VerificationResult = { if(v.decider.isPathInfeasible()){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, dAInfo) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfoes) return Q(s, h, Unit, v) } @@ -260,7 +262,7 @@ object chunkSupporter extends ChunkSupportRules { val lookupFunction = if (s1.moreCompleteExhale) moreCompleteExhaleSupporter.lookupComplete _ else lookupGreedy _ - lookupFunction(s1, s1.h, resource, args, argsExp, ve, v1, dAInfo)((s2, tSnap, v2) => + lookupFunction(s1, s1.h, resource, args, argsExp, ve, v1, analysisInfoes)((s2, tSnap, v2) => QS(s2.copy(h = s.h), s2.h, tSnap, v2)) })(Q) } @@ -272,16 +274,16 @@ object chunkSupporter extends ChunkSupportRules { argsExp: Option[Seq[ast.Exp]], ve: VerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Term, Verifier) => VerificationResult) : VerificationResult = { val id = ChunkIdentifier(resource, s.program) - val findRes = findChunk[NonQuantifiedChunk](h.values, id, args, v, dAInfo) + val findRes = findChunk[NonQuantifiedChunk](h.values, id, args, v, analysisInfoes) findRes match { - case Some(ch) if v.decider.check(IsPositive(ch.perm), Verifier.config.assertTimeout.getOrElse(0), dAInfo) => + case Some(ch) if v.decider.check(IsPositive(ch.perm), Verifier.config.assertTimeout.getOrElse(0), analysisInfoes) => Q(s, ch.snap, v) - case _ if v.decider.checkSmoke(dAInfo, isAssert = true) => + case _ if v.decider.checkSmoke(analysisInfoes, isAssert = true) => if (s.isInPackage || Verifier.config.disableInfeasibilityChecks()) { val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) Q(s, snap, v) @@ -290,7 +292,7 @@ object chunkSupporter extends ChunkSupportRules { } case _ => val failure = createFailure(ve, v, s, "looking up chunk", true) - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, dAInfo, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfoes, v.reportFurtherErrors()) if(s.retryLevel == 0 && v.reportFurtherErrors() && Verifier.config.disableInfeasibilityChecks()){ val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) failure combine Q(s, snap, v) @@ -305,10 +307,10 @@ object chunkSupporter extends ChunkSupportRules { id: ChunkIdentifer, args: Iterable[Term], v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) : Option[CH] = { val relevantChunks = findChunksWithID[CH](chunks, id) - findChunkLiterally(relevantChunks, args) orElse findChunkWithProver(relevantChunks, args, v, dAInfo) + findChunkLiterally(relevantChunks, args) orElse findChunkWithProver(relevantChunks, args, v, analysisInfoes) } def findChunksWithID[CH <: NonQuantifiedChunk: ClassTag](chunks: Iterable[Chunk], id: ChunkIdentifer): Iterable[CH] = { @@ -345,9 +347,9 @@ object chunkSupporter extends ChunkSupportRules { chunks find (ch => ch.args == args) } - private def findChunkWithProver[CH <: NonQuantifiedChunk](chunks: Iterable[CH], args: Iterable[Term], v: Verifier, dAInfo: DependencyAnalysisInfo) = { + private def findChunkWithProver[CH <: NonQuantifiedChunk](chunks: Iterable[CH], args: Iterable[Term], v: Verifier, analysisInfoes: DependencyAnalysisInfoes) = { chunks find (ch => args.size == ch.args.size && - v.decider.check(And(ch.args zip args map (x => x._1 === x._2)), Verifier.config.checkTimeout(), dAInfo)) + v.decider.check(And(ch.args zip args map (x => x._1 === x._2)), Verifier.config.checkTimeout(), analysisInfoes)) } } diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index f49de11ac..56d4c6a34 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -37,7 +37,7 @@ trait ConsumptionRules extends SymbolicExecutionRules { * consumed partial heap. * @return The result of the continuation. */ - def consume(s: State, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, dAInfo: DependencyAnalysisInfo) + def consume(s: State, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Option[Term], Verifier) => VerificationResult) : VerificationResult @@ -62,7 +62,7 @@ trait ConsumptionRules extends SymbolicExecutionRules { returnSnap: Boolean, pvef: ast.Exp => PartialVerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Option[Term], Verifier) => VerificationResult) : VerificationResult } @@ -76,11 +76,11 @@ object consumer extends ConsumptionRules { */ /** @inheritdoc */ - def consume(s: State, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, dAInfo: DependencyAnalysisInfo) + def consume(s: State, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Option[Term], Verifier) => VerificationResult) : VerificationResult = { - consumeR(s, s.h, a.whenExhaling, returnSnap, pve, v, dAInfo)((s1, h1, snap, v1) => { + consumeR(s, s.h, a.whenExhaling, returnSnap, pve, v, analysisInfoes)((s1, h1, snap, v1) => { val s2 = s1.copy(h = h1, partiallyConsumedHeap = s.partiallyConsumedHeap) Q(s2, snap, v1)}) @@ -92,7 +92,7 @@ object consumer extends ConsumptionRules { returnSnap: Boolean, pvef: ast.Exp => PartialVerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -107,7 +107,7 @@ object consumer extends ConsumptionRules { allPves ++= pves }) - consumeTlcs(s, s.h, allTlcs.result(), returnSnap, allPves.result(), v, dAInfo)((s1, h1, snap1, v1) => { + consumeTlcs(s, s.h, allTlcs.result(), returnSnap, allPves.result(), v, analysisInfoes)((s1, h1, snap1, v1) => { val s2 = s1.copy(h = h1, partiallyConsumedHeap = s.partiallyConsumedHeap) Q(s2, snap1, v1) @@ -120,7 +120,7 @@ object consumer extends ConsumptionRules { returnSnap: Boolean, pves: Seq[PartialVerificationError], v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -131,10 +131,10 @@ object consumer extends ConsumptionRules { val pve = pves.head if (tlcs.tail.isEmpty) - wrappedConsumeTlc(s, h, a, returnSnap, pve, v, dAInfo)(Q) + wrappedConsumeTlc(s, h, a, returnSnap, pve, v, analysisInfoes)(Q) else - wrappedConsumeTlc(s, h, a, returnSnap, pve, v, dAInfo)((s1, h1, snap1, v1) => { - consumeTlcs(s1, h1, tlcs.tail, returnSnap, pves.tail, v1, dAInfo)((s2, h2, snap2, v2) => + wrappedConsumeTlc(s, h, a, returnSnap, pve, v, analysisInfoes)((s1, h1, snap1, v1) => { + consumeTlcs(s1, h1, tlcs.tail, returnSnap, pves.tail, v1, analysisInfoes)((s2, h2, snap2, v2) => (snap1, snap2) match { case (Some(sn1), Some(sn2)) if returnSnap => Q(s2, h2, Some(Combine(sn1, sn2)), v2) @@ -145,14 +145,14 @@ object consumer extends ConsumptionRules { } } - private def consumeR(s: State, h: Heap, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, dAInfo: DependencyAnalysisInfo) + private def consumeR(s: State, h: Heap, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { val tlcs = a.topLevelConjuncts val pves = Seq.fill(tlcs.length)(pve) - consumeTlcs(s, h, tlcs, returnSnap, pves, v, dAInfo)(Q) + consumeTlcs(s, h, tlcs, returnSnap, pves, v, analysisInfoes)(Q) } /** Wrapper/decorator for consume that injects the following operations: @@ -165,7 +165,7 @@ object consumer extends ConsumptionRules { returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -179,16 +179,15 @@ object consumer extends ConsumptionRules { val s1 = s0.copy(h = s.h) /* s1 is s, but the retrying flag might be set */ val sepIdentifier = v1.symbExLog.openScope(new ConsumeRecord(a, s1, v.decider.pcs)) - val sourceInfo = v1.decider.pushAndGetAnalysisSourceInfo(a, Some(DependencyType.get(a, dAInfo))) + val analysisInfoes1 = analysisInfoes.addInfo(a.info, a) - consumeTlc(s1, h0, a, returnSnap, pve, v1, dAInfo)((s2, h2, snap2, v2) => { - v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) + consumeTlc(s1, h0, a, returnSnap, pve, v1, analysisInfoes1)((s2, h2, snap2, v2) => { v2.symbExLog.closeScope(sepIdentifier) QS(s2, h2, snap2, v2)}) })(Q) } - private def consumeTlc(s: State, h: Heap, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, dAInfo: DependencyAnalysisInfo) + private def consumeTlc(s: State, h: Heap, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -200,7 +199,7 @@ object consumer extends ConsumptionRules { */ if(v.decider.isPathInfeasible()){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, dAInfo) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfoes) return Q(s, h, Option.when(returnSnap)(Unit), v) } @@ -214,15 +213,15 @@ object consumer extends ConsumptionRules { case imp @ ast.Implies(e0, a0) if !a.isPure && s.moreJoins.id >= JoinMode.Impure.id => val impliesRecord = new ImpliesRecord(imp, s, v.decider.pcs, "consume") val uidImplies = v.symbExLog.openScope(impliesRecord) - consumeConditionalTlcMoreJoins(s, h, e0, a0, None, uidImplies, returnSnap, pve, v, dAInfo)(Q) + consumeConditionalTlcMoreJoins(s, h, e0, a0, None, uidImplies, returnSnap, pve, v, analysisInfoes)(Q) case imp @ ast.Implies(e0, a0) if !a.isPure => val impliesRecord = new ImpliesRecord(imp, s, v.decider.pcs, "consume") val uidImplies = v.symbExLog.openScope(impliesRecord) - evaluator.eval(s, e0, pve, v, dAInfo)((s1, t0, e0New, v1) => - branch(s1, t0, (e0, e0New), v1, dAInfo)( - (s2, v2) => consumeR(s2, h, a0, returnSnap, pve, v2, dAInfo)((s3, h1, t1, v3) => { + evaluator.eval(s, e0, pve, v, analysisInfoes)((s1, t0, e0New, v1) => + branch(s1, t0, (e0, e0New), v1, analysisInfoes)( + (s2, v2) => consumeR(s2, h, a0, returnSnap, pve, v2, analysisInfoes)((s3, h1, t1, v3) => { v3.symbExLog.closeScope(uidImplies) Q(s3, h1, t1, v3) }), @@ -234,19 +233,19 @@ object consumer extends ConsumptionRules { case ite @ ast.CondExp(e0, a1, a2) if !a.isPure && s.moreJoins.id >= JoinMode.Impure.id => val condExpRecord = new CondExpRecord(ite, s, v.decider.pcs, "consume") val uidCondExp = v.symbExLog.openScope(condExpRecord) - consumeConditionalTlcMoreJoins(s, h, e0, a1, Some(a2), uidCondExp, returnSnap, pve, v, dAInfo)(Q) + consumeConditionalTlcMoreJoins(s, h, e0, a1, Some(a2), uidCondExp, returnSnap, pve, v, analysisInfoes)(Q) case ite @ ast.CondExp(e0, a1, a2) if !a.isPure => val condExpRecord = new CondExpRecord(ite, s, v.decider.pcs, "consume") val uidCondExp = v.symbExLog.openScope(condExpRecord) - eval(s, e0, pve, v, dAInfo)((s1, t0, e0New, v1) => - branch(s1, t0, (e0, e0New), v1, dAInfo)( - (s2, v2) => consumeR(s2, h, a1, returnSnap, pve, v2, dAInfo)((s3, h1, t1, v3) => { + eval(s, e0, pve, v, analysisInfoes)((s1, t0, e0New, v1) => + branch(s1, t0, (e0, e0New), v1, analysisInfoes)( + (s2, v2) => consumeR(s2, h, a1, returnSnap, pve, v2, analysisInfoes)((s3, h1, t1, v3) => { v3.symbExLog.closeScope(uidCondExp) Q(s3, h1, t1, v3) }), - (s2, v2) => consumeR(s2, h, a2, returnSnap, pve, v2, dAInfo)((s3, h1, t1, v3) => { + (s2, v2) => consumeR(s2, h, a2, returnSnap, pve, v2, analysisInfoes)((s3, h1, t1, v3) => { v3.symbExLog.closeScope(uidCondExp) Q(s3, h1, t1, v3) }))) @@ -256,17 +255,17 @@ object consumer extends ConsumptionRules { val resource = accPred.res(s.program) val ePerm = accPred.perm - evals(s, eArgs, _ => pve, v, dAInfo)((s1, tArgs, eArgsNew, v1) => - eval(s1, ePerm, pve, v1, dAInfo)((s1a, tPerm, ePermNew, v1a) => - permissionSupporter.assertNotNegative(s1a, tPerm, ePerm, ePermNew, pve, v1a, dAInfo)((s2, v2) => { + evals(s, eArgs, _ => pve, v, analysisInfoes)((s1, tArgs, eArgsNew, v1) => + eval(s1, ePerm, pve, v1, analysisInfoes)((s1a, tPerm, ePermNew, v1a) => + permissionSupporter.assertNotNegative(s1a, tPerm, ePerm, ePermNew, pve, v1a, analysisInfoes)((s2, v2) => { val loss = if (!Verifier.config.unsafeWildcardOptimization() || (resource.isInstanceOf[ast.Location] && s2.permLocations.contains(resource.asInstanceOf[ast.Location]))) PermTimes(tPerm, s2.permissionScalingFactor) else WildcardSimplifyingPermTimes(tPerm, s2.permissionScalingFactor) val lossExp = ePermNew.map(p => ast.PermMul(p, s2.permissionScalingFactorExp.get)(p.pos, p.info, p.errT)) - val s3 = v2.heapSupporter.triggerResourceIfNeeded(s2, accPred.loc, tArgs, eArgsNew, v2, dAInfo) - v2.heapSupporter.consumeSingle(s3, h, accPred.loc, tArgs, eArgsNew, loss, lossExp, returnSnap, pve, v2, dAInfo)((s4, h4, snap, v4) => { + val s3 = v2.heapSupporter.triggerResourceIfNeeded(s2, accPred.loc, tArgs, eArgsNew, v2, analysisInfoes) + v2.heapSupporter.consumeSingle(s3, h, accPred.loc, tArgs, eArgsNew, loss, lossExp, returnSnap, pve, v2, analysisInfoes)((s4, h4, snap, v4) => { val s5 = s4.copy(constrainableARPs = s.constrainableARPs, partiallyConsumedHeap = Some(h4)) Q(s5, h4, snap, v4) @@ -296,7 +295,7 @@ object consumer extends ConsumptionRules { if (forall.triggers.isEmpty) None else Some(forall.triggers) val s0 = s.copy(functionRecorder = s.functionRecorder.enterQuantifiedExp(qpa)) - evalQuantified(s0, Forall, forall.variables, Seq(cond), ePerm +: eArgs, optTrigger, qid, pve, v, dAInfo) { + evalQuantified(s0, Forall, forall.variables, Seq(cond), ePerm +: eArgs, optTrigger, qid, pve, v, analysisInfoes) { case (s1, qvars, qvarExps, Seq(tCond), condNew, Some((Seq(tPerm, tArgs@_*), permArgsNew, tTriggers, (auxGlobals, auxNonGlobals), auxExps)), v1) => v1.heapSupporter.consumeQuantified( s = s1, @@ -325,7 +324,7 @@ object consumer extends ConsumptionRules { notInjectiveReason = QPAssertionNotInjective(resAcc), insufficientPermissionReason = insuffReason, v1, - dAInfo)((s2, h2, snap, v2) => { + analysisInfoes)((s2, h2, snap, v2) => { val s3 = s2.copy(constrainableARPs = s.constrainableARPs, functionRecorder = s2.functionRecorder.leaveQuantifiedExp(qpa)) Q(s3, h2, snap, v2) }) @@ -333,15 +332,15 @@ object consumer extends ConsumptionRules { } case let: ast.Let if !let.isPure => - letSupporter.handle[ast.Exp](s, let, pve, v, dAInfo)((s1, g1, body, v1) => { + letSupporter.handle[ast.Exp](s, let, pve, v, analysisInfoes)((s1, g1, body, v1) => { val s2 = s1.copy(g = s1.g + g1) - consumeR(s2, h, body, returnSnap, pve, v1, dAInfo)(Q)}) + consumeR(s2, h, body, returnSnap, pve, v1, analysisInfoes)(Q)}) case _: ast.InhaleExhaleExp => createFailure(viper.silicon.utils.consistency.createUnexpectedInhaleExhaleExpressionError(a), v, s, "valid AST") case _ => - evalAndAssert(s, a, returnSnap, pve, v, dAInfo)((s1, t, v1) => { + evalAndAssert(s, a, returnSnap, pve, v, analysisInfoes)((s1, t, v1) => { Q(s1, h, t, v1) }) } @@ -351,20 +350,20 @@ object consumer extends ConsumptionRules { private def consumeConditionalTlcMoreJoins(s: State, h: Heap, e0: ast.Exp, a1: ast.Exp, a2: Option[ast.Exp], scopeUid: Int, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { - eval(s, e0, pve, v, dAInfo)((s1, t0, e0New, v1) => - joiner.join[(Heap, Option[Term]), (Heap, Option[Term])](s1, v1, dAInfo, resetState = false)((s1, v1, QB) => { - branch(s1.copy(parallelizeBranches = false), t0, (e0, e0New), v1, dAInfo)( + eval(s, e0, pve, v, analysisInfoes)((s1, t0, e0New, v1) => + joiner.join[(Heap, Option[Term]), (Heap, Option[Term])](s1, v1, analysisInfoes, resetState = false)((s1, v1, QB) => { + branch(s1.copy(parallelizeBranches = false), t0, (e0, e0New), v1, analysisInfoes)( (s2, v2) => - consumeR(s2.copy(parallelizeBranches = s1.parallelizeBranches), h, a1, returnSnap, pve, v2, dAInfo)((s3, h1, t1, v3) => { + consumeR(s2.copy(parallelizeBranches = s1.parallelizeBranches), h, a1, returnSnap, pve, v2, analysisInfoes)((s3, h1, t1, v3) => { v3.symbExLog.closeScope(scopeUid) QB(s3, (h1, t1), v3) }), (s2, v2) => a2 match { - case Some(a2) => consumeR(s2.copy(parallelizeBranches = s1.parallelizeBranches), h, a2, returnSnap, pve, v2, dAInfo)((s3, h1, t1, v3) => { + case Some(a2) => consumeR(s2.copy(parallelizeBranches = s1.parallelizeBranches), h, a2, returnSnap, pve, v2, analysisInfoes)((s3, h1, t1, v3) => { v3.symbExLog.closeScope(scopeUid) QB(s3, (h1, t1), v3) }) @@ -381,7 +380,7 @@ object consumer extends ConsumptionRules { State.mergeHeap( entry1.data._1, And(entry1.pathConditions.branchConditions), Option.when(withExp)(BigAnd(entry1.pathConditions.branchConditionExps.map(_._2.get))), entry2.data._1, And(entry2.pathConditions.branchConditions), Option.when(withExp)(BigAnd(entry2.pathConditions.branchConditionExps.map(_._2.get))), - AnalysisInfo(v.decider, v.decider.dependencyAnalyzer, dAInfo.withSource(StringAnalysisSourceInfo("conditional join", e0.pos))) + AnalysisInfo(v.decider, v.decider.dependencyAnalyzer, analysisInfoes.withSource(StringAnalysisSourceInfo("conditional join", e0.pos))) ), // Assume that entry1.pcs is inverse of entry2.pcs (entry1.data._2, entry2.data._2) match { @@ -402,7 +401,7 @@ object consumer extends ConsumptionRules { } - private def evalAndAssert(s: State, e: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, dAInfo: DependencyAnalysisInfo) + private def evalAndAssert(s: State, e: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -421,23 +420,23 @@ object consumer extends ConsumptionRules { exhaleExt = false) executionFlowController.tryOrFail0(s1, v)((s2, v1, QS) => { - eval(s2, e, pve, v1, dAInfo)((s3, t, eNew, v2) => { + eval(s2, e, pve, v1, analysisInfoes)((s3, t, eNew, v2) => { val termToAssert = t match { case Quantification(q, vars, body, trgs, name, isGlob, weight) => val transformed = FunctionPreconditionTransformer.transform(body, s3.program) - v2.decider.assume(Quantification(q, vars, transformed, trgs, name+"_precondition", isGlob, weight), Option.when(withExp)(e), eNew, dAInfo) + v2.decider.assume(Quantification(q, vars, transformed, trgs, name+"_precondition", isGlob, weight), Option.when(withExp)(e), eNew, analysisInfoes) Quantification(q, vars, Implies(transformed, body), trgs, name, isGlob, weight) case _ => t } - v2.decider.assert(termToAssert, dAInfo) { + v2.decider.assert(termToAssert, analysisInfoes) { case true => - v2.decider.assume(t, Option.when(withExp)(e), eNew, dAInfo.withDependencyType(DependencyType.Internal)) + v2.decider.assume(t, Option.when(withExp)(e), eNew, analysisInfoes.withDependencyType(DependencyType.Internal)) QS(s3, v2) case false => val failure = createFailure(pve dueTo AssertionFalse(e), v2, s3, termToAssert, eNew) - if(s3.retryLevel == 0) v2.decider.handleFailedAssertionForDependencyAnalysis(t, dAInfo, assumeFailedAssertion=false) + if(s3.retryLevel == 0) v2.decider.handleFailedAssertionForDependencyAnalysis(t, analysisInfoes, assumeFailedAssertion=false) if (s3.retryLevel == 0 && v2.reportFurtherErrors()){ - v2.decider.assume(t, Option.when(withExp)(e), eNew, dAInfo.withDependencyType(DependencyType.Explicit)) + v2.decider.assume(t, Option.when(withExp)(e), eNew, analysisInfoes.withDependencyType(DependencyType.Explicit)) failure combine QS(s3, v2) } else failure}}) })((s4, v4) => { diff --git a/src/main/scala/rules/ConsumptionResult.scala b/src/main/scala/rules/ConsumptionResult.scala index e7fcafedf..3ce09a087 100644 --- a/src/main/scala/rules/ConsumptionResult.scala +++ b/src/main/scala/rules/ConsumptionResult.scala @@ -6,6 +6,7 @@ package viper.silicon.rules +import viper.silicon.dependencyAnalysis.{AnalysisInfoes, DependencyAnalysisInfoes} import viper.silicon.state.terms.{Forall, Term, Var} import viper.silicon.state.terms.perms.IsNonPositive import viper.silver.ast @@ -27,13 +28,13 @@ private case class Incomplete(permsNeeded: Term, permsNeededExp: Option[ast.Exp] } object ConsumptionResult { - def apply(term: Term, exp: Option[ast.Exp], qvars: Seq[Var], v: Verifier, timeout: Int): ConsumptionResult = { + def apply(term: Term, exp: Option[ast.Exp], qvars: Seq[Var], v: Verifier, timeout: Int, analysisInfoes: DependencyAnalysisInfoes): ConsumptionResult = { val toCheck = if (qvars.isEmpty) { IsNonPositive(term) } else { Forall(qvars, IsNonPositive(term), Seq()) } - if (v.decider.check(toCheck, timeout)) + if (v.decider.check(toCheck, timeout, analysisInfoes)) Complete() else Incomplete(term, exp) diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index ed494a03a..c0d4f5979 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -10,7 +10,7 @@ import viper.silicon import viper.silicon.Config.JoinMode import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp -import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfo, DependencyType} +import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfoes, DependencyType} import viper.silicon.interfaces._ import viper.silicon.interfaces.state.ChunkIdentifer import viper.silicon.logger.records.data.{CondExpRecord, EvaluateRecord, ImpliesRecord} @@ -38,11 +38,11 @@ import viper.silver.verifier.{CounterexampleTransformer, PartialVerificationErro */ trait EvaluationRules extends SymbolicExecutionRules { - def evals(s: State, es: Seq[ast.Exp], pvef: ast.Exp => PartialVerificationError, v: Verifier, dAInfo: DependencyAnalysisInfo) + def evals(s: State, es: Seq[ast.Exp], pvef: ast.Exp => PartialVerificationError, v: Verifier, analysisInfoes: DependencyAnalysisInfoes) (Q: (State, List[Term], Option[List[ast.Exp]], Verifier) => VerificationResult) : VerificationResult - def eval(s: State, e: ast.Exp, pve: PartialVerificationError, v: Verifier, dAInfo: DependencyAnalysisInfo) + def eval(s: State, e: ast.Exp, pve: PartialVerificationError, v: Verifier, analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Term, Option[ast.Exp], Verifier) => VerificationResult) : VerificationResult @@ -55,7 +55,7 @@ trait EvaluationRules extends SymbolicExecutionRules { name: String, pve: PartialVerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Seq[Var], Option[Seq[ast.LocalVarDecl]], Seq[Term], Option[Seq[ast.Exp]], Option[(Seq[Term], Option[Seq[ast.Exp]], Seq[Trigger], (Seq[Term], Seq[Quantification]), Option[(InsertionOrderedSet[DebugExp], InsertionOrderedSet[DebugExp])])], Verifier) => VerificationResult) : VerificationResult } @@ -64,39 +64,38 @@ object evaluator extends EvaluationRules { import consumer._ import producer._ - def evals(s: State, es: Seq[ast.Exp], pvef: ast.Exp => PartialVerificationError, v: Verifier, dAInfo: DependencyAnalysisInfo) + def evals(s: State, es: Seq[ast.Exp], pvef: ast.Exp => PartialVerificationError, v: Verifier, analysisInfoes: DependencyAnalysisInfoes) (Q: (State, List[Term], Option[List[ast.Exp]], Verifier) => VerificationResult) : VerificationResult = - evals2(s, es, Nil, pvef, v, dAInfo)(Q) + evals2(s, es, Nil, pvef, v, analysisInfoes)(Q) - private def evals2(s: State, es: Seq[ast.Exp], ts: List[Term], pvef: ast.Exp => PartialVerificationError, v: Verifier, dAInfo: DependencyAnalysisInfo) + private def evals2(s: State, es: Seq[ast.Exp], ts: List[Term], pvef: ast.Exp => PartialVerificationError, v: Verifier, analysisInfoes: DependencyAnalysisInfoes) (Q: (State, List[Term], Option[List[ast.Exp]], Verifier) => VerificationResult) : VerificationResult = { if (es.isEmpty) Q(s, ts.reverse, if (withExp) Some(List.empty) else None, v) else - eval(s, es.head, pvef(es.head), v, dAInfo)((s1, t, eNew, v1) => - evals2(s1, es.tail, t :: ts, pvef, v1, dAInfo)((s2, ts2, es2, v2) => Q(s2, ts2, eNew.map(eN => eN :: es2.get), v2))) + eval(s, es.head, pvef(es.head), v, analysisInfoes)((s1, t, eNew, v1) => + evals2(s1, es.tail, t :: ts, pvef, v1, analysisInfoes)((s2, ts2, es2, v2) => Q(s2, ts2, eNew.map(eN => eN :: es2.get), v2))) } /** Wrapper Method for eval, for logging. See Executor.scala for explanation of analogue. **/ @inline - def eval(s: State, e: ast.Exp, pve: PartialVerificationError, v: Verifier, dAInfo: DependencyAnalysisInfo) + def eval(s: State, e: ast.Exp, pve: PartialVerificationError, v: Verifier, analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Term, Option[ast.Exp], Verifier) => VerificationResult) : VerificationResult = { val sepIdentifier = v.symbExLog.openScope(new EvaluateRecord(e, s, v.decider.pcs)) - val sourceInfo = v.decider.pushAndGetAnalysisSourceInfo(e, dependencyType) + val analysisInfoes1 = analysisInfoes.addInfo(e.info, e) - eval3(s, e, pve, v, dAInfo)((s1, t, eNew, v1) => { - v1.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) + eval3(s, e, pve, v, analysisInfoes1)((s1, t, eNew, v1) => { v1.symbExLog.closeScope(sepIdentifier) Q(s1, t, eNew, v1)}) } - def eval3(s: State, e: ast.Exp, pve: PartialVerificationError, v: Verifier, dAInfo: DependencyAnalysisInfo) + def eval3(s: State, e: ast.Exp, pve: PartialVerificationError, v: Verifier, analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Term, Option[ast.Exp], Verifier) => VerificationResult) : VerificationResult = { @@ -106,7 +105,7 @@ object evaluator extends EvaluationRules { // assertionNodesForJoin.foreach(n => v.decider.dependencyAnalyzer.addAssumption(True, CompositeAnalysisSourceInfo(v.decider.analysisSourceInfoStack.getFullSourceInfo, AnalysisSourceInfo.createAnalysisSourceInfo(n)), v.decider.analysisSourceInfoStack.getAssumptionType, isJoinNode=true)) if(!Expressions.isKnownWellDefined(e, Some(s.program))){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, dAInfo) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfoes) } val sort = v.symbolConverter.toSort(e.typ) val newVar = v.decider.fresh(sort, None) // just make sure the returned term typechecks @@ -143,7 +142,7 @@ object evaluator extends EvaluationRules { reserveHeaps = Nil, exhaleExt = false) - eval2(s1, e, pve, v, dAInfo)((s2, t, eNew, v1) => { + eval2(s1, e, pve, v, analysisInfoes)((s2, t, eNew, v1) => { val s3 = if (s2.recordPossibleTriggers) e match { @@ -161,7 +160,7 @@ object evaluator extends EvaluationRules { Q(s4, t, eNew, v1)}) } - protected def eval2(s: State, e: ast.Exp, pve: PartialVerificationError, v: Verifier, dAInfo: DependencyAnalysisInfo) + protected def eval2(s: State, e: ast.Exp, pve: PartialVerificationError, v: Verifier, analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Term, Option[ast.Exp], Verifier) => VerificationResult) : VerificationResult = { val eOpt = Option.when(withExp)(e) @@ -172,9 +171,9 @@ object evaluator extends EvaluationRules { case _: ast.NullLit => Q(s, Null, eOpt, v) case ast.IntLit(bigval) => Q(s, IntLiteral(bigval), eOpt, v) - case ast.EqCmp(e0, e1) => evalBinOp(s, e0, e1, Equals, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => + case ast.EqCmp(e0, e1) => evalBinOp(s, e0, e1, Equals, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, Option.when(withExp)(ast.EqCmp(e0New.get, e1New.get)(e.pos, e.info, e.errT)), v1)) - case ast.NeCmp(e0, e1) => evalBinOp(s, e0, e1, (p0: Term, p1: Term) => Not(Equals(p0, p1)), pve, v, dAInfo)((s1, t, e0New, e1New, v1) => + case ast.NeCmp(e0, e1) => evalBinOp(s, e0, e1, (p0: Term, p1: Term) => Not(Equals(p0, p1)), pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, Option.when(withExp)(ast.NeCmp(e0New.get, e1New.get)(e.pos, e.info, e.errT)), v1)) case x: ast.LocalVarWithVersion => @@ -189,8 +188,8 @@ object evaluator extends EvaluationRules { case ast.FractionalPerm(e0, e1) => var t1: Term = null - evalBinOp(s, e0, e1, (t0, _t1) => {t1 = _t1; FractionPerm(t0, t1)}, pve, v, dAInfo)((s1, tFP, e0New, e1New, v1) => - failIfDivByZero(s1, tFP, e1, e1New, t1, predef.Zero, pve, v1, dAInfo)((s2, t, v2) + evalBinOp(s, e0, e1, (t0, _t1) => {t1 = _t1; FractionPerm(t0, t1)}, pve, v, analysisInfoes)((s1, tFP, e0New, e1New, v1) => + failIfDivByZero(s1, tFP, e1, e1New, t1, predef.Zero, pve, v1, analysisInfoes)((s2, t, v2) => Q(s2, t, e0New.map(ast.FractionalPerm(_, e1New.get)(e.pos, e.info, e.errT)), v2))) case _: ast.WildcardPerm if s.assertReadAccessOnly => @@ -201,7 +200,7 @@ object evaluator extends EvaluationRules { case _: ast.WildcardPerm => val (tVar, tConstraints, eVar) = v.decider.freshARP() val constraintExp = Option.when(withExp)(DebugExp.createInstance(s"${eVar.get.toString} > none", true)) - v.decider.assumeDefinition(tConstraints, constraintExp, dAInfo) + v.decider.assumeDefinition(tConstraints, constraintExp, analysisInfoes) /* TODO: Only record wildcards in State.constrainableARPs that are used in exhale * position. Currently, wildcards used in inhale position (only) may not be removed * from State.constrainableARPs (potentially inefficient, but should be sound). @@ -219,14 +218,14 @@ object evaluator extends EvaluationRules { Q(s1, tVar, eVar, v) case fa: ast.FieldAccess => - eval(s, fa.rcv, pve, v, dAInfo)((s1, tRcvr, eRcvr, v1) => { + eval(s, fa.rcv, pve, v, analysisInfoes)((s1, tRcvr, eRcvr, v1) => { val (debugHeapName, debugLabel) = v1.getDebugOldLabel(s1, fa.pos) val newFa = Option.when(withExp)({ if (s1.isEvalInOld) ast.FieldAccess(eRcvr.get, fa.field)(fa.pos, fa.info, fa.errT) else ast.DebugLabelledOld(ast.FieldAccess(eRcvr.get, fa.field)(), debugLabel)(fa.pos, fa.info, fa.errT) }) val ve = pve dueTo InsufficientPermission(fa) - v.heapSupporter.evalFieldAccess(s1, fa, tRcvr, eRcvr, ve, v1, dAInfo)((s2, snap, v2) => { + v.heapSupporter.evalFieldAccess(s1, fa, tRcvr, eRcvr, ve, v1, analysisInfoes)((s2, snap, v2) => { val s3 = if (Verifier.config.enableDebugging() && !s2.isEvalInOld) s2.copy(oldHeaps = s2.oldHeaps + (debugHeapName -> magicWandSupporter.getEvalHeap(s2))) else s2 @@ -235,15 +234,15 @@ object evaluator extends EvaluationRules { }) case ast.Not(e0) => - eval(s, e0, pve, v, dAInfo)((s1, t0, e0New, v1) => + eval(s, e0, pve, v, analysisInfoes)((s1, t0, e0New, v1) => Q(s1, Not(t0), e0New.map(ast.Not(_)(e.pos, e.info, e.errT)), v1)) case ast.Minus(e0) => - eval(s, e0, pve, v, dAInfo)((s1, t0, e0New, v1) => + eval(s, e0, pve, v, analysisInfoes)((s1, t0, e0New, v1) => Q(s1, Minus(0, t0), e0New.map(ast.Minus(_)(e.pos, e.info, e.errT)), v1)) case ast.Old(e0) => - evalInOldState(s, Verifier.PRE_STATE_LABEL, e0, pve, v, dAInfo)((s1, t0, e0New, v1) => + evalInOldState(s, Verifier.PRE_STATE_LABEL, e0, pve, v, analysisInfoes)((s1, t0, e0New, v1) => Q(s1, t0, e0New.map(ast.Old(_)(e.pos, e.info, e.errT)), v1)) case old@ast.DebugLabelledOld(e0, lbl) => @@ -254,11 +253,11 @@ object evaluator extends EvaluationRules { s.oldHeaps.get(heapName) match { case None => val failure = createFailure(pve dueTo LabelledStateNotReached(ast.LabelledOld(e0, heapName)(old.pos, old.info, old.errT)), v, s, "labelled state reached") - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, dAInfo, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfoes, v.reportFurtherErrors()) val freshVar = v.decider.fresh(v.symbolConverter.toSort(old.typ), None) if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, freshVar, None, v) else failure case _ => - evalInOldState(s, heapName, e0, pve, v, dAInfo)((s1, t0, _, v1) => + evalInOldState(s, heapName, e0, pve, v, analysisInfoes)((s1, t0, _, v1) => Q(s1, t0, Some(old), v1)) } @@ -266,21 +265,21 @@ object evaluator extends EvaluationRules { s.oldHeaps.get(lbl) match { case None => val failure = createFailure(pve dueTo LabelledStateNotReached(old), v, s, "labelled state reached") - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, dAInfo, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfoes, v.reportFurtherErrors()) val freshVar = v.decider.fresh(v.symbolConverter.toSort(old.typ), None) if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, freshVar, None, v) else failure case _ => - evalInOldState(s, lbl, e0, pve, v, dAInfo)((s1, t0, e0New, v1) => + evalInOldState(s, lbl, e0, pve, v, analysisInfoes)((s1, t0, e0New, v1) => Q(s1, t0, e0New.map(ast.LabelledOld(_, lbl)(old.pos, old.info, old.errT)), v1))} case l@ast.Let(x, e0, e1) => - eval(s, e0, pve, v, dAInfo)((s1, t0, e0New, v1) => { + eval(s, e0, pve, v, analysisInfoes)((s1, t0, e0New, v1) => { val t = v1.decider.appliedFresh("letvar", v1.symbolConverter.toSort(x.typ), s1.relevantQuantifiedVariables.map(_._1)) val debugExp = Option.when(withExp)(DebugExp.createInstance("letvar assignment", InsertionOrderedSet(DebugExp.createInstance(ast.EqCmp(x.localVar, e0)(), ast.EqCmp(x.localVar, e0New.get)())))) - v1.decider.assumeDefinition(BuiltinEquals(t, t0), debugExp, dAInfo) + v1.decider.assumeDefinition(BuiltinEquals(t, t0), debugExp, analysisInfoes) val newFuncRec = s1.functionRecorder.recordFreshSnapshot(t.applicable.asInstanceOf[Function]).enterLet(l) val possibleTriggersBefore = if (s1.recordPossibleTriggers) s1.possibleTriggers else Map.empty - eval(s1.copy(g = s1.g + (x.localVar, (t0, e0New)), functionRecorder = newFuncRec), e1, pve, v1, dAInfo)((s2, t2, e1New, v2) => { + eval(s1.copy(g = s1.g + (x.localVar, (t0, e0New)), functionRecorder = newFuncRec), e1, pve, v1, analysisInfoes)((s2, t2, e1New, v2) => { val newPossibleTriggers = if (s2.recordPossibleTriggers) { val addedTriggers = s2.possibleTriggers -- possibleTriggersBefore.keys val addedTriggersReplaced = addedTriggers.map(at => at._1.replace(x.localVar, e0) -> at._2) @@ -295,30 +294,30 @@ object evaluator extends EvaluationRules { /* Strict evaluation of AND */ case ast.And(e0, e1) if Verifier.config.disableShortCircuitingEvaluations() => - evalBinOp(s, e0, e1, (t1, t2) => And(t1, t2), pve, v, dAInfo)((s1, t, e0New, e1New, v1) => + evalBinOp(s, e0, e1, (t1, t2) => And(t1, t2), pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(ast.And(_, e1New.get)(e.pos, e.info, e.errT)), v1)) /* Short-circuiting evaluation of AND */ case ae @ ast.And(_, _) => val flattened = flattenOperator(ae, {case ast.And(e0, e1) => Seq(e0, e1)}) - evalSeqShortCircuit(And, s, flattened, pve, v, dAInfo)(Q) + evalSeqShortCircuit(And, s, flattened, pve, v, analysisInfoes)(Q) /* Strict evaluation of OR */ case ast.Or(e0, e1) if Verifier.config.disableShortCircuitingEvaluations() => - evalBinOp(s, e0, e1, (t1, t2) => Or(t1, t2), pve, v, dAInfo)((s1, t, e0New, e1New, v1) => + evalBinOp(s, e0, e1, (t1, t2) => Or(t1, t2), pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(ast.Or(_, e1New.get)(e.pos, e.info, e.errT)), v1)) /* Short-circuiting evaluation of OR */ case oe @ ast.Or(_, _) => val flattened = flattenOperator(oe, {case ast.Or(e0, e1) => Seq(e0, e1)}) - evalSeqShortCircuit(Or, s, flattened, pve, v, dAInfo)(Q) + evalSeqShortCircuit(Or, s, flattened, pve, v, analysisInfoes)(Q) case implies @ ast.Implies(e0, e1) => val impliesRecord = new ImpliesRecord(implies, s, v.decider.pcs, "Implies") val uidImplies = v.symbExLog.openScope(impliesRecord) - eval(s, e0, pve, v, dAInfo)((s1, t0, e0New, v1) => - evalImplies(s1, t0, (e0, e0New), e1, implies.info == FromShortCircuitingAnd, pve, v1, dAInfo)((s2, t1, e1New, v2) => { + eval(s, e0, pve, v, analysisInfoes)((s1, t0, e0New, v1) => + evalImplies(s1, t0, (e0, e0New), e1, implies.info == FromShortCircuitingAnd, pve, v1, analysisInfoes)((s2, t1, e1New, v2) => { v2.symbExLog.closeScope(uidImplies) val implExpP = e0New.map(ast.Implies(_, e1New.get)(e.pos, e.info, e.errT)) Q(s2, t1, implExpP, v2) @@ -327,11 +326,11 @@ object evaluator extends EvaluationRules { case condExp @ ast.CondExp(e0, e1, e2) => val condExpRecord = new CondExpRecord(condExp, s, v.decider.pcs, "CondExp") val uidCondExp = v.symbExLog.openScope(condExpRecord) - eval(s, e0, pve, v, dAInfo)((s1, t0, e0New, v1) => - joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s1, v1, dAInfo)((s2, v2, QB) => - brancher.branch(s2.copy(parallelizeBranches = false), t0, (e0, e0New), v2, dAInfo.withDependencyType(DependencyType.Internal))( - (s3, v3) => eval(s3.copy(parallelizeBranches = s2.parallelizeBranches), e1, pve, v3, dAInfo)((s4, t4, e4, v4) => QB(s4, (t4, e4), v4)), - (s3, v3) => eval(s3.copy(parallelizeBranches = s2.parallelizeBranches), e2, pve, v3, dAInfo)((s4, t4, e4, v4) => QB(s4, (t4, e4), v4))) + eval(s, e0, pve, v, analysisInfoes)((s1, t0, e0New, v1) => + joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s1, v1, analysisInfoes)((s2, v2, QB) => + brancher.branch(s2.copy(parallelizeBranches = false), t0, (e0, e0New), v2, analysisInfoes.withDependencyType(DependencyType.Internal))( + (s3, v3) => eval(s3.copy(parallelizeBranches = s2.parallelizeBranches), e1, pve, v3, analysisInfoes)((s4, t4, e4, v4) => QB(s4, (t4, e4), v4)), + (s3, v3) => eval(s3.copy(parallelizeBranches = s2.parallelizeBranches), e2, pve, v3, analysisInfoes)((s4, t4, e4, v4) => QB(s4, (t4, e4), v4))) )(entries => { /* TODO: If branch(...) took orElse-continuations that are executed if a branch is dead, then then comparisons with t0/Not(t0) wouldn't be necessary. */ @@ -353,88 +352,88 @@ object evaluator extends EvaluationRules { /* Integers */ case ast.Add(e0, e1) => - evalBinOp(s, e0, e1, Plus, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.Add(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) + evalBinOp(s, e0, e1, Plus, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.Add(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case ast.Sub(e0, e1) => - evalBinOp(s, e0, e1, Minus, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.Sub(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) + evalBinOp(s, e0, e1, Minus, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.Sub(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case ast.Mul(e0, e1) => - evalBinOp(s, e0, e1, Times, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.Mul(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) + evalBinOp(s, e0, e1, Times, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.Mul(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case ast.Div(e0, e1) => - evalBinOp(s, e0, e1, Div, pve, v, dAInfo)((s1, tDiv, e0New, e1New, v1) => - failIfDivByZero(s1, tDiv, e1, e1New, tDiv.p1, 0, pve, v1, dAInfo)((s2, t, v2) + evalBinOp(s, e0, e1, Div, pve, v, analysisInfoes)((s1, tDiv, e0New, e1New, v1) => + failIfDivByZero(s1, tDiv, e1, e1New, tDiv.p1, 0, pve, v1, analysisInfoes)((s2, t, v2) => Q(s2, t, e0New.map(e0p => ast.Div(e0p, e1New.get)(e.pos, e.info, e.errT)), v2))) case ast.Mod(e0, e1) => - evalBinOp(s, e0, e1, Mod, pve, v, dAInfo)((s1, tMod, e0New, e1New, v1) => - failIfDivByZero(s1, tMod, e1, e1New, tMod.p1, 0, pve, v1, dAInfo)((s2, t, v2) + evalBinOp(s, e0, e1, Mod, pve, v, analysisInfoes)((s1, tMod, e0New, e1New, v1) => + failIfDivByZero(s1, tMod, e1, e1New, tMod.p1, 0, pve, v1, analysisInfoes)((s2, t, v2) => Q(s2, t, e0New.map(e0p => ast.Mod(e0p, e1New.get)(e.pos, e.info, e.errT)), v2))) case ast.LeCmp(e0, e1) => - evalBinOp(s, e0, e1, AtMost, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.LeCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) + evalBinOp(s, e0, e1, AtMost, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.LeCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case ast.LtCmp(e0, e1) => - evalBinOp(s, e0, e1, Less, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.LtCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) + evalBinOp(s, e0, e1, Less, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.LtCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case ast.GeCmp(e0, e1) => - evalBinOp(s, e0, e1, AtLeast, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.GeCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) + evalBinOp(s, e0, e1, AtLeast, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.GeCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case ast.GtCmp(e0, e1) => - evalBinOp(s, e0, e1, Greater, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.GtCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) + evalBinOp(s, e0, e1, Greater, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.GtCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) /* Permissions */ case ast.PermAdd(e0, e1) => - evalBinOp(s, e0, e1, PermPlus, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.PermAdd(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) + evalBinOp(s, e0, e1, PermPlus, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.PermAdd(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case ast.PermSub(e0, e1) => - evalBinOp(s, e0, e1, PermMinus, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.PermSub(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) + evalBinOp(s, e0, e1, PermMinus, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.PermSub(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case ast.PermMinus(e0) => - eval(s, e0, pve, v, dAInfo)((s1, t0, e0New, v1) => + eval(s, e0, pve, v, analysisInfoes)((s1, t0, e0New, v1) => Q(s1, PermMinus(NoPerm, t0), e0New.map(e0p => ast.PermMinus(e0p)(e.pos, e.info, e.errT)), v1)) case ast.PermMul(e0, e1) => - evalBinOp(s, e0, e1, PermTimes, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.PermMul(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) + evalBinOp(s, e0, e1, PermTimes, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.PermMul(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case ast.DebugPermMin(e0, e1) => - evalBinOp(s, e0, e1, PermMin, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.DebugPermMin(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) + evalBinOp(s, e0, e1, PermMin, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.DebugPermMin(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case ast.IntPermMul(e0, e1) => - eval(s, e0, pve, v, dAInfo)((s1, t0, e0New, v1) => - eval(s1, e1, pve, v1, dAInfo)((s2, t1, e1New, v2) => + eval(s, e0, pve, v, analysisInfoes)((s1, t0, e0New, v1) => + eval(s1, e1, pve, v1, analysisInfoes)((s2, t1, e1New, v2) => Q(s2, IntPermTimes(t0, t1), e0New.map(e0p => ast.IntPermMul(e0p, e1New.get)(e.pos, e.info, e.errT)), v2))) case ast.PermDiv(e0, e1) => - eval(s, e0, pve, v, dAInfo)((s1, t0, e0New, v1) => - eval(s1, e1, pve, v1, dAInfo)((s2, t1, e1New, v2) => - failIfDivByZero(s2, PermIntDiv(t0, t1), e1, e1New, t1, 0, pve, v2, dAInfo)((s3, t, v3) + eval(s, e0, pve, v, analysisInfoes)((s1, t0, e0New, v1) => + eval(s1, e1, pve, v1, analysisInfoes)((s2, t1, e1New, v2) => + failIfDivByZero(s2, PermIntDiv(t0, t1), e1, e1New, t1, 0, pve, v2, analysisInfoes)((s3, t, v3) => Q(s3, t, e0New.map(e0p => ast.PermDiv(e0p, e1New.get)(e.pos, e.info, e.errT)), v3)))) case ast.PermPermDiv(e0, e1) => - eval(s, e0, pve, v, dAInfo)((s1, t0, e0New, v1) => - eval(s1, e1, pve, v1, dAInfo)((s2, t1, e1New, v2) => - failIfDivByZero(s2, PermPermDiv(t0, t1), e1, e1New, t1, FractionPermLiteral(Rational(0, 1)), pve, v2, dAInfo)((s3, t, v3) => + eval(s, e0, pve, v, analysisInfoes)((s1, t0, e0New, v1) => + eval(s1, e1, pve, v1, analysisInfoes)((s2, t1, e1New, v2) => + failIfDivByZero(s2, PermPermDiv(t0, t1), e1, e1New, t1, FractionPermLiteral(Rational(0, 1)), pve, v2, analysisInfoes)((s3, t, v3) => Q(s3, t, e0New.map(e0p => ast.PermPermDiv(e0p, e1New.get)(e.pos, e.info, e.errT)), v3)))) case ast.PermLeCmp(e0, e1) => - evalBinOp(s, e0, e1, AtMost, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.PermLeCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) + evalBinOp(s, e0, e1, AtMost, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.PermLeCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case ast.PermLtCmp(e0, e1) => - evalBinOp(s, e0, e1, Less, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.PermLtCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) + evalBinOp(s, e0, e1, Less, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.PermLtCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case ast.PermGeCmp(e0, e1) => - evalBinOp(s, e0, e1, AtLeast, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.PermGeCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) + evalBinOp(s, e0, e1, AtLeast, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.PermGeCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case ast.PermGtCmp(e0, e1) => - evalBinOp(s, e0, e1, Greater, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.PermGtCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) + evalBinOp(s, e0, e1, Greater, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.PermGtCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) /* Others */ /* Domains not handled directly */ case dfa @ ast.DomainFuncApp(funcName, eArgs, m) => - evals(s, eArgs, _ => pve, v, dAInfo)((s1, tArgs, eArgsNew, v1) => { + evals(s, eArgs, _ => pve, v, analysisInfoes)((s1, tArgs, eArgsNew, v1) => { val inSorts = tArgs map (_.sort) val outSort = v1.symbolConverter.toSort(dfa.typ) val fi = v1.symbolConverter.toFunction(s.program.findDomainFunction(funcName), inSorts :+ outSort, s.program) @@ -442,7 +441,7 @@ object evaluator extends EvaluationRules { Q(s1, App(fi, tArgs), dfaP, v1)}) case bf @ ast.BackendFuncApp(funcName, eArgs) => - evals(s, eArgs, _ => pve, v, dAInfo)((s1, tArgs, eArgsNew, v1) => { + evals(s, eArgs, _ => pve, v, analysisInfoes)((s1, tArgs, eArgsNew, v1) => { val func = s.program.findDomainFunction(funcName) val fi = v1.symbolConverter.toFunction(func, s.program) val bfP = Option.when(withExp)(ast.BackendFuncApp(funcName, eArgsNew.get)(bf.pos, bf.info, bf.typ, bf.interpretation, bf.errT)) @@ -450,8 +449,8 @@ object evaluator extends EvaluationRules { case ast.CurrentPerm(resacc) => val h = s.partiallyConsumedHeap.getOrElse(s.h) - evalResourceAccess(s, resacc, pve, v, dAInfo)((s1, identifier, args, eArgsNew, v1) => { - v1.heapSupporter.evalCurrentPerm(s1, h, resacc, identifier, args, eArgsNew, v1, dAInfo)((s2, t, v2) => + evalResourceAccess(s, resacc, pve, v, analysisInfoes)((s1, identifier, args, eArgsNew, v1) => { + v1.heapSupporter.evalCurrentPerm(s1, h, resacc, identifier, args, eArgsNew, v1, analysisInfoes)((s2, t, v2) => Q(s2, t, Option.when(withExp)(e), v2)) }) @@ -472,7 +471,7 @@ object evaluator extends EvaluationRules { val s2 = s1.copy(s1.g + gVars, quantifiedVariables = varPairs.map(v => v._1 -> Option.when(withExp)(v._2)) ++ s1.quantifiedVariables) - evals(s2, args, _ => pve, v, dAInfo)((s3, ts, es, v3) => { + evals(s2, args, _ => pve, v, analysisInfoes)((s3, ts, es, v3) => { val possibleConds = v3.heapSupporter.collectForPermConditions(s3, resource, varPairs, ts, es) def evalOptions(s: State, @@ -485,7 +484,7 @@ object evaluator extends EvaluationRules { val impliesRecord = new ImpliesRecord(null, s, v.decider.pcs, "ForPerm") val uidImplies = v.symbExLog.openScope(impliesRecord) val (t, (e0, e1), qVars, defs, triggers) = conds.head - evalImplies(s.copy(g = s.g + defs), t, (e0, e1), body, false, pve, v, dAInfo)((sNext, tImplies, bodyNew, vNext) => { + evalImplies(s.copy(g = s.g + defs), t, (e0, e1), body, false, pve, v, analysisInfoes)((sNext, tImplies, bodyNew, vNext) => { val tQuant = SimplifyingForall(qVars, tImplies, triggers) val eQuantNew = Option.when(withExp)(ast.Forall(varsNew.get, Seq(), ast.Implies(e0, bodyNew.get)())()) v.symbExLog.closeScope(uidImplies) @@ -552,7 +551,7 @@ object evaluator extends EvaluationRules { } val name = s"prog.$posString" val s0 = s.copy(functionRecorder = s.functionRecorder.enterQuantifiedExp(sourceQuant)) - evalQuantified(s0, qantOp, eQuant.variables, Nil, Seq(body), Some(eTriggers), name, pve, v, dAInfo){ + evalQuantified(s0, qantOp, eQuant.variables, Nil, Seq(body), Some(eTriggers), name, pve, v, analysisInfoes){ case (s1, tVars, eVars, _, _, Some((Seq(tBody), bodyNew, tTriggers, (tAuxGlobal, tAux), auxExps)), v1) => val tAuxHeapIndep = tAux.flatMap(v.quantifierSupporter.makeTriggersHeapIndependent(_, v1.decider.fresh)) val auxGlobalsExp = auxExps.map(_._1) @@ -560,10 +559,10 @@ object evaluator extends EvaluationRules { val commentGlobal = "Nested auxiliary terms: globals (aux)" v1.decider.prover.comment(commentGlobal) v1.decider.dependencyAnalyzer.disableTransitiveEdges() - v1.decider.assume(tAuxGlobal, Option.when(withExp)(DebugExp.createInstance(description=commentGlobal, children=auxGlobalsExp.get)), enforceAssumption = false, dAInfo.withDependencyType(DependencyType.Internal)) + v1.decider.assume(tAuxGlobal, Option.when(withExp)(DebugExp.createInstance(description=commentGlobal, children=auxGlobalsExp.get)), enforceAssumption = false, analysisInfoes.withDependencyType(DependencyType.Internal)) val commentNonGlobals = "Nested auxiliary terms: non-globals (aux)" v1.decider.prover.comment(commentNonGlobals) - v1.decider.assume(tAuxHeapIndep/*tAux*/, Option.when(withExp)(DebugExp.createInstance(description=commentNonGlobals, children=auxNonGlobalsExp.get)), enforceAssumption = false, dAInfo.withDependencyType(DependencyType.Internal)) + v1.decider.assume(tAuxHeapIndep/*tAux*/, Option.when(withExp)(DebugExp.createInstance(description=commentNonGlobals, children=auxNonGlobalsExp.get)), enforceAssumption = false, analysisInfoes.withDependencyType(DependencyType.Internal)) v1.decider.dependencyAnalyzer.enableTransitiveEdges() if (qantOp == Exists) { // For universal quantification, the non-global auxiliary assumptions will contain the information that @@ -577,7 +576,7 @@ object evaluator extends EvaluationRules { val exp = ast.Forall(eQuant.variables, eTriggers, body)(sourceQuant.pos, sourceQuant.info, sourceQuant.errT) DebugExp.createInstance(exp, expNew) }) - v1.decider.assume(Quantification(Forall, tVars, FunctionPreconditionTransformer.transform(tBody, s1.program), tTriggers, name, quantWeight), debugExp, dAInfo) + v1.decider.assume(Quantification(Forall, tVars, FunctionPreconditionTransformer.transform(tBody, s1.program), tTriggers, name, quantWeight), debugExp, analysisInfoes) } val tQuant = Quantification(qantOp, tVars, tBody, tTriggers, name, quantWeight) @@ -586,11 +585,11 @@ object evaluator extends EvaluationRules { Q(s2, tQuant, eQuantNew, v1) case (s1, _, _, _, _, None, v1) => // This should not happen unless the current path is dead. - if (v1.decider.checkSmoke(dAInfo, isAssert = true)) { + if (v1.decider.checkSmoke(analysisInfoes, isAssert = true)) { Unreachable() } else { val failure = createFailure(pve.dueTo(InternalReason(sourceQuant, "Quantifier evaluation failed.")), v1, s1, "quantifier could be evaluated") - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(False, dAInfo, v1.reportFurtherErrors()) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfoes, v1.reportFurtherErrors()) val freshVar = v1.decider.fresh(v1.symbolConverter.toSort(sourceQuant.typ), None) if(s1.retryLevel == 0 && v1.reportFurtherErrors()) failure combine Q(s1, freshVar, None, v1) else failure } @@ -599,7 +598,7 @@ object evaluator extends EvaluationRules { case fapp @ ast.FuncApp(funcName, eArgs) => val func = s.program.findFunction(funcName) val dependencyType = v.decider.analysisSourceInfoStack.getDependencyType - evals2(s, eArgs, Nil, _ => pve, v, dAInfo)((s1, tArgs, eArgsNew, v1) => { + evals2(s, eArgs, Nil, _ => pve, v, analysisInfoes)((s1, tArgs, eArgsNew, v1) => { // bookkeeper.functionApplications += 1 val joinFunctionArgs = tArgs //++ c2a.quantifiedVariables.filterNot(tArgs.contains) val (debugHeapName, debugLabel) = v1.getDebugOldLabel(s1, fapp.pos) @@ -614,7 +613,7 @@ object evaluator extends EvaluationRules { * Hence, the joinedFApp will take two arguments, namely, i*i and i, * although the latter is not necessary. */ - joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s1a, v1, dAInfo)((s2, v2, QB) => { + joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s1a, v1, analysisInfoes)((s2, v2, QB) => { val pres = func.pres.map(_.transform { /* [Malte 2018-08-20] Two examples of the test suite, one of which is the regression * for Carbon issue #210, fail if the subsequent code that strips out triggers from @@ -674,14 +673,14 @@ object evaluator extends EvaluationRules { s2.assertReadAccessOnly /* should currently always be false */ else true) val oldIsRelevantJoinNode = v2.decider.analysisSourceInfoStack.isJoinRelevantNode v2.decider.analysisSourceInfoStack.isJoinRelevantNode = true - consumes(s3, pres, true, _ => pvePre, v2, dAInfo)((s4, snap, v3) => { + consumes(s3, pres, true, _ => pvePre, v2, analysisInfoes)((s4, snap, v3) => { v3.decider.analysisSourceInfoStack.isJoinRelevantNode = oldIsRelevantJoinNode val snap1 = snap.get.convert(sorts.Snap) val preFApp = App(functionSupporter.preconditionVersion(v3.symbolConverter.toFunction(func)), snap1 :: tArgs) val preExp = Option.when(withExp)({ DebugExp.createInstance(Some(s"precondition of ${func.name}(${eArgsNew.get.mkString(", ")}) holds"), None, None, InsertionOrderedSet.empty) }) - v3.decider.assume(preFApp, preExp, dAInfo) + v3.decider.assume(preFApp, preExp, analysisInfoes) val funcAnn = func.info.getUniqueInfo[AnnotationInfo] val tFApp = funcAnn match { case Some(a) if a.values.contains("opaque") => @@ -711,7 +710,7 @@ object evaluator extends EvaluationRules { /* TODO: The join-function is heap-independent, and it is not obvious how a * joined snapshot could be defined and represented */ - })(join(func.typ, s"joined_${func.name}", joinFunctionArgs, Option.when(withExp)(eArgs), v1, dAInfo))((s6, r, v4) + })(join(func.typ, s"joined_${func.name}", joinFunctionArgs, Option.when(withExp)(eArgs), v1, analysisInfoes))((s6, r, v4) => Q(s6, r._1, r._2, v4))}) case ast.Unfolding( @@ -721,11 +720,11 @@ object evaluator extends EvaluationRules { val predicate = s.program.findPredicate(predicateName) if (s.cycles(predicate) < Verifier.config.recursivePredicateUnfoldings()) { v.decider.startDebugSubExp() - evals(s, eArgs, _ => pve, v, dAInfo)((s1, tArgs, eArgsNew, v1) => - eval(s1, ePerm.getOrElse(ast.FullPerm()()), pve, v1, dAInfo)((s2, tPerm, ePermNew, v2) => - v2.decider.assert(IsPositive(tPerm), dAInfo) { // TODO: Replace with permissionSupporter.assertNotNegative + evals(s, eArgs, _ => pve, v, analysisInfoes)((s1, tArgs, eArgsNew, v1) => + eval(s1, ePerm.getOrElse(ast.FullPerm()()), pve, v1, analysisInfoes)((s2, tPerm, ePermNew, v2) => + v2.decider.assert(IsPositive(tPerm), analysisInfoes) { // TODO: Replace with permissionSupporter.assertNotNegative case true => - joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s2, v2, dAInfo)((s3, v3, QB) => { + joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s2, v2, analysisInfoes)((s3, v3, QB) => { val s4 = s3.incCycleCounter(predicate) .copy(recordVisited = true) /* [2014-12-10 Malte] The commented code should replace the code following @@ -737,7 +736,7 @@ object evaluator extends EvaluationRules { // val c4 = c3.decCycleCounter(predicate) // eval(σ1, eIn, pve, c4)((tIn, c5) => // QB(tIn, c5))}) - consume(s4, acc, true, pve, v3, dAInfo)((s5, snap, v4) => { + consume(s4, acc, true, pve, v3, analysisInfoes)((s5, snap, v4) => { val fr6 = s5.functionRecorder.recordSnapshot(pa, v4.decider.pcs.branchConditions, snap.get) .changeDepthBy(+1) @@ -752,7 +751,7 @@ object evaluator extends EvaluationRules { if (!Verifier.config.disableFunctionUnfoldTrigger()) { val eArgsString = eArgsNew.mkString(", ") val debugExp = Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}($eArgsString))", isInternal_ = true)) - v4.decider.assume(App(s.predicateData(predicate.name).triggerFunction, snap.get.convert(terms.sorts.Snap) +: tArgs), debugExp, dAInfo.withDependencyType(DependencyType.Trigger)) + v4.decider.assume(App(s.predicateData(predicate.name).triggerFunction, snap.get.convert(terms.sorts.Snap) +: tArgs), debugExp, analysisInfoes.withDependencyType(DependencyType.Trigger)) } val body = predicate.body.get /* Only non-abstract predicates can be unfolded */ val s7 = s6.scalePermissionFactor(tPerm, ePermNew) @@ -762,7 +761,7 @@ object evaluator extends EvaluationRules { if (s7a.predicateData(predicate.name).predContents.isDefined) { val toReplace: silicon.Map[Term, Term] = silicon.Map.from(s7a.predicateData(predicate.name).params.get.zip(Seq(snap.get) ++ tArgs)) - predicateSupporter.producePredicateContents(s7a, s7a.predicateData(predicate.name).predContents.get, toReplace, v4, dAInfo.withDependencyType(DependencyType.Internal), true)((s8, v5) => { + predicateSupporter.producePredicateContents(s7a, s7a.predicateData(predicate.name).predContents.get, toReplace, v4, analysisInfoes.withDependencyType(DependencyType.Internal), true)((s8, v5) => { val s9 = s8.copy(g = s7.g, functionRecorder = s8.functionRecorder.changeDepthBy(-1), recordVisited = s3.recordVisited, @@ -771,10 +770,10 @@ object evaluator extends EvaluationRules { constrainableARPs = s1.constrainableARPs) .decCycleCounter(predicate) val s10 = v5.stateConsolidator(s9).consolidateOptionally(s9, v5) - eval(s10, eIn, pve, v5, dAInfo)((s9, t9, e9, v9) => QB(s9, (t9, e9), v9)) + eval(s10, eIn, pve, v5, analysisInfoes)((s9, t9, e9, v9) => QB(s9, (t9, e9), v9)) }) } else { - produce(s7a, toSf(snap.get), body, pve, v4, dAInfo)((s8, v5) => { + produce(s7a, toSf(snap.get), body, pve, v4, analysisInfoes)((s8, v5) => { val s9 = s8.copy(g = s7.g, functionRecorder = s8.functionRecorder.changeDepthBy(-1), recordVisited = s3.recordVisited, @@ -783,18 +782,18 @@ object evaluator extends EvaluationRules { constrainableARPs = s1.constrainableARPs) .decCycleCounter(predicate) val s10 = v5.stateConsolidator(s9).consolidateOptionally(s9, v5) - eval(s10, eIn, pve, v5, dAInfo)((s9, t9, e9, v9) => QB(s9, (t9, e9), v9))}) + eval(s10, eIn, pve, v5, analysisInfoes)((s9, t9, e9, v9) => QB(s9, (t9, e9), v9))}) } }) })(join(eIn.typ, "joined_unfolding", s2.relevantQuantifiedVariables.map(_._1), - Option.when(withExp)(s2.relevantQuantifiedVariables.map(_._2.get)), v2, dAInfo))((s12, r12, v7) + Option.when(withExp)(s2.relevantQuantifiedVariables.map(_._2.get)), v2, analysisInfoes))((s12, r12, v7) => { v7.decider.finishDebugSubExp(s"unfolded(${predicate.name})") Q(s12, r12._1, r12._2, v7)}) case false => v2.decider.finishDebugSubExp(s"unfolded(${predicate.name})") val failure = createFailure(pve dueTo NonPositivePermission(ePerm.get), v2, s2, IsPositive(tPerm), ePermNew.map(p => ast.PermGtCmp(p, ast.NoPerm()())(p.pos, p.info, p.errT))) - if(s2.retryLevel == 0) v2.decider.handleFailedAssertionForDependencyAnalysis(False, dAInfo, v2.reportFurtherErrors()) + if(s2.retryLevel == 0) v2.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfoes, v2.reportFurtherErrors()) val freshVar = v2.decider.fresh(v2.symbolConverter.toSort(e.typ), None) // TODO ake: function recorder if(s2.retryLevel == 0 && v2.reportFurtherErrors() && Verifier.config.disableInfeasibilityChecks()) failure combine Q(s2, freshVar, None, v2) else failure })) @@ -805,123 +804,123 @@ object evaluator extends EvaluationRules { } case ast.Applying(wand, eIn) => - joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s, v, dAInfo)((s1, v1, QB) => - magicWandSupporter.applyWand(s1, wand, pve, v1)((s2, v2) => { - eval(s2, eIn, pve, v2, dAInfo)((s3, t, eInNew, v3) => QB(s3, (t, eInNew), v3)) + joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s, v, analysisInfoes)((s1, v1, QB) => + magicWandSupporter.applyWand(s1, wand, pve, v1, analysisInfoes)((s2, v2) => { + eval(s2, eIn, pve, v2, analysisInfoes)((s3, t, eInNew, v3) => QB(s3, (t, eInNew), v3)) }))(join(eIn.typ, "joined_applying", s.relevantQuantifiedVariables.map(_._1), - Option.when(withExp)(s.relevantQuantifiedVariables.map(_._2.get)), v, dAInfo))((s4, r4, v4) + Option.when(withExp)(s.relevantQuantifiedVariables.map(_._2.get)), v, analysisInfoes))((s4, r4, v4) => Q(s4, r4._1, r4._2, v4)) case ast.Asserting(eAss, eIn) => - consume(s, eAss, false, pve, v, dAInfo /* TODO ake: explicit assertion? */)((s2, _, v2) => { + consume(s, eAss, false, pve, v, analysisInfoes /* TODO ake: explicit assertion? */)((s2, _, v2) => { val s3 = s2.copy(g = s.g, h = s.h) - eval(s3, eIn, pve, v2, dAInfo)(Q) + eval(s3, eIn, pve, v2, analysisInfoes)(Q) }) /* Sequences */ - case ast.SeqContains(e0, e1) => evalBinOp(s, e1, e0, SeqIn, pve, v, dAInfo)((s1, t, e1New, e0New, v1) => + case ast.SeqContains(e0, e1) => evalBinOp(s, e1, e0, SeqIn, pve, v, analysisInfoes)((s1, t, e1New, e0New, v1) => Q(s1, t, e0New.map(e0p => ast.SeqContains(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) /* Note the reversed order of the arguments! */ case ast.SeqIndex(e0, e1) => - evals2(s, Seq(e0, e1), Nil, _ => pve, v, dAInfo)({case (s1, Seq(t0, t1), esNew, v1) => + evals2(s, Seq(e0, e1), Nil, _ => pve, v, analysisInfoes)({case (s1, Seq(t0, t1), esNew, v1) => val eNew = esNew.map(es => ast.SeqIndex(es.head, es(1))(e.pos, e.info, e.errT)) if (s1.triggerExp) { Q(s1, SeqAt(t0, t1), eNew, v1) } else { - v1.decider.assert(AtLeast(t1, IntLiteral(0)), dAInfo) { + v1.decider.assert(AtLeast(t1, IntLiteral(0)), analysisInfoes) { case true => - v1.decider.assert(Less(t1, SeqLength(t0)), dAInfo) { + v1.decider.assert(Less(t1, SeqLength(t0)), analysisInfoes) { case true => Q(s1, SeqAt(t0, t1), eNew, v1) case false => val assertExp2 = Option.when(withExp)(ast.LtCmp(e1, ast.SeqLength(e0)())(e1.pos, e1.info, e1.errT)) val failure = createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, Less(t1, SeqLength(t0)), assertExp2) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(Less(t1, SeqLength(t0)), dAInfo, assumeFailedAssertion=false) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(Less(t1, SeqLength(t0)), analysisInfoes, assumeFailedAssertion=false) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { val assertExp2 = Option.when(withExp)(ast.LeCmp(e1, ast.SeqLength(e0)())()) val assertExp2New = esNew.map(es => ast.LeCmp(es(1), ast.SeqLength(es.head)())()) - v1.decider.assume(Less(t1, SeqLength(t0)), assertExp2, assertExp2New, dAInfo.withDependencyType(DependencyType.Explicit)) + v1.decider.assume(Less(t1, SeqLength(t0)), assertExp2, assertExp2New, analysisInfoes.withDependencyType(DependencyType.Explicit)) failure combine Q(s1, SeqAt(t0, t1), eNew, v1) } else failure} case false => val assertExp1 = Option.when(withExp)(ast.GeCmp(e1, ast.IntLit(0)())(e1.pos, e1.info, e1.errT)) val assertExp1New = Option.when(withExp)(ast.GeCmp(esNew.get(1), ast.IntLit(0)())(e1.pos, e1.info, e1.errT)) val failure1 = createFailure(pve dueTo SeqIndexNegative(e0, e1), v1, s1, AtLeast(t1, IntLiteral(0)), assertExp1New) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(AtLeast(t1, IntLiteral(0)), dAInfo, assumeFailedAssertion=false) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(AtLeast(t1, IntLiteral(0)), analysisInfoes, assumeFailedAssertion=false) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { - v1.decider.assume(AtLeast(t1, IntLiteral(0)), assertExp1, assertExp1New, dAInfo.withDependencyType(DependencyType.Explicit)) + v1.decider.assume(AtLeast(t1, IntLiteral(0)), assertExp1, assertExp1New, analysisInfoes.withDependencyType(DependencyType.Explicit)) val assertExp2 = Option.when(withExp)(ast.LtCmp(e1, ast.SeqLength(e0)())(e1.pos, e1.info, e1.errT)) val assertExp2New = Option.when(withExp)(ast.LtCmp(esNew.get(1), ast.SeqLength(esNew.get(0))())(e1.pos, e1.info, e1.errT)) - v1.decider.assert(Less(t1, SeqLength(t0)), dAInfo) { + v1.decider.assert(Less(t1, SeqLength(t0)), analysisInfoes) { case true => failure1 combine Q(s1, SeqAt(t0, t1), eNew, v1) case false => val failure2 = failure1 combine createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, Less(t1, SeqLength(t0)), assertExp2New) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(Less(t1, SeqLength(t0)), dAInfo, assumeFailedAssertion=false) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(Less(t1, SeqLength(t0)), analysisInfoes, assumeFailedAssertion=false) if (v1.reportFurtherErrors()) { - v1.decider.assume(Less(t1, SeqLength(t0)), assertExp2, assertExp2New, dAInfo.withDependencyType(DependencyType.Explicit)) + v1.decider.assume(Less(t1, SeqLength(t0)), assertExp2, assertExp2New, analysisInfoes.withDependencyType(DependencyType.Explicit)) failure2 combine Q(s1, SeqAt(t0, t1), eNew, v1) } else failure2} } else failure1}}}) - case ast.SeqAppend(e0, e1) => evalBinOp(s, e0, e1, SeqAppend, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => + case ast.SeqAppend(e0, e1) => evalBinOp(s, e0, e1, SeqAppend, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.SeqAppend(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) - case ast.SeqDrop(e0, e1) => evalBinOp(s, e0, e1, SeqDrop, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => + case ast.SeqDrop(e0, e1) => evalBinOp(s, e0, e1, SeqDrop, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.SeqDrop(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) - case ast.SeqTake(e0, e1) => evalBinOp(s, e0, e1, SeqTake, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => + case ast.SeqTake(e0, e1) => evalBinOp(s, e0, e1, SeqTake, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.SeqTake(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) - case ast.SeqLength(e0) => eval(s, e0, pve, v, dAInfo)((s1, t0, e0New, v1) => + case ast.SeqLength(e0) => eval(s, e0, pve, v, analysisInfoes)((s1, t0, e0New, v1) => Q(s1, SeqLength(t0), e0New.map(e0p => ast.SeqLength(e0p)(e.pos, e.info, e.errT)), v1)) case ast.EmptySeq(typ) => Q(s, SeqNil(v.symbolConverter.toSort(typ)), Option.when(withExp)(e), v) - case ast.RangeSeq(e0, e1) => evalBinOp(s, e0, e1, SeqRanged, pve, v, dAInfo)((s1, t, e0New, e1New, v1) => + case ast.RangeSeq(e0, e1) => evalBinOp(s, e0, e1, SeqRanged, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.RangeSeq(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case ast.SeqUpdate(e0, e1, e2) => - evals2(s, Seq(e0, e1, e2), Nil, _ => pve, v, dAInfo)({ case (s1, Seq(t0, t1, t2), esNew, v1) => + evals2(s, Seq(e0, e1, e2), Nil, _ => pve, v, analysisInfoes)({ case (s1, Seq(t0, t1, t2), esNew, v1) => val eNew = esNew.map(es => ast.SeqUpdate(es.head, es(1), es(2))(e.pos, e.info, e.errT)) if (s1.triggerExp) { Q(s1, SeqUpdate(t0, t1, t2), eNew, v1) } else { val assertExp = Option.when(withExp)(ast.GeCmp(e1, ast.IntLit(0)())(e1.pos, e1.info, e1.errT)) val assertExpNew = Option.when(withExp)(ast.GeCmp(esNew.get(1), ast.IntLit(0)())(e1.pos, e1.info, e1.errT)) - v1.decider.assert(AtLeast(t1, IntLiteral(0)), dAInfo) { + v1.decider.assert(AtLeast(t1, IntLiteral(0)), analysisInfoes) { case true => val assertExp2New = Option.when(withExp)(ast.LtCmp(esNew.get(1), ast.SeqLength(esNew.get(0))())(e1.pos, e1.info, e1.errT)) - v1.decider.assert(Less(t1, SeqLength(t0)), dAInfo) { + v1.decider.assert(Less(t1, SeqLength(t0)), analysisInfoes) { case true => Q(s1, SeqUpdate(t0, t1, t2), eNew, v1) case false => val failure = createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, Less(t1, SeqLength(t0)), assertExp2New) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(Less(t1, SeqLength(t0)), dAInfo, assumeFailedAssertion=false) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(Less(t1, SeqLength(t0)), analysisInfoes, assumeFailedAssertion=false) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { val assertExp3 = Option.when(withExp)(ast.LeCmp(e1, ast.SeqLength(e0)())()) val assertExp3New = Option.when(withExp)(ast.LeCmp(esNew.get(1), ast.SeqLength(esNew.get(0))())()) - v1.decider.assume(Less(t1, SeqLength(t0)), assertExp3, assertExp3New, dAInfo.withDependencyType(DependencyType.Explicit)) + v1.decider.assume(Less(t1, SeqLength(t0)), assertExp3, assertExp3New, analysisInfoes.withDependencyType(DependencyType.Explicit)) failure combine Q(s1, SeqUpdate(t0, t1, t2), eNew, v1)} else failure} case false => val failure1 = createFailure(pve dueTo SeqIndexNegative(e0, e1), v1, s1, AtLeast(t1, IntLiteral(0)), assertExpNew) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(AtLeast(t1, IntLiteral(0)), dAInfo, assumeFailedAssertion=false) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(AtLeast(t1, IntLiteral(0)), analysisInfoes, assumeFailedAssertion=false) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { - v1.decider.assume(AtLeast(t1, IntLiteral(0)), assertExp, assertExpNew, dAInfo.withDependencyType(DependencyType.Explicit)) + v1.decider.assume(AtLeast(t1, IntLiteral(0)), assertExp, assertExpNew, analysisInfoes.withDependencyType(DependencyType.Explicit)) val assertExp2 = Option.when(withExp)(ast.LtCmp(e1, ast.SeqLength(e0)())(e1.pos, e1.info, e1.errT)) val assertExp2New = Option.when(withExp)(ast.LtCmp(esNew.get(1), ast.SeqLength(esNew.get(0))())(e1.pos, e1.info, e1.errT)) - v1.decider.assert(Less(t1, SeqLength(t0)), dAInfo) { + v1.decider.assert(Less(t1, SeqLength(t0)), analysisInfoes) { case true => failure1 combine Q(s1, SeqUpdate(t0, t1, t2), eNew, v1) case false => val failure2 = failure1 combine createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, Less(t1, SeqLength(t0)), assertExp2New) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(Less(t1, SeqLength(t0)), dAInfo, assumeFailedAssertion=false) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(Less(t1, SeqLength(t0)), analysisInfoes, assumeFailedAssertion=false) if (v1.reportFurtherErrors()) { - v1.decider.assume(Less(t1, SeqLength(t0)), assertExp2, assertExp2New, dAInfo.withDependencyType(DependencyType.Explicit)) + v1.decider.assume(Less(t1, SeqLength(t0)), assertExp2, assertExp2New, analysisInfoes.withDependencyType(DependencyType.Explicit)) failure2 combine Q(s1, SeqUpdate(t0, t1, t2), eNew, v1) } else failure2} } else failure1}}}) case seq@ast.ExplicitSeq(es) => - evals2(s, es, Nil, _ => pve, v, dAInfo)((s1, tEs, esNew, v1) => { + evals2(s, es, Nil, _ => pve, v, analysisInfoes)((s1, tEs, esNew, v1) => { val tSeq = tEs.tail.foldLeft[SeqTerm](SeqSingleton(tEs.head))((tSeq, te) => SeqAppend(tSeq, SeqSingleton(te))) @@ -930,7 +929,7 @@ object evaluator extends EvaluationRules { val exp = ast.EqCmp(ast.SeqLength(seq)(), ast.IntLit(es.size)())(seq.pos, seq.info, seq.errT) DebugExp.createInstance(exp, expNew) }) - v1.decider.assume(SeqLength(tSeq) === IntLiteral(es.size), debugExp, dAInfo) + v1.decider.assume(SeqLength(tSeq) === IntLiteral(es.size), debugExp, analysisInfoes) Q(s1, tSeq, esNew.map(en => ast.ExplicitSeq(en)(e.pos, e.info, e.errT)), v1)}) /* Sets and multisets */ @@ -941,68 +940,68 @@ object evaluator extends EvaluationRules { Q(s, EmptyMultiset(v.symbolConverter.toSort(typ)), Option.when(withExp)(e), v) case ast.ExplicitSet(es) => - evals2(s, es, Nil, _ => pve, v, dAInfo)((s1, tEs, esNew, v1) => { + evals2(s, es, Nil, _ => pve, v, analysisInfoes)((s1, tEs, esNew, v1) => { val tSet = tEs.tail.foldLeft[SetTerm](SingletonSet(tEs.head))((tSet, te) => SetAdd(tSet, te)) Q(s1, tSet, esNew.map(es => ast.ExplicitSet(es)(e.pos, e.info, e.errT)), v1)}) case ast.ExplicitMultiset(es) => - evals2(s, es, Nil, _ => pve, v, dAInfo)((s1, tEs, esNew, v1) => { + evals2(s, es, Nil, _ => pve, v, analysisInfoes)((s1, tEs, esNew, v1) => { val tMultiset = tEs.tail.foldLeft[MultisetTerm](SingletonMultiset(tEs.head))((tMultiset, te) => MultisetAdd(tMultiset, te)) Q(s1, tMultiset, esNew.map(es => ast.ExplicitMultiset(es)(e.pos, e.info, e.errT)), v1)}) case ast.AnySetUnion(e0, e1) => e.typ match { - case _: ast.SetType => evalBinOp(s, e0, e1, SetUnion, pve, v, dAInfo)((s1, t, e0New, e1New, v1) + case _: ast.SetType => evalBinOp(s, e0, e1, SetUnion, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.AnySetUnion(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) - case _: ast.MultisetType => evalBinOp(s, e0, e1, MultisetUnion, pve, v, dAInfo)((s1, t, e0New, e1New, v1) + case _: ast.MultisetType => evalBinOp(s, e0, e1, MultisetUnion, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.AnySetUnion(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case _ => sys.error("Expected a (multi)set-typed expression but found %s (%s) of sort %s" .format(e, e.getClass.getName, e.typ)) } case ast.AnySetIntersection(e0, e1) => e.typ match { - case _: ast.SetType => evalBinOp(s, e0, e1, SetIntersection, pve, v, dAInfo)((s1, t, e0New, e1New, v1) + case _: ast.SetType => evalBinOp(s, e0, e1, SetIntersection, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.AnySetIntersection(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) - case _: ast.MultisetType => evalBinOp(s, e0, e1, MultisetIntersection, pve, v, dAInfo)((s1, t, e0New, e1New, v1) + case _: ast.MultisetType => evalBinOp(s, e0, e1, MultisetIntersection, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.AnySetIntersection(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case _ => sys.error("Expected a (multi)set-typed expression but found %s (%s) of sort %s" .format(e, e.getClass.getName, e.typ)) } case ast.AnySetSubset(e0, e1) => e0.typ match { - case _: ast.SetType => evalBinOp(s, e0, e1, SetSubset, pve, v, dAInfo)((s1, t, e0New, e1New, v1) + case _: ast.SetType => evalBinOp(s, e0, e1, SetSubset, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.AnySetSubset(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) - case _: ast.MultisetType => evalBinOp(s, e0, e1, MultisetSubset, pve, v, dAInfo)((s1, t, e0New, e1New, v1) + case _: ast.MultisetType => evalBinOp(s, e0, e1, MultisetSubset, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.AnySetSubset(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case _ => sys.error("Expected a (multi)set-typed expression but found %s (%s) of sort %s" .format(e, e.getClass.getName, e.typ)) } case ast.AnySetMinus(e0, e1) => e.typ match { - case _: ast.SetType => evalBinOp(s, e0, e1, SetDifference, pve, v, dAInfo)((s1, t, e0New, e1New, v1) + case _: ast.SetType => evalBinOp(s, e0, e1, SetDifference, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.AnySetMinus(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) - case _: ast.MultisetType => evalBinOp(s, e0, e1, MultisetDifference, pve, v, dAInfo)((s1, t, e0New, e1New, v1) + case _: ast.MultisetType => evalBinOp(s, e0, e1, MultisetDifference, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.AnySetMinus(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case _ => sys.error("Expected a (multi)set-typed expression but found %s (%s) of sort %s" .format(e, e.getClass.getName, e.typ)) } case ast.AnySetContains(e0, e1) => e1.typ match { - case _: ast.SetType => evalBinOp(s, e0, e1, SetIn, pve, v, dAInfo)((s1, t, e0New, e1New, v1) + case _: ast.SetType => evalBinOp(s, e0, e1, SetIn, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.AnySetContains(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) - case _: ast.MultisetType => evalBinOp(s, e0, e1, (t0, t1) => MultisetCount(t1, t0), pve, v, dAInfo)((s1, t, e0New, e1New, v1) + case _: ast.MultisetType => evalBinOp(s, e0, e1, (t0, t1) => MultisetCount(t1, t0), pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.AnySetContains(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case _ => sys.error("Expected a (multi)set-typed expression but found %s (%s) of sort %s" .format(e, e.getClass.getName, e.typ)) } case ast.AnySetCardinality(e0) => e0.typ match { - case _: ast.SetType => eval(s, e0, pve, v, dAInfo)((s1, t0, e0New, v1) + case _: ast.SetType => eval(s, e0, pve, v, analysisInfoes)((s1, t0, e0New, v1) => Q(s1, SetCardinality(t0), e0New.map(e0p => ast.AnySetCardinality(e0p)(e.pos, e.info, e.errT)), v1)) - case _: ast.MultisetType => eval(s, e0, pve, v, dAInfo)((s1, t0, e0New, v1) + case _: ast.MultisetType => eval(s, e0, pve, v, analysisInfoes)((s1, t0, e0New, v1) => Q(s1, MultisetCardinality(t0), e0New.map(e0p => ast.AnySetCardinality(e0p)(e.pos, e.info, e.errT)), v1)) case _ => sys.error("Expected a (multi)set-typed expression but found %s (%s) of type %s" .format(e0, e0.getClass.getName, e0.typ)) @@ -1013,29 +1012,29 @@ object evaluator extends EvaluationRules { case ast.EmptyMap(keyType, valueType) => Q(s, EmptyMap(v.symbolConverter.toSort(keyType), v.symbolConverter.toSort(valueType)), Option.when(withExp)(e), v) case em: ast.ExplicitMap => - eval(s, em.desugared, pve, v, dAInfo)((s1, t0, _, v1) => Q(s1, t0, Option.when(withExp)(em), v1)) + eval(s, em.desugared, pve, v, analysisInfoes)((s1, t0, _, v1) => Q(s1, t0, Option.when(withExp)(em), v1)) case ast.MapCardinality(base) => - eval(s, base, pve, v, dAInfo)((s1, t0, baseNew, v1) => Q(s1, MapCardinality(t0), baseNew.map(ast.MapCardinality(_)(e.pos, e.info, e.errT)), v1)) + eval(s, base, pve, v, analysisInfoes)((s1, t0, baseNew, v1) => Q(s1, MapCardinality(t0), baseNew.map(ast.MapCardinality(_)(e.pos, e.info, e.errT)), v1)) case ast.MapDomain(base) => - eval(s, base, pve, v, dAInfo)((s1, t0, baseNew, v1) => Q(s1, MapDomain(t0), baseNew.map(ast.MapDomain(_)(e.pos, e.info, e.errT)), v1)) + eval(s, base, pve, v, analysisInfoes)((s1, t0, baseNew, v1) => Q(s1, MapDomain(t0), baseNew.map(ast.MapDomain(_)(e.pos, e.info, e.errT)), v1)) case ast.MapRange(base) => - eval(s, base, pve, v, dAInfo)((s1, t0, baseNew, v1) => Q(s1, MapRange(t0), baseNew.map(ast.MapRange(_)(e.pos, e.info, e.errT)), v1)) + eval(s, base, pve, v, analysisInfoes)((s1, t0, baseNew, v1) => Q(s1, MapRange(t0), baseNew.map(ast.MapRange(_)(e.pos, e.info, e.errT)), v1)) case ml@ast.MapLookup(base, key) => - evals2(s, Seq(base, key), Nil, _ => pve, v, dAInfo)({ + evals2(s, Seq(base, key), Nil, _ => pve, v, analysisInfoes)({ case (s1, Seq(baseT, keyT), esNew, v1) if s1.triggerExp => Q(s1, MapLookup(baseT, keyT), esNew.map(es => ast.MapLookup(es(0), es(1))(e.pos, e.info, e.errT)), v1) case (s1, Seq(baseT, keyT), esNew, v1) => val eNew = esNew.map(es => ast.MapLookup(es(0), es(1))(e.pos, e.info, e.errT)) - v1.decider.assert(SetIn(keyT, MapDomain(baseT)), dAInfo) { + v1.decider.assert(SetIn(keyT, MapDomain(baseT)), analysisInfoes) { case true => Q(s1, MapLookup(baseT, keyT), eNew, v1) case false => val assertExp = Option.when(withExp)(ast.MapContains(key, base)(ml.pos, ml.info, ml.errT)) val assertExpNew = Option.when(withExp)(ast.MapContains(esNew.get(1), esNew.get(0))(ml.pos, ml.info, ml.errT)) val failure1 = createFailure(pve dueTo MapKeyNotContained(base, key), v1, s1, SetIn(keyT, MapDomain(baseT)), assertExpNew) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(SetIn(keyT, MapDomain(baseT)), dAInfo, assumeFailedAssertion=false) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(SetIn(keyT, MapDomain(baseT)), analysisInfoes, assumeFailedAssertion=false) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { - v1.decider.assume(SetIn(keyT, MapDomain(baseT)), assertExp, assertExpNew, dAInfo.withDependencyType(DependencyType.Explicit)) + v1.decider.assume(SetIn(keyT, MapDomain(baseT)), assertExp, assertExpNew, analysisInfoes.withDependencyType(DependencyType.Explicit)) failure1 combine Q(s1, MapLookup(baseT, keyT), eNew, v1) } else { failure1 @@ -1044,13 +1043,13 @@ object evaluator extends EvaluationRules { }) case ast.MapUpdate(base, key, value) => - evals2(s, Seq(base, key, value), Nil, _ => pve, v, dAInfo)({ + evals2(s, Seq(base, key, value), Nil, _ => pve, v, analysisInfoes)({ case (s1, Seq(baseT, keyT, valueT), esNew, v1) => Q(s1, MapUpdate(baseT, keyT, valueT), esNew.map(es => ast.MapUpdate(es(0), es(1), es(2))(e.pos, e.info, e.errT)), v1) }) case ast.MapContains(key, base) => - evals2(s, Seq(key, base), Nil, _ => pve, v, dAInfo)({ + evals2(s, Seq(key, base), Nil, _ => pve, v, analysisInfoes)({ case (s1, Seq(keyT, baseT), esNew, v1) => Q(s1, SetIn(keyT, MapDomain(baseT)), esNew.map(es => ast.MapContains(es(0), es(1))(e.pos, e.info, e.errT)), v1) }) @@ -1081,7 +1080,7 @@ object evaluator extends EvaluationRules { name: String, pve: PartialVerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Seq[Var], /* Variables from vars */ Option[Seq[ast.LocalVarDecl]], @@ -1109,18 +1108,18 @@ object evaluator extends EvaluationRules { type R = (State, Seq[Term], Option[Seq[ast.Exp]], Option[(Seq[Term], Option[Seq[ast.Exp]], Seq[Trigger], (Seq[Term], Seq[Quantification]), Option[(InsertionOrderedSet[DebugExp], InsertionOrderedSet[DebugExp])], Map[ast.Exp, Term])]) executionFlowController.locallyWithResult[R](s1, v)((s2, v1, QB) => { val preMark = v1.decider.setPathConditionMark() - evals(s2, es1, _ => pve, v1, dAInfo)((s3, ts1, es1New, v2) => { + evals(s2, es1, _ => pve, v1, analysisInfoes)((s3, ts1, es1New, v2) => { val bc = And(ts1) // ME: If bc is unsatisfiable, we are assuming false here. In that case, evaluating es2 and the triggers // may not return any value (e.g. if es2 contains a field read for which we don't have permission, a smoke // check succeeds, then the continuation for evals(es2) is never invoked). This caused issue #842. // In this case, we return None. val expPair = (viper.silicon.utils.ast.BigAnd(es1), es1New.map(viper.silicon.utils.ast.BigAnd(_))) - v2.decider.setCurrentBranchCondition(bc, expPair, dAInfo) + v2.decider.setCurrentBranchCondition(bc, expPair, analysisInfoes) var es2AndTriggerTerms: Option[(Seq[Term], Option[Seq[ast.Exp]], Seq[Trigger], (Seq[Term], Seq[Quantification]), Option[(InsertionOrderedSet[DebugExp], InsertionOrderedSet[DebugExp])], Map[ast.Exp, Term])] = None var finalState = s3 - val es2AndTriggerResult = evals(s3, es2, _ => pve, v2, dAInfo)((s4, ts2, es2New, v3) => { - evalTriggers(s4, optTriggers.getOrElse(Nil), pve, v3, dAInfo)((s5, tTriggers, _) => { // TODO: v4 isn't forward - problem? + val es2AndTriggerResult = evals(s3, es2, _ => pve, v2, analysisInfoes)((s4, ts2, es2New, v3) => { + evalTriggers(s4, optTriggers.getOrElse(Nil), pve, v3, analysisInfoes)((s5, tTriggers, _) => { // TODO: v4 isn't forward - problem? val (auxGlobals, auxNonGlobalQuants) = v3.decider.pcs.after(preMark).quantified(quant, tVars, tTriggers, s"$name-aux", isGlobal = false, bc) val auxExps = @@ -1150,13 +1149,13 @@ object evaluator extends EvaluationRules { fromShortCircuitingAnd: Boolean, pve: PartialVerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Term, Option[ast.Exp], Verifier) => VerificationResult) : VerificationResult = { - joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s, v, dAInfo)((s1, v1, QB) => - brancher.branch(s1.copy(parallelizeBranches = false), tLhs, eLhs, v1, dAInfo.withDependencyType(DependencyType.Internal), fromShortCircuitingAnd = fromShortCircuitingAnd)( - (s2, v2) => eval(s2.copy(parallelizeBranches = s1.parallelizeBranches), eRhs, pve, v2, dAInfo)((s2, tRhs, eRhsNew, v2) => QB(s2, (tRhs, eRhsNew), v2)), + joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s, v, analysisInfoes)((s1, v1, QB) => + brancher.branch(s1.copy(parallelizeBranches = false), tLhs, eLhs, v1, analysisInfoes.withDependencyType(DependencyType.Internal), fromShortCircuitingAnd = fromShortCircuitingAnd)( + (s2, v2) => eval(s2.copy(parallelizeBranches = s1.parallelizeBranches), eRhs, pve, v2, analysisInfoes)((s2, tRhs, eRhsNew, v2) => QB(s2, (tRhs, eRhsNew), v2)), (s2, v2) => QB(s2.copy(parallelizeBranches = s1.parallelizeBranches), (True, Option.when(withExp)(ast.TrueLit()())), v2)) )(entries => { assert(entries.length <= 2) @@ -1175,7 +1174,7 @@ object evaluator extends EvaluationRules { e: ast.Exp, pve: PartialVerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Term, Option[ast.Exp], Verifier) => VerificationResult) : VerificationResult = { @@ -1184,7 +1183,7 @@ object evaluator extends EvaluationRules { val s2 = v.stateConsolidator(s1).consolidateOptionally(s1, v) val possibleTriggersBefore: Map[ast.Exp, Term] = if (s.recordPossibleTriggers) s.possibleTriggers else Map.empty - eval(s2, e, pve, v, dAInfo)((s3, t, eNew, v1) => { + eval(s2, e, pve, v, analysisInfoes)((s3, t, eNew, v1) => { val newPossibleTriggers = if (s.recordPossibleTriggers) { // For all new possible trigger expressions e and translated term t, // make sure we remember t as the term for old[label](e) instead. @@ -1214,10 +1213,10 @@ object evaluator extends EvaluationRules { Q(s4, t, eNew, v1)}) } - def evalResourceAccess(s: State, resacc: ast.ResourceAccess, pve: PartialVerificationError, v: Verifier, dAInfo: DependencyAnalysisInfo) + def evalResourceAccess(s: State, resacc: ast.ResourceAccess, pve: PartialVerificationError, v: Verifier, analysisInfoes: DependencyAnalysisInfoes) (Q: (State, ChunkIdentifer, Seq[Term], Option[Seq[ast.Exp]], Verifier) => VerificationResult) : VerificationResult = { - evals(s, resacc.args(s.program), _ => pve, v, dAInfo)((s1, tArgs, eArgsNew, v1) => + evals(s, resacc.args(s.program), _ => pve, v, analysisInfoes)((s1, tArgs, eArgsNew, v1) => Q(s1, ChunkIdentifier(resacc.res(s1.program), s1.program), tArgs, eArgsNew, v1)) } @@ -1227,11 +1226,11 @@ object evaluator extends EvaluationRules { termOp: ((Term, Term)) => T, pve: PartialVerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, T, Option[ast.Exp], Option[ast.Exp], Verifier) => VerificationResult) : VerificationResult = { - evalBinOp(s, e0, e1, (t0, t1) => termOp((t0, t1)), pve, v, dAInfo)(Q) + evalBinOp(s, e0, e1, (t0, t1) => termOp((t0, t1)), pve, v, analysisInfoes)(Q) } private def evalBinOp[T <: Term] @@ -1241,12 +1240,12 @@ object evaluator extends EvaluationRules { termOp: (Term, Term) => T, pve: PartialVerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, T, Option[ast.Exp], Option[ast.Exp], Verifier) => VerificationResult) : VerificationResult = { - eval(s, e0, pve, v, dAInfo)((s1, t0, e0New, v1) => - eval(s1, e1, pve, v1, dAInfo)((s2, t1, e1New, v2) => + eval(s, e0, pve, v, analysisInfoes)((s1, t0, e0New, v1) => + eval(s1, e1, pve, v1, analysisInfoes)((s2, t1, e1New, v2) => Q(s2, termOp(t0, t1), e0New, e1New, v2))) } @@ -1258,20 +1257,20 @@ object evaluator extends EvaluationRules { tZero: Term, pve: PartialVerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Term, Verifier) => VerificationResult) : VerificationResult = { - v.decider.assert(tDivisor !== tZero, dAInfo){ + v.decider.assert(tDivisor !== tZero, analysisInfoes){ case true => Q(s, t, v) case false => val (notZeroExp, notZeroExpNew) = if (withExp) { (Some(ast.NeCmp(eDivisor, ast.IntLit(0)())(eDivisor.pos, eDivisor.info, eDivisor.errT)), Some(ast.NeCmp(eDivisorNew.get, ast.IntLit(0)())(eDivisor.pos, eDivisor.info, eDivisor.errT))) } else { (None, None) } val failure = createFailure(pve dueTo DivisionByZero(eDivisor), v, s, tDivisor !== tZero, notZeroExpNew) - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(tDivisor !== tZero, dAInfo, assumeFailedAssertion=false) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(tDivisor !== tZero, analysisInfoes, assumeFailedAssertion=false) if (s.retryLevel == 0 && v.reportFurtherErrors()) { - v.decider.assume(tDivisor !== tZero, notZeroExp, notZeroExpNew, dAInfo.withDependencyType(DependencyType.Explicit)) + v.decider.assume(tDivisor !== tZero, notZeroExp, notZeroExpNew, analysisInfoes.withDependencyType(DependencyType.Explicit)) failure combine Q(s, t, v) } else failure } @@ -1281,11 +1280,11 @@ object evaluator extends EvaluationRules { silverTriggers: Seq[ast.Trigger], pve: PartialVerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Seq[Trigger], Verifier) => VerificationResult) : VerificationResult = { - evalTriggers(s, silverTriggers map (_.exps), Nil, pve, v, dAInfo)((s1, tTriggersSets, v1) => { + evalTriggers(s, silverTriggers map (_.exps), Nil, pve, v, analysisInfoes)((s1, tTriggersSets, v1) => { /* [2015-12-15 Malte] * Evaluating triggers that did not occur in the body (and whose corresponding term has * therefore not already been recorded in the context) might introduce new path conditions, @@ -1314,7 +1313,7 @@ object evaluator extends EvaluationRules { tTriggersSets: TriggerSets[Term], pve: PartialVerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, TriggerSets[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -1322,15 +1321,15 @@ object evaluator extends EvaluationRules { Q(s, tTriggersSets, v) else { if (eTriggerSets.head.collect{case fa: ast.FieldAccess => fa; case pa: ast.PredicateAccess => pa; case wand: ast.MagicWand => wand }.nonEmpty ) { - evalHeapTrigger(s, eTriggerSets.head, pve, v, dAInfo)((s1, ts, v1) => - evalTriggers(s1, eTriggerSets.tail, tTriggersSets :+ ts, pve, v1, dAInfo)(Q)) + evalHeapTrigger(s, eTriggerSets.head, pve, v, analysisInfoes)((s1, ts, v1) => + evalTriggers(s1, eTriggerSets.tail, tTriggersSets :+ ts, pve, v1, analysisInfoes)(Q)) } else { - evalTrigger(s, eTriggerSets.head, pve, v, dAInfo)((s1, ts, v1) => - evalTriggers(s1, eTriggerSets.tail, tTriggersSets :+ ts, pve, v1, dAInfo)(Q)) + evalTrigger(s, eTriggerSets.head, pve, v, analysisInfoes)((s1, ts, v1) => + evalTriggers(s1, eTriggerSets.tail, tTriggersSets :+ ts, pve, v1, analysisInfoes)(Q)) }} } - private def evalTrigger(s: State, exps: Seq[ast.Exp], pve: PartialVerificationError, v: Verifier, dAInfo: DependencyAnalysisInfo) + private def evalTrigger(s: State, exps: Seq[ast.Exp], pve: PartialVerificationError, v: Verifier, analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Seq[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -1402,7 +1401,7 @@ object evaluator extends EvaluationRules { */ val r = - evals(s.copy(triggerExp = true), remainingTriggerExpressions, _ => pve, v, dAInfo)((_, remainingTriggerTerms, _, v1) => { + evals(s.copy(triggerExp = true), remainingTriggerExpressions, _ => pve, v, analysisInfoes)((_, remainingTriggerTerms, _, v1) => { optRemainingTriggerTerms = Some(remainingTriggerTerms) pcDelta = v1.decider.pcs.after(preMark).assumptions //decider.π -- πPre pcDeltaExp = v1.decider.pcs.after(preMark).assumptionExps @@ -1418,7 +1417,7 @@ object evaluator extends EvaluationRules { (r, optRemainingTriggerTerms) match { case (Success(), Some(remainingTriggerTerms)) => // TODO ake: wrap pcDelta with labels? - v.decider.assume(pcDelta, Option.when(withExp)(DebugExp.createInstance("pcDeltaExp", children = pcDeltaExp)), enforceAssumption = false, dAInfo.withDependencyType(DependencyType.Internal)) + v.decider.assume(pcDelta, Option.when(withExp)(DebugExp.createInstance("pcDeltaExp", children = pcDeltaExp)), enforceAssumption = false, analysisInfoes.withDependencyType(DependencyType.Internal)) Q(s, cachedTriggerTerms ++ remainingTriggerTerms, v) case _ => for (e <- remainingTriggerExpressions) @@ -1433,7 +1432,7 @@ object evaluator extends EvaluationRules { joinFunctionArgs: Seq[Term], joinFunctionArgsExp: Option[Seq[ast.Exp]], v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (entries: Seq[JoinDataEntry[(Term, Option[ast.Exp])]]) : (State, (Term, Option[ast.Exp])) = { @@ -1468,13 +1467,13 @@ object evaluator extends EvaluationRules { var sJoined = entries.tail.foldLeft(entries.head.s)((sAcc, entry) => sAcc.merge(entry.s)) sJoined = sJoined.copy(functionRecorder = sJoined.functionRecorder.recordPathSymbol(joinSymbol)) - joinDefEqs foreach { case (t, exp, expNew) => v.decider.assume(t, exp, expNew, dAInfo.withDependencyType(DependencyType.Internal))} + joinDefEqs foreach { case (t, exp, expNew) => v.decider.assume(t, exp, expNew, analysisInfoes.withDependencyType(DependencyType.Internal))} (sJoined, (joinTerm, joinExp)) } } - def evalHeapTrigger(s: State, exps: Seq[ast.Exp], pve: PartialVerificationError, v: Verifier, dAInfo: DependencyAnalysisInfo) + def evalHeapTrigger(s: State, exps: Seq[ast.Exp], pve: PartialVerificationError, v: Verifier, analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Seq[Term], Verifier) => VerificationResult) : VerificationResult = { var triggers: Seq[Term] = Seq() var triggerAxioms: Seq[Term] = Seq() @@ -1482,18 +1481,18 @@ object evaluator extends EvaluationRules { exps foreach { case ra: ast.ResourceAccess if s.isUsedAsTrigger(ra.res(s.program)) => - val (axioms, trigs, _, smDef) = generateResourceTrigger(ra, s, pve, v, dAInfo) + val (axioms, trigs, _, smDef) = generateResourceTrigger(ra, s, pve, v, analysisInfoes) triggers = triggers ++ trigs triggerAxioms = triggerAxioms ++ axioms smDefs = smDefs ++ smDef - case e => evalTrigger(s.copy(triggerExp = true), Seq(e), pve, v, dAInfo)((_, t, _) => { + case e => evalTrigger(s.copy(triggerExp = true), Seq(e), pve, v, analysisInfoes)((_, t, _) => { triggers = triggers ++ t Success() }) } val triggerString = exps.mkString(", ") - v.decider.assume(triggerAxioms, Option.when(withExp)(DebugExp.createInstance(s"Heap Triggers ($triggerString)")), enforceAssumption = false, dAInfo.withDependencyType(DependencyType.Trigger)) + v.decider.assume(triggerAxioms, Option.when(withExp)(DebugExp.createInstance(s"Heap Triggers ($triggerString)")), enforceAssumption = false, analysisInfoes.withDependencyType(DependencyType.Trigger)) var fr = s.functionRecorder for (smDef <- smDefs){ fr = fr.recordFvfAndDomain(smDef) @@ -1505,7 +1504,7 @@ object evaluator extends EvaluationRules { s: State, pve: PartialVerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) : (Seq[Term], Seq[Term], Term, Seq[SnapshotMapDefinition]) = { var axioms = Seq.empty[Term] var triggers = Seq.empty[Term] @@ -1526,7 +1525,7 @@ object evaluator extends EvaluationRules { s, resource, codomainQVars, relevantChunks, v, optSmDomainDefinitionCondition) val s1 = s.copy(smCache = smCache1) - evals(s1.copy(triggerExp = true), eArgs, _ => pve, v, dAInfo)((_, tArgs, _, _) => { + evals(s1.copy(triggerExp = true), eArgs, _ => pve, v, analysisInfoes)((_, tArgs, _, _) => { axioms = axioms ++ smDef1.valueDefinitions mostRecentTrig = ResourceTriggerFunction(resource, smDef1.sm, tArgs, s.program) triggers = triggers :+ mostRecentTrig @@ -1545,7 +1544,7 @@ object evaluator extends EvaluationRules { exps: Seq[ast.Exp], pve: PartialVerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Term, Option[ast.Exp], Verifier) => VerificationResult) : VerificationResult = { assert( @@ -1556,16 +1555,16 @@ object evaluator extends EvaluationRules { val stop = if (constructor == Or) True else False - eval(s, exps.head, pve, v, dAInfo)((s1, t0, e0New, v1) => { + eval(s, exps.head, pve, v, analysisInfoes)((s1, t0, e0New, v1) => { t0 match { case _ if exps.tail.isEmpty => Q(s1, t0, e0New, v1) // Done, if no expressions left (necessary) case `stop` => Q(s1, t0, e0New, v1) // Done, if last expression was true/false for or/and (optimisation) case _ => val expPair = if (constructor == Or) (exps.head, e0New) else (ast.Not(exps.head)(), e0New.map(ast.Not(_)())) - joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s1, v1, dAInfo)((s2, v2, QB) => - brancher.branch(s2.copy(parallelizeBranches = false), if (constructor == Or) t0 else Not(t0), expPair, v2, dAInfo.withDependencyType(DependencyType.Internal), fromShortCircuitingAnd = true)( + joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s1, v1, analysisInfoes)((s2, v2, QB) => + brancher.branch(s2.copy(parallelizeBranches = false), if (constructor == Or) t0 else Not(t0), expPair, v2, analysisInfoes.withDependencyType(DependencyType.Internal), fromShortCircuitingAnd = true)( (s3, v3) => QB(s3.copy(parallelizeBranches = s2.parallelizeBranches), (t0, e0New), v3), - (s3, v3) => evalSeqShortCircuit(constructor, s3.copy(parallelizeBranches = s2.parallelizeBranches), exps.tail, pve, v3, dAInfo)((s2, t2, e2, v2) => QB(s2, (t2, e2), v2))) + (s3, v3) => evalSeqShortCircuit(constructor, s3.copy(parallelizeBranches = s2.parallelizeBranches), exps.tail, pve, v3, analysisInfoes)((s2, t2, e2, v2) => QB(s2, (t2, e2), v2))) ){case Seq(ent) => (ent.s, ent.data) case Seq(ent1, ent2) => diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index cd36ec2ce..09c0671ed 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -10,8 +10,8 @@ import viper.silicon.Config.JoinMode import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.decider.RecordedPathConditions -import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType -import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, CompositeAnalysisSourceInfo, DependencyAnalysisInfo, DependencyAnalysisInfoWithoutSource, DependencyAnalyzer, DependencyType, FullDependencyAnalysisInfo, NoDependencyAnalysisInfo, SimpleAssumptionNode} +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes +import viper.silicon.dependencyAnalysis.{AssumptionType, DependencyAnalysisInfoes, DependencyAnalyzer, DependencyType, StringAnalysisSourceInfo} import viper.silicon.interfaces._ import viper.silicon.interfaces.state.{NonQuantifiedChunk, QuantifiedChunk} import viper.silicon.logger.records.data.{CommentRecord, ConditionalEdgeRecord, ExecuteRecord, MethodCallRecord} @@ -68,12 +68,12 @@ object executor extends ExecutionRules { val condEdgeRecord = new ConditionalEdgeRecord(ce.condition, s, v.decider.pcs) val sepIdentifier = v.symbExLog.openScope(condEdgeRecord) val s1 = handleOutEdge(s, edge, v) - val dAInfo = FullDependencyAnalysisInfo.create(ce.condition) - eval(s1, ce.condition, IfFailed(ce.condition), v, dAInfo)((s2, tCond, condNew, v1) => + val analysisInfoes = DefaultDependencyAnalysisInfoes.addInfo(ce.condition.info, ce.condition) + eval(s1, ce.condition, IfFailed(ce.condition), v, analysisInfoes)((s2, tCond, condNew, v1) => /* Using branch(...) here ensures that the edge condition is recorded * as a branch condition on the pathcondition stack. */ - brancher.branch(s2.copy(parallelizeBranches = false), tCond, (ce.condition, condNew), v1, dAInfo)( + brancher.branch(s2.copy(parallelizeBranches = false), tCond, (ce.condition, condNew), v1, analysisInfoes)( (s3, v3) => exec(s3.copy(parallelizeBranches = s2.parallelizeBranches), ce.target, ce.kind, v3, joinPoint)((s4, v4) => { v4.symbExLog.closeScope(sepIdentifier) @@ -94,7 +94,8 @@ object executor extends ExecutionRules { def handleOutEdge(s: State, edge: SilverEdge, v: Verifier): State = { edge.kind match { case cfg.Kind.Out if !v.decider.isPathInfeasible() => - val (fr1, h1) = v.stateConsolidator(s).merge(s.functionRecorder, s, s.h, s.invariantContexts.head, v, dAInfo) + val analysisInfoes = DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes // TODO ake + val (fr1, h1) = v.stateConsolidator(s).merge(s.functionRecorder, s, s.h, s.invariantContexts.head, v, analysisInfoes) val s1 = s.copy(functionRecorder = fr1, h = h1, invariantContexts = s.invariantContexts.tail) s1 @@ -147,12 +148,12 @@ object executor extends ExecutionRules { case _ => false }) - val dAInfo = FullDependencyAnalysisInfo.create(cedge1.condition) + val analysisInfoes = DefaultDependencyAnalysisInfoes.addInfo(cedge1.condition.info, cedge1.condition) - eval(s, cedge1.condition, pvef(cedge1.condition), v, dAInfo)((s1, t0, condNew, v1) => + eval(s, cedge1.condition, pvef(cedge1.condition), v, analysisInfoes)((s1, t0, condNew, v1) => // The type arguments here are Null because there is no need to pass any join data. - joiner.join[scala.Null, scala.Null](s1, v1, dAInfo, resetState = false)((s2, v2, QB) => { - brancher.branch(s2, t0, (cedge1.condition, condNew), v2, dAInfo)( + joiner.join[scala.Null, scala.Null](s1, v1, analysisInfoes, resetState = false)((s2, v2, QB) => { + brancher.branch(s2, t0, (cedge1.condition, condNew), v2, analysisInfoes)( // Follow only until join point. (s3, v3) => follow(s3, edge1, v3, Some(newJoinPoint))((s, v) => QB(s, null, v)), (s3, v3) => follow(s3, edge2, v3, Some(newJoinPoint))((s, v) => QB(s, null, v)) @@ -183,9 +184,9 @@ object executor extends ExecutionRules { if Verifier.config.parallelizeBranches() && cond2 == ast.Not(cond1)() => val condEdgeRecord = new ConditionalEdgeRecord(thenEdge.condition, s, v.decider.pcs) val sepIdentifier = v.symbExLog.openScope(condEdgeRecord) - val dAInfo = FullDependencyAnalysisInfo.create(thenEdge.condition) - val res = eval(s, thenEdge.condition, IfFailed(thenEdge.condition), v, dAInfo)((s2, tCond, eCondNew, v1) => - brancher.branch(s2, tCond, (thenEdge.condition, eCondNew), v1, dAInfo)( + val analysisInfoes = DefaultDependencyAnalysisInfoes.addInfo(thenEdge.condition.info, thenEdge.condition) + val res = eval(s, thenEdge.condition, IfFailed(thenEdge.condition), v, analysisInfoes)((s2, tCond, eCondNew, v1) => + brancher.branch(s2, tCond, (thenEdge.condition, eCondNew), v1, analysisInfoes)( (s3, v3) => { follow(s3, thenEdge, v3, joinPoint)(Q) }, @@ -258,6 +259,10 @@ object executor extends ExecutionRules { map.updated(x, xNew)})) val sBody = s.copy(g = gBody, h = v.heapSupporter.getEmptyHeap(s.program)) + val analysisInfoesInv = DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes + val analysisInfoesLoopInternal = DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes + .withSource(StringAnalysisSourceInfo(s"Loop ${block.id}", ast.NoPosition)).withDependencyType(DependencyType.Internal) + val edges = s.methodCfg.outEdges(block) val (outEdges, otherEdges) = edges partition(_.kind == cfg.Kind.Out) val sortedEdges = otherEdges ++ outEdges @@ -270,7 +275,7 @@ object executor extends ExecutionRules { (executionFlowController.locally(sBody, v)((s0, v0) => { v0.decider.prover.comment("Loop head block: Check well-definedness of invariant") val mark = v0.decider.setPathConditionMark() - produces(s0, freshSnap, invs, ContractNotWellformed, v0, AssumptionType.LoopInvariant)((s1, v1) => { + produces(s0, freshSnap, invs, ContractNotWellformed, v0, analysisInfoesInv)((s1, v1) => { phase1data = phase1data :+ (s1, v1.decider.pcs.after(mark), v1.decider.freshFunctions /* [BRANCH-PARALLELISATION] */, @@ -279,8 +284,7 @@ object executor extends ExecutionRules { })}) combine executionFlowController.locally(s, v)((s0, v0) => { v0.decider.prover.comment("Loop head block: Establish invariant") - val dAInfoInv = DependencyAnalysisInfoWithoutSource(DependencyType.Invariant) // FIXME ake: NoInfo? - consumes(s0, invs, false, LoopInvariantNotEstablished, v0, dAInfoInv)((sLeftover, _, v1) => { + consumes(s0, invs, false, LoopInvariantNotEstablished, v0, analysisInfoesInv)((sLeftover, _, v1) => { v1.decider.prover.comment("Loop head block: Execute statements of loop head block (in invariant state)") phase1data.foldLeft(Success(): VerificationResult) { case (result, _) if !result.continueVerification => result @@ -290,9 +294,9 @@ object executor extends ExecutionRules { v2.decider.declareAndRecordAsFreshFunctions(ff1 -- v2.decider.freshFunctions) /* [BRANCH-PARALLELISATION] */ v2.decider.declareAndRecordAsFreshMacros(fm1.filter(!v2.decider.freshMacros.contains(_))) /* [BRANCH-PARALLELISATION] */ if(v2.decider.pcs.getCurrentInfeasibilityNode.isEmpty) v2.decider.pcs.setCurrentInfeasibilityNode(pcs.infeasibilityNodeId) - v2.decider.assume(pcs.assumptions map (t => v.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t))), Some(pcs.assumptionExps), "Loop invariant", enforceAssumption=false, assumptionType=AssumptionType.Internal) + v2.decider.assume(pcs.assumptions map (t => v.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t))), Some(pcs.assumptionExps), "Loop invariant", enforceAssumption=false, analysisInfoesLoopInternal) v2.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) - if (!Verifier.config.disableInfeasibilityChecks() && v2.decider.checkSmoke()) + if (!Verifier.config.disableInfeasibilityChecks() && v2.decider.checkSmoke(analysisInfoesLoopInternal)) Success() else { execs(s3, stmts, v2)((s4, v3) => { @@ -302,7 +306,7 @@ object executor extends ExecutionRules { case (result, _) if !result.continueVerification => result case (intermediateResult, eCond) => intermediateResult combine executionFlowController.locally(s4, v3)((s5, v4) => { - eval(s5, eCond, WhileFailed(eCond), v4, Some(DependencyAnalyzer.extractDependencyTypeFromInfo(eCond.info).getOrElse(DependencyType.PathCondition)))((_, _, _, _) => + eval(s5, eCond, WhileFailed(eCond), v4, DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes)((_, _, _, _) => Success()) }) } @@ -316,8 +320,8 @@ object executor extends ExecutionRules { * attempting to re-establish the invariant. */ v.decider.prover.comment("Loop head block: Re-establish invariant") - val dAInfoInv = DependencyAnalysisInfoWithoutSource(DependencyType.Invariant) // FIXME ake: NoInfo? - consumes(s, invs, false, e => LoopInvariantNotPreserved(e), v, dAInfoInv)((_, _, _) => + val analysisInfoesInv = DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes + consumes(s, invs, false, e => LoopInvariantNotPreserved(e), v, analysisInfoesInv)((_, _, _) => Success()) } } @@ -337,14 +341,14 @@ object executor extends ExecutionRules { (Q: (State, Verifier) => VerificationResult) : VerificationResult = { val sepIdentifier = v.symbExLog.openScope(new ExecuteRecord(stmt, s, v.decider.pcs)) - val dAInfo = FullDependencyAnalysisInfo.create(stmt) - exec2(s, stmt, v, dAInfo)((s1, v1) => { + val analysisInfoes = DefaultDependencyAnalysisInfoes.addInfo(stmt.info, stmt) + exec2(s, stmt, v, analysisInfoes)((s1, v1) => { v1.symbExLog.closeScope(sepIdentifier) Q(s1, v1) }) } - def exec2(state: State, stmt: ast.Stmt, v: Verifier, dAInfo: DependencyAnalysisInfo) + def exec2(state: State, stmt: ast.Stmt, v: Verifier, analysisInfoes: DependencyAnalysisInfoes) (continuation: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -354,10 +358,10 @@ object executor extends ExecutionRules { // assertionNodesForJoin.foreach(n => v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, CompositeAnalysisSourceInfo(v.decider.analysisSourceInfoStack.getFullSourceInfo.getTopLevelSource, AnalysisSourceInfo.createAnalysisSourceInfo(n)), v.decider.analysisSourceInfoStack.getDependencyType, isJoinNode=true)) if(Statements.hasProofObligations(stmt, state.program)){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, dAInfo) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfoes) } if(Statements.introducesSmtAssumptions(stmt)){ - v.decider.dependencyAnalyzer.addAssumption(True, dAInfo) + v.decider.dependencyAnalyzer.addAssumption(True, analysisInfoes) } return continuation(state, v) } @@ -392,8 +396,8 @@ object executor extends ExecutionRules { Q(s.copy(g = s.g + (x -> (t, newExp))), v) case ass @ ast.LocalVarAssign(x, rhs) => - eval(s, rhs, AssignmentFailed(ass), v, dAInfo)((s1, tRhs, rhsNew, v1) => { - val (t, e) = ssaifyRhs(tRhs, rhs, rhsNew, x.name, x.typ, v, s1, dAInfo) + eval(s, rhs, AssignmentFailed(ass), v, analysisInfoes)((s1, tRhs, rhsNew, v1) => { + val (t, e) = ssaifyRhs(tRhs, rhs, rhsNew, x.name, x.typ, v, s1, analysisInfoes) Q(s1.copy(g = s1.g + (x, (t, e))), v1)}) /* TODO: Encode assignments e1.f := e2 as @@ -405,10 +409,10 @@ object executor extends ExecutionRules { case ass @ ast.FieldAssign(ast.FieldAccess(eRcvr, field), rhs) => assert(!s.exhaleExt) val pve = AssignmentFailed(ass) - eval(s, eRcvr, pve, v, dAInfo)((s1, tRcvr, eRcvrNew, v1) => { - eval(s1, rhs, pve, v1, dAInfo)((s2, tRhs, eRhsNew, v2) => { - val (tSnap, _) = ssaifyRhs(tRhs, rhs, eRhsNew, field.name, field.typ, v2, s2, dAInfo) - v2.heapSupporter.execFieldAssign(s2, ass, tRcvr, eRcvrNew, tSnap, eRhsNew, pve, v2, dAInfo)(Q) + eval(s, eRcvr, pve, v, analysisInfoes)((s1, tRcvr, eRcvrNew, v1) => { + eval(s1, rhs, pve, v1, analysisInfoes)((s2, tRhs, eRhsNew, v2) => { + val (tSnap, _) = ssaifyRhs(tRhs, rhs, eRhsNew, field.name, field.typ, v2, s2, analysisInfoes) + v2.heapSupporter.execFieldAssign(s2, ass, tRcvr, eRcvrNew, tSnap, eRhsNew, pve, v2, analysisInfoes)(Q) }) }) @@ -417,7 +421,7 @@ object executor extends ExecutionRules { val debugExp = Option.when(withExp)(ast.NeCmp(x, ast.NullLit()())()) val debugExpSubst = Option.when(withExp)(ast.NeCmp(eRcvrNew.get, ast.NullLit()())()) val (debugHeapName, debugLabel) = v.getDebugOldLabel(s, stmt.pos) - v.decider.assume(tRcvr !== Null, debugExp, debugExpSubst, dAInfo) + v.decider.assume(tRcvr !== Null, debugExp, debugExpSubst, analysisInfoes) val eRcvr = Option.when(withExp)(Seq(x)) val p = FullPerm @@ -431,7 +435,7 @@ object executor extends ExecutionRules { val fld = flds.head val snap = v.decider.fresh(fld.name, v.symbolConverter.toSort(fld.typ), Option.when(withExp)(extractPTypeFromExp(x))) val snapExp = Option.when(withExp)(ast.DebugLabelledOld(ast.FieldAccess(eRcvrNew.get, fld)(), debugLabel)(stmt.pos, stmt.info, stmt.errT)) - v.heapSupporter.produceSingle(s, fld, Seq(tRcvr), eRcvr, snap, snapExp, p, pExp, NullPartialVerificationError, false, v, dAInfo)((s1, v1) => { + v.heapSupporter.produceSingle(s, fld, Seq(tRcvr), eRcvr, snap, snapExp, p, pExp, NullPartialVerificationError, false, v, analysisInfoes)((s1, v1) => { addFieldPerms(s1, flds.tail, v1)(QB) }) } @@ -441,7 +445,7 @@ object executor extends ExecutionRules { addFieldPerms(s, fields, v)((s0, v0) => { val s1 = s0.copy(g = s0.g + (x, (tRcvr, eRcvrNew))) val s2 = if (withExp) s1.copy(oldHeaps = s1.oldHeaps + (debugHeapName -> magicWandSupporter.getEvalHeap(s1))) else s1 - v0.decider.assume(ts, Option.when(withExp)(DebugExp.createInstance(Some("Reference Disjointness"), esNew, esNew, InsertionOrderedSet.empty)), enforceAssumption = false, dAInfo) + v0.decider.assume(ts, Option.when(withExp)(DebugExp.createInstance(Some("Reference Disjointness"), esNew, esNew, InsertionOrderedSet.empty)), enforceAssumption = false, analysisInfoes) Q(s2, v0) }) @@ -450,25 +454,25 @@ object executor extends ExecutionRules { /* We're done */ Success() case _ => - produce(s, freshSnap, a, InhaleFailed(inhale), v, dAInfo)((s1, v1) => { + produce(s, freshSnap, a, InhaleFailed(inhale), v, analysisInfoes)((s1, v1) => { v1.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterInhale) - if(v1.decider.isDependencyAnalysisEnabled && a.isInstanceOf[ast.FalseLit]) v1.decider.checkSmokeAndSetInfeasibilityNode(dAInfo) + if(v1.decider.isDependencyAnalysisEnabled && a.isInstanceOf[ast.FalseLit]) v1.decider.checkSmokeAndSetInfeasibilityNode(analysisInfoes) Q(s1, v1)}) } case exhale @ ast.Exhale(a) => val pve = ExhaleFailed(exhale) - consume(s, a, false, pve, v, dAInfo)((s1, _, v1) => + consume(s, a, false, pve, v, analysisInfoes)((s1, _, v1) => Q(s1, v1)) case assert @ ast.Assert(a: ast.FalseLit) if !s.isInPackage => /* "assert false" triggers a smoke check. If successful, we backtrack. */ executionFlowController.tryOrFail0(s.copy(h = magicWandSupporter.getEvalHeap(s)), v)((s1, v1, QS) => { - if (v1.decider.checkSmoke(dAInfo, isAssert = true)) + if (v1.decider.checkSmoke(analysisInfoes, isAssert = true)) QS(s1.copy(h = s.h), v1) else { val failure = createFailure(AssertFailed(assert) dueTo AssertionFalse(a), v1, s1, False, true, Option.when(withExp)(a)) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(False, dAInfo, v1.reportFurtherErrors()) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfoes, v1.reportFurtherErrors()) if(s1.retryLevel == 0 && v1.reportFurtherErrors()) failure combine QS(s1, v1) else failure } })((s2, v2) => @@ -480,7 +484,7 @@ object executor extends ExecutionRules { case assert @ ast.Assert(a) if Verifier.config.disableSubsumption() => val r = - consume(s, a, false, AssertFailed(assert), v, dAInfo)((_, _, _) => + consume(s, a, false, AssertFailed(assert), v, analysisInfoes)((_, _, _) => Success()) r combine Q(s, v) @@ -496,11 +500,11 @@ object executor extends ExecutionRules { * hUsed (reserveHeaps.head) instead of consuming them. hUsed is later discarded and replaced * by s.h. By copying hUsed to s.h the contained permissions remain available inside the wand. */ - consume(s, a, false, pve, v, dAInfo)((s2, _, v1) => { + consume(s, a, false, pve, v, analysisInfoes)((s2, _, v1) => { Q(s2.copy(h = s2.reserveHeaps.head), v1) }) } else - consume(s, a, false, pve, v, dAInfo)((s1, _, v1) => { + consume(s, a, false, pve, v, analysisInfoes)((s1, _, v1) => { val s2 = s1.copy(h = s.h, reserveHeaps = s.reserveHeaps) Q(s2, v1)}) @@ -509,7 +513,7 @@ object executor extends ExecutionRules { case ast.MethodCall(methodName, _, _) if !Verifier.config.disableHavocHack407() && methodName.startsWith(hack407_method_name_prefix) => - val analysisInfo = v.decider.getAnalysisInfo(dAInfo) + val analysisInfo = v.decider.getAnalysisInfo(analysisInfoes) val resourceName = methodName.stripPrefix(hack407_method_name_prefix) val member = s.program.collectFirst { case m: ast.Field if m.name == resourceName => m @@ -548,7 +552,7 @@ object executor extends ExecutionRules { val sepIdentifier = v.symbExLog.openScope(mcLog) val paramLog = new CommentRecord("Parameters", s, v.decider.pcs) val paramId = v.symbExLog.openScope(paramLog) - evals(s, eArgs, _ => pveCall, v, dAInfo)((s1, tArgs, eArgsNew, v1) => { + evals(s, eArgs, _ => pveCall, v, analysisInfoes)((s1, tArgs, eArgsNew, v1) => { v1.symbExLog.closeScope(paramId) val exampleTrafo = CounterexampleTransformer({ case ce: SiliconCounterexample => ce.withStore(s1.g) @@ -565,14 +569,14 @@ object executor extends ExecutionRules { recordVisited = true) v1.decider.analysisSourceInfoStack.isJoinRelevantNode = true - consumes(s2, meth.pres, false, _ => pvePre, v1, dAInfo)((s3, _, v2) => { + consumes(s2, meth.pres, false, _ => pvePre, v1, analysisInfoes)((s3, _, v2) => { v2.symbExLog.closeScope(preCondId) val postCondLog = new CommentRecord("Postcondition", s3, v2.decider.pcs) val postCondId = v2.symbExLog.openScope(postCondLog) val outs = meth.formalReturns.map(_.localVar) val gOuts = Store(outs.map(x => (x, v2.decider.fresh(x))).toMap) val s4 = s3.copy(g = s3.g + gOuts, oldHeaps = s3.oldHeaps + (Verifier.PRE_STATE_LABEL -> magicWandSupporter.getEvalHeap(s1))) - produces(s4, freshSnap, meth.posts, _ => pveCallTransformed, v2, dAInfo)((s5, v3) => { + produces(s4, freshSnap, meth.posts, _ => pveCallTransformed, v2, analysisInfoes)((s5, v3) => { v3.decider.analysisSourceInfoStack.isJoinRelevantNode = false v3.symbExLog.closeScope(postCondId) v3.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) @@ -590,11 +594,11 @@ object executor extends ExecutionRules { val ePerm = pap.perm val predicate = s.program.findPredicate(predicateName) val pve = FoldFailed(fold) - evals(s, eArgs, _ => pve, v, dAInfo)((s1, tArgs, eArgsNew, v1) => - eval(s1, ePerm, pve, v1, dAInfo)((s2, tPerm, ePermNew, v2) => - permissionSupporter.assertPositive(s2, tPerm, if (withExp) ePermNew.get else ePerm, pve, v2, dAInfo)((s3, v3) => { + evals(s, eArgs, _ => pve, v, analysisInfoes)((s1, tArgs, eArgsNew, v1) => + eval(s1, ePerm, pve, v1, analysisInfoes)((s2, tPerm, ePermNew, v2) => + permissionSupporter.assertPositive(s2, tPerm, if (withExp) ePermNew.get else ePerm, pve, v2, analysisInfoes)((s3, v3) => { val wildcards = s3.constrainableARPs -- s1.constrainableARPs - predicateSupporter.fold(s3, predAcc, tArgs, eArgsNew, tPerm, ePermNew, wildcards, pve, v3, dAInfo)((s4, v4) => { + predicateSupporter.fold(s3, predAcc, tArgs, eArgsNew, tPerm, ePermNew, wildcards, pve, v3, analysisInfoes)((s4, v4) => { v3.decider.finishDebugSubExp(s"folded ${predAcc.toString}") Q(s4, v4) } @@ -606,13 +610,13 @@ object executor extends ExecutionRules { val ePerm = pap.perm val predicate = s.program.findPredicate(predicateName) val pve = UnfoldFailed(unfold) - evals(s, eArgs, _ => pve, v, dAInfo)((s1, tArgs, eArgsNew, v1) => - eval(s1, ePerm, pve, v1, dAInfo)((s2, tPerm, ePermNew, v2) => { - val s2a = v2.heapSupporter.triggerResourceIfNeeded(s2, pa, tArgs, eArgsNew, v2, dAInfo) + evals(s, eArgs, _ => pve, v, analysisInfoes)((s1, tArgs, eArgsNew, v1) => + eval(s1, ePerm, pve, v1, analysisInfoes)((s2, tPerm, ePermNew, v2) => { + val s2a = v2.heapSupporter.triggerResourceIfNeeded(s2, pa, tArgs, eArgsNew, v2, analysisInfoes) - permissionSupporter.assertPositive(s2a, tPerm, if (withExp) ePermNew.get else ePerm, pve, v2, dAInfo)((s3, v3) => { + permissionSupporter.assertPositive(s2a, tPerm, if (withExp) ePermNew.get else ePerm, pve, v2, analysisInfoes)((s3, v3) => { val wildcards = s3.constrainableARPs -- s1.constrainableARPs - predicateSupporter.unfold(s3, predicate, tArgs, eArgsNew, tPerm, ePermNew, wildcards, pve, v3, pa, dAInfo)( + predicateSupporter.unfold(s3, predicate, tArgs, eArgsNew, tPerm, ePermNew, wildcards, pve, v3, pa, analysisInfoes)( (s4, v4) => { v2.decider.finishDebugSubExp(s"unfolded ${pa.toString}") Q(s4, v4) @@ -622,7 +626,7 @@ object executor extends ExecutionRules { case pckg @ ast.Package(wand, proofScript) => val pve = PackageFailed(pckg) - magicWandSupporter.packageWand(s.copy(isInPackage = true), wand, proofScript, pve, v, dAInfo)((s1, chWand, v1) => { + magicWandSupporter.packageWand(s.copy(isInPackage = true), wand, proofScript, pve, v, analysisInfoes)((s1, chWand, v1) => { val hOps = s1.reserveHeaps.head + chWand assert(s.exhaleExt || s1.reserveHeaps.length == 1) @@ -648,7 +652,7 @@ object executor extends ExecutionRules { val s3 = chWand match { case ch: QuantifiedMagicWandChunk => - v1.heapSupporter.triggerResourceIfNeeded(s2, wand, ch.singletonArgs.get, ch.singletonArgExps, v1, dAInfo) + v1.heapSupporter.triggerResourceIfNeeded(s2, wand, ch.singletonArgs.get, ch.singletonArgExps, v1, analysisInfoes) case _ => s2 } @@ -657,13 +661,13 @@ object executor extends ExecutionRules { case apply @ ast.Apply(e) => val pve = ApplyFailed(apply) - magicWandSupporter.applyWand(s, e, pve, v, dAInfo)(Q) + magicWandSupporter.applyWand(s, e, pve, v, analysisInfoes)(Q) case havoc: ast.Quasihavoc => - havocSupporter.execHavoc(havoc, v, s, dAInfo)(Q) + havocSupporter.execHavoc(havoc, v, s, analysisInfoes)(Q) case havocall: ast.Quasihavocall => - havocSupporter.execHavocall(havocall, v, s, dAInfo)(Q) + havocSupporter.execHavocall(havocall, v, s, analysisInfoes)(Q) case viper.silicon.extensions.TryBlock(body) => var bodySucceeded = false @@ -687,7 +691,7 @@ object executor extends ExecutionRules { executed } - private def ssaifyRhs(rhs: Term, rhsExp: ast.Exp, rhsExpNew: Option[ast.Exp], name: String, typ: ast.Type, v: Verifier, s : State, dAInfo: DependencyAnalysisInfo): (Term, Option[ast.Exp]) = { + private def ssaifyRhs(rhs: Term, rhsExp: ast.Exp, rhsExpNew: Option[ast.Exp], name: String, typ: ast.Type, v: Verifier, s : State, analysisInfoes: DependencyAnalysisInfoes): (Term, Option[ast.Exp]) = { rhs match { case _: Var | _: Literal if !v.decider.isDependencyAnalysisEnabled => (rhs, rhsExpNew) @@ -714,7 +718,7 @@ object executor extends ExecutionRules { } else { (None, None) } - v.decider.assumeDefinition(BuiltinEquals(t, rhs), debugExp, dAInfo) + v.decider.assumeDefinition(BuiltinEquals(t, rhs), debugExp, analysisInfoes) (t, eNew) } } diff --git a/src/main/scala/rules/HavocSupporter.scala b/src/main/scala/rules/HavocSupporter.scala index d422bc02a..808d54319 100644 --- a/src/main/scala/rules/HavocSupporter.scala +++ b/src/main/scala/rules/HavocSupporter.scala @@ -7,7 +7,7 @@ package viper.silicon.rules import viper.silicon.debugger.DebugExp -import viper.silicon.dependencyAnalysis.DependencyAnalysisInfo +import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfoes} import viper.silicon.interfaces.VerificationResult import viper.silicon.rules.evaluator.{eval, evalQuantified, evals} import viper.silicon.state._ @@ -35,7 +35,7 @@ object havocSupporter extends SymbolicExecutionRules { def execHavoc(havoc: ast.Quasihavoc, v: Verifier, s: State, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -44,15 +44,15 @@ object havocSupporter extends SymbolicExecutionRules { // If there is no havoc condition, use True as the condition val lhsExpr = havoc.lhs.getOrElse(ast.TrueLit()(havoc.pos)) - eval(s, lhsExpr, pve, v, dAInfo)((s0, lhsTerm, _, v0) => { - evals(s0, havoc.exp.args(s0.program), _ => pve, v0, dAInfo)((s1, tRcvrs, _, v1) => { + eval(s, lhsExpr, pve, v, analysisInfoes)((s0, lhsTerm, _, v0) => { + evals(s0, havoc.exp.args(s0.program), _ => pve, v0, analysisInfoes)((s1, tRcvrs, _, v1) => { val resource = havoc.exp.res(s1.program) // Call the havoc helper function, which returns a new heap, which is // partially havocked. Since we are executing a Havoc statement, we wrap // the HavocHelperData inside of a HavocOneData case (as opposed to HavocAllData). val condInfo = HavocOneData(tRcvrs) - val newHeap = v1.heapSupporter.havocResource(s1, lhsTerm, resource, condInfo, v1, dAInfo) + val newHeap = v1.heapSupporter.havocResource(s1, lhsTerm, resource, condInfo, v1, analysisInfoes) Q(s1.copy(h = newHeap), v1) }) @@ -73,7 +73,7 @@ object havocSupporter extends SymbolicExecutionRules { def execHavocall(havocall: ast.Quasihavocall, v: Verifier, s: State, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -99,7 +99,7 @@ object havocSupporter extends SymbolicExecutionRules { name = qid, pve = pve, v = v, - dAInfo = dAInfo) + analysisInfoes = analysisInfoes) { case (s1, tVars, eVars, Seq(tCond), _, Some((tArgs, eArgs, Seq(), _, _)), v1) => // Seq() represents an empty list of Triggers @@ -123,11 +123,11 @@ object havocSupporter extends SymbolicExecutionRules { val notInjectiveReason = QuasihavocallNotInjective(havocall) val comment = "QP receiver injectivity check is well-defined" val injectivityDebugExp = Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)) - v.decider.assume(FunctionPreconditionTransformer.transform(receiverInjectivityCheck, s.program), injectivityDebugExp, dAInfo) - v.decider.assert(receiverInjectivityCheck, dAInfo) { + v.decider.assume(FunctionPreconditionTransformer.transform(receiverInjectivityCheck, s.program), injectivityDebugExp, analysisInfoes) + v.decider.assert(receiverInjectivityCheck, analysisInfoes) { case false => val failure = createFailure(pve dueTo notInjectiveReason, v, s1, receiverInjectivityCheck, "QP receiver injective") - if(s1.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(receiverInjectivityCheck, dAInfo, v.reportFurtherErrors()) + if(s1.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(receiverInjectivityCheck, analysisInfoes, v.reportFurtherErrors()) if(s1.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s1, v1) else failure case true => // Generate the inverse axioms @@ -148,13 +148,13 @@ object havocSupporter extends SymbolicExecutionRules { ) val comment = "Definitional axioms for havocall inverse functions" v.decider.prover.comment(comment) - v.decider.assume(inverseFunctions.definitionalAxioms, Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, dAInfo=dAInfo) + v.decider.assume(inverseFunctions.definitionalAxioms, Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, analysisInfoes=analysisInfoes) // Call the havoc helper function, which returns a new heap, which is // partially havocked. Since we are executing a Havocall statement, we wrap // the HavocHelperData inside of a HavocAllData case. val condInfo = HavocallData(inverseFunctions, codomainQVars, imagesOfCodomain) - val newHeap = v1.heapSupporter.havocResource(s1, tCond, resource, condInfo, v1, dAInfo) + val newHeap = v1.heapSupporter.havocResource(s1, tCond, resource, condInfo, v1, analysisInfoes) Q(s1.copy(h = newHeap), v1) } diff --git a/src/main/scala/rules/HeapSupporter.scala b/src/main/scala/rules/HeapSupporter.scala index 9b5fd09db..79d85b53d 100644 --- a/src/main/scala/rules/HeapSupporter.scala +++ b/src/main/scala/rules/HeapSupporter.scala @@ -37,7 +37,7 @@ trait HeapSupportRules extends SymbolicExecutionRules { eRcvr: Option[ast.Exp], ve: VerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Term, Verifier) => VerificationResult) : VerificationResult @@ -48,7 +48,7 @@ trait HeapSupportRules extends SymbolicExecutionRules { tArgs: Seq[Term], eArgs: Option[Seq[ast.Exp]], v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Term, Verifier) => VerificationResult): VerificationResult def isPossibleTrigger(s: State, fa: ast.FieldAccess): Boolean @@ -61,7 +61,7 @@ trait HeapSupportRules extends SymbolicExecutionRules { eRhsNew: Option[ast.Exp], pve: PartialVerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Verifier) => VerificationResult) : VerificationResult @@ -70,7 +70,7 @@ trait HeapSupportRules extends SymbolicExecutionRules { tArgs: Seq[Term], eArgs: Option[Seq[ast.Exp]], v: Verifier, - dAInfo: DependencyAnalysisInfo): State + analysisInfoes: DependencyAnalysisInfoes): State def consumeSingle(s: State, h: Heap, @@ -82,7 +82,7 @@ trait HeapSupportRules extends SymbolicExecutionRules { returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult): VerificationResult def consumeQuantified(s: State, @@ -111,7 +111,7 @@ trait HeapSupportRules extends SymbolicExecutionRules { notInjectiveReason: => ErrorReason, insufficientPermissionReason: => ErrorReason, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult): VerificationResult def produceSingle(s: State, @@ -125,7 +125,7 @@ trait HeapSupportRules extends SymbolicExecutionRules { pve: PartialVerificationError, mergeAndTrigger: Boolean, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Verifier) => VerificationResult): VerificationResult def produceQuantified(s: State, @@ -153,7 +153,7 @@ trait HeapSupportRules extends SymbolicExecutionRules { negativePermissionReason: => ErrorReason, notInjectiveReason: => ErrorReason, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Verifier) => VerificationResult): VerificationResult def havocResource(s: State, @@ -161,7 +161,7 @@ trait HeapSupportRules extends SymbolicExecutionRules { resource: ast.Resource, condInfo: HavocHelperData, v: Verifier, - dAInfo: DependencyAnalysisInfo): Heap + analysisInfoes: DependencyAnalysisInfoes): Heap def collectForPermConditions(s: State, resource: ast.Resource, @@ -186,12 +186,12 @@ class DefaultHeapSupportRules extends HeapSupportRules { eRhsNew: Option[ast.Exp], pve: PartialVerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { if(v.decider.isPathInfeasible()){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, dAInfo) - v.decider.dependencyAnalyzer.addAssumption(False, dAInfo) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfoes) + v.decider.dependencyAnalyzer.addAssumption(False, analysisInfoes) return Q(s, v) } @@ -201,11 +201,10 @@ class DefaultHeapSupportRules extends HeapSupportRules { val (relevantChunks, otherChunks) = quantifiedChunkSupporter.splitHeap[QuantifiedFieldChunk](s.h, BasicChunkIdentifier(field.name)) val hints = quantifiedChunkSupporter.extractHints(None, Seq(tRcvr)) - val chunkOrderHeuristics = quantifiedChunkSupporter.singleReceiverChunkOrderHeuristic(Seq(tRcvr), hints, v, dAInfo) - val s2 = triggerResourceIfNeeded(s, ass.lhs, Seq(tRcvr), eRcvrNew.map(Seq(_)), v, dAInfo) + val chunkOrderHeuristics = quantifiedChunkSupporter.singleReceiverChunkOrderHeuristic(Seq(tRcvr), hints, v, analysisInfoes) + val s2 = triggerResourceIfNeeded(s, ass.lhs, Seq(tRcvr), eRcvrNew.map(Seq(_)), v, analysisInfoes) v.decider.clearModel() - val lhsSourceInfo = TransitivityAnalysisSourceInfo(v.decider.analysisSourceInfoStack.getFullSourceInfo, AnalysisSourceInfo.createAnalysisSourceInfo(ass.lhs)) - v.decider.analysisSourceInfoStack.setForcedSource(lhsSourceInfo,v.decider.analysisSourceInfoStack.getDependencyType) // splitting lhs and rhs to make permission flow analysis more precise + val lhsSourceInfoes = analysisInfoes.withMergeInfo(SimpleDependencyAnalysisMerge(AnalysisSourceInfo.createAnalysisSourceInfo(ass.lhs))) // splitting lhs and rhs to make permission flow analysis more precise val result = quantifiedChunkSupporter.removePermissions( s2, relevantChunks, @@ -219,49 +218,44 @@ class DefaultHeapSupportRules extends HeapSupportRules { Option.when(withExp)(ast.FullPerm()()), chunkOrderHeuristics, v, - dAInfo + lhsSourceInfoes ) - v.decider.analysisSourceInfoStack.removeForcedSource() result match { case (Complete(), s3, remainingChunks) => val h3 = Heap(remainingChunks ++ otherChunks) val (sm, smValueDef) = quantifiedChunkSupporter.singletonSnapshotMap(s3, field, Seq(tRcvr), tRhs, v) v.decider.prover.comment("Definitional axioms for singleton-FVF's value") val debugExp = Option.when(withExp)(DebugExp.createInstance("Definitional axioms for singleton-FVF's value", isInternal_ = true)) - v.decider.assumeDefinition(smValueDef, debugExp, dAInfo) - // FIXME ake: Field assignment optimization - val dAInfoInt = FullDependencyAnalysisInfo(lhsSourceInfo, dAInfo.getDependencyType NoDependencyAnalysisMerge(), NoDependencyAnalysisJoin()) // TODO ake: review - v.decider.analysisSourceInfoStack.setForcedSource(lhsSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType) // splitting lhs and rhs to make permission flow analysis more precise + v.decider.assumeDefinition(smValueDef, debugExp, analysisInfoes) val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(Seq(`?r`), Option.when(withExp)(Seq(ast.LocalVarDecl("r", ast.Ref)(ass.pos, ass.info, ass.errT))), - field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), sm, s.program, v, AssumptionType.Internal, isExhale=false) + field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), sm, s.program, v, lhsSourceInfoes.withDependencyType(DependencyType.Internal), isExhale=false) if (s3.heapDependentTriggers.contains(field)) { val debugExp2 = Option.when(withExp)(DebugExp.createInstance(s"FieldTrigger(${eRcvrNew.toString}.${field.name})")) - v.decider.assume(FieldTrigger(field.name, sm, tRcvr), debugExp2, dAInfo.withDependencyType(DependencyType.Trigger)) + v.decider.assume(FieldTrigger(field.name, sm, tRcvr), debugExp2, lhsSourceInfoes.withDependencyType(DependencyType.Trigger)) } - v.decider.analysisSourceInfoStack.removeForcedSource() - v.decider.dependencyAnalyzer.addCustomTransitiveDependency(lhsSourceInfo, v.decider.analysisSourceInfoStack.getFullSourceInfo) + // TODO ake +// v.decider.dependencyAnalyzer.addCustomTransitiveDependency(lhsSourceInfoes.getMergeInfo, v.decider.analysisSourceInfoStack.getFullSourceInfo) val s4 = s3.copy(h = h3 + ch) val (debugHeapName, _) = v.getDebugOldLabel(s4, ass.lhs.pos) val s5 = if (withExp) s4.copy(oldHeaps = s4.oldHeaps + (debugHeapName -> magicWandSupporter.getEvalHeap(s4))) else s4 Q(s5, v) case (Incomplete(_, _), s3, _) => val failure = createFailure(ve, v, s3, "sufficient permission") - if(s3.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, dAInfo, v.reportFurtherErrors()) + if(s3.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfoes, v.reportFurtherErrors()) if(s3.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s3, v) else failure } } else { val description = s"consume ${ass.pos}: $ass" - val lhsSourceInfo = TransitivityAnalysisSourceInfo(v.decider.analysisSourceInfoStack.getFullSourceInfo, AnalysisSourceInfo.createAnalysisSourceInfo(ass.lhs)) - v.decider.analysisSourceInfoStack.setForcedSource(lhsSourceInfo, v.decider.analysisSourceInfoStack.getDependencyType) // splitting lhs and rhs to make permission flow analysis more precise - chunkSupporter.consume(s, s.h, field, Seq(tRcvr), eRcvrNew.map(Seq(_)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), false, ve, v, description, dAInfo)((s3, h3, _, v3) => { + val lhsSourceInfoes = analysisInfoes.withMergeInfo(SimpleDependencyAnalysisMerge(AnalysisSourceInfo.createAnalysisSourceInfo(ass.lhs))) // splitting lhs and rhs to make permission flow analysis more precise + chunkSupporter.consume(s, s.h, field, Seq(tRcvr), eRcvrNew.map(Seq(_)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), false, ve, v, description, lhsSourceInfoes)((s3, h3, _, v3) => { val id = BasicChunkIdentifier(field.name) - val newChunk = BasicChunk.apply(FieldID, id, Seq(tRcvr), eRcvrNew.map(Seq(_)), tRhs, eRhsNew, FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), v3.decider.getAnalysisInfo(dAInfo.withDependencyType(DependencyType.Internal))) - v.decider.analysisSourceInfoStack.removeForcedSource() + val newChunk = BasicChunk.apply(FieldID, id, Seq(tRcvr), eRcvrNew.map(Seq(_)), tRhs, eRhsNew, FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), v3.decider.getAnalysisInfo(lhsSourceInfoes.withDependencyType(DependencyType.Internal))) chunkSupporter.produce(s3, h3, newChunk, v3)((s4, h4, v4) => { val s5 = s4.copy(h = h4) val (debugHeapName, _) = v4.getDebugOldLabel(s5, ass.lhs.pos) val s6 = if (withExp) s5.copy(oldHeaps = s5.oldHeaps + (debugHeapName -> magicWandSupporter.getEvalHeap(s5))) else s5 - v4.decider.dependencyAnalyzer.addCustomTransitiveDependency(lhsSourceInfo, v4.decider.analysisSourceInfoStack.getFullSourceInfo) + // TODO ake +// v4.decider.dependencyAnalyzer.addCustomTransitiveDependency(lhsSourceInfo, v4.decider.analysisSourceInfoStack.getFullSourceInfo) Q(s6, v4) }) }) @@ -275,11 +269,11 @@ class DefaultHeapSupportRules extends HeapSupportRules { tArgs: Seq[Term], eArgs: Option[Seq[ast.Exp]], v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Term, Verifier) => VerificationResult): VerificationResult = { if(v.decider.isPathInfeasible()){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, dAInfo) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfoes) return Q(s, NoPerm, v) } @@ -308,7 +302,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { case w: ast.MagicWand => MagicWandIdentifier(w, s2.program).toString } DebugExp.createInstance(s"Resource trigger(${name}($argsString))", isInternal_ = true) - }), dAInfo.withDependencyType(DependencyType.Trigger)) + }), analysisInfoes.withDependencyType(DependencyType.Trigger)) } val currentPermAmount = ResourcePermissionLookup(res, pmDef.pm, tArgs, s2.program) @@ -318,7 +312,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { v.decider.prover.comment(s"perm($resAcc) ~~> assume upper permission bound") val (debugHeapName, debugLabel) = v.getDebugOldLabel(s2, resAcc.pos, Some(h)) val exp = Option.when(withExp)(ast.PermLeCmp(ast.DebugLabelledOld(ast.CurrentPerm(resAcc)(), debugLabel)(), ast.FullPerm()())()) - v.decider.assume(PermAtMost(currentPermAmount, FullPerm), exp, exp.map(s2.substituteVarsInExp(_)), dAInfo.withDependencyType(DependencyType.Internal)) + v.decider.assume(PermAtMost(currentPermAmount, FullPerm), exp, exp.map(s2.substituteVarsInExp(_)), analysisInfoes.withDependencyType(DependencyType.Internal)) val s3 = if (Verifier.config.enableDebugging()) s2.copy(oldHeaps = s2.oldHeaps + (debugHeapName -> h)) else s2 s3 case _ => s2 @@ -343,11 +337,11 @@ class DefaultHeapSupportRules extends HeapSupportRules { eRcvr: Option[ast.Exp], ve: VerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Term, Verifier) => VerificationResult) : VerificationResult = { if(v.decider.isPathInfeasible()){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, dAInfo) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfoes) val sort = v.symbolConverter.toSort(fa.field.typ) val newVar = v.decider.fresh(sort, None) // just make sure the returned term typechecks @@ -367,11 +361,11 @@ class DefaultHeapSupportRules extends HeapSupportRules { * quantifier in whose body field 'fa.field' was accessed) * which is protected by a trigger term that we currently don't have. */ - v.decider.assume(And(fvfDef.valueDefinitions), Option.when(withExp)(DebugExp.createInstance("Value definitions", isInternal_ = true)), dAInfo.withDependencyType(DependencyType.Internal)) + v.decider.assume(And(fvfDef.valueDefinitions), Option.when(withExp)(DebugExp.createInstance("Value definitions", isInternal_ = true)), analysisInfoes.withDependencyType(DependencyType.Internal)) if (s.heapDependentTriggers.contains(fa.field)) { val trigger = FieldTrigger(fa.field.name, fvfDef.sm, tRcvr) val triggerExp = Option.when(withExp)(DebugExp.createInstance(s"FieldTrigger(${eRcvr.toString()}.${fa.field.name})")) - v.decider.assume(trigger, triggerExp, dAInfo.withDependencyType(DependencyType.Trigger)) + v.decider.assume(trigger, triggerExp, analysisInfoes.withDependencyType(DependencyType.Trigger)) } if (s.triggerExp) { val fvfLookup = Lookup(fa.field.name, fvfDef.sm, tRcvr) @@ -380,10 +374,10 @@ class DefaultHeapSupportRules extends HeapSupportRules { Q(s2, fvfLookup, v) } else { val toAssert = IsPositive(totalPermissions.replace(`?r`, tRcvr)) - v.decider.assert(toAssert, dAInfo) { + v.decider.assert(toAssert, analysisInfoes) { case false => val failure = createFailure(ve, v, s, toAssert, Option.when(withExp)(perms.IsPositive(ast.CurrentPerm(fa)())())) - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(toAssert, dAInfo, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(toAssert, analysisInfoes, v.reportFurtherErrors()) val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(fa.field, s, v), Option.when(withExp)(PUnknown())) if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, snap, v) else failure case true => @@ -418,7 +412,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { if (s2.heapDependentTriggers.contains(fa.field)) { val trigger = FieldTrigger(fa.field.name, sm, tRcvr) val triggerExp = Option.when(withExp)(DebugExp.createInstance(s"FieldTrigger(${eRcvr.toString()}.${fa.field.name})")) - v.decider.assume(trigger, triggerExp, dAInfo.withDependencyType(DependencyType.Trigger)) + v.decider.assume(trigger, triggerExp, analysisInfoes.withDependencyType(DependencyType.Trigger)) } val (permCheck, permCheckExp, s3) = if (s2.triggerExp) { @@ -434,10 +428,10 @@ class DefaultHeapSupportRules extends HeapSupportRules { } (Implies(lhs, IsPositive(totalPerms)), Option.when(withExp)(perms.IsPositive(ast.CurrentPerm(fa)(fa.pos, fa.info, fa.errT))(fa.pos, fa.info, fa.errT)), s3) } - v.decider.assert(permCheck,dAInfo) { + v.decider.assert(permCheck,analysisInfoes) { case false => val failure = createFailure(ve, v, s3, permCheck, permCheckExp) - if(s3.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(permCheck, dAInfo, v.reportFurtherErrors()) + if(s3.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(permCheck, analysisInfoes, v.reportFurtherErrors()) val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(fa.field, s3, v), Option.when(withExp)(PUnknown())) if(s3.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s3, snap, v) else failure case true => @@ -450,7 +444,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { } } else { val resource = fa.res(s.program) - chunkSupporter.lookup(s, s.h, resource, Seq(tRcvr), Option.when(withExp)(Seq(eRcvr.get)), ve, v, dAInfo)((s2, h2, tSnap, v2) => { + chunkSupporter.lookup(s, s.h, resource, Seq(tRcvr), Option.when(withExp)(Seq(eRcvr.get)), ve, v, analysisInfoes)((s2, h2, tSnap, v2) => { val fr = s2.functionRecorder.recordSnapshot(fa, v2.decider.pcs.branchConditions, tSnap) val s3 = s2.copy(h = h2, functionRecorder = fr) Q(s3, tSnap, v) @@ -463,7 +457,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { tArgs: Seq[Term], eArgs: Option[Seq[ast.Exp]], v: Verifier, - dAInfo: DependencyAnalysisInfo): State = { + analysisInfoes: DependencyAnalysisInfoes): State = { if (s.isUsedAsTrigger(resAcc.res(s.program))) { val resource = resAcc.res(s.program) val chunkId = ChunkIdentifier(resource, s.program) @@ -485,7 +479,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { val eArgsStr = eArgs.mkString(", ") val debugExp = Option.when(withExp)(DebugExp.createInstance(Some(s"Resource trigger(${name}($eArgsStr))"), Some(resAcc), Some(resAcc), None, isInternal_ = true, InsertionOrderedSet.empty)) - v.decider.assume(trigger(smDef1.sm), debugExp, dAInfo.withDependencyType(DependencyType.Trigger)) + v.decider.assume(trigger(smDef1.sm), debugExp, analysisInfoes.withDependencyType(DependencyType.Trigger)) s.copy(smCache = smCache1, functionRecorder = s.functionRecorder.recordFvfAndDomain(smDef1)) } else { s @@ -503,7 +497,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { pve: PartialVerificationError, mergeAndTrigger: Boolean, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { val useQPs = s.isQuantifiedResource(resource) if (useQPs) { @@ -511,17 +505,17 @@ class DefaultHeapSupportRules extends HeapSupportRules { val tFormalArgs = s.getFormalArgVars(resource, v) val eFormalArgs = Option.when(withExp)(s.getFormalArgDecls(resource)) quantifiedChunkSupporter.produceSingleLocation( - s, resource, tFormalArgs, eFormalArgs, tArgs, eArgs, tSnap, tPerm, ePerm, trigger, mergeAndTrigger, v, dAInfo)(Q) + s, resource, tFormalArgs, eFormalArgs, tArgs, eArgs, tSnap, tPerm, ePerm, trigger, mergeAndTrigger, v, analysisInfoes)(Q) } else { resource match { case w: ast.MagicWand => - magicWandSupporter.createChunk(s, w, MagicWandSnapshot(tSnap), pve, v, dAInfo)((s2, chWand, v2) => + magicWandSupporter.createChunk(s, w, MagicWandSnapshot(tSnap), pve, v, analysisInfoes)((s2, chWand, v2) => chunkSupporter.produce(s2, s2.h, chWand, v2)((s3, h3, v3) => Q(s3.copy(h = h3), v3))) case _ => val chunkId = ChunkIdentifier(resource, s.program) val (resId, snap1) = if (resource.isInstanceOf[ast.Field]) (FieldID, tSnap) else (PredicateID, tSnap.convert(sorts.Snap)) - val ch = BasicChunk.apply(resId, chunkId.asInstanceOf[BasicChunkIdentifier], tArgs, eArgs, snap1, eSnap, tPerm, ePerm, v.decider.getAnalysisInfo(dAInfo)) + val ch = BasicChunk.apply(resId, chunkId.asInstanceOf[BasicChunkIdentifier], tArgs, eArgs, snap1, eSnap, tPerm, ePerm, v.decider.getAnalysisInfo(analysisInfoes)) if (mergeAndTrigger) { chunkSupporter.produce(s, s.h, ch, v)((s2, h2, v2) => { if (resource.isInstanceOf[ast.Predicate] && Verifier.config.enablePredicateTriggersOnInhale() && s2.functionRecorder == NoopFunctionRecorder @@ -529,7 +523,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { val predicate = resource.asInstanceOf[ast.Predicate] val argsString = eArgs.mkString(", ") val debugExp = Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}($argsString))", isInternal_ = true)) - v2.decider.assume(App(s2.predicateData(predicate.name).triggerFunction, snap1 +: tArgs), debugExp, dAInfo.withDependencyType(DependencyType.Trigger)) + v2.decider.assume(App(s2.predicateData(predicate.name).triggerFunction, snap1 +: tArgs), debugExp, analysisInfoes.withDependencyType(DependencyType.Trigger)) } Q(s2.copy(h = h2), v2) }) @@ -550,10 +544,10 @@ class DefaultHeapSupportRules extends HeapSupportRules { returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult): VerificationResult = { if(v.decider.isPathInfeasible()){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, dAInfo) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfoes) return Q(s, h, Some(Unit), v) } @@ -563,14 +557,14 @@ class DefaultHeapSupportRules extends HeapSupportRules { val tFormalArgs = s.getFormalArgVars(resource, v) val eFormalArgs = Option.when(withExp)(s.getFormalArgDecls(resource)) quantifiedChunkSupporter.consumeSingleLocation( - s, h, tFormalArgs, eFormalArgs, tArgs, eArgs, resAcc, tPerm, ePerm, returnSnap, None, pve, v, dAInfo)(Q) + s, h, tFormalArgs, eFormalArgs, tArgs, eArgs, resAcc, tPerm, ePerm, returnSnap, None, pve, v, analysisInfoes)(Q) } else { val ve = resAcc match { case l: ast.LocationAccess => pve dueTo InsufficientPermission(l) case w: ast.MagicWand => pve dueTo MagicWandChunkNotFound(w) } val description = s"consume ${resAcc.pos}: $resAcc" - chunkSupporter.consume(s, h, resource, tArgs, eArgs, tPerm, ePerm, returnSnap, ve, v, description, dAInfo)(Q) + chunkSupporter.consume(s, h, resource, tArgs, eArgs, tPerm, ePerm, returnSnap, ve, v, description, analysisInfoes)(Q) } } @@ -599,7 +593,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { negativePermissionReason: => ErrorReason, notInjectiveReason: => ErrorReason, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Verifier) => VerificationResult): VerificationResult = { val tSnap = resource match { case f: ast.Field => @@ -636,7 +630,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { negativePermissionReason, notInjectiveReason, v, - dAInfo + analysisInfoes )(Q) } @@ -666,10 +660,10 @@ class DefaultHeapSupportRules extends HeapSupportRules { notInjectiveReason: => ErrorReason, insufficientPermissionReason: => ErrorReason, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult): VerificationResult = { if(v.decider.isPathInfeasible()){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, dAInfo) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfoes) return Q(s, h, Some(Unit), v) } @@ -700,7 +694,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { notInjectiveReason, insufficientPermissionReason, v, - dAInfo + analysisInfoes )(Q) } @@ -709,11 +703,11 @@ class DefaultHeapSupportRules extends HeapSupportRules { resource: ast.Resource, condInfo: HavocHelperData, v: Verifier, - dAInfo: DependencyAnalysisInfo): Heap = { + analysisInfoes: DependencyAnalysisInfoes): Heap = { if (s.isQuantifiedResource(resource)) { - havocQuantifiedResource(s, lhs, resource, condInfo, v, dAInfo) + havocQuantifiedResource(s, lhs, resource, condInfo, v, analysisInfoes) } else { - havocNonQuantifiedResource(s, lhs, resource, condInfo, v, dAInfo) + havocNonQuantifiedResource(s, lhs, resource, condInfo, v, analysisInfoes) } } @@ -735,7 +729,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { resource: ast.Resource, condInfo: HavocHelperData, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) : Heap = { val id = ChunkIdentifier(resource, s.program) @@ -746,12 +740,12 @@ class DefaultHeapSupportRules extends HeapSupportRules { val havockedSnap = v.decider.fresh("mwsf", sorts.MagicWandSnapFunction, Option.when(withExp)(PUnknown())) val cond = replacementCond(lhs, ch.args, condInfo) val magicWandSnapshot = MagicWandSnapshot(Ite(cond, havockedSnap, ch.snap.mwsf)) - NonQuantifiedChunk.withSnap(ch, magicWandSnapshot, None, v.decider.getAnalysisInfo(dAInfo)) + NonQuantifiedChunk.withSnap(ch, magicWandSnapshot, None, v.decider.getAnalysisInfo(analysisInfoes)) case ch => val havockedSnap = freshSnap(ch.snap.sort, v) val cond = replacementCond(lhs, ch.args, condInfo) - NonQuantifiedChunk.withSnap(ch, Ite(cond, havockedSnap, ch.snap), None, v.decider.getAnalysisInfo(dAInfo)) + NonQuantifiedChunk.withSnap(ch, Ite(cond, havockedSnap, ch.snap), None, v.decider.getAnalysisInfo(analysisInfoes)) } Heap(otherChunks ++ newChunks) } @@ -778,7 +772,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { resource: ast.Resource, condInfo: HavocHelperData, v: Verifier, - dAInfo: DependencyAnalysisInfo) : Heap = { + analysisInfoes: DependencyAnalysisInfoes) : Heap = { // Quantified field chunks are of the form R(r; sm, pm). // Conceptually, quantified predicate/wand chunks look like R(r1, ..., rn; sm, pm). @@ -831,9 +825,9 @@ class DefaultHeapSupportRules extends HeapSupportRules { v.decider.prover.comment("axiomatized snapshot map after havoc") val debugExp = Option.when(withExp)(DebugExp.createInstance("havoc new axiom", isInternal_ = true)) - v.decider.assume(newAxiom, debugExp, dAInfo) + v.decider.assume(newAxiom, debugExp, analysisInfoes) - QuantifiedChunk.withSnapshotMap(ch, newSm, v.decider.getAnalysisInfo(dAInfo)) + QuantifiedChunk.withSnapshotMap(ch, newSm, v.decider.getAnalysisInfo(analysisInfoes)) } Heap(newChunks ++ otherChunks) } diff --git a/src/main/scala/rules/Joiner.scala b/src/main/scala/rules/Joiner.scala index fa7be35c0..496d975fc 100644 --- a/src/main/scala/rules/Joiner.scala +++ b/src/main/scala/rules/Joiner.scala @@ -9,7 +9,7 @@ package viper.silicon.rules import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.decider.RecordedPathConditions -import viper.silicon.dependencyAnalysis.{AnalysisInfo, AssumptionType, DependencyAnalysisInfo, StringAnalysisSourceInfo} +import viper.silicon.dependencyAnalysis.{AnalysisInfo, AssumptionType, DependencyAnalysisInfoes, DependencyType, StringAnalysisSourceInfo} import viper.silicon.interfaces.{Success, VerificationResult} import viper.silicon.logger.records.structural.JoiningRecord import viper.silicon.state.State @@ -20,22 +20,25 @@ import viper.silver.ast import viper.silver.ast.NoPosition case class JoinDataEntry[D](s: State, data: D, pathConditions: RecordedPathConditions) { + + private val analysisInfoes = DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes.withSource(StringAnalysisSourceInfo("merge", NoPosition)).withDependencyType(DependencyType.Internal) // TODO ake + // Instead of merging states by calling State.merge, // we can directly merge JoinDataEntries to obtain new States, // and the join data entries themselves provide information about the path conditions to State.merge. def pathConditionAwareMerge(other: JoinDataEntry[D], v: Verifier): State = { - val res = State.merge(this.s, this.pathConditions, other.s, other.pathConditions, AnalysisInfo(v.decider, v.decider.dependencyAnalyzer, StringAnalysisSourceInfo("merge", NoPosition), AssumptionType.Internal, isJoinNode=false)) + val res = State.merge(this.s, this.pathConditions, other.s, other.pathConditions, AnalysisInfo(v.decider, v.decider.dependencyAnalyzer, analysisInfoes)) v.stateConsolidator(s).consolidate(res, v) } def pathConditionAwareMergeWithoutConsolidation(other: JoinDataEntry[D], v: Verifier): State = { - State.merge(this.s, this.pathConditions, other.s, other.pathConditions, AnalysisInfo(v.decider, v.decider.dependencyAnalyzer, StringAnalysisSourceInfo("merge", NoPosition), AssumptionType.Internal, isJoinNode=false)) + State.merge(this.s, this.pathConditions, other.s, other.pathConditions, AnalysisInfo(v.decider, v.decider.dependencyAnalyzer, analysisInfoes)) } } trait JoiningRules extends SymbolicExecutionRules { - def join[D, JD](s: State, v: Verifier, dAInfo: DependencyAnalysisInfo, resetState: Boolean = true) + def join[D, JD](s: State, v: Verifier, analysisInfoes: DependencyAnalysisInfoes, resetState: Boolean = true) (block: (State, Verifier, (State, D, Verifier) => VerificationResult) => VerificationResult) (merge: Seq[JoinDataEntry[D]] => (State, JD)) (Q: (State, JD, Verifier) => VerificationResult) @@ -43,7 +46,7 @@ trait JoiningRules extends SymbolicExecutionRules { } object joiner extends JoiningRules { - def join[D, JD](s: State, v: Verifier, dAInfo: DependencyAnalysisInfo, resetState: Boolean = true) + def join[D, JD](s: State, v: Verifier, analysisInfoes: DependencyAnalysisInfoes, resetState: Boolean = true) (block: (State, Verifier, (State, D, Verifier) => VerificationResult) => VerificationResult) (merge: Seq[JoinDataEntry[D]] => (State, JD)) (Q: (State, JD, Verifier) => VerificationResult) @@ -104,13 +107,13 @@ object joiner extends JoiningRules { val pcsExp = Option.when(withExp)(entry.pathConditions.conditionalizedExp) val comment = "Joined path conditions" v.decider.prover.comment(comment) - v.decider.assume(pcs, Option.when(withExp)(DebugExp.createInstance(comment, InsertionOrderedSet(pcsExp.get))), enforceAssumption = false, dAInfo) + v.decider.assume(pcs, Option.when(withExp)(DebugExp.createInstance(comment, InsertionOrderedSet(pcsExp.get))), enforceAssumption = false, analysisInfoes) feasibleBranches = And(entry.pathConditions.branchConditions) :: feasibleBranches feasibleBranchesExp = feasibleBranchesExp.map(fbe => BigAnd(entry.pathConditions.branchConditionExps.map(_._1)) :: fbe) feasibleBranchesExpNew = feasibleBranchesExpNew.map(fbe => BigAnd(entry.pathConditions.branchConditionExps.map(_._2.get)) :: fbe) }) // Assume we are in a feasible branch - v.decider.assume(Or(feasibleBranches), Option.when(withExp)(DebugExp.createInstance(Some("Feasible Branches"), feasibleBranchesExp.map(BigOr(_)), feasibleBranchesExpNew.map(BigOr(_)), InsertionOrderedSet.empty)), dAInfo) + v.decider.assume(Or(feasibleBranches), Option.when(withExp)(DebugExp.createInstance(Some("Feasible Branches"), feasibleBranchesExp.map(BigOr(_)), feasibleBranchesExpNew.map(BigOr(_)), InsertionOrderedSet.empty)), analysisInfoes) Q(sJoined, dataJoined, v) } } diff --git a/src/main/scala/rules/LetSupporter.scala b/src/main/scala/rules/LetSupporter.scala index 577971960..5c29016e4 100644 --- a/src/main/scala/rules/LetSupporter.scala +++ b/src/main/scala/rules/LetSupporter.scala @@ -6,24 +6,24 @@ package viper.silicon.rules -import viper.silicon.dependencyAnalysis.DependencyAnalysisInfo -import viper.silver.ast -import viper.silver.verifier.PartialVerificationError +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfoes import viper.silicon.interfaces.VerificationResult -import viper.silicon.state.{State, Store} import viper.silicon.state.terms.Term +import viper.silicon.state.{State, Store} import viper.silicon.verifier.Verifier +import viper.silver.ast +import viper.silver.verifier.PartialVerificationError trait LetSupportRules extends SymbolicExecutionRules { def handle[E <: ast.Exp] (s: State, e: ast.Exp, pve: PartialVerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Store, E, Verifier) => VerificationResult) : VerificationResult def handle[E <: ast.Exp] (s: State, let: ast.Let, pve: PartialVerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Store, E, Verifier) => VerificationResult) : VerificationResult } @@ -33,23 +33,23 @@ object letSupporter extends LetSupportRules { def handle[E <: ast.Exp] (s: State, e: ast.Exp, pve: PartialVerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Store, E, Verifier) => VerificationResult) : VerificationResult = { e match { - case let: ast.Let => handle(s, Nil, let, pve, v, dAInfo)(Q) + case let: ast.Let => handle(s, Nil, let, pve, v, analysisInfoes)(Q) case _ => Q(s, Store(), e.asInstanceOf[E], v) } } def handle[E <: ast.Exp] (s: State, let: ast.Let, pve: PartialVerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Store, E, Verifier) => VerificationResult) : VerificationResult = { - handle(s, Nil, let, pve, v, dAInfo)(Q) + handle(s, Nil, let, pve, v, analysisInfoes)(Q) } private def handle[E <: ast.Exp] @@ -58,17 +58,17 @@ object letSupporter extends LetSupportRules { let: ast.Let, pve: PartialVerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Store, E, Verifier) => VerificationResult) : VerificationResult = { val ast.Let(x, exp, body) = let - eval(s, exp, pve, v, dAInfo)((s1, t, expNew, v1) => { + eval(s, exp, pve, v, analysisInfoes)((s1, t, expNew, v1) => { val bindings1 = bindings :+ (x.localVar, (t, expNew)) val s2 = s1.copy(s1.g + (x.localVar, (t, expNew))) body match { - case nestedLet: ast.Let => handle(s2, bindings1, nestedLet, pve, v1, dAInfo)(Q) + case nestedLet: ast.Let => handle(s2, bindings1, nestedLet, pve, v1, analysisInfoes)(Q) case _ => Q(s2, Store(bindings1), body.asInstanceOf[E], v1)}}) } } diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 481f2ce40..594d327e4 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -10,7 +10,7 @@ import viper.silicon._ import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.decider.RecordedPathConditions -import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfo, DependencyType, ExpAnalysisSourceInfo} +import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfoes, DependencyType, ExpAnalysisSourceInfo, StringAnalysisSourceInfo} import viper.silicon.interfaces._ import viper.silicon.interfaces.state._ import viper.silicon.state._ @@ -18,7 +18,7 @@ import viper.silicon.state.terms._ import viper.silicon.utils.{freshSnap, toSf} import viper.silicon.verifier.Verifier import viper.silver.ast -import viper.silver.ast.{Exp, Stmt} +import viper.silver.ast.{Exp, NoPosition, Stmt} import viper.silver.cfg.Edge import viper.silver.cfg.silver.SilverCfg.SilverBlock import viper.silver.parser.PUnknown @@ -91,12 +91,12 @@ object magicWandSupporter extends SymbolicExecutionRules { snap: MagicWandSnapshot, pve: PartialVerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, MagicWandChunk, Verifier) => VerificationResult) : VerificationResult = { - evaluateWandArguments(s, wand, pve, v, dAInfo)((s1, ts, esNew, v1) => { + evaluateWandArguments(s, wand, pve, v, analysisInfoes)((s1, ts, esNew, v1) => { val newChunk = MagicWandChunk(MagicWandIdentifier(wand, s.program), s1.g.values, ts, esNew, snap, FullPerm, - Option.when(withExp)(ast.FullPerm()(wand.pos, wand.info, wand.errT)), v.decider.getAnalysisInfo(dAInfo)) + Option.when(withExp)(ast.FullPerm()(wand.pos, wand.info, wand.errT)), v.decider.getAnalysisInfo(analysisInfoes)) Q(s1, newChunk, v1) }) } @@ -114,14 +114,14 @@ object magicWandSupporter extends SymbolicExecutionRules { * @param v Verifier instance * @return Fresh instance of [[viper.silicon.state.terms.MagicWandSnapshot]] */ - def createMagicWandSnapshot(abstractLhs: Var, rhsSnapshot: Term, v: Verifier, dAInfo: DependencyAnalysisInfo): MagicWandSnapshot = { + def createMagicWandSnapshot(abstractLhs: Var, rhsSnapshot: Term, v: Verifier, analysisInfoes: DependencyAnalysisInfoes): MagicWandSnapshot = { val mwsf = v.decider.fresh("mwsf", sorts.MagicWandSnapFunction, Option.when(withExp)(PUnknown())) val magicWandSnapshot = MagicWandSnapshot(mwsf) v.decider.assumeDefinition(Forall( abstractLhs, MWSFLookup(mwsf, abstractLhs) === rhsSnapshot, Trigger(MWSFLookup(mwsf, abstractLhs)) - ), Option.when(withExp)(DebugExp.createInstance("Magic wand snapshot definition", isInternal_ = true)), dAInfo) + ), Option.when(withExp)(DebugExp.createInstance("Magic wand snapshot definition", isInternal_ = true)), analysisInfoes) magicWandSnapshot } @@ -136,13 +136,13 @@ object magicWandSupporter extends SymbolicExecutionRules { wand: ast.MagicWand, pve: PartialVerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Seq[Term], Option[Seq[ast.Exp]], Verifier) => VerificationResult) : VerificationResult = { val s1 = s.copy(exhaleExt = false) val es = wand.subexpressionsToEvaluate(s.program) - evals(s1, es, _ => pve, v, dAInfo)((s2, ts, esNew, v1) => { + evals(s1, es, _ => pve, v, analysisInfoes)((s2, ts, esNew, v1) => { Q(s2.copy(exhaleExt = s.exhaleExt), ts, esNew, v1) }) } @@ -155,12 +155,12 @@ object magicWandSupporter extends SymbolicExecutionRules { failure: Failure, qvars: Seq[Var], v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (consumeFunction: (State, Heap, Term, Option[ast.Exp], Verifier) => (ConsumptionResult, State, Heap, Option[CH])) (Q: (State, Stack[Heap], Stack[Option[CH]], Verifier) => VerificationResult) : VerificationResult = { - val initialConsumptionResult = ConsumptionResult(pLoss, pLossExp, qvars, v, Verifier.config.checkTimeout()) + val initialConsumptionResult = ConsumptionResult(pLoss, pLossExp, qvars, v, Verifier.config.checkTimeout(), analysisInfoes) /* TODO: Introduce a dedicated timeout for the permission check performed by ConsumptionResult, * instead of using checkTimeout. Reason: checkTimeout is intended for checks that are * optimisations, e.g. detecting if a chunk provided no permissions or if a branch is @@ -186,7 +186,7 @@ object magicWandSupporter extends SymbolicExecutionRules { case (Some(ch1: QuantifiedBasicChunk), Some(ch2: QuantifiedBasicChunk)) => ch1.snapshotMap === ch2.snapshotMap case _ => True } - v.decider.assume(tEq, Option.when(withExp)(DebugExp.createInstance("Snapshots", isInternal_ = true)), dAInfo) + v.decider.assume(tEq, Option.when(withExp)(DebugExp.createInstance("Snapshots", isInternal_ = true)), analysisInfoes) /* In the future it might be worth to recheck whether the permissions needed, in the case of * success being an instance of Incomplete, are zero. @@ -201,7 +201,7 @@ object magicWandSupporter extends SymbolicExecutionRules { * from heap, i.e. that tEq does not result in already having the required permissions before * consuming from heap. */ - if (v.decider.checkSmoke(dAInfo)) { + if (v.decider.checkSmoke(analysisInfoes)) { (Complete(), sOut, h +: hps, cch +: cchs) } else { (success, sOut, h +: hps, cch +: cchs) @@ -213,7 +213,7 @@ object magicWandSupporter extends SymbolicExecutionRules { assert(consumedChunks.length == hs.length) Q(s1, heaps.reverse, consumedChunks.reverse, v) case Incomplete(_, _) => - if(s1.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, dAInfo, v.reportFurtherErrors()) + if(s1.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfoes, v.reportFurtherErrors()) if(s1.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s1, heaps.reverse, consumedChunks.reverse, v) else failure } } @@ -242,7 +242,7 @@ object magicWandSupporter extends SymbolicExecutionRules { proofScript: ast.Seqn, pve: PartialVerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Chunk, Verifier) => VerificationResult) : VerificationResult = { @@ -315,16 +315,16 @@ object magicWandSupporter extends SymbolicExecutionRules { freshSnapRoot: Var, snapRhs: Term, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) : VerificationResult = { val preMark = v.decider.setPathConditionMark() v.decider.prover.comment(s"Create MagicWandSnapFunction for wand $wand") - val wandSnapshot = this.createMagicWandSnapshot(freshSnapRoot, snapRhs, v, dAInfo) + val wandSnapshot = this.createMagicWandSnapshot(freshSnapRoot, snapRhs, v, analysisInfoes) val bodyVars = wand.subexpressionsToEvaluate(s.program) - evals(s, bodyVars, _ => pve, v, dAInfo)((s2, tArgs, eArgsNew, v2) => { + evals(s, bodyVars, _ => pve, v, analysisInfoes)((s2, tArgs, eArgsNew, v2) => { // Currently, the snapshot of a wand differs depending on whether it is a quantified magic wand or not. // Therefore, we have to keep the case distinction here and cannot leave everything but the chunk creation // to the HeapSupporter. @@ -335,14 +335,14 @@ object magicWandSupporter extends SymbolicExecutionRules { val (sm, smValueDef) = quantifiedChunkSupporter.singletonSnapshotMap(s2, wand, tArgs, snapshotTerm, v2) v2.decider.prover.comment("Definitional axioms for singleton-SM's value") val debugExp = Option.when(withExp)(DebugExp.createInstance("Definitional axioms for singleton-SM's value", true)) - v2.decider.assumeDefinition(smValueDef, debugExp, dAInfo) + v2.decider.assumeDefinition(smValueDef, debugExp, analysisInfoes) val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(formalVars, formalVarExps, wand, tArgs, - eArgsNew, FullPerm, Option.when(withExp)(ast.FullPerm()()), sm, s.program, v, dAInfo, isExhale=false) + eArgsNew, FullPerm, Option.when(withExp)(ast.FullPerm()()), sm, s.program, v, analysisInfoes, isExhale=false) val conservedPcs = s2.conservedPcs.head :+ v2.decider.pcs.after(preMark).definitionsOnly (s2, ch, conservedPcs.flatMap(_.conditionalized), Option.when(withExp)(conservedPcs.flatMap(_.conditionalizedExp)), v2) } else { val ch = MagicWandChunk.apply(MagicWandIdentifier(wand, s.program), s2.g.values, tArgs, eArgsNew, wandSnapshot, FullPerm, - Option.when(withExp)(ast.FullPerm()(wand.pos, wand.info, wand.errT)), v.decider.getAnalysisInfo(dAInfo)) + Option.when(withExp)(ast.FullPerm()(wand.pos, wand.info, wand.errT)), v.decider.getAnalysisInfo(analysisInfoes)) val conservedPcs = s2.conservedPcs.head :+ v2.decider.pcs.after(preMark).definitionsOnly // Partition path conditions into a set which include the freshSnapRoot and those which do not val (pcsWithFreshSnapRoot, pcsWithoutFreshSnapRoot) = conservedPcs.flatMap(pcs => pcs.conditionalized).partition(_.contains(freshSnapRoot)) @@ -376,8 +376,8 @@ object magicWandSupporter extends SymbolicExecutionRules { val freshSnapRoot = freshSnap(sorts.Snap, v1) // Produce the wand's LHS. - val dAInfoLeft = dAInfo.withSource(ExpAnalysisSourceInfo(wand.left, wand.left.pos)) - produce(s1.copy(conservingSnapshotGeneration = true), toSf(freshSnapRoot), wand.left, pve, v1, dAInfoLeft)((sLhs, v2) => { + val analysisInfoesLeft = analysisInfoes.withSource(ExpAnalysisSourceInfo(wand.left, wand.left.pos)) + produce(s1.copy(conservingSnapshotGeneration = true), toSf(freshSnapRoot), wand.left, pve, v1, analysisInfoesLeft)((sLhs, v2) => { val proofScriptCfg = proofScript.toCfg() val emptyHeap = v2.heapSupporter.getEmptyHeap(sLhs.program) @@ -413,10 +413,10 @@ object magicWandSupporter extends SymbolicExecutionRules { // This part indirectly calls the methods `this.transfer` and `this.consumeFromMultipleHeaps`. consume( proofScriptState.copy(oldHeaps = s2.oldHeaps, reserveCfgs = proofScriptState.reserveCfgs.tail), - wand.right, true, pve, proofScriptVerifier, dAInfo + wand.right, true, pve, proofScriptVerifier, analysisInfoes )((s3, snapRhs, v3) => { analysisLabels = v.decider.pcs.after(prePackageMark).analysisLabels - createWandChunkAndRecordResults(s3.copy(exhaleExt = false, oldHeaps = s.oldHeaps), freshSnapRoot, snapRhs.get, v3, dAInfo) + createWandChunkAndRecordResults(s3.copy(exhaleExt = false, oldHeaps = s.oldHeaps), freshSnapRoot, snapRhs.get, v3, analysisInfoes) }) }) }) @@ -428,11 +428,11 @@ object magicWandSupporter extends SymbolicExecutionRules { // Moreover, we need to set reserveHeaps to structurally match [State RHS] below. val emptyHeap = v.heapSupporter.getEmptyHeap(sEmp.program) val s1 = sEmp.copy(reserveHeaps = emptyHeap +: emptyHeap +: emptyHeap +: s.reserveHeaps.tail) - createWandChunkAndRecordResults(s1, freshSnap(sorts.Snap, v), freshSnap(sorts.Snap, v), v, dAInfo) + createWandChunkAndRecordResults(s1, freshSnap(sorts.Snap, v), freshSnap(sorts.Snap, v), v, analysisInfoes) } // some of the analysis labels, introduced while verifying the package statement, might be needed later on -> reassume them - analysisLabels foreach (l => v.decider.assume(v.decider.wrapWithDependencyAnalysisLabel(l, Set.empty, Set(l)), None, dAInfo.withDependencyType(DependencyType.Internal))) + analysisLabels foreach (l => v.decider.assume(v.decider.wrapWithDependencyAnalysisLabel(l, Set.empty, Set(l)), None, analysisInfoes.withDependencyType(DependencyType.Internal))) recordedBranches.foldLeft(tempResult)((prevRes, recordedState) => { prevRes && { @@ -448,10 +448,10 @@ object magicWandSupporter extends SymbolicExecutionRules { val exp = viper.silicon.utils.ast.BigAnd(branchConditionsExp.map(_._1)) val expNew = Option.when(withExp)(viper.silicon.utils.ast.BigAnd(branchConditionsExp.map(_._2.get))) // Set the branch conditions - v1.decider.setCurrentBranchCondition(And(branchConditions map (t => v1.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t)))), (exp, expNew), dAInfo) + v1.decider.setCurrentBranchCondition(And(branchConditions map (t => v1.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t)))), (exp, expNew), analysisInfoes) // Recreate all path conditions in the Z3 proof script that we recorded for that branch - v1.decider.assume(conservedPcs._1 map (t => v1.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t))), conservedPcs._2, dAInfo.withDependencyType(DependencyType.Internal)) + v1.decider.assume(conservedPcs._1 map (t => v1.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t))), conservedPcs._2, analysisInfoes.withDependencyType(DependencyType.Internal)) // Execute the continuation Q Q(s2, magicWandChunk, v1) @@ -474,13 +474,13 @@ object magicWandSupporter extends SymbolicExecutionRules { wand: ast.MagicWand, pve: PartialVerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { // Consume the magic wand instance "A --* B". - consume(s, wand, true, pve, v, dAInfo)((s1, snapWand, v1) => { + consume(s, wand, true, pve, v, analysisInfoes)((s1, snapWand, v1) => { // Consume the wand's LHS "A". - consume(s1, wand.left, true, pve, v1, dAInfo)((s2, snapLhs, v2) => { + consume(s1, wand.left, true, pve, v1, analysisInfoes)((s2, snapLhs, v2) => { /* It is assumed that snap and MagicWandSnapshot.abstractLhs are structurally the same. * Equating the two snapshots is sound iff a wand is applied only once. * The old solution in this case did use this assumption: @@ -497,13 +497,13 @@ object magicWandSupporter extends SymbolicExecutionRules { case SortWrapper(snapshot: MagicWandSnapshot, _) => snapshot.applyToMWSF(snapLhs.get) // Fallback solution for quantified magic wands case predicateLookup: PredicateLookup => - v2.decider.assume(snapLhs.get === First(snapWand.get), Option.when(withExp)(DebugExp.createInstance("Magic wand snapshot", isInternal_ = true)), dAInfo) + v2.decider.assume(snapLhs.get === First(snapWand.get), Option.when(withExp)(DebugExp.createInstance("Magic wand snapshot", isInternal_ = true)), analysisInfoes) Second(predicateLookup) case _ => snapWand.get } // Produce the wand's RHS. - produce(s3.copy(conservingSnapshotGeneration = true), toSf(magicWandSnapshotLookup), wand.right, pve, v2, dAInfo)((s4, v3) => { + produce(s3.copy(conservingSnapshotGeneration = true), toSf(magicWandSnapshotLookup), wand.right, pve, v2, analysisInfoes)((s4, v3) => { // Recreate old state without the magic wand, and the state with the oldHeap called lhs. val s5 = s4.copy(g = s1.g, conservingSnapshotGeneration = s3.conservingSnapshotGeneration) @@ -523,7 +523,7 @@ object magicWandSupporter extends SymbolicExecutionRules { failure: Failure, qvars: Seq[Var], v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (consumeFunction: (State, Heap, Term, Option[ast.Exp], Verifier) => (ConsumptionResult, State, Heap, Option[CH])) (Q: (State, Option[CH], Verifier) => VerificationResult) : VerificationResult = { @@ -540,13 +540,13 @@ object magicWandSupporter extends SymbolicExecutionRules { */ val preMark = v.decider.setPathConditionMark() executionFlowController.tryOrFail2[Stack[Heap], Stack[Option[CH]]](s, v)((s1, v1, QS) => - this.consumeFromMultipleHeaps(s1, s1.reserveHeaps.tail, perms, permsExp, failure, qvars, v1, dAInfo)(consumeFunction)(QS) + this.consumeFromMultipleHeaps(s1, s1.reserveHeaps.tail, perms, permsExp, failure, qvars, v1, analysisInfoes)(consumeFunction)(QS) )((s2, hs2, chs2, v2) => { val conservedPcs = s2.conservedPcs.head :+ v2.decider.pcs.after(preMark) val s3 = s2.copy(conservedPcs = conservedPcs +: s2.conservedPcs.tail, reserveHeaps = s.reserveHeaps.head +: hs2) val usedChunks = chs2.flatten - val (fr4, hUsed) = v2.stateConsolidator(s2).merge(s3.functionRecorder, s2, s2.reserveHeaps.head, Heap(usedChunks), v2, dAInfo) + val (fr4, hUsed) = v2.stateConsolidator(s2).merge(s3.functionRecorder, s2, s2.reserveHeaps.head, Heap(usedChunks), v2, analysisInfoes) val s4 = s3.copy(functionRecorder = fr4, reserveHeaps = hUsed +: s3.reserveHeaps.tail) @@ -591,8 +591,10 @@ object magicWandSupporter extends SymbolicExecutionRules { * is consumed from hOps and permissions for the predicate are added to the state's * heap. After a statement is executed those permissions are transferred to hOps. */ + + val analysisInfoes = DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes.withSource(StringAnalysisSourceInfo("merge", NoPosition)).withDependencyType(DependencyType.Internal) // TODO ake val emptyHeap = v.heapSupporter.getEmptyHeap(newState.program) - val (fr, hOpsJoinUsed) = v.stateConsolidator(newState).merge(newState.functionRecorder, newState, newState.reserveHeaps(1), newState.h, v, dAInfo) + val (fr, hOpsJoinUsed) = v.stateConsolidator(newState).merge(newState.functionRecorder, newState, newState.reserveHeaps(1), newState.h, v, analysisInfoes) newState.copy(functionRecorder = fr, h = emptyHeap, reserveHeaps = emptyHeap +: hOpsJoinUsed +: newState.reserveHeaps.drop(2)) } else newState diff --git a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala index 93c357412..3c9997564 100644 --- a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala +++ b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala @@ -7,7 +7,7 @@ package viper.silicon.rules import viper.silicon.debugger.DebugExp -import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfo, DependencyType} +import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfoes, DependencyType} import viper.silicon.interfaces.state._ import viper.silicon.interfaces.{Success, VerificationResult} import viper.silicon.resources.{FieldID, NonQuantifiedPropertyInterpreter, Resources} @@ -108,6 +108,8 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { Implies(And(argumentEqualities, IsPositive(ch.perm)), `?s` === ch.snap) }) + val analysisInfoes = DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes // TODO ake + val taggedSummarisingSnapshot = summarisingSnapshotDefinitions .collectFirst { @@ -122,8 +124,8 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { case None => // We have not yet checked for a definite alias val id = ChunkIdentifier(resource, s.program) - val potentialAlias = chunkSupporter.findChunk[NonQuantifiedChunk](relevantChunks, id, args, v, dAInfo) - potentialAlias.filter(c => v.decider.check(IsPositive(c.perm), Verifier.config.checkTimeout(), dAInfo)).map(_.snap) + val potentialAlias = chunkSupporter.findChunk[NonQuantifiedChunk](relevantChunks, id, args, v, analysisInfoes) + potentialAlias.filter(c => v.decider.check(IsPositive(c.perm), Verifier.config.checkTimeout(), analysisInfoes)).map(_.snap) case Some(v) => // We have checked for a definite alias and may or may not have found one. v @@ -165,16 +167,18 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { // query to check if the permission amount we have is sufficient to get the correct counterexample. If we perform // the query in two parts (one part here, one part in our caller to see if the permission amount is sufficient), // the counterexample might be wrong. + val analysisInfoes = DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes // TODO ake + if (relevantChunks.size == 1 && !Verifier.config.counterexample.isDefined) { val chunk = relevantChunks.head val argsEqual = And(chunk.args.zip(args).map { case (t1, t2) => t1 === t2 }) - if (v.decider.check(argsEqual, Verifier.config.checkTimeout(), dAInfo)) { + if (v.decider.check(argsEqual, Verifier.config.checkTimeout(), analysisInfoes)) { return Q(s, chunk.snap, chunk.perm, chunk.permExp, v) } } val (s1, taggedSnap, snapDefs, permSum, permSumExp) = summariseOnly(s, relevantChunks, resource, args, argsExp, knownValue, v) - v.decider.assumeDefinition(And(snapDefs), Option.when(withExp)(DebugExp.createInstance("Snapshot", isInternal_ = true)), dAInfo.withDependencyType(DependencyType.Internal)) + v.decider.assumeDefinition(And(snapDefs), Option.when(withExp)(DebugExp.createInstance("Snapshot", isInternal_ = true)), analysisInfoes.withDependencyType(DependencyType.Internal)) // v.decider.assume(PermAtMost(permSum, FullPerm())) /* Done in StateConsolidator instead */ val s2 = @@ -198,7 +202,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { argsExp: Option[Seq[ast.Exp]], ve: VerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Term, Verifier) => VerificationResult) : VerificationResult = { @@ -206,7 +210,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { val relevantChunks = findChunksWithID[NonQuantifiedChunk](h.values, id).toSeq if (relevantChunks.isEmpty) { - if (v.decider.checkSmoke(dAInfo, isAssert = true)) { + if (v.decider.checkSmoke(analysisInfoes, isAssert = true)) { if (s.isInPackage || Verifier.config.disableInfeasibilityChecks()) { val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) Q(s, snap, v) @@ -215,7 +219,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { } } else { val failure = createFailure(ve, v, s, False, "branch is dead") - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, dAInfo, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfoes, v.reportFurtherErrors()) if(s.retryLevel == 0 && v.reportFurtherErrors() && Verifier.config.disableInfeasibilityChecks()){ val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) failure combine Q(s, snap, v) @@ -225,12 +229,12 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { } } else { summarise(s, relevantChunks, resource, args, argsExp, None, v)((s1, snap, permSum, permSumExp, v1) => - v.decider.assert(IsPositive(permSum), dAInfo) { + v.decider.assert(IsPositive(permSum), analysisInfoes) { case true => Q(s1, snap, v1) case false => val failure = createFailure(ve, v, s1, IsPositive(permSum), permSumExp.map(IsPositive(_)())) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(IsPositive(permSum), dAInfo, v1.reportFurtherErrors()) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(IsPositive(permSum), analysisInfoes, v1.reportFurtherErrors()) if(s1.retryLevel == 0 && v1.reportFurtherErrors()) failure combine Q(s1, snap, v1) else failure }) } @@ -246,14 +250,14 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { returnSnap: Boolean, ve: VerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { if (!s.assertReadAccessOnly) - actualConsumeComplete(s, h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, dAInfo)(Q) + actualConsumeComplete(s, h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, analysisInfoes)(Q) else - summariseHeapAndAssertReadAccess(s, h, resource, perms, args, argsExp, returnSnap, ve, v, dAInfo)(Q) + summariseHeapAndAssertReadAccess(s, h, resource, perms, args, argsExp, returnSnap, ve, v, analysisInfoes)(Q) } private def summariseHeapAndAssertReadAccess(s: State, @@ -265,7 +269,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { returnSnap: Boolean, ve: VerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -274,22 +278,22 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { if (returnSnap) { summarise(s, relevantChunks, resource, args, argsExp, None, v)((s1, snap, permSum, permSumExp, v1) => - v.decider.assert(Implies(IsPositive(perm), IsPositive(permSum)), dAInfo) { + v.decider.assert(Implies(IsPositive(perm), IsPositive(permSum)), analysisInfoes) { case true => Q(s1, h, Some(snap), v1) case false => val failure = createFailure(ve, v, s1, IsPositive(permSum), permSumExp.map(IsPositive(_)())) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(Implies(IsPositive(perm), IsPositive(permSum)), dAInfo, v1.reportFurtherErrors()) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(Implies(IsPositive(perm), IsPositive(permSum)), analysisInfoes, v1.reportFurtherErrors()) if(s1.retryLevel == 0 && v1.reportFurtherErrors()) failure combine Q(s1, h, Some(snap), v1) else failure }) } else { val (s1, permSum, permSumExp) = permSummariseOnly(s, relevantChunks, resource, args, argsExp) - v.decider.assert(Implies(IsPositive(perm), IsPositive(permSum)), dAInfo) { + v.decider.assert(Implies(IsPositive(perm), IsPositive(permSum)), analysisInfoes) { case true => Q(s1, h, None, v) case false => val failure = createFailure(ve, v, s1, IsPositive(permSum), permSumExp.map(IsPositive(_)())) - if(s1.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(Implies(IsPositive(perm), IsPositive(permSum)), dAInfo, v.reportFurtherErrors()) + if(s1.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(Implies(IsPositive(perm), IsPositive(permSum)), analysisInfoes, v.reportFurtherErrors()) if(s1.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s1, h, None, v) else failure } } @@ -305,7 +309,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { returnSnap: Boolean, ve: VerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -319,16 +323,16 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { if (relevantChunks.isEmpty) { // if no permission is exhaled, return none - v.decider.assert(perms === NoPerm, dAInfo) { + v.decider.assert(perms === NoPerm, analysisInfoes) { case true => Q(s, h, None, v) case false => val failure = createFailure(ve, v, s, perms === NoPerm, permsExp.map(pe => ast.EqCmp(pe, ast.NoPerm()())(pe.pos, pe.info, pe.errT))) - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(perms === NoPerm, dAInfo, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(perms === NoPerm, analysisInfoes, v.reportFurtherErrors()) if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, h, None, v) else failure } } else { if (!terms.utils.consumeExactRead(perms, s.constrainableARPs)) { - actualConsumeCompleteConstrainable(s, relevantChunks, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, dAInfo)((s1, updatedChunks, optSnap, v2) => { + actualConsumeCompleteConstrainable(s, relevantChunks, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, analysisInfoes)((s1, updatedChunks, optSnap, v2) => { Q(s1, Heap(updatedChunks ++ otherChunks), optSnap, v2) }) } else { @@ -339,8 +343,8 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { val newChunks = ListBuffer[NonQuantifiedChunk]() var moreNeeded = true - val definiteAlias = chunkSupporter.findChunk[NonQuantifiedChunk](relevantChunks, id, args, v, dAInfo).filter(c => - v.decider.check(IsPositive(c.perm), Verifier.config.checkTimeout(), dAInfo) + val definiteAlias = chunkSupporter.findChunk[NonQuantifiedChunk](relevantChunks, id, args, v, analysisInfoes).filter(c => + v.decider.check(IsPositive(c.perm), Verifier.config.checkTimeout(), analysisInfoes) ) val sortFunction: (NonQuantifiedChunk, NonQuantifiedChunk) => Boolean = (ch1, ch2) => { @@ -381,16 +385,16 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { pSum = PermPlus(pSum, Ite(eq, ch.perm, NoPerm)) pSumExp = eqExp.map(eq => ast.PermAdd(pSumExp.get, ast.CondExp(eq, ch.permExp.get, ast.NoPerm()())(eq.pos, eq.info, eq.errT))()) - val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, pTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, pTakenExp.get)(pe.pos, pe.info, pe.errT)), v.decider.getAnalysisInfo(dAInfo)).asInstanceOf[NonQuantifiedChunk] - val _ = GeneralChunk.withPerm(ch, pTaken, None, v.decider.getAnalysisInfo(dAInfo), isExhale=true) + val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, pTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, pTakenExp.get)(pe.pos, pe.info, pe.errT)), v.decider.getAnalysisInfo(analysisInfoes)).asInstanceOf[NonQuantifiedChunk] + val _ = GeneralChunk.withPerm(ch, pTaken, None, v.decider.getAnalysisInfo(analysisInfoes), isExhale=true) pNeeded = PermMinus(pNeeded, pTaken) pNeededExp = permsExp.map(pe => ast.PermSub(pNeededExp.get, pTakenExp.get)(pe.pos, pe.info, pe.errT)) - if (!v.decider.check(IsNonPositive(newChunk.perm), Verifier.config.splitTimeout(), dAInfo.withDependencyType(DependencyType.Internal))) { + if (!v.decider.check(IsNonPositive(newChunk.perm), Verifier.config.splitTimeout(), analysisInfoes.withDependencyType(DependencyType.Internal))) { newChunks.append(newChunk) } - moreNeeded = !v.decider.check(pNeeded === NoPerm, Verifier.config.splitTimeout(), dAInfo) + moreNeeded = !v.decider.check(pNeeded === NoPerm, Verifier.config.splitTimeout(), analysisInfoes) } else { newChunks.append(ch) } @@ -403,7 +407,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { newChunks foreach { ch => val resource = Resources.resourceDescriptions(ch.resourceID) val pathCond = interpreter.buildPathConditionsForChunk(ch, resource.instanceProperties(s.mayAssumeUpperBounds)) - pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), dAInfo)) + pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), analysisInfoes)) } val newHeap = Heap(allChunks) @@ -413,7 +417,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { if (returnSnap) { summarise(s0, relevantChunks.toSeq, resource, args, argsExp, Some(definiteAlias.map(_.snap)), v)((s1, snap, _, _, v1) => { - val condSnap = Some(if (v1.decider.check(IsPositive(perms), Verifier.config.checkTimeout(), dAInfo.withDependencyType(DependencyType.Internal))) { + val condSnap = Some(if (v1.decider.check(IsPositive(perms), Verifier.config.checkTimeout(), analysisInfoes.withDependencyType(DependencyType.Internal))) { snap } else { Ite(IsPositive(perms), snap.convert(sorts.Snap), Unit) @@ -421,12 +425,12 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { if (!moreNeeded) { Q(s1, newHeap, condSnap, v1) } else { - v1.decider.assert(pNeeded === NoPerm, dAInfo) { + v1.decider.assert(pNeeded === NoPerm, analysisInfoes) { case true => Q(s1, newHeap, condSnap, v1) case false => val failure = createFailure(ve, v1, s1, pNeeded === NoPerm, pNeededExp.map(pn => ast.EqCmp(pn, ast.NoPerm()())(pn.pos, pn.info, pn.errT))) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(pNeeded === NoPerm, dAInfo, v1.reportFurtherErrors()) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(pNeeded === NoPerm, analysisInfoes, v1.reportFurtherErrors()) if(s1.retryLevel == 0 && v1.reportFurtherErrors()) failure combine Q(s1, newHeap, condSnap, v1) else failure } } @@ -435,12 +439,12 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { if (!moreNeeded) { Q(s0, newHeap, None, v) } else { - v.decider.assert(pNeeded === NoPerm, dAInfo) { + v.decider.assert(pNeeded === NoPerm, analysisInfoes) { case true => Q(s0, newHeap, None, v) case false => val failure = createFailure(ve, v, s0, pNeeded === NoPerm, pNeededExp.map(pn => ast.EqCmp(pn, ast.NoPerm()())(pn.pos, pn.info, pn.errT))) - if(s0.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(pNeeded === NoPerm, dAInfo, v.reportFurtherErrors()) + if(s0.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(pNeeded === NoPerm, analysisInfoes, v.reportFurtherErrors()) if(s0.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s0, newHeap, None, v) else failure } } @@ -459,7 +463,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { returnSnap: Boolean, ve: VerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, ListBuffer[NonQuantifiedChunk], Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -496,13 +500,13 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { ast.Implies(ast.Not(eqExp.get)(), ast.EqCmp(permTakenExp.get, ast.NoPerm()())())(pe.pos, pe.info, pe.errT)))) - v.decider.assume(constraint, Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp)), dAInfo) + v.decider.assume(constraint, Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp)), analysisInfoes) newFr = newFr.recordPathSymbol(permTaken.applicable.asInstanceOf[Function]).recordConstraint(constraint) @unused // required in order to ensure a sound dependency analysis - val _ = GeneralChunk.withPerm(ch, permTaken, None, v.decider.getAnalysisInfo(dAInfo), isExhale=true) - NonQuantifiedChunk.withPerm(ch, PermMinus(ch.perm, permTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, permTakenExp.get)(pe.pos, pe.info, pe.errT)), v.decider.getAnalysisInfo(dAInfo)) + val _ = GeneralChunk.withPerm(ch, permTaken, None, v.decider.getAnalysisInfo(analysisInfoes), isExhale=true) + NonQuantifiedChunk.withPerm(ch, PermMinus(ch.perm, permTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, permTakenExp.get)(pe.pos, pe.info, pe.errT)), v.decider.getAnalysisInfo(analysisInfoes)) }) val totalTakenBounds = @@ -514,16 +518,16 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { val constraintExp = permsExp.map(pe => ast.Implies(ast.NeCmp(totalPermSumExp.get, ast.NoPerm()())(), ast.And(ast.PermLeCmp(ast.NoPerm()(), totalPermTakenExp.get)(), ast.PermLeCmp(totalPermTakenExp.get, totalPermSumExp.get)())(pe.pos, pe.info, pe.errT))()) - v.decider.assume(totalTakenBounds, constraintExp, constraintExp, dAInfo) + v.decider.assume(totalTakenBounds, constraintExp, constraintExp, analysisInfoes) newFr = newFr.recordConstraint(totalTakenBounds) val s1 = s.copy(functionRecorder = newFr) - v.decider.assert(Implies(PermLess(NoPerm, perms), totalPermTaken !== NoPerm), dAInfo) { + v.decider.assert(Implies(PermLess(NoPerm, perms), totalPermTaken !== NoPerm), analysisInfoes) { case true => val constraintExp = permsExp.map(pe => ast.EqCmp(pe, totalPermTakenExp.get)()) - v.decider.assume(perms === totalPermTaken, Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp)), dAInfo) + v.decider.assume(perms === totalPermTaken, Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp)), analysisInfoes) if (returnSnap) { summarise(s1, relevantChunks.toSeq, resource, args, argsExp, None, v)((s2, snap, _, _, v1) => Q(s2, updatedChunks, Some(snap), v1)) @@ -533,7 +537,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { case false => v.decider.finishDebugSubExp(s"consume permissions for ${resource.toString()}") val failure = createFailure(ve, v, s, totalPermTaken !== NoPerm, totalPermTakenExp.map(tpt => ast.NeCmp(tpt, ast.NoPerm()())())) - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(Implies(PermLess(NoPerm, perms), totalPermTaken !== NoPerm), dAInfo, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(Implies(PermLess(NoPerm, perms), totalPermTaken !== NoPerm), analysisInfoes, v.reportFurtherErrors()) if(s.retryLevel == 0 && v.reportFurtherErrors()){ val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) // TODO ake: function recorder failure combine Q(s1, updatedChunks, if(returnSnap) Some(snap) else None, v) @@ -546,7 +550,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { private val freeReceiver = Var(Identifier("?rcvr"), sorts.Ref, false) private val freeReceiverExp = ast.LocalVar("?rcvr", ast.Ref)() - def assumeFieldPermissionUpperBounds(h: Heap, v: Verifier, dAInfo: DependencyAnalysisInfo): Unit = { + def assumeFieldPermissionUpperBounds(h: Heap, v: Verifier, analysisInfoes: DependencyAnalysisInfoes): Unit = { // TODO: Instead of "manually" assuming such upper bounds, appropriate PropertyInterpreters // should be used, see StateConsolidator val relevantChunksPerField = MMap.empty[String, MList[BasicChunk]] @@ -574,7 +578,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { relevantChunks foreach (chunk => { val instantiatedPermSum = permissionSum.replace(freeReceiver, chunk.args.head) val exp = permissionSumExp.map(pse => ast.PermLeCmp(replaceVarsInExp(pse, Seq(freeReceiverExp.name), Seq(chunk.argsExp.get.head)), ast.FullPerm()())()) - v.decider.assume(PermAtMost(instantiatedPermSum, FullPerm), exp, exp, dAInfo.withDependencyType(DependencyType.Internal)) + v.decider.assume(PermAtMost(instantiatedPermSum, FullPerm), exp, exp, analysisInfoes.withDependencyType(DependencyType.Internal)) }) } } diff --git a/src/main/scala/rules/PermissionSupporter.scala b/src/main/scala/rules/PermissionSupporter.scala index 5be402858..17f6fda83 100644 --- a/src/main/scala/rules/PermissionSupporter.scala +++ b/src/main/scala/rules/PermissionSupporter.scala @@ -6,7 +6,7 @@ package viper.silicon.rules -import viper.silicon.dependencyAnalysis.DependencyAnalysisInfo +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfoes import viper.silicon.interfaces.VerificationResult import viper.silicon.state.State import viper.silicon.state.terms.{Term, Var, perms} @@ -16,7 +16,7 @@ import viper.silver.verifier.PartialVerificationError import viper.silver.verifier.reasons.{NegativePermission, NonPositivePermission} object permissionSupporter extends SymbolicExecutionRules { - def assertNotNegative(s: State, tPerm: Term, ePerm: ast.Exp, ePermNew: Option[ast.Exp], pve: PartialVerificationError, v: Verifier, dAInfo: DependencyAnalysisInfo) + def assertNotNegative(s: State, tPerm: Term, ePerm: ast.Exp, ePermNew: Option[ast.Exp], pve: PartialVerificationError, v: Verifier, analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -24,18 +24,18 @@ object permissionSupporter extends SymbolicExecutionRules { case k: Var if s.constrainableARPs.contains(k) => Q(s, v) case _ => - v.decider.assert(perms.IsNonNegative(tPerm), dAInfo) { + v.decider.assert(perms.IsNonNegative(tPerm), analysisInfoes) { case true => Q(s, v) case false => val assertExp = ePermNew.map(ep => perms.IsNonNegative(ep)(ep.pos, ep.info, ep.errT)) val failure = createFailure(pve dueTo NegativePermission(ePerm), v, s, perms.IsNonNegative(tPerm), assertExp) - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(perms.IsNonNegative(tPerm), dAInfo, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(perms.IsNonNegative(tPerm), analysisInfoes, v.reportFurtherErrors()) if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, v) else failure } } } - def assertPositive(s: State, tPerm: Term, ePerm: ast.Exp, pve: PartialVerificationError, v: Verifier, dAInfo: DependencyAnalysisInfo) + def assertPositive(s: State, tPerm: Term, ePerm: ast.Exp, pve: PartialVerificationError, v: Verifier, analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -43,11 +43,11 @@ object permissionSupporter extends SymbolicExecutionRules { case k: Var if s.constrainableARPs.contains(k) => Q(s, v) case _ => - v.decider.assert(perms.IsPositive(tPerm), dAInfo) { + v.decider.assert(perms.IsPositive(tPerm), analysisInfoes) { case true => Q(s, v) case false => val failure = createFailure(pve dueTo NonPositivePermission(ePerm), v, s, perms.IsPositive(tPerm), Option.when(withExp)(perms.IsPositive(ePerm)())) - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(perms.IsPositive(tPerm), dAInfo, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(perms.IsPositive(tPerm), analysisInfoes, v.reportFurtherErrors()) if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, v) else failure } } diff --git a/src/main/scala/rules/PredicateSupporter.scala b/src/main/scala/rules/PredicateSupporter.scala index a94819679..b96b28d9d 100644 --- a/src/main/scala/rules/PredicateSupporter.scala +++ b/src/main/scala/rules/PredicateSupporter.scala @@ -10,7 +10,7 @@ import viper.silicon import viper.silicon.Config.JoinMode import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp -import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfo, DependencyType} +import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfoes, DependencyType} import viper.silicon.interfaces.VerificationResult import viper.silicon.interfaces.state.{ChunkIdentifer, GeneralChunk, NonQuantifiedChunk} import viper.silicon.resources.FieldID @@ -33,7 +33,7 @@ trait PredicateSupportRules extends SymbolicExecutionRules { constrainableWildcards: InsertionOrderedSet[Var], pve: PartialVerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Verifier) => VerificationResult) : VerificationResult @@ -47,7 +47,7 @@ trait PredicateSupportRules extends SymbolicExecutionRules { pve: PartialVerificationError, v: Verifier, pa: ast.PredicateAccess, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Verifier) => VerificationResult) : VerificationResult } @@ -65,7 +65,7 @@ object predicateSupporter extends PredicateSupportRules { constrainableWildcards: InsertionOrderedSet[Var], pve: PartialVerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -79,33 +79,33 @@ object predicateSupporter extends PredicateSupportRules { val s1 = s.copy(g = gIns, smDomainNeeded = true) .scalePermissionFactor(tPerm, ePerm) - consume(s1, body, true, pve, v, dAInfo)((s1a, snap, v1) => { + consume(s1, body, true, pve, v, analysisInfoes)((s1a, snap, v1) => { if (!Verifier.config.disableFunctionUnfoldTrigger()) { val predTrigger = App(s1a.predicateData(predicate.name).triggerFunction, snap.get.convert(terms.sorts.Snap) +: tArgs) val eArgsString = eArgs.mkString(", ") - v1.decider.assume(predTrigger, Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}($eArgsString))")), dAInfo) + v1.decider.assume(predTrigger, Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}($eArgsString))")), analysisInfoes) } val s2 = s1a.copy(g = s.g, smDomainNeeded = s.smDomainNeeded, permissionScalingFactor = s.permissionScalingFactor, permissionScalingFactorExp = s.permissionScalingFactorExp).setConstrainable(constrainableWildcards, false) - v1.heapSupporter.produceSingle(s2, predicate, tArgs, eArgs, snap.get.convert(s2.predicateSnapMap(predicate.name)), None, tPerm, ePerm, pve, true, v1, dAInfo)((s3, v3) => { - val s4 = v3.heapSupporter.triggerResourceIfNeeded(s3, pa, tArgs, eArgs, v3, dAInfo) + v1.heapSupporter.produceSingle(s2, predicate, tArgs, eArgs, snap.get.convert(s2.predicateSnapMap(predicate.name)), None, tPerm, ePerm, pve, true, v1, analysisInfoes)((s3, v3) => { + val s4 = v3.heapSupporter.triggerResourceIfNeeded(s3, pa, tArgs, eArgs, v3, analysisInfoes) Q(s4, v3) }) }) } - def producePredicateContents(s: State, tree: PredicateContentsTree, toReplace: silicon.Map[Term, Term], v: Verifier, dAInfo: DependencyAnalysisInfo, isUnfolding: Boolean = false) + def producePredicateContents(s: State, tree: PredicateContentsTree, toReplace: silicon.Map[Term, Term], v: Verifier, analysisInfoes: DependencyAnalysisInfoes, isUnfolding: Boolean = false) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { tree match { case PredicateLeafNode(h, assumptions) => val debugExp = Option.when(withExp)(DebugExp.createInstance("Assumption from unfolded predicate body")) - assumptions.foreach(a => v.decider.assume(a.replace(toReplace), debugExp, dAInfo)) - val substChunks = h.values.map(chunk => GeneralChunk.permScale(GeneralChunk.substitute(chunk.asInstanceOf[GeneralChunk], toReplace, v.decider.getAnalysisInfo(dAInfo)), s.permissionScalingFactor, s.permissionScalingFactorExp, v.decider.getAnalysisInfo(dAInfo))) + assumptions.foreach(a => v.decider.assume(a.replace(toReplace), debugExp, analysisInfoes)) + val substChunks = h.values.map(chunk => GeneralChunk.permScale(GeneralChunk.substitute(chunk.asInstanceOf[GeneralChunk], toReplace, v.decider.getAnalysisInfo(analysisInfoes)), s.permissionScalingFactor, s.permissionScalingFactorExp, v.decider.getAnalysisInfo(analysisInfoes))) val quantifiedResourceIdentifiers: Set[ChunkIdentifer] = s.qpPredicates.map(p => BasicChunkIdentifier(p.name)) ++ s.qpFields.map(f => BasicChunkIdentifier(f.name)) ++ s.qpMagicWands @@ -119,28 +119,28 @@ object predicateSupporter extends PredicateSupportRules { case _ => s.program.findPredicate(bc.id.name) } val (sm, smValueDef) = quantifiedChunkSupporter.singletonSnapshotMap(s, resource, bc.args, bc.snap, v) - v.decider.assumeDefinition(smValueDef, None, dAInfo) + v.decider.assumeDefinition(smValueDef, None, analysisInfoes) val codQvars = bc.resourceID match { case FieldID => Seq(`?r`) case _ => s.predicateFormalVarMap(resource.asInstanceOf[ast.Predicate].name) } newFr = newFr.recordFvfAndDomain(SnapshotMapDefinition(resource, sm, Seq(smValueDef), Seq())) - quantifiedChunkSupporter.createSingletonQuantifiedChunk(codQvars, None, resource, bc.args, None, bc.perm, None, sm, s.program, v, dAInfo, isExhale=false) + quantifiedChunkSupporter.createSingletonQuantifiedChunk(codQvars, None, resource, bc.args, None, bc.perm, None, sm, s.program, v, analysisInfoes, isExhale=false) case mwc: MagicWandChunk => val wand = mwc.id.ghostFreeWand val bodyVars = wand.subexpressionsToEvaluate(s.program) val codQvars = bodyVars.indices.toList.map(i => Var(Identifier(s"x$i"), v.symbolConverter.toSort(bodyVars(i).typ), false)) val (sm, smValueDef) = quantifiedChunkSupporter.singletonSnapshotMap(s, wand, mwc.args, mwc.snap, v) - v.decider.assumeDefinition(smValueDef, None, dAInfo) + v.decider.assumeDefinition(smValueDef, None, analysisInfoes) newFr = newFr.recordFvfAndDomain(SnapshotMapDefinition(wand, sm, Seq(smValueDef), Seq())) - quantifiedChunkSupporter.createSingletonQuantifiedChunk(codQvars, None, wand, mwc.args, None, mwc.perm, None, sm, s.program, v, dAInfo, isExhale=false) + quantifiedChunkSupporter.createSingletonQuantifiedChunk(codQvars, None, wand, mwc.args, None, mwc.perm, None, sm, s.program, v, analysisInfoes, isExhale=false) } } else { c } }) val substHeap = Heap(substChunksOptQps) - val (fr1, h1) = v.stateConsolidator(s).merge(newFr, s, s.h, substHeap, v, dAInfo) + val (fr1, h1) = v.stateConsolidator(s).merge(newFr, s, s.h, substHeap, v, analysisInfoes) val s1 = s.copy(h = h1, functionRecorder = fr1) Q(s1, v) @@ -148,13 +148,13 @@ object predicateSupporter extends PredicateSupportRules { val substCond = cond.replace(toReplace) if (!isUnfolding && s.moreJoins.id >= JoinMode.Impure.id) { - joiner.join[scala.Null, scala.Null](s, v, dAInfo, resetState = false)((s1, v1, QB) => { - brancher.branch(s1, substCond, condExp, v1, dAInfo)( + joiner.join[scala.Null, scala.Null](s, v, analysisInfoes, resetState = false)((s1, v1, QB) => { + brancher.branch(s1, substCond, condExp, v1, analysisInfoes)( (s2, v2) => { - producePredicateContents(s2, left, toReplace, v2, dAInfo, isUnfolding)((s3, v3) => QB(s3, null, v3)) + producePredicateContents(s2, left, toReplace, v2, analysisInfoes, isUnfolding)((s3, v3) => QB(s3, null, v3)) }, (s2, v2) => { - producePredicateContents(s2, right, toReplace, v2, dAInfo, isUnfolding)((s3, v3) => QB(s3, null, v3)) + producePredicateContents(s2, right, toReplace, v2, analysisInfoes, isUnfolding)((s3, v3) => QB(s3, null, v3)) } ) }) (entries => { @@ -169,12 +169,12 @@ object predicateSupporter extends PredicateSupportRules { (s2, null) }) ((sp, _, vp) => Q(sp, vp)) } else { - brancher.branch(s, substCond, condExp, v, dAInfo)( + brancher.branch(s, substCond, condExp, v, analysisInfoes)( (s1, v1) => { - producePredicateContents(s1, left, toReplace, v1, dAInfo, isUnfolding)(Q) + producePredicateContents(s1, left, toReplace, v1, analysisInfoes, isUnfolding)(Q) }, (s2, v2) => { - producePredicateContents(s2, right, toReplace, v2, dAInfo, isUnfolding)(Q) + producePredicateContents(s2, right, toReplace, v2, analysisInfoes, isUnfolding)(Q) } ) } @@ -191,7 +191,7 @@ object predicateSupporter extends PredicateSupportRules { pve: PartialVerificationError, v: Verifier, pa: ast.PredicateAccess, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -203,19 +203,19 @@ object predicateSupporter extends PredicateSupportRules { val body = predicate.body.get /* Only non-abstract predicates can be unfolded */ val s1 = s.scalePermissionFactor(tPerm, ePerm) - v.heapSupporter.consumeSingle(s1, s1.h, pa, tArgs, eArgs, tPerm, ePerm, true, pve, v, dAInfo)((s2, h2, snap, v1) => { + v.heapSupporter.consumeSingle(s1, s1.h, pa, tArgs, eArgs, tPerm, ePerm, true, pve, v, analysisInfoes)((s2, h2, snap, v1) => { val s3 = s2.copy(g = gIns, h = h2) .setConstrainable(constrainableWildcards, false) if (s3.predicateData(predicate.name).predContents.isDefined) { val toReplace: silicon.Map[Term, Term] = silicon.Map.from(s3.predicateData(predicate.name).params.get.zip(Seq(snap.get) ++ tArgs)) - producePredicateContents(s3, s3.predicateData(predicate.name).predContents.get, toReplace, v1, dAInfo, false)((s4, v4) => { + producePredicateContents(s3, s3.predicateData(predicate.name).predContents.get, toReplace, v1, analysisInfoes, false)((s4, v4) => { v4.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterUnfold) if (!Verifier.config.disableFunctionUnfoldTrigger()) { val predicateTrigger = App(s4.predicateData(predicate.name).triggerFunction, snap.get.convert(terms.sorts.Snap) +: tArgs) val eargs = eArgs.mkString(", ") - v4.decider.assume(predicateTrigger, Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}($eargs))")), dAInfo.withDependencyType(DependencyType.Trigger)) + v4.decider.assume(predicateTrigger, Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}($eargs))")), analysisInfoes.withDependencyType(DependencyType.Trigger)) } Q(s4.copy(g = s.g, permissionScalingFactor = s.permissionScalingFactor, @@ -223,14 +223,14 @@ object predicateSupporter extends PredicateSupportRules { v4) }) } else { - produce(s3, toSf(snap.get), body, pve, v1, dAInfo)((s4, v2) => { + produce(s3, toSf(snap.get), body, pve, v1, analysisInfoes)((s4, v2) => { v2.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterUnfold) if (!Verifier.config.disableFunctionUnfoldTrigger()) { val predicateTrigger = App(s4.predicateData(predicate.name).triggerFunction, snap.get.convert(terms.sorts.Snap) +: tArgs) val eargs = eArgs.mkString(", ") - v2.decider.assume(predicateTrigger, Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}($eargs))")), dAInfo.withDependencyType(DependencyType.Trigger)) + v2.decider.assume(predicateTrigger, Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}($eargs))")), analysisInfoes.withDependencyType(DependencyType.Trigger)) } Q(s4.copy(g = s.g, permissionScalingFactor = s.permissionScalingFactor, diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index 6787c1747..e5785abb9 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -8,7 +8,7 @@ package viper.silicon.rules import viper.silicon.Config.JoinMode import viper.silicon.debugger.DebugExp -import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfo, DependencyType} +import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfoes, DependencyType} import viper.silicon.interfaces.{Unreachable, VerificationResult} import viper.silicon.logger.records.data.{CondExpRecord, ImpliesRecord, ProduceRecord} import viper.silicon.state._ @@ -41,7 +41,7 @@ trait ProductionRules extends SymbolicExecutionRules { a: ast.Exp, pve: PartialVerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Verifier) => VerificationResult) : VerificationResult @@ -66,7 +66,7 @@ trait ProductionRules extends SymbolicExecutionRules { as: Seq[ast.Exp], pvef: ast.Exp => PartialVerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Verifier) => VerificationResult) : VerificationResult } @@ -106,11 +106,11 @@ object producer extends ProductionRules { a: ast.Exp, pve: PartialVerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Verifier) => VerificationResult) : VerificationResult = - produceR(s, sf, a.whenInhaling, pve, v, dAInfo)(Q) + produceR(s, sf, a.whenInhaling, pve, v, analysisInfoes)(Q) /** @inheritdoc */ def produces(s: State, @@ -118,7 +118,7 @@ object producer extends ProductionRules { as: Seq[ast.Exp], pvef: ast.Exp => PartialVerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -133,7 +133,7 @@ object producer extends ProductionRules { allPves ++= pves }) - produceTlcs(s, sf, allTlcs.result(), allPves.result(), v, dAInfo)(Q) + produceTlcs(s, sf, allTlcs.result(), allPves.result(), v, analysisInfoes)(Q) } private def produceTlcs(s: State, @@ -141,7 +141,7 @@ object producer extends ProductionRules { as: Seq[ast.Exp], pves: Seq[PartialVerificationError], v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -151,31 +151,29 @@ object producer extends ProductionRules { val a = as.head.whenInhaling val pve = pves.head - val sourceInfo = v.decider.pushAndGetAnalysisSourceInfo(a, Some(DependencyType.get(a, DependencyType.make(dAInfo)))) + val analysisInfoes1 = analysisInfoes.addInfo(a.info, a) if (as.tail.isEmpty) - wrappedProduceTlc(s, sf, a, pve, v, dAInfo)((s1, v1) => { - v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) + wrappedProduceTlc(s, sf, a, pve, v, analysisInfoes1)((s1, v1) => { Q(s1, v1) }) else { try { val (sf0, sf1) = - v.snapshotSupporter.createSnapshotPair(s, sf, a, viper.silicon.utils.ast.BigAnd(as.tail), v, dAInfo) + v.snapshotSupporter.createSnapshotPair(s, sf, a, viper.silicon.utils.ast.BigAnd(as.tail), v, analysisInfoes1) /* TODO: Refactor createSnapshotPair s.t. it can be used with Seq[Exp], * then remove use of BigAnd; for one it is not efficient since * the tail of the (decreasing list parameter as) is BigAnd-ed * over and over again. */ - wrappedProduceTlc(s, sf0, a, pve, v, dAInfo)((s1, v1) => { - v1.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) - produceTlcs(s1, sf1, as.tail, pves.tail, v1, dAInfo)(Q) + wrappedProduceTlc(s, sf0, a, pve, v, analysisInfoes1)((s1, v1) => { + produceTlcs(s1, sf1, as.tail, pves.tail, v1, analysisInfoes)(Q) }) } catch { // We will get an IllegalArgumentException from createSnapshotPair if sf(...) returns Unit. // This should never happen if we're in a reachable state, so here we check for that // (without timeout, since there is no fallback) and stop verifying the current branch. - case _: IllegalArgumentException if v.decider.check(False, Verifier.config.assertTimeout.getOrElse(0), dAInfo) => + case _: IllegalArgumentException if v.decider.check(False, Verifier.config.assertTimeout.getOrElse(0), analysisInfoes) => Unreachable() } @@ -188,14 +186,14 @@ object producer extends ProductionRules { a: ast.Exp, pve: PartialVerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { val tlcs = a.topLevelConjuncts val pves = Seq.fill(tlcs.length)(pve) - produceTlcs(s, sf, tlcs, pves, v, dAInfo)(Q) + produceTlcs(s, sf, tlcs, pves, v, analysisInfoes)(Q) } /** Wrapper/decorator for consume that injects the following operations: @@ -206,21 +204,21 @@ object producer extends ProductionRules { a: ast.Exp, pve: PartialVerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { if(v.decider.isPathInfeasible()){ if(!Expressions.isKnownWellDefined(a, Some(s.program))){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, dAInfo) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfoes) } - v.decider.dependencyAnalyzer.addAssumption(True, dAInfo) + v.decider.dependencyAnalyzer.addAssumption(True, analysisInfoes) return Q(s, v) } val sepIdentifier = v.symbExLog.openScope(new ProduceRecord(a, s, v.decider.pcs)) - produceTlc(s, sf, a, pve, v, dAInfo)((s1, v1) => { + produceTlc(s, sf, a, pve, v, analysisInfoes)((s1, v1) => { v1.symbExLog.closeScope(sepIdentifier) Q(s1, v1)}) } @@ -230,7 +228,7 @@ object producer extends ProductionRules { a: ast.Exp, pve: PartialVerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (continuation: (State, Verifier) => VerificationResult) : VerificationResult = { v.logger.debug(s"\nPRODUCE ${viper.silicon.utils.ast.sourceLineColumn(a)}: $a") @@ -244,16 +242,16 @@ object producer extends ProductionRules { val impliesRecord = new ImpliesRecord(imp, s, v.decider.pcs, "produce") val uidImplies = v.symbExLog.openScope(impliesRecord) - eval(s, e0, pve, v, dAInfo)((s1, t0, e0New, v1) => + eval(s, e0, pve, v, analysisInfoes)((s1, t0, e0New, v1) => // The type arguments here are Null because there is no need to pass any join data. - joiner.join[scala.Null, scala.Null](s1, v1, dAInfo, resetState = false)((s1, v1, QB) => - branch(s1.copy(parallelizeBranches = false), t0, (e0, e0New), v1, dAInfo)( - (s2, v2) => produceR(s2.copy(parallelizeBranches = s1.parallelizeBranches), sf, a0, pve, v2, dAInfo)((s3, v3) => { + joiner.join[scala.Null, scala.Null](s1, v1, analysisInfoes, resetState = false)((s1, v1, QB) => + branch(s1.copy(parallelizeBranches = false), t0, (e0, e0New), v1, analysisInfoes)( + (s2, v2) => produceR(s2.copy(parallelizeBranches = s1.parallelizeBranches), sf, a0, pve, v2, analysisInfoes)((s3, v3) => { v3.symbExLog.closeScope(uidImplies) QB(s3, null, v3) }), (s2, v2) => { - v2.decider.assume(sf(sorts.Snap, v2) === Unit, Option.when(withExp)(DebugExp.createInstance("Empty snapshot", true)), dAInfo) + v2.decider.assume(sf(sorts.Snap, v2) === Unit, Option.when(withExp)(DebugExp.createInstance("Empty snapshot", true)), analysisInfoes) /* TODO: Avoid creating a fresh var (by invoking) `sf` that is not used * otherwise. In order words, only make this assumption if `sf` has * already been used, e.g. in a snapshot equality such as `s0 == (s1, s2)`. @@ -277,14 +275,14 @@ object producer extends ProductionRules { val impliesRecord = new ImpliesRecord(imp, s, v.decider.pcs, "produce") val uidImplies = v.symbExLog.openScope(impliesRecord) - eval(s, e0, pve, v, dAInfo)((s1, t0, e0New, v1) => - branch(s1, t0, (e0, e0New), v1, dAInfo)( - (s2, v2) => produceR(s2, sf, a0, pve, v2, dAInfo)((s3, v3) => { + eval(s, e0, pve, v, analysisInfoes)((s1, t0, e0New, v1) => + branch(s1, t0, (e0, e0New), v1, analysisInfoes)( + (s2, v2) => produceR(s2, sf, a0, pve, v2, analysisInfoes)((s3, v3) => { v3.symbExLog.closeScope(uidImplies) Q(s3, v3) }), (s2, v2) => { - v2.decider.assume(sf(sorts.Snap, v2) === Unit, Option.when(withExp)(DebugExp.createInstance("Empty snapshot", isInternal_ = true)), dAInfo) + v2.decider.assume(sf(sorts.Snap, v2) === Unit, Option.when(withExp)(DebugExp.createInstance("Empty snapshot", isInternal_ = true)), analysisInfoes) /* TODO: Avoid creating a fresh var (by invoking) `sf` that is not used * otherwise. In order words, only make this assumption if `sf` has * already been used, e.g. in a snapshot equality such as `s0 == (s1, s2)`. @@ -297,15 +295,15 @@ object producer extends ProductionRules { val condExpRecord = new CondExpRecord(ite, s, v.decider.pcs, "produce") val uidCondExp = v.symbExLog.openScope(condExpRecord) - eval(s, e0, pve, v, dAInfo)((s1, t0, e0New, v1) => + eval(s, e0, pve, v, analysisInfoes)((s1, t0, e0New, v1) => // The type arguments here are Null because there is no need to pass any join data. - joiner.join[scala.Null, scala.Null](s1, v1, dAInfo, resetState = false)((s1, v1, QB) => - branch(s1.copy(parallelizeBranches = false), t0, (e0, e0New), v1, dAInfo)( - (s2, v2) => produceR(s2.copy(parallelizeBranches = s1.parallelizeBranches), sf, a1, pve, v2, dAInfo)((s3, v3) => { + joiner.join[scala.Null, scala.Null](s1, v1, analysisInfoes, resetState = false)((s1, v1, QB) => + branch(s1.copy(parallelizeBranches = false), t0, (e0, e0New), v1, analysisInfoes)( + (s2, v2) => produceR(s2.copy(parallelizeBranches = s1.parallelizeBranches), sf, a1, pve, v2, analysisInfoes)((s3, v3) => { v3.symbExLog.closeScope(uidCondExp) QB(s3, null, v3) }), - (s2, v2) => produceR(s2.copy(parallelizeBranches = s1.parallelizeBranches), sf, a2, pve, v2, dAInfo)((s3, v3) => { + (s2, v2) => produceR(s2.copy(parallelizeBranches = s1.parallelizeBranches), sf, a2, pve, v2, analysisInfoes)((s3, v3) => { v3.symbExLog.closeScope(uidCondExp) QB(s3, null, v3) })) @@ -325,29 +323,29 @@ object producer extends ProductionRules { val condExpRecord = new CondExpRecord(ite, s, v.decider.pcs, "produce") val uidCondExp = v.symbExLog.openScope(condExpRecord) - eval(s, e0, pve, v, dAInfo)((s1, t0, e0New, v1) => - branch(s1, t0, (e0, e0New), v1, dAInfo)( - (s2, v2) => produceR(s2, sf, a1, pve, v2, dAInfo)((s3, v3) => { + eval(s, e0, pve, v, analysisInfoes)((s1, t0, e0New, v1) => + branch(s1, t0, (e0, e0New), v1, analysisInfoes)( + (s2, v2) => produceR(s2, sf, a1, pve, v2, analysisInfoes)((s3, v3) => { v3.symbExLog.closeScope(uidCondExp) Q(s3, v3) }), - (s2, v2) => produceR(s2, sf, a2, pve, v2, dAInfo)((s3, v3) => { + (s2, v2) => produceR(s2, sf, a2, pve, v2, analysisInfoes)((s3, v3) => { v3.symbExLog.closeScope(uidCondExp) Q(s3, v3) }))) case let: ast.Let if !let.isPure => - letSupporter.handle[ast.Exp](s, let, pve, v, dAInfo)((s1, g1, body, v1) => - produceR(s1.copy(g = s1.g + g1), sf, body, pve, v1, dAInfo)(Q)) + letSupporter.handle[ast.Exp](s, let, pve, v, analysisInfoes)((s1, g1, body, v1) => + produceR(s1.copy(g = s1.g + g1), sf, body, pve, v1, analysisInfoes)(Q)) case accPred: ast.AccessPredicate => val eArgs = accPred.loc.args(s.program) val ePerm = accPred.perm val resource = accPred.res(s.program) - evals(s, eArgs, _ => pve, v, dAInfo)((s1, tArgs, eArgsNew, v1) => - eval(s1, ePerm, pve, v1, dAInfo)((s1a, tPerm, ePermNew, v1a) => - permissionSupporter.assertNotNegative(s1a, tPerm, ePerm, ePermNew, pve, v1a, dAInfo)((s1b, v2) => { + evals(s, eArgs, _ => pve, v, analysisInfoes)((s1, tArgs, eArgsNew, v1) => + eval(s1, ePerm, pve, v1, analysisInfoes)((s1a, tPerm, ePermNew, v1a) => + permissionSupporter.assertNotNegative(s1a, tPerm, ePerm, ePermNew, pve, v1a, analysisInfoes)((s1b, v2) => { val s2 = s1b.copy(constrainableARPs = s.constrainableARPs) val snap = sf(v2.snapshotSupporter.optimalSnapshotSort(resource, s2, v2), v2) val gain = if (!Verifier.config.unsafeWildcardOptimization() || @@ -356,7 +354,7 @@ object producer extends ProductionRules { else WildcardSimplifyingPermTimes(tPerm, s2.permissionScalingFactor) val gainExp = ePermNew.map(p => ast.PermMul(p, s2.permissionScalingFactorExp.get)(p.pos, p.info, p.errT)) - v2.heapSupporter.produceSingle(s2, resource, tArgs, eArgsNew, snap, None, gain, gainExp, pve, true, v2, dAInfo)(Q) + v2.heapSupporter.produceSingle(s2, resource, tArgs, eArgsNew, snap, None, gain, gainExp, pve, true, v2, analysisInfoes)(Q) }))) @@ -380,12 +378,12 @@ object producer extends ProductionRules { if (forall.triggers.isEmpty) None else Some(forall.triggers) val s0 = s.copy(functionRecorder = s.functionRecorder.enterQuantifiedExp(qpa)) - evalQuantified(s0, Forall, forall.variables, Seq(cond), ePerm +: eArgs, optTrigger, qid, pve, v, dAInfo) { + evalQuantified(s0, Forall, forall.variables, Seq(cond), ePerm +: eArgs, optTrigger, qid, pve, v, analysisInfoes) { case (s1, qvars, qvarExps, Seq(tCond), eCondNew, Some((Seq(tPerm, tArgs@_*), permArgs, tTriggers, (auxGlobals, auxNonGlobals), auxExps)), v1) => val s1a = s1.copy(constrainableARPs = s.constrainableARPs) v1.heapSupporter.produceQuantified(s1a, sf, forall, resource, qvars, qvarExps, tFormalArgs, eFormalArgs, qid, optTrigger, tTriggers, auxGlobals, auxNonGlobals, auxExps.map(_._1), auxExps.map(_._2), tCond, eCondNew.map(_.head), tArgs, permArgs.map(_.tail), tPerm, permArgs.map(_.head), pve, NegativePermission(ePerm), - QPAssertionNotInjective(resAcc), v1, dAInfo)((s2, v2) => { + QPAssertionNotInjective(resAcc), v1, analysisInfoes)((s2, v2) => { Q(s2.copy(functionRecorder = s2.functionRecorder.leaveQuantifiedExp(qpa)), v2) }) case (s1, _, _, _, _, None, v1) => Q(s1.copy(constrainableARPs = s.constrainableARPs), v1) @@ -397,9 +395,9 @@ object producer extends ProductionRules { /* Any regular expressions, i.e. boolean and arithmetic. */ case _ => v.decider.assume(sf(sorts.Snap, v) === Unit, - Option.when(withExp)(DebugExp.createInstance("Empty snapshot", true)), dAInfo) /* TODO: See comment for case ast.Implies above */ - eval(s, a, pve, v, dAInfo)((s1, t, aNew, v1) => { - v1.decider.assume(t, Option.when(withExp)(a), aNew, dAInfo) + Option.when(withExp)(DebugExp.createInstance("Empty snapshot", true)), analysisInfoes) /* TODO: See comment for case ast.Implies above */ + eval(s, a, pve, v, analysisInfoes)((s1, t, aNew, v1) => { + v1.decider.assume(t, Option.when(withExp)(a), aNew, analysisInfoes) Q(s1, v1)}) } diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 41d26e6b9..50ea6a1d0 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -10,6 +10,7 @@ import viper.silicon import viper.silicon.Map import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes import viper.silicon.dependencyAnalysis._ import viper.silicon.interfaces.VerificationResult import viper.silicon.interfaces.state._ @@ -26,6 +27,7 @@ import viper.silicon.utils.freshSnap import viper.silicon.utils.notNothing.NotNothing import viper.silicon.verifier.Verifier import viper.silver.ast +import viper.silver.ast.NoPosition import viper.silver.parser.PUnknown import viper.silver.reporter.InternalWarningMessage import viper.silver.verifier.reasons.{InsufficientPermission, MagicWandChunkNotFound} @@ -190,7 +192,7 @@ trait QuantifiedChunkSupport extends SymbolicExecutionRules { sm: Term, program: ast.Program, v: Verifier, - dAInfo: DependencyAnalysisInfo, + analysisInfoes: DependencyAnalysisInfoes, isExhale: Boolean) : QuantifiedBasicChunk @@ -232,7 +234,7 @@ trait QuantifiedChunkSupport extends SymbolicExecutionRules { qidPrefix: String, v: Verifier, program: ast.Program, - dAInfo: DependencyAnalysisInfo, + analysisInfoes: DependencyAnalysisInfoes, isExhale: Boolean) : (QuantifiedBasicChunk, InverseFunctions) @@ -245,7 +247,7 @@ trait QuantifiedChunkSupport extends SymbolicExecutionRules { def hintBasedChunkOrderHeuristic(hints: Seq[Term]) : Seq[QuantifiedBasicChunk] => Seq[QuantifiedBasicChunk] - def findChunk(chunks: Iterable[Chunk], chunk: QuantifiedChunk, v: Verifier, dAInfo: DependencyAnalysisInfo): Option[QuantifiedChunk] + def findChunk(chunks: Iterable[Chunk], chunk: QuantifiedChunk, v: Verifier, analysisInfoes: DependencyAnalysisInfoes): Option[QuantifiedChunk] /** Merge the snapshots of two quantified heap chunks that denote the same field locations * @@ -292,7 +294,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { sm: Term, program: ast.Program, v: Verifier, - dAInfo: DependencyAnalysisInfo, + analysisInfoes: DependencyAnalysisInfoes, isExhale: Boolean) : QuantifiedBasicChunk = { @@ -323,7 +325,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { hints, program, v, - dAInfo, + analysisInfoes, isExhale) } @@ -347,7 +349,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { qidPrefix: String, v: Verifier, program: ast.Program, - dAInfo: DependencyAnalysisInfo, + analysisInfoes: DependencyAnalysisInfoes, isExhale: Boolean) : (QuantifiedBasicChunk, InverseFunctions) = { @@ -391,7 +393,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { hints, program, v, - dAInfo, + analysisInfoes, isExhale) (ch, inverseFunctions) @@ -436,7 +438,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { hints: Seq[Term], program: ast.Program, v: Verifier, - dAInfo: DependencyAnalysisInfo, + analysisInfoes: DependencyAnalysisInfoes, isExhale: Boolean) : QuantifiedBasicChunk = { @@ -457,7 +459,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optSingletonArguments.map(_.head), optSingletonArgumentsExp.map(_.head), hints, - v.decider.getAnalysisInfo(dAInfo), + v.decider.getAnalysisInfo(analysisInfoes), isExhale) case predicate: ast.Predicate => @@ -474,7 +476,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optSingletonArguments, optSingletonArgumentsExp, hints, - v.decider.getAnalysisInfo(dAInfo), + v.decider.getAnalysisInfo(analysisInfoes), isExhale) case wand: ast.MagicWand => @@ -491,7 +493,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optSingletonArguments, optSingletonArgumentsExp, hints, - v.decider.getAnalysisInfo(dAInfo), + v.decider.getAnalysisInfo(analysisInfoes), isExhale) case other => @@ -671,16 +673,16 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { smDef: SnapshotMapDefinition, v: Verifier) : (PermMapDefinition, PmCache) = { - val dAInfo = FullDependencyAnalysisInfo.create("summarizing heap", DependencyType.Internal, NoDependencyAnalysisMerge(), NoDependencyAnalysisJoin()) + val analysisInfoes = DependencyAnalysisInfoes.create("summarizing heap", DependencyType.Internal, NoDependencyAnalysisMerge()) val res = Verifier.config.mapCache(s.pmCache.get(resource, relevantChunks)) match { case Some(pmDef) => - v.decider.assume(pmDef.valueDefinitions, Option.when(withExp)(DebugExp.createInstance("value definitions", isInternal_ = true)), enforceAssumption = false, dAInfo=dAInfo) + v.decider.assume(pmDef.valueDefinitions, Option.when(withExp)(DebugExp.createInstance("value definitions", isInternal_ = true)), enforceAssumption = false, analysisInfoes=analysisInfoes) (pmDef, s.pmCache) case _ => val (pm, valueDef) = quantifiedChunkSupporter.summarisePerm(s, relevantChunks, formalQVars, resource, smDef, v) val pmDef = PermMapDefinition(resource, pm, valueDef) - v.decider.assume(valueDef, Option.when(withExp)(DebugExp.createInstance("value definitions", isInternal_ = true)), enforceAssumption = false, dAInfo=dAInfo) + v.decider.assume(valueDef, Option.when(withExp)(DebugExp.createInstance("value definitions", isInternal_ = true)), enforceAssumption = false, analysisInfoes=analysisInfoes) (pmDef, s.pmCache + ((resource, relevantChunks) -> pmDef)) } res @@ -710,7 +712,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optSmDomainDefinitionCondition: Option[Term] = None, optQVarsInstantiations: Option[Seq[Term]] = None) : (SnapshotMapDefinition, SnapshotMapCache) = { - val dAInfo = FullDependencyAnalysisInfo.create("summarizing heap", DependencyType.Internal, NoDependencyAnalysisMerge(), NoDependencyAnalysisJoin()) + val analysisInfoes = DependencyAnalysisInfoes.create("summarizing heap", DependencyType.Internal, NoDependencyAnalysisMerge()) def emitSnapshotMapDefinition(s: State, smDef: SnapshotMapDefinition, @@ -723,7 +725,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { case None => val comment = "Definitional axioms for snapshot map domain" v.decider.prover.comment(comment) - v.decider.assume(smDef.domainDefinitions, Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, dAInfo=dAInfo) + v.decider.assume(smDef.domainDefinitions, Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, analysisInfoes=analysisInfoes) case Some(_instantiations) => // TODO: Avoid pattern matching on resource val instantiations = resource match { @@ -735,7 +737,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.prover.comment(comment) // TODO: Avoid cast to Quantification v.decider.assume(smDef.domainDefinitions.map(_.asInstanceOf[Quantification].instantiate(instantiations)), - Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, dAInfo=dAInfo) + Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, analysisInfoes=analysisInfoes) } } @@ -743,7 +745,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { case None => val comment = "Definitional axioms for snapshot map values" v.decider.prover.comment(comment) - v.decider.assume(smDef.valueDefinitions, Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, dAInfo=dAInfo) + v.decider.assume(smDef.valueDefinitions, Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, analysisInfoes=analysisInfoes) case Some(_instantiations) => // TODO: Avoid pattern matching on resource val instantiations = resource match { @@ -755,7 +757,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.prover.comment(comment) // TODO: Avoid cast to Quantification v.decider.assume(smDef.valueDefinitions.map(_.asInstanceOf[Quantification].instantiate(instantiations)), - Option.when(withExp)(DebugExp.createInstance(comment, true)), enforceAssumption = false, dAInfo=dAInfo) + Option.when(withExp)(DebugExp.createInstance(comment, true)), enforceAssumption = false, analysisInfoes=analysisInfoes) } } @@ -861,7 +863,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { negativePermissionReason: => ErrorReason, notInjectiveReason: => ErrorReason, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -892,7 +894,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { qidPrefix = qid, v = v, program = s.program, - dAInfo = dAInfo, + analysisInfoes = analysisInfoes, isExhale = false) val (effectiveTriggers, effectiveTriggersQVars, effectiveTriggersQVarExps) = optTrigger match { @@ -938,9 +940,9 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val commentGlobals = "Nested auxiliary terms: globals" v.decider.prover.comment(commentGlobals) - val dAInfoGlobals = FullDependencyAnalysisInfo.create(commentGlobals, DependencyType.Internal, NoDependencyAnalysisMerge(), NoDependencyAnalysisJoin()) + val analysisInfoesGlobals = DependencyAnalysisInfoes.create(commentGlobals, DependencyType.Internal, NoDependencyAnalysisMerge()) v.decider.assume(auxGlobals, Option.when(withExp)(DebugExp.createInstance(description=commentGlobals, children=auxGlobalsExp.get)), - enforceAssumption = false, dAInfo=dAInfoGlobals) + enforceAssumption = false, analysisInfoes=analysisInfoesGlobals) val commentNonGlobals = "Nested auxiliary terms: non-globals" v.decider.prover.comment(commentNonGlobals) @@ -948,13 +950,13 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { auxNonGlobals.map(_.copy( vars = effectiveTriggersQVars, triggers = effectiveTriggers)), - Option.when(withExp)(DebugExp.createInstance(description=commentNonGlobals, children=auxNonGlobalsExp.get)), enforceAssumption = false, dAInfo=dAInfo.withDependencyType(DependencyType.Internal)) + Option.when(withExp)(DebugExp.createInstance(description=commentNonGlobals, children=auxNonGlobalsExp.get)), enforceAssumption = false, analysisInfoes=analysisInfoes.withDependencyType(DependencyType.Internal)) val nonNegImplication = Implies(tCond, perms.IsNonNegative(tPerm)) val nonNegImplicationExp = eCond.map(c => ast.Implies(c, ast.PermGeCmp(ePerm.get, ast.NoPerm()())())(c.pos, c.info, c.errT)) val nonNegTerm = Forall(qvars, Implies(FunctionPreconditionTransformer.transform(nonNegImplication, s.program), nonNegImplication), Nil) // TODO: Replace by QP-analogue of permissionSupporter.assertNotNegative - v.decider.assert(nonNegTerm, dAInfo) { + v.decider.assert(nonNegTerm, analysisInfoes) { case true => /* TODO: Can we omit/simplify the injectivity check in certain situations? */ @@ -976,7 +978,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.prover.comment(comment) val completeReceiverInjectivityCheck = Implies(FunctionPreconditionTransformer.transform(receiverInjectivityCheck, s.program), receiverInjectivityCheck) - v.decider.assert(completeReceiverInjectivityCheck, dAInfo) { + v.decider.assert(completeReceiverInjectivityCheck, analysisInfoes) { case true => val ax = inverseFunctions.axiomInversesOfInvertibles val inv = inverseFunctions.copy(axiomInversesOfInvertibles = Forall(ax.vars, ax.body, effectiveTriggers)) @@ -985,8 +987,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.prover.comment(comment) val definitionalAxiomMark = v.decider.setPathConditionMark() v.decider.assume(inv.definitionalAxioms.map(a => FunctionPreconditionTransformer.transform(a, s.program)), - Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, dAInfo=dAInfo) - v.decider.assume(inv.definitionalAxioms, Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, dAInfo=dAInfo) + Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, analysisInfoes=analysisInfoes) + v.decider.assume(inv.definitionalAxioms, Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, analysisInfoes=analysisInfoes) val conservedPcs = if (s.recordPcs) (s.conservedPcs.head :+ v.decider.pcs.after(definitionalAxiomMark)) +: s.conservedPcs.tail else s.conservedPcs @@ -1009,9 +1011,9 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { triggers = effectiveTriggers, qidPrefix = qid ) - v.decider.assume(pcsForChunk, pcsForChunkExp, pcsForChunkExp, dAInfo.withDependencyType(DependencyType.Internal)) + v.decider.assume(pcsForChunk, pcsForChunkExp, pcsForChunkExp, analysisInfoes.withDependencyType(DependencyType.Internal)) }) - val (fr1, h1) = v.stateConsolidator(s).merge(s.functionRecorder, s, s.h, Heap(Seq(ch)), v) + val (fr1, h1) = v.stateConsolidator(s).merge(s.functionRecorder, s, s.h, Heap(Seq(ch)), v, DefaultDependencyAnalysisInfoes) val (smCache1, fr2) = if (s.isUsedAsTrigger(resource)){ // TODO: Why not formalQVars? Used as codomainVars, see above. @@ -1033,7 +1035,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val qvarsToInv = inv.qvarsToInversesOf(codomainVars) val condOfInv = tCond.replace(qvarsToInv) v.decider.assume(Forall(codomainVars, Implies(condOfInv, trigger), Trigger(inv.inversesOf(codomainVars))), - Option.when(withExp)(DebugExp.createInstance("Inverse Trigger", true)), dAInfo.withDependencyType(DependencyType.Trigger)) + Option.when(withExp)(DebugExp.createInstance("Inverse Trigger", true)), analysisInfoes.withDependencyType(DependencyType.Trigger)) val newFuncRec = fr1.recordFvfAndDomain(smDef1) (smCache1, newFuncRec) } else { @@ -1047,12 +1049,12 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { Q(s1, v) case false => val failure = createFailure(pve dueTo notInjectiveReason, v, s, receiverInjectivityCheck, "QP receiver is injective") - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(completeReceiverInjectivityCheck, dAInfo, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(completeReceiverInjectivityCheck, analysisInfoes, v.reportFurtherErrors()) if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, v) else failure } case false => val failure = createFailure(pve dueTo negativePermissionReason, v, s, nonNegImplication, nonNegImplicationExp) - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(nonNegTerm, dAInfo, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(nonNegTerm, analysisInfoes, v.reportFurtherErrors()) if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, v) else failure } @@ -1070,7 +1072,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { resourceTriggerFactory: Term => Term, /* Trigger with some snapshot */ mergeAndTrigger: Boolean, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -1079,19 +1081,19 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val comment = "Definitional axioms for singleton-SM's value" v.decider.prover.comment(comment) val definitionalAxiomMark = v.decider.setPathConditionMark() - v.decider.assumeDefinition(smValueDef, Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), dAInfo) + v.decider.assumeDefinition(smValueDef, Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), analysisInfoes) val conservedPcs = if (s.recordPcs) (s.conservedPcs.head :+ v.decider.pcs.after(definitionalAxiomMark)) +: s.conservedPcs.tail else s.conservedPcs - val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(formalQVars, formalQVarsExp, resource, tArgs, eArgs, tPerm, ePerm, sm, s.program, v, dAInfo, isExhale=false) + val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(formalQVars, formalQVarsExp, resource, tArgs, eArgs, tPerm, ePerm, sm, s.program, v, analysisInfoes, isExhale=false) val s1 = if (mergeAndTrigger) { - val (fr1, h1) = v.stateConsolidator(s).merge(s.functionRecorder, s, s.h, Heap(Seq(ch)), v) + val (fr1, h1) = v.stateConsolidator(s).merge(s.functionRecorder, s, s.h, Heap(Seq(ch)), v, DefaultDependencyAnalysisInfoes) val interpreter = new NonQuantifiedPropertyInterpreter(h1.values, v) val resourceDescription = Resources.resourceDescriptions(ch.resourceID) val pcs = interpreter.buildPathConditionsForChunk(ch, resourceDescription.instanceProperties(s.mayAssumeUpperBounds)) - pcs.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), dAInfo)) + pcs.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), analysisInfoes)) val smCache1 = if (s.isUsedAsTrigger(resource)) { val (relevantChunks, _) = @@ -1099,7 +1101,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val (smDef1, smCache1) = quantifiedChunkSupporter.summarisingSnapshotMap( s, resource, formalQVars, relevantChunks, v) - v.decider.assume(resourceTriggerFactory(smDef1.sm), Option.when(withExp)(DebugExp.createInstance("Resource Trigger", true)), dAInfo.withDependencyType(DependencyType.Trigger)) + v.decider.assume(resourceTriggerFactory(smDef1.sm), Option.when(withExp)(DebugExp.createInstance("Resource Trigger", true)), analysisInfoes.withDependencyType(DependencyType.Trigger)) smCache1 } else { s.smCache @@ -1143,7 +1145,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { notInjectiveReason: => ErrorReason, insufficientPermissionReason: => ErrorReason, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -1180,7 +1182,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val comment = "Nested auxiliary terms: globals" v.decider.prover.comment(comment) v.decider.analysisSourceInfoStack.setForcedSource(comment) - v.decider.assume(auxGlobals, Option.when(withExp)(DebugExp.createInstance(description=comment, children=auxGlobalsExp.get)), enforceAssumption = false, dAInfo=dAInfo.withDependencyType(DependencyType.Internal)) + v.decider.assume(auxGlobals, Option.when(withExp)(DebugExp.createInstance(description=comment, children=auxGlobalsExp.get)), enforceAssumption = false, analysisInfoes=analysisInfoes.withDependencyType(DependencyType.Internal)) v.decider.analysisSourceInfoStack.removeForcedSource() val comment2 = "Nested auxiliary terms: non-globals" @@ -1191,10 +1193,10 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.assume( auxNonGlobals.map(_.copy( vars = effectiveTriggersQVars, - triggers = effectiveTriggers)), Option.when(withExp)(DebugExp.createInstance(description=comment2, children=auxNonGlobalsExp.get)), enforceAssumption = false, dAInfo=dAInfo.withDependencyType(DependencyType.Internal)) + triggers = effectiveTriggers)), Option.when(withExp)(DebugExp.createInstance(description=comment2, children=auxNonGlobalsExp.get)), enforceAssumption = false, analysisInfoes=analysisInfoes.withDependencyType(DependencyType.Internal)) case Some(_) => /* Explicit triggers were provided. */ - v.decider.assume(auxNonGlobals, Option.when(withExp)(DebugExp.createInstance(description=comment2, children=auxNonGlobalsExp.get)), enforceAssumption = false, dAInfo=dAInfo.withDependencyType(DependencyType.Internal)) + v.decider.assume(auxNonGlobals, Option.when(withExp)(DebugExp.createInstance(description=comment2, children=auxNonGlobalsExp.get)), enforceAssumption = false, analysisInfoes=analysisInfoes.withDependencyType(DependencyType.Internal)) } val nonNegImplication = Implies(tCond, perms.IsNonNegative(tPerm)) @@ -1202,11 +1204,11 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val nonNegTerm = Forall(qvars, Implies(FunctionPreconditionTransformer.transform(nonNegImplication, s.program), nonNegImplication), Nil) val nonNegExp = qvarExps.map(qv => ast.Forall(qv, Nil, nonNegImplicationExp.get)()) // TODO: Replace by QP-analogue of permissionSupporter.assertNotNegative - v.decider.assert(nonNegTerm, dAInfo) { + v.decider.assert(nonNegTerm, analysisInfoes) { case true => val hints = quantifiedChunkSupporter.extractHints(Some(tCond), tArgs) val chunkOrderHeuristics = - qpAppChunkOrderHeuristics(inverseFunctions.invertibles, qvars, hints, v, dAInfo) + qpAppChunkOrderHeuristics(inverseFunctions.invertibles, qvars, hints, v, analysisInfoes) val loss = if (!Verifier.config.unsafeWildcardOptimization() || (resource.isInstanceOf[ast.Location] && s.permLocations.contains(resource.asInstanceOf[ast.Location]))) PermTimes(tPerm, s.permissionScalingFactor) @@ -1238,7 +1240,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { program = s.program) v.decider.prover.comment("Check receiver injectivity") val completeReceiverInjectivityCheck = Implies(FunctionPreconditionTransformer.transform(receiverInjectivityCheck, s.program), receiverInjectivityCheck) - v.decider.assert(completeReceiverInjectivityCheck, dAInfo) { + v.decider.assert(completeReceiverInjectivityCheck, analysisInfoes) { case true => val qvarsToInvOfLoc = inverseFunctions.qvarsToInversesOf(formalQVars) val condOfInvOfLoc = tCond.replace(qvarsToInvOfLoc) @@ -1252,8 +1254,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.prover.comment("Definitional axioms for inverse functions") v.decider.assume(inverseFunctions.definitionalAxioms.map(a => FunctionPreconditionTransformer.transform(a, s.program)), - Option.when(withExp)(DebugExp.createInstance("Inverse Function Axioms", isInternal_ = true)), enforceAssumption = false, dAInfo = dAInfo) - v.decider.assume(inverseFunctions.definitionalAxioms, Option.when(withExp)(DebugExp.createInstance("Inverse function axiom", isInternal_ = true)), enforceAssumption = false, dAInfo=dAInfo) + Option.when(withExp)(DebugExp.createInstance("Inverse Function Axioms", isInternal_ = true)), enforceAssumption = false, analysisInfoes = analysisInfoes) + v.decider.assume(inverseFunctions.definitionalAxioms, Option.when(withExp)(DebugExp.createInstance("Inverse function axiom", isInternal_ = true)), enforceAssumption = false, analysisInfoes=analysisInfoes) if (s.isUsedAsTrigger(resource)){ v.decider.assume( @@ -1261,7 +1263,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { formalQVars, Implies(condOfInvOfLoc, ResourceTriggerFunction(resource, smDef1.get.sm, formalQVars, s.program)), Trigger(inverseFunctions.inversesOf(formalQVars)))), - Option.when(withExp)(DebugExp.createInstance("Inverse Function", isInternal_ = true)), enforceAssumption = false, dAInfo=dAInfo) + Option.when(withExp)(DebugExp.createInstance("Inverse Function", isInternal_ = true)), enforceAssumption = false, analysisInfoes=analysisInfoes) } @@ -1275,7 +1277,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { createFailure(pve dueTo insufficientPermissionReason/*InsufficientPermission(acc.loc)*/, v, s, "consuming QP"), formalQVars, v, - dAInfo)((s2, heap, rPerm, rPermExp, v2) => { + analysisInfoes)((s2, heap, rPerm, rPermExp, v2) => { val (relevantChunks, otherChunks) = quantifiedChunkSupporter.splitHeap[QuantifiedBasicChunk]( heap, ChunkIdentifier(resource, s.program)) @@ -1293,7 +1295,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { rPermExp, chunkOrderHeuristics, v2, - dAInfo) + analysisInfoes) val optSmDomainDefinitionCondition2 = if (s3.smDomainNeeded) Some(And(condOfInvOfLoc, IsPositive(lossOfInvOfLoc), And(imagesOfFormalQVars))) else None @@ -1325,15 +1327,15 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { qid, v2, s.program, - dAInfo, + analysisInfoes, isExhale = true ) val debugExp = Option.when(withExp)(DebugExp.createInstance("Inverse functions for quantified permission", isInternal_ = true)) - v.decider.assume(FunctionPreconditionTransformer.transform(inverseFunctions.axiomInvertiblesOfInverses, s3.program), debugExp, dAInfo) - v.decider.assume(inverseFunctions.axiomInvertiblesOfInverses, debugExp, dAInfo) + v.decider.assume(FunctionPreconditionTransformer.transform(inverseFunctions.axiomInvertiblesOfInverses, s3.program), debugExp, analysisInfoes) + v.decider.assume(inverseFunctions.axiomInvertiblesOfInverses, debugExp, analysisInfoes) val substitutedAxiomInversesOfInvertibles = inverseFunctions.axiomInversesOfInvertibles.replace(formalQVars, tArgs) - v.decider.assume(FunctionPreconditionTransformer.transform(substitutedAxiomInversesOfInvertibles, s3.program), debugExp, dAInfo) - v.decider.assume(substitutedAxiomInversesOfInvertibles, debugExp, dAInfo) + v.decider.assume(FunctionPreconditionTransformer.transform(substitutedAxiomInversesOfInvertibles, s3.program), debugExp, analysisInfoes) + v.decider.assume(substitutedAxiomInversesOfInvertibles, debugExp, analysisInfoes) val h2 = Heap(remainingChunks ++ otherChunks) val s4 = s3.copy(smCache = smCache2, constrainableARPs = s.constrainableARPs) @@ -1362,7 +1364,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { lossExp, chunkOrderHeuristics, v, - dAInfo + analysisInfoes ) permissionRemovalResult match { case (Complete(), s2, remainingChunks) => @@ -1389,13 +1391,13 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { } case (Incomplete(_, _), s2, _) => val failure = createFailure(pve dueTo insufficientPermissionReason, v, s2, "QP consume") - if(s2.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, dAInfo, v.reportFurtherErrors()) + if(s2.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfoes, v.reportFurtherErrors()) if(s2.retryLevel == 0 && v.reportFurtherErrors() && Verifier.config.disableInfeasibilityChecks()) failure combine Q(s2, s2.h, None, v) else failure } } case false => val failure = createFailure(pve dueTo notInjectiveReason, v, s, receiverInjectivityCheck, "QP receiver injective") - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(receiverInjectivityCheck, dAInfo, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(receiverInjectivityCheck, analysisInfoes, v.reportFurtherErrors()) if(s.retryLevel == 0 && v.reportFurtherErrors()){ val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) // TODO ake: function recorder failure combine Q(s, s.h, if(returnSnap) Some(snap) else None, v) @@ -1405,7 +1407,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { } case false => val failure = createFailure(pve dueTo negativePermissionReason, v, s, nonNegTerm, nonNegExp) - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(nonNegTerm, dAInfo, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(nonNegTerm, analysisInfoes, v.reportFurtherErrors()) if(s.retryLevel == 0 && v.reportFurtherErrors()){ val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) failure combine Q(s, s.h, if(returnSnap) Some(snap) else None, v) @@ -1428,7 +1430,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optChunkOrderHeuristic: Option[Seq[QuantifiedBasicChunk] => Seq[QuantifiedBasicChunk]], pve: PartialVerificationError, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -1440,7 +1442,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { heuristics case None => quantifiedChunkSupporter.singleReceiverChunkOrderHeuristic(arguments, - quantifiedChunkSupporter.extractHints(None, arguments), v, dAInfo) + quantifiedChunkSupporter.extractHints(None, arguments), v, analysisInfoes) } if (s.exhaleExt) { @@ -1449,7 +1451,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { case wand: ast.MagicWand => createFailure(pve dueTo MagicWandChunkNotFound(wand), v, s, "single QP consume inside package") case _ => sys.error(s"Found resource $resourceAccess, which is not yet supported as a quantified resource.") } - magicWandSupporter.transfer(s, permissions, permissionsExp, failure, Seq(), v, dAInfo)((s1, h1, rPerm, rPermExp, v1) => { + magicWandSupporter.transfer(s, permissions, permissionsExp, failure, Seq(), v, analysisInfoes)((s1, h1, rPerm, rPermExp, v1) => { val (relevantChunks, otherChunks) = quantifiedChunkSupporter.splitHeap[QuantifiedBasicChunk](h1, chunkIdentifier) val (result, s2, remainingChunks) = quantifiedChunkSupporter.removePermissions( @@ -1465,7 +1467,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { rPermExp, chunkOrderHeuristics, v, - dAInfo + analysisInfoes ) val h2 = Heap(remainingChunks ++ otherChunks) val (smDef1, smCache1) = @@ -1484,7 +1486,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { } val consumedChunk = quantifiedChunkSupporter.createSingletonQuantifiedChunk( - codomainQVars, codomainQVarsExp, resource, arguments, argumentsExp, permsTaken, permsTakenExp, smDef1.sm, s.program, v1, dAInfo, isExhale=true) + codomainQVars, codomainQVarsExp, resource, arguments, argumentsExp, permsTaken, permsTakenExp, smDef1.sm, s.program, v1, analysisInfoes, isExhale=true) val s3 = s2.copy(functionRecorder = s2.functionRecorder.recordFvfAndDomain(smDef1), smCache = smCache1) (result, s3, h2, Some(consumedChunk)) @@ -1515,7 +1517,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { permissionsExp, chunkOrderHeuristics, v, - dAInfo + analysisInfoes ) result match { case (Complete(), s1, remainingChunks) => @@ -1543,7 +1545,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { case wand: ast.MagicWand => createFailure(pve dueTo MagicWandChunkNotFound(wand), v, s, "single QP consume") case _ => sys.error(s"Found resource $resourceAccess, which is not yet supported as a quantified resource.") } - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, dAInfo, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfoes, v.reportFurtherErrors()) if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, s.h, None, v) else failure } } @@ -1556,7 +1558,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { perms: Term, permsExp: Option[ast.Exp], v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) : ConsumptionResult = { var permsAvailable: Term = NoPerm @@ -1573,7 +1575,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { // final check val result = - if (v.decider.check(tookEnoughCheck, Verifier.config.assertTimeout.getOrElse(0), dAInfo) /* This check is a must-check, i.e. an assert */ ) + if (v.decider.check(tookEnoughCheck, Verifier.config.assertTimeout.getOrElse(0), analysisInfoes) /* This check is a must-check, i.e. an assert */ ) Complete() else Incomplete(PermMinus(permsAvailable, perms), permsAvailableExp.map(pa => ast.PermSub(pa, permsExp.get)())) @@ -1597,7 +1599,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { permsExp: Option[ast.Exp], // p(rs) chunkOrderHeuristic: Seq[QuantifiedBasicChunk] => Seq[QuantifiedBasicChunk], v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) : (ConsumptionResult, State, Seq[QuantifiedBasicChunk]) = { val rmPermRecord = new CommentRecord("removePermissions", s, v.decider.pcs) @@ -1614,7 +1616,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val constrainPermissions = !consumeExactRead(perms, s.constrainableARPs) if (s.assertReadAccessOnly) { - val result = assertReadPermission(s, candidates, codomainQVars, condition, perms, permsExp, v, dAInfo) + val result = assertReadPermission(s, candidates, codomainQVars, condition, perms, permsExp, v, analysisInfoes) return (result, s, relevantChunks) } @@ -1678,23 +1680,23 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { if (constrainPermissions) { v.decider.prover.comment(s"Constrain original permissions $perms") - v.decider.assume(permissionConstraint, permissionConstraintExp, permissionConstraintExp, dAInfo) + v.decider.assume(permissionConstraint, permissionConstraintExp, permissionConstraintExp, analysisInfoes) remainingChunks = - remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.getAnalysisInfo(dAInfo)) + remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.getAnalysisInfo(analysisInfoes)) } else { v.decider.prover.comment(s"Chunk depleted?") - val chunkDepleted = v.decider.check(depletedCheck, Verifier.config.splitTimeout(), dAInfo.withDependencyType(DependencyType.Internal)) + val chunkDepleted = v.decider.check(depletedCheck, Verifier.config.splitTimeout(), analysisInfoes.withDependencyType(DependencyType.Internal)) if (!chunkDepleted) { val unusedCheck = Forall(codomainQVars, ithPTaken === NoPerm, Nil) - val chunkUnused = v.decider.check(unusedCheck, Verifier.config.checkTimeout(), dAInfo.withDependencyType(DependencyType.Internal)) + val chunkUnused = v.decider.check(unusedCheck, Verifier.config.checkTimeout(), analysisInfoes.withDependencyType(DependencyType.Internal)) if (chunkUnused) { remainingChunks = remainingChunks :+ ithChunk } else { remainingChunks = - remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.getAnalysisInfo(dAInfo)) + remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.getAnalysisInfo(analysisInfoes)) } }else{ - val _ = GeneralChunk.withPerm(ithChunk, ithPTaken, None, v.decider.getAnalysisInfo(dAInfo), isExhale=true) + val _ = GeneralChunk.withPerm(ithChunk, ithPTaken, None, v.decider.getAnalysisInfo(analysisInfoes), isExhale=true) } } @@ -1707,7 +1709,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { Forall(codomainQVars, Implies(condition, ithPNeeded === NoPerm), Nil) v.decider.prover.comment(s"Intermediate check if already taken enough permissions") - success = if (v.decider.check(tookEnoughCheck, Verifier.config.splitTimeout(), dAInfo)) { + success = if (v.decider.check(tookEnoughCheck, Verifier.config.splitTimeout(), analysisInfoes)) { Complete() } else { Incomplete(ithPNeeded, ithPNeededExp) @@ -1717,7 +1719,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.prover.comment("Final check if taken enough permissions") success = - if (success.isComplete || v.decider.check(tookEnoughCheck, Verifier.config.assertTimeout.getOrElse(0), dAInfo) /* This check is a must-check, i.e. an assert */) + if (success.isComplete || v.decider.check(tookEnoughCheck, Verifier.config.assertTimeout.getOrElse(0), analysisInfoes) /* This check is a must-check, i.e. an assert */) Complete() else success @@ -2036,7 +2038,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { matchingChunks ++ otherChunks } - override def findChunk(chunks: Iterable[Chunk], chunk: QuantifiedChunk, v: Verifier, dAInfo: DependencyAnalysisInfo): Option[QuantifiedChunk] = { + override def findChunk(chunks: Iterable[Chunk], chunk: QuantifiedChunk, v: Verifier, analysisInfoes: DependencyAnalysisInfoes): Option[QuantifiedChunk] = { val lr = chunk match { case qfc: QuantifiedFieldChunk if qfc.invs.isDefined => val qvarsAndInverses = qfc.invs.get.qvarsToInverses.map(qvi => (qvi._1, App(qvi._2, qfc.invs.get.additionalArguments.toSeq ++ qfc.quantifiedVars))) @@ -2077,11 +2079,11 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { // Hence, we need to compare the conditions for equality in addition to verifying that the receivers match. val equalityCond = And(cond.replace(chunk.quantifiedVars, singletonArguments), cCond.replace(ch.quantifiedVars, cSingletonArguments)) - val result = v.decider.check(And(equalityCond, equalityTerm), Verifier.config.checkTimeout(), dAInfo) + val result = v.decider.check(And(equalityCond, equalityTerm), Verifier.config.checkTimeout(), analysisInfoes) if (result) { // Learn the equality val debugExp = Option.when(withExp)(DebugExp.createInstance("Chunks alias", isInternal_ = true)) - v.decider.assume(equalityTerm, debugExp, dAInfo.withDependencyType(DependencyType.Internal)) + v.decider.assume(equalityTerm, debugExp, analysisInfoes.withDependencyType(DependencyType.Internal)) } result case _ => false @@ -2107,11 +2109,11 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val condReplaced = cCond.replace(cQvars, quantVars) val secondReplaced = p._2.replace(cQvars, quantVars) val equalityTerm = SimplifyingForall(quantVars, And(Seq(p._1 === secondReplaced, cond === condReplaced)), Seq()) - val result = v.decider.check(equalityTerm, Verifier.config.checkTimeout(), dAInfo) + val result = v.decider.check(equalityTerm, Verifier.config.checkTimeout(), analysisInfoes) if (result) { // Learn the equality val debugExp = Option.when(withExp)(DebugExp.createInstance("Chunks alias", isInternal_ = true)) - v.decider.assume(equalityTerm, debugExp, dAInfo.withDependencyType(DependencyType.Internal)) + v.decider.assume(equalityTerm, debugExp, analysisInfoes.withDependencyType(DependencyType.Internal)) } result } else { @@ -2173,7 +2175,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { (fr2, sm, Forall(qVars, smDef, triggers)) } - def qpAppChunkOrderHeuristics(receiverTerms: Seq[Term], quantVars: Seq[Var], hints: Seq[Term], v: Verifier, dAInfo: DependencyAnalysisInfo) + def qpAppChunkOrderHeuristics(receiverTerms: Seq[Term], quantVars: Seq[Var], hints: Seq[Term], v: Verifier, analysisInfoes: DependencyAnalysisInfoes) : Seq[QuantifiedBasicChunk] => Seq[QuantifiedBasicChunk] = { // Heuristics that looks for quantified chunks that have the same shape (as in, the same number and types of // quantified variables) and identical receiver terms. @@ -2199,7 +2201,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { receiverTerms.zip(cInvertibles).forall(p => { if (cQvars.length == quantVars.length && cQvars.zip(quantVars).forall(vars => vars._1.sort == vars._2.sort)) { val secondReplaced = p._2.replace(cQvars, quantVars) - v.decider.check(SimplifyingForall(quantVars, p._1 === secondReplaced, Seq()), Verifier.config.checkTimeout(), dAInfo) + v.decider.check(SimplifyingForall(quantVars, p._1 === secondReplaced, Seq()), Verifier.config.checkTimeout(), analysisInfoes) } else { false } @@ -2215,7 +2217,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { } } - def singleReceiverChunkOrderHeuristic(receiver: Seq[Term], hints: Seq[Term], v: Verifier, dAInfo: DependencyAnalysisInfo) + def singleReceiverChunkOrderHeuristic(receiver: Seq[Term], hints: Seq[Term], v: Verifier, analysisInfoes: DependencyAnalysisInfoes) : Seq[QuantifiedBasicChunk] => Seq[QuantifiedBasicChunk] = { // Heuristic that emulates greedy Silicon behavior for consuming single-receiver permissions. // First: Find singleton chunks that have the same receiver syntactically. @@ -2232,7 +2234,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { } else { val greedyMatch = chunks.find(c => c.singletonArguments match { case Some(args) if args.length == receiver.length => - args.zip(receiver).forall(ts => v.decider.check(ts._1 === ts._2, Verifier.config.checkTimeout(), dAInfo)) + args.zip(receiver).forall(ts => v.decider.check(ts._1 === ts._2, Verifier.config.checkTimeout(), analysisInfoes)) case _ => false }).toSeq diff --git a/src/main/scala/rules/StateConsolidator.scala b/src/main/scala/rules/StateConsolidator.scala index 535e57cd9..8ac332b47 100644 --- a/src/main/scala/rules/StateConsolidator.scala +++ b/src/main/scala/rules/StateConsolidator.scala @@ -9,6 +9,7 @@ package viper.silicon.rules import viper.silicon.Config import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes import viper.silicon.dependencyAnalysis._ import viper.silicon.interfaces.state._ import viper.silicon.logger.records.data.{CommentRecord, SingleMergeRecord} @@ -26,11 +27,11 @@ import scala.annotation.unused trait StateConsolidationRules extends SymbolicExecutionRules { def consolidate(s: State, v: Verifier): State def consolidateOptionally(s: State, v: Verifier): State - def merge(fr: FunctionRecorder, s: State, h: Heap, newH: Heap, v: Verifier, dAInfo: DependencyAnalysisInfo): (FunctionRecorder, Heap) - def merge(fr: FunctionRecorder, s: State, h: Heap, ch: NonQuantifiedChunk, v: Verifier, dAInfo: DependencyAnalysisInfo): (FunctionRecorder, Heap) + def merge(fr: FunctionRecorder, s: State, h: Heap, newH: Heap, v: Verifier, analysisInfoes: DependencyAnalysisInfoes): (FunctionRecorder, Heap) + def merge(fr: FunctionRecorder, s: State, h: Heap, ch: NonQuantifiedChunk, v: Verifier, analysisInfoes: DependencyAnalysisInfoes): (FunctionRecorder, Heap) - protected def assumeUpperPermissionBoundForQPFields(s: State, v: Verifier, dAInfo: DependencyAnalysisInfo): State - protected def assumeUpperPermissionBoundForQPFields(s: State, heaps: Seq[Heap], v: Verifier, dAInfo: DependencyAnalysisInfo): State + protected def assumeUpperPermissionBoundForQPFields(s: State, v: Verifier, analysisInfoes: DependencyAnalysisInfoes): State + protected def assumeUpperPermissionBoundForQPFields(s: State, heaps: Seq[Heap], v: Verifier, analysisInfoes: DependencyAnalysisInfoes): State } /** Performs the minimal work necessary for any consolidator: merging two heaps combines the chunk @@ -42,15 +43,15 @@ class MinimalStateConsolidator extends StateConsolidationRules { def consolidate(s: State, @unused v: Verifier): State = s def consolidateOptionally(s: State, @unused v: Verifier): State = s - def merge(fr: FunctionRecorder, s: State, h: Heap, newH: Heap, v: Verifier, dAInfo: DependencyAnalysisInfo): (FunctionRecorder, Heap) = + def merge(fr: FunctionRecorder, s: State, h: Heap, newH: Heap, v: Verifier, analysisInfoes: DependencyAnalysisInfoes): (FunctionRecorder, Heap) = (fr, Heap(h.values ++ newH.values)) - def merge(fr: FunctionRecorder, s: State, h: Heap, ch: NonQuantifiedChunk, v: Verifier, dAInfo: DependencyAnalysisInfo): (FunctionRecorder, Heap) = + def merge(fr: FunctionRecorder, s: State, h: Heap, ch: NonQuantifiedChunk, v: Verifier, analysisInfoes: DependencyAnalysisInfoes): (FunctionRecorder, Heap) = (fr, h + ch) - protected def assumeUpperPermissionBoundForQPFields(s: State, @unused v: Verifier, @unused dAInfo: DependencyAnalysisInfo): State = s + protected def assumeUpperPermissionBoundForQPFields(s: State, @unused v: Verifier, @unused analysisInfoes: DependencyAnalysisInfoes): State = s - protected def assumeUpperPermissionBoundForQPFields(s: State, @unused heaps: Seq[Heap], @unused v: Verifier, @unused dAInfo: DependencyAnalysisInfo): State = s + protected def assumeUpperPermissionBoundForQPFields(s: State, @unused heaps: Seq[Heap], @unused v: Verifier, @unused analysisInfoes: DependencyAnalysisInfoes): State = s } /** Default implementation that merges as many known-alias chunks as possible, and deduces various @@ -62,7 +63,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol val comLog = new CommentRecord("state consolidation", s, v.decider.pcs) val sepIdentifier = v.symbExLog.openScope(comLog) - val dAInfo = FullDependencyAnalysisInfo.create("state consolidation", DependencyType.Internal, NoDependencyAnalysisMerge(), NoDependencyAnalysisJoin()) // TODO ake: review + val analysisInfoes = DependencyAnalysisInfoes.create("state consolidation", DependencyType.Internal, NoDependencyAnalysisMerge()) // TODO ake: review v.decider.prover.comment("[state consolidation]") v.decider.prover.saturate(config.proverSaturationTimeouts.beforeIteration) @@ -82,9 +83,9 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol val roundLog = new CommentRecord("Round " + fixedPointRound, s, v.decider.pcs) val roundSepIdentifier = v.symbExLog.openScope(roundLog) - val (_functionRecorder, _mergedChunks, _newChunks, snapEqs) = singleMerge(functionRecorder, destChunks, newChunks, s.functionRecorderQuantifiedVariables().map(_._1), v, dAInfo) + val (_functionRecorder, _mergedChunks, _newChunks, snapEqs) = singleMerge(functionRecorder, destChunks, newChunks, s.functionRecorderQuantifiedVariables().map(_._1), v, analysisInfoes) - snapEqs foreach (t => v.decider.assume(t, Option.when(withExp)(DebugExp.createInstance("Snapshot Equations", true)), dAInfo)) + snapEqs foreach (t => v.decider.assume(t, Option.when(withExp)(DebugExp.createInstance("Snapshot Equations", true)), analysisInfoes)) functionRecorder = _functionRecorder mergedChunks = _mergedChunks @@ -102,12 +103,12 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol mergedChunks.filter(_.isInstanceOf[BasicChunk]) foreach { case ch: BasicChunk => val resource = Resources.resourceDescriptions(ch.resourceID) val pathCond = interpreter.buildPathConditionsForChunk(ch, resource.instanceProperties(s.mayAssumeUpperBounds)) - pathCond.foreach(p => v.decider.assume(v.decider.wrapWithDependencyAnalysisLabel(p._1, Set(ch)), Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), dAInfo.withMergeInfo(NoDependencyAnalysisMerge()))) + pathCond.foreach(p => v.decider.assume(v.decider.wrapWithDependencyAnalysisLabel(p._1, Set(ch)), Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), analysisInfoes)) } Resources.resourceDescriptions foreach { case (id, desc) => val pathCond = interpreter.buildPathConditionsForResource(id, desc.delayedProperties(s.mayAssumeUpperBounds)) - pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), dAInfo.withMergeInfo(NoDependencyAnalysisMerge()))) + pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), analysisInfoes)) } v.symbExLog.closeScope(sepIdentifier) @@ -118,7 +119,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol h = mergedHeaps.head, reserveHeaps = mergedHeaps.tail) - val s2 = assumeUpperPermissionBoundForQPFields(s1, v, dAInfo) + val s2 = assumeUpperPermissionBoundForQPFields(s1, v, analysisInfoes) s2 } @@ -126,24 +127,26 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol if (s.retrying) consolidate(s, v) else s - def merge(fr: FunctionRecorder, s: State, h: Heap, ch: NonQuantifiedChunk, v: Verifier, dAInfo: DependencyAnalysisInfo): (FunctionRecorder, Heap) = { - merge(fr, s, h, Heap(Seq(ch)), v, dAInfo) + def merge(fr: FunctionRecorder, s: State, h: Heap, ch: NonQuantifiedChunk, v: Verifier, analysisInfoes: DependencyAnalysisInfoes): (FunctionRecorder, Heap) = { + merge(fr, s, h, Heap(Seq(ch)), v, analysisInfoes) } - def merge(fr1: FunctionRecorder, s: State, h: Heap, newH: Heap, v: Verifier, dAInfo: DependencyAnalysisInfo): (FunctionRecorder, Heap) = { + def merge(fr1: FunctionRecorder, s: State, h: Heap, newH: Heap, v: Verifier, analysisInfoes: DependencyAnalysisInfoes): (FunctionRecorder, Heap) = { if(v.decider.isPathInfeasible()) return (fr1, h) + val analysisInfoes1 = analysisInfoes.addInfo("merge", ast.NoPosition, DependencyType.Internal) + val mergeLog = new CommentRecord("Merge", null, v.decider.pcs) val sepIdentifier = v.symbExLog.openScope(mergeLog) - val (fr2, mergedChunks, newlyAddedChunks, snapEqs) = singleMerge(fr1, h.values.toSeq, newH.values.toSeq, s.functionRecorderQuantifiedVariables().map(_._1), v, dAInfo) + val (fr2, mergedChunks, newlyAddedChunks, snapEqs) = singleMerge(fr1, h.values.toSeq, newH.values.toSeq, s.functionRecorderQuantifiedVariables().map(_._1), v, analysisInfoes) - v.decider.assume(snapEqs, Option.when(withExp)(DebugExp.createInstance("Snapshot", isInternal_ = true)), enforceAssumption = false, dAInfo.withDependencyType(DependencyType.Internal)) + v.decider.assume(snapEqs, Option.when(withExp)(DebugExp.createInstance("Snapshot", isInternal_ = true)), enforceAssumption = false, analysisInfoes.withDependencyType(DependencyType.Internal)) val interpreter = new NonQuantifiedPropertyInterpreter(mergedChunks, v) newlyAddedChunks.filter(_.isInstanceOf[BasicChunk]) foreach { case ch: BasicChunk => val resource = Resources.resourceDescriptions(ch.resourceID) val pathCond = interpreter.buildPathConditionsForChunk(ch, resource.instanceProperties(s.mayAssumeUpperBounds)) - pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), dAInfo.withDependencyType(DependencyType.Internal))) + pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), analysisInfoes.withDependencyType(DependencyType.Internal))) } v.symbExLog.closeScope(sepIdentifier) @@ -155,7 +158,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol newChunks: Seq[Chunk], qvars: Seq[Var], v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) : (FunctionRecorder, Seq[Chunk], Seq[Chunk], @@ -174,10 +177,10 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol * nextChunk: current chunk from the sequence of new chunks/of chunks to merge into the * sequence of destination chunks */ - val dAInfo = FullDependencyAnalysisInfo.create("stat_consolidation", DependencyType.Internal, NoDependencyAnalysisMerge(), NoDependencyAnalysisJoin()) // TODO ake: review - val res = findMatchingChunk(accMergedChunks, nextChunk, v, dAInfo) match { + val analysisInfoes = DependencyAnalysisInfoes.create("stat_consolidation", DependencyType.Internal, NoDependencyAnalysisMerge()) // TODO ake: review + val res = findMatchingChunk(accMergedChunks, nextChunk, v, analysisInfoes) match { case Some(ch) => - val resMerge = mergeChunks(fr1, ch, nextChunk, qvars, v, dAInfo) + val resMerge = mergeChunks(fr1, ch, nextChunk, qvars, v, analysisInfoes) resMerge match { case Some((fr2, newChunk, snapEq)) => @@ -194,28 +197,28 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol result } - private def findMatchingChunk(chunks: Iterable[Chunk], chunk: Chunk, v: Verifier, dAInfo: DependencyAnalysisInfo): Option[Chunk] = { + private def findMatchingChunk(chunks: Iterable[Chunk], chunk: Chunk, v: Verifier, analysisInfoes: DependencyAnalysisInfoes): Option[Chunk] = { chunk match { case chunk: BasicChunk => - chunkSupporter.findChunk[BasicChunk](chunks, chunk.id, chunk.args, v, dAInfo) - case chunk: QuantifiedChunk => quantifiedChunkSupporter.findChunk(chunks, chunk, v, dAInfo) + chunkSupporter.findChunk[BasicChunk](chunks, chunk.id, chunk.args, v, analysisInfoes) + case chunk: QuantifiedChunk => quantifiedChunkSupporter.findChunk(chunks, chunk, v, analysisInfoes) case _ => None } } // Merges two chunks that are aliases (i.e. that have the same id and the args are proven to be equal) // and returns the merged chunk or None, if the chunks could not be merged - private def mergeChunks(fr1: FunctionRecorder, chunk1: Chunk, chunk2: Chunk, qvars: Seq[Var], v: Verifier, dAInfo: DependencyAnalysisInfo): Option[(FunctionRecorder, Chunk, Term)] = { - val result = mergeChunks1(fr1, chunk1, chunk2, qvars, v, dAInfo) + private def mergeChunks(fr1: FunctionRecorder, chunk1: Chunk, chunk2: Chunk, qvars: Seq[Var], v: Verifier, analysisInfoes: DependencyAnalysisInfoes): Option[(FunctionRecorder, Chunk, Term)] = { + val result = mergeChunks1(fr1, chunk1, chunk2, qvars, v, analysisInfoes) result.map({case (fRec, ch, snapEq) => // v.decider.dependencyAnalyzer.addPermissionDependencies(Set(chunk1, chunk2), Set(), ch) (fRec, ch, v.decider.wrapWithDependencyAnalysisLabel(snapEq, Set(chunk1, chunk2)))}) } - private def mergeChunks1(fr1: FunctionRecorder, chunk1: Chunk, chunk2: Chunk, qvars: Seq[Var], v: Verifier, dAInfo: DependencyAnalysisInfo): Option[(FunctionRecorder, Chunk, Term)] = (chunk1, chunk2) match { + private def mergeChunks1(fr1: FunctionRecorder, chunk1: Chunk, chunk2: Chunk, qvars: Seq[Var], v: Verifier, analysisInfoes: DependencyAnalysisInfoes): Option[(FunctionRecorder, Chunk, Term)] = (chunk1, chunk2) match { case (BasicChunk(rid1, id1, args1, args1Exp, snap1, snap1Exp, perm1, perm1Exp), BasicChunk(_, _, _, _, snap2, _, perm2, perm2Exp)) => val (fr2, combinedSnap, snapEq) = combineSnapshots(fr1, snap1, snap2, perm1, perm2, qvars, v) - Some(fr2, BasicChunk(rid1, id1, args1, args1Exp, combinedSnap, snap1Exp, PermPlus(perm1, perm2), perm1Exp.map(p1 => ast.PermAdd(p1, perm2Exp.get)()), v.decider.getAnalysisInfo(dAInfo.withDependencyType(DependencyType.Internal))), snapEq) + Some(fr2, BasicChunk(rid1, id1, args1, args1Exp, combinedSnap, snap1Exp, PermPlus(perm1, perm2), perm1Exp.map(p1 => ast.PermAdd(p1, perm2Exp.get)()), v.decider.getAnalysisInfo(analysisInfoes.withDependencyType(DependencyType.Internal))), snapEq) case (l@QuantifiedFieldChunk(id1, fvf1, condition1, condition1Exp, perm1, perm1Exp, invs1, singletonRcvr1, singletonRcvr1Exp, hints1), r@QuantifiedFieldChunk(_, fvf2, _, _, perm2, perm2Exp, _, _, _, hints2)) => assert(l.quantifiedVars == Seq(`?r`)) @@ -225,14 +228,14 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol val permSum = PermPlus(perm1, perm2) val permSumExp = perm1Exp.map(p1 => ast.PermAdd(p1, perm2Exp.get)()) val bestHints = if (hints1.nonEmpty) hints1 else hints2 - Some(fr2, QuantifiedFieldChunk(id1, combinedSnap, condition1, condition1Exp, permSum, permSumExp, invs1, singletonRcvr1, singletonRcvr1Exp, bestHints, v.decider.getAnalysisInfo(dAInfo.withDependencyType(DependencyType.Internal))), snapEq) + Some(fr2, QuantifiedFieldChunk(id1, combinedSnap, condition1, condition1Exp, permSum, permSumExp, invs1, singletonRcvr1, singletonRcvr1Exp, bestHints, v.decider.getAnalysisInfo(analysisInfoes.withDependencyType(DependencyType.Internal))), snapEq) case (l@QuantifiedPredicateChunk(id1, qVars1, qVars1Exp, psf1, _, _, perm1, perm1Exp, _, _, _, _), r@QuantifiedPredicateChunk(_, qVars2, qVars2Exp, psf2, condition2, condition2Exp, perm2, perm2Exp, invs2, singletonArgs2, singletonArgs2Exp, hints2)) => val (fr2, combinedSnap, snapEq) = quantifiedChunkSupporter.combinePredicateSnapshotMaps(fr1, id1.name, qVars2, qvars, psf1, psf2, l.perm.replace(qVars1, qVars2), r.perm, v) val permSum = PermPlus(perm1.replace(qVars1, qVars2), perm2) val permSumExp = perm1Exp.map(p1 => ast.PermAdd(p1.replace(qVars1Exp.get.zip(qVars2Exp.get).toMap), perm2Exp.get)()) - Some(fr2, QuantifiedPredicateChunk(id1, qVars2, qVars2Exp, combinedSnap, condition2, condition2Exp, permSum, permSumExp, invs2, singletonArgs2, singletonArgs2Exp, hints2, v.decider.getAnalysisInfo(dAInfo.withDependencyType(DependencyType.Internal))), snapEq) + Some(fr2, QuantifiedPredicateChunk(id1, qVars2, qVars2Exp, combinedSnap, condition2, condition2Exp, permSum, permSumExp, invs2, singletonArgs2, singletonArgs2Exp, hints2, v.decider.getAnalysisInfo(analysisInfoes.withDependencyType(DependencyType.Internal))), snapEq) case _ => None } @@ -265,10 +268,10 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol } } - protected def assumeUpperPermissionBoundForQPFields(s: State, v: Verifier, dAInfo: DependencyAnalysisInfo): State = - assumeUpperPermissionBoundForQPFields(s, s.h +: s.reserveHeaps, v, dAInfo) + protected def assumeUpperPermissionBoundForQPFields(s: State, v: Verifier, analysisInfoes: DependencyAnalysisInfoes): State = + assumeUpperPermissionBoundForQPFields(s, s.h +: s.reserveHeaps, v, analysisInfoes) - protected def assumeUpperPermissionBoundForQPFields(s: State, heaps: Seq[Heap], v: Verifier, dAInfo: DependencyAnalysisInfo): State = { + protected def assumeUpperPermissionBoundForQPFields(s: State, heaps: Seq[Heap], v: Verifier, analysisInfoes: DependencyAnalysisInfoes): State = { heaps.foldLeft(s) { case (si, heap) => val chunks: Seq[QuantifiedFieldChunk] = heap.values.collect({ case ch: QuantifiedFieldChunk => ch }).to(Seq) @@ -300,7 +303,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol Some(DebugExp.createInstance(exp, exp)) } else { None } v.decider.assume( - Forall(receiver, PermAtMost(currentPermAmount, FullPerm), Trigger(trigger), "qp-fld-prm-bnd"), debugExp, dAInfo.withDependencyType(DependencyType.Internal)) + Forall(receiver, PermAtMost(currentPermAmount, FullPerm), Trigger(trigger), "qp-fld-prm-bnd"), debugExp, analysisInfoes.withDependencyType(DependencyType.Internal)) } else { /* If we don't use heap-dependent triggers, the trigger x.f does not work. Instead, we assume the permission @@ -317,7 +320,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol val exp = ast.PermLeCmp(permExp, ast.FullPerm()())() Some(DebugExp.createInstance(exp, exp)) } else { None } - v.decider.assume(PermAtMost(PermLookup(field.name, pmDef.pm, chunk.singletonRcvr.get), FullPerm), debugExp, dAInfo.withDependencyType(DependencyType.Internal)) + v.decider.assume(PermAtMost(PermLookup(field.name, pmDef.pm, chunk.singletonRcvr.get), FullPerm), debugExp, analysisInfoes.withDependencyType(DependencyType.Internal)) } else { val chunkReceivers = chunk.invs.get.inverses.map(i => App(i, chunk.invs.get.additionalArguments ++ chunk.quantifiedVars)) val triggers = chunkReceivers.map(r => Trigger(r)).toSeq @@ -333,7 +336,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol Some(DebugExp.createInstance(exp, exp)) } else { None } v.decider.assume( - Forall(chunk.quantifiedVars, PermAtMost(currentPermAmount, FullPerm), triggers, "qp-fld-prm-bnd"), debugExp, dAInfo.withDependencyType(DependencyType.Internal)) + Forall(chunk.quantifiedVars, PermAtMost(currentPermAmount, FullPerm), triggers, "qp-fld-prm-bnd"), debugExp, analysisInfoes.withDependencyType(DependencyType.Internal)) } } @@ -405,15 +408,15 @@ class LastRetryFailOnlyStateConsolidator(config: Config) extends LastRetryStateC * - Merging heaps and assuming QP permission bounds is equivalent to [[MinimalStateConsolidator]] */ class MinimalRetryingStateConsolidator(config: Config) extends RetryingStateConsolidator(config) { - override def merge(fr: FunctionRecorder, s: State, h: Heap, newH: Heap, v: Verifier, dAInfo: DependencyAnalysisInfo): (FunctionRecorder, Heap) = + override def merge(fr: FunctionRecorder, s: State, h: Heap, newH: Heap, v: Verifier, analysisInfoes: DependencyAnalysisInfoes): (FunctionRecorder, Heap) = (fr, Heap(h.values ++ newH.values)) - override def merge(fr: FunctionRecorder, s: State, h: Heap, ch: NonQuantifiedChunk, v: Verifier, dAInfo: DependencyAnalysisInfo): (FunctionRecorder, Heap) = + override def merge(fr: FunctionRecorder, s: State, h: Heap, ch: NonQuantifiedChunk, v: Verifier, analysisInfoes: DependencyAnalysisInfoes): (FunctionRecorder, Heap) = (fr, h + ch) - override protected def assumeUpperPermissionBoundForQPFields(s: State, @unused v: Verifier, dAInfo: DependencyAnalysisInfo): State = s + override protected def assumeUpperPermissionBoundForQPFields(s: State, @unused v: Verifier, analysisInfoes: DependencyAnalysisInfoes): State = s - override protected def assumeUpperPermissionBoundForQPFields(s: State, @unused heaps: Seq[Heap], @unused v: Verifier, dAInfo: DependencyAnalysisInfo): State = s + override protected def assumeUpperPermissionBoundForQPFields(s: State, @unused heaps: Seq[Heap], @unused v: Verifier, analysisInfoes: DependencyAnalysisInfoes): State = s } /** A variant of [[DefaultStateConsolidator]] that aims to work best when Silicon is run in @@ -433,12 +436,12 @@ class MoreComplexExhaleStateConsolidator(config: Config) extends DefaultStateCon // silver\src\test\resources\quantifiedpermissions\sets\generalised_shape.sil // to fail. - val dAInfo = FullDependencyAnalysisInfo.create("state consolidation", DependencyType.Internal, NoDependencyAnalysisMerge(), NoDependencyAnalysisJoin()) // TODO ake: review + val analysisInfoes = DependencyAnalysisInfoes.create("state consolidation", DependencyType.Internal, NoDependencyAnalysisMerge()) // TODO ake: review if (s.retrying) { // TODO: apply to all heaps (s.h +: s.reserveHeaps, as done below) // NOTE: Doing this regardless of s.retrying might improve completeness in certain (rare) cases - moreCompleteExhaleSupporter.assumeFieldPermissionUpperBounds(s.h, v, dAInfo) + moreCompleteExhaleSupporter.assumeFieldPermissionUpperBounds(s.h, v, analysisInfoes) } s diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index b2334197b..5f46ed36f 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -9,7 +9,7 @@ package viper.silicon.supporters import com.typesafe.scalalogging.Logger import viper.silicon.Map import viper.silicon.decider.Decider -import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyAnalysisJoinNodeInfo, DependencyAnalyzer, DependencyGraphInterpreter, DependencyType, SimpleAssertionNode, SimpleAssumptionNode} +import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyAnalysisInfoes, DependencyAnalysisJoinNodeInfo, DependencyAnalyzer, DependencyGraphInterpreter, DependencyType, SimpleAssertionNode, SimpleAssumptionNode} import viper.silicon.interfaces._ import viper.silicon.logger.records.data.WellformednessCheckRecord import viper.silicon.rules.{consumer, executionFlowController, executor, producer} @@ -78,10 +78,7 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif new java.io.File(s"${Verifier.config.tempDirectory()}/${method.name}.dot")) } - val annotatedAssumptionTypeOpt = DependencyAnalyzer.extractDependencyTypeFromInfo(method.info).map(_.assumptionType) - var postConditionType = AssumptionType.getPostcondType(method.body.isEmpty, DependencyAnalyzer.extractDependencyTypeFromInfo(method.info)) - - val presAssertionNodeForJoin = pres.flatMap(_.topLevelConjuncts).map(pc => SimpleAssertionNode(True, AssumptionType.Precondition, AnalysisSourceInfo.createAnalysisSourceInfo(pc), isClosed=false, isJoinNode=true)) + val presAssertionNodeForJoin = pres.flatMap(_.topLevelConjuncts).map(pc => SimpleAssertionNode(True, AnalysisSourceInfo.createAnalysisSourceInfo(pc), AssumptionType.Precondition, isClosed=false, isJoinNode=true)) presAssertionNodeForJoin foreach v.decider.dependencyAnalyzer.addAssertionNode @@ -89,9 +86,8 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif if(daJoinNodeInfoOpt.isDefined){ val infodaJoinNodeInfo = daJoinNodeInfoOpt.get // v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(infodaJoinNodeInfo.sourceInfo, DependencyType.make(AssumptionType.CustomInternal)) -// postConditionType = AssumptionType.CustomInternal val postCondNodes = (posts ++ pres).flatMap(_.topLevelConjuncts).map(pc => SimpleAssumptionNode(True, None, AnalysisSourceInfo.createAnalysisSourceInfo(pc), AssumptionType.ImplicitPostcondition, isClosed=false, isJoinNode=true)) - val postCondAssertNodes = (posts ++ pres).flatMap(_.topLevelConjuncts).map(pc => SimpleAssertionNode(True, AssumptionType.ImplicitPostcondition, AnalysisSourceInfo.createAnalysisSourceInfo(pc), isClosed=false, isJoinNode=true)) + val postCondAssertNodes = (posts ++ pres).flatMap(_.topLevelConjuncts).map(pc => SimpleAssertionNode(True, AnalysisSourceInfo.createAnalysisSourceInfo(pc), AssumptionType.ImplicitPostcondition, isClosed=false, isJoinNode=true)) val customJoinNode = infodaJoinNodeInfo.getAssertionNode postCondNodes foreach v.decider.dependencyAnalyzer.addAssumptionNode postCondAssertNodes foreach v.decider.dependencyAnalyzer.addAssertionNode @@ -110,14 +106,14 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif * rules in Smans' paper. */ executionFlowController.locally(s, v)((s1, v1) => { - produces(s1, freshSnap, pres, ContractNotWellformed, v1, annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Precondition))((s2, v2) => { + produces(s1, freshSnap, pres, ContractNotWellformed, v1, DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes)((s2, v2) => { v2.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) val s2a = s2.copy(oldHeaps = s2.oldHeaps + (Verifier.PRE_STATE_LABEL -> s2.h)) ( executionFlowController.locally(s2a, v2)((s3, v3) => { val s4 = s3.copy(h = v3.heapSupporter.getEmptyHeap(s3.program)) val impLog = new WellformednessCheckRecord(posts, s, v.decider.pcs) val sepIdentifier = symbExLog.openScope(impLog) - produces(s4, freshSnap, posts, ContractNotWellformed, v3, postConditionType)((_, _) => { + produces(s4, freshSnap, posts, ContractNotWellformed, v3, DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes)((_, _) => { symbExLog.closeScope(sepIdentifier) Success()})}) && { @@ -126,11 +122,11 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif if(method.body.isEmpty) v3.decider.removeDependencyAnalyzer() exec(s3, body, v3)((s4, v4) => { if(method.body.isEmpty) v3.decider.dependencyAnalyzer = da - consumes(s4, posts, false, postViolated, v4, DependencyType.make(postConditionType))((_, _, _) => + consumes(s4, posts, false, postViolated, v4, DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes)((_, _, _) => Success())})}) } )})}) if(method.body.isEmpty){ - v.decider.dependencyAnalyzer.addDependenciesForAbstractMembers(method.pres.flatMap(_.topLevelConjuncts), method.posts.flatMap(_.topLevelConjuncts), postConditionType) + v.decider.dependencyAnalyzer.addDependenciesForAbstractMembers(method.pres.flatMap(_.topLevelConjuncts), method.posts.flatMap(_.topLevelConjuncts), DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes) } val allErrors = (result :: result.previous.toList).filter(_.isInstanceOf[Failure]).map(_.asInstanceOf[Failure]) diff --git a/src/main/scala/supporters/PredicateVerificationUnit.scala b/src/main/scala/supporters/PredicateVerificationUnit.scala index a5ff5489c..e616809a1 100644 --- a/src/main/scala/supporters/PredicateVerificationUnit.scala +++ b/src/main/scala/supporters/PredicateVerificationUnit.scala @@ -8,22 +8,22 @@ package viper.silicon.supporters import com.typesafe.scalalogging.Logger import viper.silicon.common.collections.immutable.InsertionOrderedSet -import viper.silicon.dependencyAnalysis.{AssumptionType, DependencyAnalyzer, NoDependencyAnalysisInfo} -import viper.silver.ast -import viper.silver.ast.Program -import viper.silver.components.StatefulComponent -import viper.silver.verifier.errors._ import viper.silicon.decider.Decider -import viper.silicon.{Map, toMap} +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfoes +import viper.silicon.interfaces._ import viper.silicon.interfaces.decider.ProverLike -import viper.silicon.state._ +import viper.silicon.rules.executionFlowController import viper.silicon.state.State.OldHeaps +import viper.silicon.state._ import viper.silicon.state.terms._ -import viper.silicon.interfaces._ -import viper.silicon.rules.executionFlowController import viper.silicon.supporters.functions.{ActualFunctionRecorder, FunctionRecorder, FunctionRecorderHandler, NoopFunctionRecorder} -import viper.silicon.verifier.{Verifier, VerifierComponent} import viper.silicon.utils.{freshSnap, toSf} +import viper.silicon.verifier.{Verifier, VerifierComponent} +import viper.silicon.{Map, toMap} +import viper.silver.ast +import viper.silver.ast.Program +import viper.silver.components.StatefulComponent +import viper.silver.verifier.errors._ class PredicateData(val predicate: ast.Predicate) /* Note: Holding a reference to a fixed symbol converter (instead of going @@ -123,7 +123,7 @@ trait DefaultPredicateVerificationUnitProvider extends VerifierComponent { v: Ve /* locallyXXX { magicWandSupporter.checkWandsAreSelfFraming(σ.γ, σ.h, predicate, c)} &&*/ executionFlowController.locally(s, v)((s1, _) => { - produce(s1, toSf(snap), body, err, v, NoDependencyAnalysisInfo())((s2, v2) => { + produce(s1, toSf(snap), body, err, v, DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes)((s2, v2) => { val branchConds = v2.decider.pcs.branchConditions.reverse val branchCondExps = v2.decider.pcs.branchConditionExps.reverse assert(branchConds.length == branchCondExps.length) diff --git a/src/main/scala/supporters/SnapshotSupporter.scala b/src/main/scala/supporters/SnapshotSupporter.scala index 46e17d531..dbc672a93 100644 --- a/src/main/scala/supporters/SnapshotSupporter.scala +++ b/src/main/scala/supporters/SnapshotSupporter.scala @@ -7,7 +7,7 @@ package viper.silicon.supporters import viper.silicon.debugger.DebugExp -import viper.silicon.dependencyAnalysis.DependencyAnalysisInfo +import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfoes} import viper.silicon.state.terms.{Combine, First, Second, Sort, Term, Unit, sorts} import viper.silicon.state.{MagicWandIdentifier, State, SymbolConverter} import viper.silicon.utils.toSf @@ -28,7 +28,7 @@ trait SnapshotSupporter { a0: ast.Exp, a1: ast.Exp, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) : ((Sort, Verifier) => Term, (Sort, Verifier) => Term) } @@ -120,10 +120,10 @@ class DefaultSnapshotSupporter(symbolConverter: SymbolConverter) extends Snapsho a0: ast.Exp, a1: ast.Exp, v: Verifier, - dAInfo: DependencyAnalysisInfo) + analysisInfoes: DependencyAnalysisInfoes) : ((Sort, Verifier) => Term, (Sort, Verifier) => Term) = { - val (snap0, snap1) = createSnapshotPair(s, sf(sorts.Snap, v), a0, a1, v, dAInfo) + val (snap0, snap1) = createSnapshotPair(s, sf(sorts.Snap, v), a0, a1, v, analysisInfoes) val sf0 = toSf(snap0) val sf1 = toSf(snap1) @@ -131,7 +131,7 @@ class DefaultSnapshotSupporter(symbolConverter: SymbolConverter) extends Snapsho (sf0, sf1) } - private def createSnapshotPair(@unused s: State, snap: Term, @unused a0: ast.Exp, @unused a1: ast.Exp, v: Verifier, dAInfo: DependencyAnalysisInfo): (Term, Term) = { + private def createSnapshotPair(@unused s: State, snap: Term, @unused a0: ast.Exp, @unused a1: ast.Exp, v: Verifier, analysisInfoes: DependencyAnalysisInfoes): (Term, Term) = { /* [2015-11-17 Malte] If both fresh snapshot terms and first/second datatypes * are used, then the overall test suite verifies in 2min 10sec, whereas * it takes 2min 20sec when only first/second datatypes are used. Might be @@ -163,7 +163,7 @@ class DefaultSnapshotSupporter(symbolConverter: SymbolConverter) extends Snapsho (snap0, snap1, snap === Combine(snap0, snap1)) } - v.decider.assume(snapshotEq, Option.when(Verifier.config.enableDebugging())(DebugExp.createInstance("Snapshot", true)), dAInfo) + v.decider.assume(snapshotEq, Option.when(Verifier.config.enableDebugging())(DebugExp.createInstance("Snapshot", true)), analysisInfoes) (snap0, snap1) } diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index 879194606..4007524ef 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -192,7 +192,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver assertReadAccessOnly = !Verifier.config.respectFunctionPrePermAmounts()) - val presAssertionNodeForJoin = function.pres.flatMap(_.topLevelConjuncts).map(pc => SimpleAssertionNode(True, AssumptionType.Precondition, AnalysisSourceInfo.createAnalysisSourceInfo(pc), isClosed=false, isJoinNode=true)) + val presAssertionNodeForJoin = function.pres.flatMap(_.topLevelConjuncts).map(pc => SimpleAssertionNode(True, AnalysisSourceInfo.createAnalysisSourceInfo(pc), AssumptionType.Precondition, isClosed=false, isJoinNode=true)) presAssertionNodeForJoin foreach v.decider.dependencyAnalyzer.addAssertionNode /* Phase 1: Check well-definedness of the specifications */ @@ -211,7 +211,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver if (function.body.isEmpty) { decider.dependencyAnalyzer.addNodes(v.decider.prover.getPreambleAnalysisNodes) - decider.dependencyAnalyzer.addDependenciesForAbstractMembers(function.pres.flatMap(_.topLevelConjuncts), function.posts.flatMap(_.topLevelConjuncts), getPostconditionType(function)) + decider.dependencyAnalyzer.addDependenciesForAbstractMembers(function.pres.flatMap(_.topLevelConjuncts), function.posts.flatMap(_.topLevelConjuncts), DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes /* TODO ake */) result1 } else { /* Phase 2: Verify the function's postcondition */ @@ -251,14 +251,14 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver val result = executionFlowController.locally(s, v)((s0, _) => { val preMark = decider.setPathConditionMark() - produces(s0, toSf(`?s`), pres, ContractNotWellformed, v, AssumptionType.Precondition)((s1, _) => { + produces(s0, toSf(`?s`), pres, ContractNotWellformed, v, DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes)((s1, _) => { val relevantPathConditionStack = decider.pcs.after(preMark) phase1Data :+= Phase1Data(s1, relevantPathConditionStack.branchConditions, relevantPathConditionStack.branchConditionExps, relevantPathConditionStack.assumptions, Option.when(evaluator.withExp)(relevantPathConditionStack.assumptionExps)) // The postcondition must be produced with a fresh snapshot (different from `?s`) because // the postcondition's snapshot structure is most likely different than that of the // precondition - produces(s1, freshSnap, posts, ContractNotWellformed, v, getPostconditionType(function))((s2, _) => { + produces(s1, freshSnap, posts, ContractNotWellformed, v, DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes)((s2, _) => { recorders :+= s2.functionRecorder Success()})})}) @@ -267,10 +267,6 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver (result, phase1Data) } - private def getPostconditionType(function: ast.Function) = { - AssumptionType.getPostcondType(function.body.isEmpty, DependencyAnalyzer.extractDependencyTypeFromInfo(function.info)) - } - private def verify(function: ast.Function, phase1data: Seq[Phase1Data]) : VerificationResult = { @@ -285,17 +281,14 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver var recorders: Seq[FunctionRecorder] = Vector.empty val wExp = evaluator.withExp - val annotatedAssumptionTypeOpt = DependencyAnalyzer.extractAssumptionTypeFromInfo(function.info) - var postConditionType = getPostconditionType(function) decider.dependencyAnalyzer.addNodes(v.decider.prover.getPreambleAnalysisNodes) val daJoinNodeInfoOpt = function.info.getUniqueInfo[DependencyAnalysisJoinNodeInfo] if(daJoinNodeInfoOpt.isDefined){ val infodaJoinNodeInfo = daJoinNodeInfoOpt.get // v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(infodaJoinNodeInfo.sourceInfo, DependencyType.make(AssumptionType.CustomInternal)) -// postConditionType = AssumptionType.CustomInternal val postCondNodes = (posts ++ function.pres).flatMap(_.topLevelConjuncts).map(pc => SimpleAssumptionNode(True, None, AnalysisSourceInfo.createAnalysisSourceInfo(pc), AssumptionType.ImplicitPostcondition, isClosed=false, isJoinNode=true)) - val postCondAssertNodes = (posts ++ function.pres).flatMap(_.topLevelConjuncts).map(pc => SimpleAssertionNode(True, AssumptionType.ImplicitPostcondition, AnalysisSourceInfo.createAnalysisSourceInfo(pc), isClosed=false, isJoinNode=true)) + val postCondAssertNodes = (posts ++ function.pres).flatMap(_.topLevelConjuncts).map(pc => SimpleAssertionNode(True, AnalysisSourceInfo.createAnalysisSourceInfo(pc), AssumptionType.ImplicitPostcondition, isClosed=false, isJoinNode=true)) val customJoinNode = infodaJoinNodeInfo.getAssertionNode postCondNodes foreach v.decider.dependencyAnalyzer.addAssumptionNode @@ -313,25 +306,23 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver case (intermediateResult, Phase1Data(sPre, bcsPre, bcsPreExp, pcsPre, pcsPreExp)) => intermediateResult && executionFlowController.locally(sPre, v)((s1, _) => { val labelledBcsPre = And(bcsPre map (t => v.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t)))) - decider.setCurrentBranchCondition(labelledBcsPre, (BigAnd(bcsPreExp.map(_._1)), Option.when(wExp)(BigAnd(bcsPreExp.map(_._2.get)))), annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Precondition)) + val precondAnalysisSourceInfoes = DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes /* TODO ake */ + decider.setCurrentBranchCondition(labelledBcsPre, (BigAnd(bcsPreExp.map(_._1)), Option.when(wExp)(BigAnd(bcsPreExp.map(_._2.get)))), precondAnalysisSourceInfoes) val labelledPcsPre = pcsPre map (t => v.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t))) - decider.assume(labelledPcsPre, pcsPreExp, s"precondition of ${function.name}", enforceAssumption=false, assumptionType=annotatedAssumptionTypeOpt.getOrElse(AssumptionType.Precondition)) + decider.assume(labelledPcsPre, pcsPreExp, s"precondition of ${function.name}", enforceAssumption=false, precondAnalysisSourceInfoes) v.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) - eval(s1, body, FunctionNotWellformed(function), v, Some(DependencyType.make(AssumptionType.FunctionBody)))((s2, tBody, bodyNew, _) => { + eval(s1, body, FunctionNotWellformed(function), v, DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes)((s2, tBody, bodyNew, _) => { val debugExp = if (wExp) { val e = ast.EqCmp(ast.Result(function.typ)(), body)(function.pos, function.info, function.errT) val eNew = ast.EqCmp(ast.Result(function.typ)(), bodyNew.get)(function.pos, function.info, function.errT) Some(DebugExp.createInstance(e, eNew)) } else { None } - decider.analysisSourceInfoStack.setForcedSource(AnalysisSourceInfo.createAnalysisSourceInfo(body), DependencyType.get(body, DependencyType.make(AssumptionType.FunctionBody))) - decider.assume(BuiltinEquals(data.formalResult, tBody), debugExp, AssumptionType.FunctionBody) - decider.analysisSourceInfoStack.removeForcedSource() - consumes(s2, posts, false, postconditionViolated, v, DependencyType.make(postConditionType))((s3, _, _) => { + val bodyAnalysisSourceInfoes = DependencyAnalysisInfoes.create(AnalysisSourceInfo.createAnalysisSourceInfo(body), DependencyType.get(body,DependencyType.make(AssumptionType.FunctionBody))) + decider.assume(BuiltinEquals(data.formalResult, tBody), debugExp, bodyAnalysisSourceInfoes) + consumes(s2, posts, false, postconditionViolated, v, DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes)((s3, _, _) => { recorders :+= s3.functionRecorder Success()})})})} -// if(daJoinNodeInfoOpt.isDefined) v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(daJoinNodeInfoOpt.get.sourceInfo) - data.advancePhase(recorders) result From 9862a5ad18b26c54a3be6924c0fb03d59eba2277 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 30 Mar 2026 17:35:29 +0200 Subject: [PATCH 402/474] refactor dependency type --- src/main/scala/decider/Decider.scala | 39 +++--- .../dependencyAnalysis/AnalysisInfo.scala | 86 +----------- .../AnalysisSourceInfo.scala | 132 ------------------ .../DependencyAnalysisInfo.scala | 10 +- .../DependencyAnalysisNode.scala | 4 +- .../DependencyAnalyzer.scala | 16 +-- .../dependencyAnalysis/DependencyGraph.scala | 2 +- .../DependencyGraphImporter.scala | 1 + .../DependencyGraphInterpreter.scala | 2 +- .../FrontendDependencyAnalysisInfo.scala | 51 ------- .../dependencyAnalysis/SourceInfoStack.scala | 123 ---------------- .../UserLevelDependencyAnalysisNode.scala | 5 +- .../scala/interfaces/decider/Prover.scala | 5 +- src/main/scala/interfaces/state/Chunks.scala | 3 +- .../NonQuantifiedPropertyInterpreter.scala | 3 +- src/main/scala/rules/Brancher.scala | 35 ++--- src/main/scala/rules/ChunkSupporter.scala | 3 +- src/main/scala/rules/Consumer.scala | 1 + src/main/scala/rules/Evaluator.scala | 9 +- .../scala/rules/ExecutionFlowController.scala | 5 - src/main/scala/rules/Executor.scala | 7 +- src/main/scala/rules/HeapSupporter.scala | 1 + src/main/scala/rules/Joiner.scala | 3 +- src/main/scala/rules/MagicWandSupporter.scala | 5 +- .../rules/MoreCompleteExhaleSupporter.scala | 3 +- src/main/scala/rules/PredicateSupporter.scala | 3 +- src/main/scala/rules/Producer.scala | 2 +- .../scala/rules/QuantifiedChunkSupport.scala | 14 +- src/main/scala/rules/StateConsolidator.scala | 2 +- src/main/scala/supporters/Domains.scala | 7 +- .../scala/supporters/MethodSupporter.scala | 36 ++--- .../supporters/functions/FunctionData.scala | 5 +- .../functions/FunctionVerificationUnit.scala | 44 +++--- .../verifier/VerificationPoolManager.scala | 4 +- 34 files changed, 128 insertions(+), 543 deletions(-) delete mode 100644 src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala delete mode 100644 src/main/scala/dependencyAnalysis/FrontendDependencyAnalysisInfo.scala delete mode 100644 src/main/scala/dependencyAnalysis/SourceInfoStack.scala diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 9229fbb55..dd9e32694 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -10,7 +10,6 @@ import com.typesafe.scalalogging.Logger import viper.silicon._ import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp -import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType import viper.silicon.dependencyAnalysis._ import viper.silicon.interfaces._ import viper.silicon.interfaces.decider._ @@ -23,6 +22,7 @@ import viper.silicon.verifier.{Verifier, VerifierComponent} import viper.silver.ast import viper.silver.ast.{LocalVarWithVersion, Member, NoPosition} import viper.silver.components.StatefulComponent +import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, DependencyAnalysisJoinNodeInfo, DependencyType} import viper.silver.parser.{PKw, PPrimitiv, PReserved, PType} import viper.silver.reporter.{ConfigurationConfirmation, InternalWarningMessage} import viper.silver.verifier.{DependencyNotFoundError, Model} @@ -65,8 +65,6 @@ trait Decider { def registerChunk[CH <: GeneralChunk](buildChunk: Term => CH, perm: Term, analysisInfo: AnalysisInfo, isExhale: Boolean): CH def registerDerivedChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, analysisInfo: AnalysisInfo, isExhale: Boolean, createLabel: Boolean=true): CH def wrapWithDependencyAnalysisLabel(term: Term, sourceChunks: Iterable[Chunk] = Set.empty, sourceTerms: Iterable[Term] = Set.empty): Term - def pushAndGetAnalysisSourceInfo(e: ast.Exp, dependencyType: Option[DependencyType]): AnalysisSourceInfo - def pushAndGetAnalysisSourceInfo(stmt: ast.Stmt, dependencyType: Option[DependencyType]): AnalysisSourceInfo def isPathInfeasible(): Boolean def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp], analysisInfoes: DependencyAnalysisInfoes): Unit @@ -116,7 +114,6 @@ trait Decider { def statistics(): Map[String, String] var dependencyAnalyzer: DependencyAnalyzer - var analysisSourceInfoStack: AnalysisSourceInfoStack def initDependencyAnalyzer(member: Member, preambleNodes: Iterable[DependencyAnalysisNode]): Unit def removeDependencyAnalyzer(): Unit def getAnalysisInfo(daInfoes: DependencyAnalysisInfoes): AnalysisInfo @@ -152,7 +149,6 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => private val _debuggerAssumedTerms: mutable.Set[Term] = mutable.Set.empty var dependencyAnalyzer: DependencyAnalyzer = new NoDependencyAnalyzer() - var analysisSourceInfoStack: AnalysisSourceInfoStack = AnalysisSourceInfoStack() def isDependencyAnalysisEnabled: Boolean = Verifier.config.enableDependencyAnalysis() && !dependencyAnalyzer.isInstanceOf[NoDependencyAnalyzer] @@ -164,12 +160,10 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => }else{ removeDependencyAnalyzer() } - analysisSourceInfoStack = AnalysisSourceInfoStack() } override def removeDependencyAnalyzer(): Unit = { dependencyAnalyzer = new NoDependencyAnalyzer - analysisSourceInfoStack = AnalysisSourceInfoStack() } def getAnalysisInfo(analysisInfoes: DependencyAnalysisInfoes): AnalysisInfo = AnalysisInfo(this, dependencyAnalyzer, analysisInfoes) @@ -377,35 +371,36 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } private def pushAnalysisSourceInfo(sourceInfo: AnalysisSourceInfo, info: ast.Info, dependencyType: Option[DependencyType]): Unit = { - analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo, dependencyType.getOrElse(analysisSourceInfoStack.getDependencyType)) - if (info.getUniqueInfo[DependencyAnalysisJoinNodeInfo].isDefined) { - // add assertions and assumptions nodes that can be used for a graph join - val joinNodeInfo = info.getUniqueInfo[DependencyAnalysisJoinNodeInfo].get - val currentTopLevelSource = analysisSourceInfoStack.getFullSourceInfo.getTopLevelSource - val assertionNode = joinNodeInfo.getAssertionNode(currentTopLevelSource, analysisSourceInfoStack.getDependencyType.assertionType) - val assumptionNode = joinNodeInfo.getAssumptionNode(currentTopLevelSource) - dependencyAnalyzer.addAssertionNode(assertionNode) - dependencyAnalyzer.addAssumptionNode(assumptionNode) - dependencyAnalyzer.addDependency(Some(assumptionNode.id), Some(assertionNode.id)) - } +// analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo, dependencyType.getOrElse(analysisSourceInfoStack.getDependencyType)) + // TODO ake: frontend join +// if (info.getUniqueInfo[DependencyAnalysisJoinNodeInfo].isDefined) { +// // add assertions and assumptions nodes that can be used for a graph join +// val joinNodeInfo = info.getUniqueInfo[DependencyAnalysisJoinNodeInfo].get +// val currentTopLevelSource = analysisSourceInfoStack.getFullSourceInfo.getTopLevelSource +// val assertionNode = joinNodeInfo.getAssertionNode(currentTopLevelSource, analysisSourceInfoStack.getDependencyType.assertionType) +// val assumptionNode = joinNodeInfo.getAssumptionNode(currentTopLevelSource) +// dependencyAnalyzer.addAssertionNode(assertionNode) +// dependencyAnalyzer.addAssumptionNode(assumptionNode) +// dependencyAnalyzer.addDependency(Some(assumptionNode.id), Some(assertionNode.id)) +// } } def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp], analysisInfoes: DependencyAnalysisInfoes): Unit = { if (finalExp.isDefined) { - assume(assumptions=InsertionOrderedSet((t, Some(DebugExp.createInstance(e.get, finalExp.get)))), analysisSourceInfoStack.getFullSourceInfo, enforceAssumption = false, isDefinition = false, analysisInfoes) + assume(assumptions=InsertionOrderedSet((t, Some(DebugExp.createInstance(e.get, finalExp.get)))), analysisInfoes.getSourceInfo, enforceAssumption = false, isDefinition = false, analysisInfoes) } else { - assume(assumptions=InsertionOrderedSet((t, None)), analysisSourceInfoStack.getFullSourceInfo, enforceAssumption = false, isDefinition = false, analysisInfoes) + assume(assumptions=InsertionOrderedSet((t, None)), analysisInfoes.getSourceInfo, enforceAssumption = false, isDefinition = false, analysisInfoes) } } def assume(t: Term, debugExp: Option[DebugExp], analysisInfoes: DependencyAnalysisInfoes): Unit = { - assume(InsertionOrderedSet(Seq((t, debugExp))), analysisSourceInfoStack.getFullSourceInfo, enforceAssumption = false, isDefinition = false, analysisInfoes) + assume(InsertionOrderedSet(Seq((t, debugExp))), analysisInfoes.getSourceInfo, enforceAssumption = false, isDefinition = false, analysisInfoes) } def assumeDefinition(t: Term, debugExp: Option[DebugExp], analysisInfoes: DependencyAnalysisInfoes): Unit = { - assume(InsertionOrderedSet(Seq((t, debugExp))), analysisSourceInfoStack.getFullSourceInfo, enforceAssumption=false, isDefinition=true, analysisInfoes) + assume(InsertionOrderedSet(Seq((t, debugExp))), analysisInfoes.getSourceInfo, enforceAssumption=false, isDefinition=true, analysisInfoes) } def assume(assumptions: InsertionOrderedSet[(Term, Option[DebugExp])], analysisSourceInfo: AnalysisSourceInfo, enforceAssumption: Boolean, isDefinition: Boolean, analysisInfoes: DependencyAnalysisInfoes): Unit = { diff --git a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala index 0403a5b40..8603fa10e 100644 --- a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala +++ b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala @@ -1,92 +1,8 @@ package viper.silicon.dependencyAnalysis -object AssumptionType extends Enumeration { - type AssumptionType = Value - val Explicit, LoopInvariant, PathCondition, Rewrite, SourceCode, DomainAxiom, Implicit, Internal, Trigger, ExplicitPostcondition, ImplicitPostcondition, ImportedPostcondition, MethodCall, FunctionBody, Precondition, Ghost, Annotation, CustomInternal, Unknown = Value - - def fromString(s: String): Option[Value] = values.find(_.toString == s) - - def explicitAssumptionTypes: Set[AssumptionType] = Set(Explicit, ExplicitPostcondition) - def postconditionTypes: Set[AssumptionType] = Set(ImplicitPostcondition, ExplicitPostcondition, ImportedPostcondition) - def preconditionTypes: Set[AssumptionType] = Set(Precondition) - def explicitAssertionTypes: Set[AssumptionType] = Set(Explicit, ImplicitPostcondition, ExplicitPostcondition) - def internalTypes: Set[AssumptionType] = Set(Internal, Trigger, CustomInternal) // will always be hidden from user - def joinConditionTypes: Set[AssumptionType] = preconditionTypes ++ postconditionTypes ++ Set(FunctionBody, MethodCall) - def importedTypes: Set[AssumptionType] = Set(ImportedPostcondition) - def verificationAnnotationTypes: Set[AssumptionType] = Set(FunctionBody, LoopInvariant, Rewrite, ExplicitPostcondition, ImplicitPostcondition, ImportedPostcondition, Precondition, Explicit, DomainAxiom, Annotation) - def sourceCodeTypes: Set[AssumptionType] = AssumptionType.values.diff(explicitAssumptionTypes ++ explicitAssertionTypes ++ verificationAnnotationTypes ++ internalTypes) - - def getMaxPriorityAssumptionType(types: Set[AssumptionType]): Option[AssumptionType] = { - val priorityList = List(ExplicitPostcondition, Explicit) ++ internalTypes.toList ++ verificationAnnotationTypes.toList ++ sourceCodeTypes.toList ++ values.toList - priorityList.find(t => types.contains(t)) - } - - def getPostcondType(isAbstractFunction: Boolean, dependencyType: Option[DependencyType]=None, isImported: Boolean=false): AssumptionType = { - if(isImported) return ImportedPostcondition - - dependencyType.flatMap(_.assertionType match { - case AssumptionType.Explicit | AssumptionType.ExplicitPostcondition => Some(AssumptionType.ExplicitPostcondition) - case AssumptionType.ImportedPostcondition => Some(AssumptionType.ImportedPostcondition) - case AssumptionType.ImplicitPostcondition => Some(AssumptionType.ImplicitPostcondition) - case AssumptionType.Internal => Some(AssumptionType.Internal) - case AssumptionType.CustomInternal => Some(AssumptionType.CustomInternal) - case AssumptionType.Annotation | AssumptionType.Ghost => None - case _ => None - }).getOrElse( - if(isAbstractFunction) AssumptionType.ExplicitPostcondition else AssumptionType.ImplicitPostcondition - ) - } -} - -import viper.silicon.dependencyAnalysis.AssumptionType._ import viper.silicon.decider.Decider -import viper.silver.ast - -object DependencyType { - - val Implicit: DependencyType = DependencyType(AssumptionType.Implicit, AssumptionType.Implicit) - val SourceCode: DependencyType = DependencyType(AssumptionType.SourceCode, AssumptionType.SourceCode) - val Explicit: DependencyType = DependencyType(AssumptionType.Explicit, AssumptionType.Explicit) - val ExplicitAssertion: DependencyType = DependencyType(AssumptionType.Ghost, AssumptionType.Explicit) - val ExplicitAssumption: DependencyType = DependencyType(AssumptionType.Explicit, AssumptionType.Implicit) - val PathCondition: DependencyType = DependencyType(AssumptionType.PathCondition, AssumptionType.Implicit) - val Invariant: DependencyType = DependencyType(AssumptionType.LoopInvariant, AssumptionType.LoopInvariant) - val Rewrite: DependencyType = DependencyType(AssumptionType.Rewrite, AssumptionType.Rewrite) - val Internal: DependencyType = DependencyType(AssumptionType.Internal, AssumptionType.Internal) - val Trigger: DependencyType = DependencyType(AssumptionType.Trigger, AssumptionType.Trigger) - val MethodCall: DependencyType = DependencyType(AssumptionType.MethodCall, AssumptionType.MethodCall) - val Ghost: DependencyType = DependencyType.make(AssumptionType.Ghost) - val Annotation: DependencyType = DependencyType.make(AssumptionType.Annotation) - - def make(singleType: AssumptionType): DependencyType = DependencyType(singleType, singleType) - - def get(stmt: ast.Stmt): DependencyType = { - val annotatedDependencyType = DependencyAnalyzer.extractDependencyTypeFromInfo(stmt.info) - if(annotatedDependencyType.isDefined) return annotatedDependencyType.get - - stmt match { - case _: ast.MethodCall => MethodCall - case _: ast.NewStmt | _: ast.AbstractAssign => SourceCode - case _: ast.Exhale | _: ast.Assert => ExplicitAssertion - case _: ast.Inhale | _: ast.Assume => ExplicitAssumption - case _: ast.Fold | _: ast.Unfold | _: ast.Package | _: ast.Apply => Rewrite - case _: ast.Quasihavoc | _: ast.Quasihavocall => DependencyType.Implicit - case _ => DependencyType.make(Unknown) /* TODO: should not happen */ - } - } - - def get(exp: ast.Exp, dependencyType: DependencyType): DependencyType = DependencyAnalyzer.extractDependencyTypeFromInfo(exp.info).getOrElse(dependencyType) - - def getMaxPriorityType(types: Set[DependencyType]): Option[DependencyType] = { - val assumptionType = AssumptionType.getMaxPriorityAssumptionType(types.map(_.assumptionType)) - val assertionType = AssumptionType.getMaxPriorityAssumptionType(types.map(_.assertionType)) - if(assumptionType.isDefined && assertionType.isDefined) Some(DependencyType(assumptionType.get, assertionType.get)) else None - } - -} - -case class DependencyType(assumptionType: AssumptionType, assertionType: AssumptionType) +import viper.silver.dependencyAnalysis.DependencyType case class AnalysisInfo(decider: Decider, dependencyAnalyzer: DependencyAnalyzer, analysisInfoes: DependencyAnalysisInfoes) { def withDependencyType(dependencyType: DependencyType) = this.copy(analysisInfoes=analysisInfoes.withDependencyType(dependencyType)) diff --git a/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala b/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala deleted file mode 100644 index 8fc4cf0ee..000000000 --- a/src/main/scala/dependencyAnalysis/AnalysisSourceInfo.scala +++ /dev/null @@ -1,132 +0,0 @@ -package viper.silicon.dependencyAnalysis - -import viper.silicon.state.terms.True -import viper.silver.ast -import viper.silver.ast._ - -object AnalysisSourceInfo { - def extractPositionString(p: Position): String = { - p match { - case NoPosition => "???" - case filePos: AbstractSourcePosition => filePos.file.getFileName.toString + " @ line " + filePos.line - case filePos: FilePosition => filePos.file.getFileName.toString + " @ line " + filePos.line - case lineColumn: HasLineColumn => "line " + lineColumn.line.toString - case VirtualPosition(identifier) => "label " + identifier - } - } - - def createAnalysisSourceInfo(exp: ast.Exp): AnalysisSourceInfo = { - val depInfo = exp.info.getUniqueInfo[FrontendDependencyAnalysisInfo] - if(depInfo.isDefined) depInfo.get.getAnalysisSourceInfo - else ExpAnalysisSourceInfo(exp, exp.pos) - } - - def createAnalysisSourceInfo(stmt: ast.Stmt): AnalysisSourceInfo = { - val depInfo = stmt.info.getUniqueInfo[FrontendDependencyAnalysisInfo] - if(depInfo.isDefined) depInfo.get.getAnalysisSourceInfo - else StmtAnalysisSourceInfo(stmt, stmt.pos) - } - - def createAnalysisSourceInfo(description: String, pos: Position): AnalysisSourceInfo = StringAnalysisSourceInfo(description, pos) - -} - -trait AnalysisSourceInfo extends ast.Info { - override def toString: String = getPositionString - - def getDescription: String - - def getLineNumber: Option[Int] = getPosition match { - case column: HasLineColumn => Some(column.line) - case _ => None - } - - def getPositionString: String = { - AnalysisSourceInfo.extractPositionString(getPosition) - } - - def getPosition: Position - - /** - * @return the analysis source info used for lifting low-level graphs to higher-level graphs - * and presenting dependency results to the user - */ - def getTopLevelSource: AnalysisSourceInfo = this - - /** - * @return the analysis source info used for adding transitive edges within a source exp/stmt - */ - def getSourceForTransitiveEdges: AnalysisSourceInfo = getTopLevelSource - - /** - * @return the analysis source info used for joining graphs - */ - def getFineGrainedSource: AnalysisSourceInfo = this - - def isAnalysisEnabled: Boolean = true - - - override def comment: Seq[String] = Nil - override def isCached: Boolean = false -} - -case class NoAnalysisSourceInfo() extends AnalysisSourceInfo { - override def getPosition: Position = NoPosition - - override def getDescription: String = "" - - override def equals(obj: Any): Boolean = false -} - -case class ExpAnalysisSourceInfo(source: ast.Exp, pos: Position) extends AnalysisSourceInfo { - - override def toString: String = getDescription + " (" + super.toString + ")" - - override def getPosition: Position = pos - - override def isAnalysisEnabled: Boolean = DependencyAnalyzer.extractEnableAnalysisFromInfo(source.info).getOrElse(true) - - override def getDescription: String = source.toString.replaceAll("\n", "\t") -} - -case class StmtAnalysisSourceInfo(source: ast.Stmt, pos: Position) extends AnalysisSourceInfo { - - override def toString: String = getDescription + " (" + super.toString + ")" - override def getPosition: Position = pos - - override def isAnalysisEnabled: Boolean = DependencyAnalyzer.extractEnableAnalysisFromInfo(source.info).getOrElse(true) - - override def getDescription: String = source.toString().replaceAll("\n", "\t") -} - -case class StringAnalysisSourceInfo(description: String, position: Position) extends AnalysisSourceInfo { - override def toString: String = getDescription + " (" + getPositionString + ")" - override def getPosition: Position = position - - override def getDescription: String = description.replaceAll("\n", "\t") -} - -case class TransitivityAnalysisSourceInfo(actualSource: AnalysisSourceInfo, transitivitySource: AnalysisSourceInfo) extends AnalysisSourceInfo { - - override def getPosition: Position = actualSource.getPosition - override def toString: String = getTopLevelSource.toString - - override def getSourceForTransitiveEdges: AnalysisSourceInfo = transitivitySource.getTopLevelSource - override def getTopLevelSource: AnalysisSourceInfo = actualSource.getTopLevelSource - override def getFineGrainedSource: AnalysisSourceInfo = actualSource.getFineGrainedSource - override def isAnalysisEnabled: Boolean = actualSource.isAnalysisEnabled - - override def getDescription: String = actualSource.getDescription -} - -case class CompositeAnalysisSourceInfo(coarseGrainedSource: AnalysisSourceInfo, fineGrainedSource: AnalysisSourceInfo) extends AnalysisSourceInfo { - override def toString: String = getTopLevelSource.toString - override def getPosition: Position = coarseGrainedSource.getPosition - - override def getTopLevelSource: AnalysisSourceInfo = coarseGrainedSource.getTopLevelSource - override def getFineGrainedSource: AnalysisSourceInfo = fineGrainedSource.getFineGrainedSource - - override def isAnalysisEnabled: Boolean = coarseGrainedSource.isAnalysisEnabled && fineGrainedSource.isAnalysisEnabled - - override def getDescription: String = coarseGrainedSource.getDescription -} diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala index a4a869034..817f8b635 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala @@ -2,7 +2,8 @@ package viper.silicon.dependencyAnalysis import viper.silicon.dependencyAnalysis.JoinType.JoinType import viper.silver.ast -import viper.silver.ast.NoPosition +import viper.silver.ast.{AbstractAssign, Apply, Assert, Assume, DependencyTypeInfo, Exhale, ExtensionStmt, Fold, Goto, If, Inhale, Label, LocalVarDeclStmt, MethodCall, NewStmt, NoPosition, Quasihavoc, Quasihavocall, Seqn, Unfold, While} +import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, DependencyType, StringAnalysisSourceInfo} trait AnalysisInfoes { @@ -41,7 +42,8 @@ case class DependencyAnalysisInfoes(sourceInfoes: List[AnalysisSourceInfo], depe def getDependencyType: DependencyType = dependencyTypes.head.dependencyType - def getMergeInfo: DependencyAnalysisMergeInfo = mergeInfoes.head // TODO + def getMergeInfo: DependencyAnalysisMergeInfo = NoDependencyAnalysisMerge() +// mergeInfoes.head // TODO def getJoinInfo: List[DependencyAnalysisJoinInfo] = joinInfoes @@ -65,11 +67,7 @@ object DependencyAnalysisInfoes { create(StringAnalysisSourceInfo(infoString, NoPosition), dependencyType) } -case class DependencyTypeInfo(dependencyType: DependencyType) extends ast.Info { - override def comment: Seq[String] = Nil - override def isCached: Boolean = false -} object JoinType extends Enumeration { diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala index 2e9bc65da..ece8090e1 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala @@ -1,10 +1,10 @@ package viper.silicon.dependencyAnalysis -import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType import viper.silicon.interfaces.state.Chunk import viper.silicon.state.terms.{False, Term, Var} import viper.silver.ast.Position -import viper.silver.dependencyAnalysis.AbstractDependencyAnalysisNode +import viper.silver.dependencyAnalysis.{AbstractDependencyAnalysisNode, AnalysisSourceInfo, AssumptionType, NoAnalysisSourceInfo} +import viper.silver.dependencyAnalysis.AssumptionType.AssumptionType trait DependencyAnalysisNode extends AbstractDependencyAnalysisNode { diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index 3e237fc0d..8b5cfe338 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -1,14 +1,13 @@ package viper.silicon.dependencyAnalysis -import viper.silicon.dependencyAnalysis.AssumptionType.{AssumptionType, CustomInternal} -import viper.silicon.dependencyAnalysis.DependencyAnalyzer.{runtimeOverheadPermissionNodes, startTimeMeasurement, stopTimeMeasurementAndAddToTotal, timeToAddTransitiveEdges, timeToMergeNodes, timeToProcessUnsatCore, timeToRemoveInternalNodes} +import viper.silicon.dependencyAnalysis.DependencyAnalyzer._ import viper.silicon.interfaces.state.{Chunk, GeneralChunk} import viper.silicon.state.terms._ import viper.silicon.verifier.Verifier import viper.silver.ast -import viper.silver.ast.utility.Expressions.isKnownWellDefined -import viper.silver.ast.{Apply, Applying, Assert, Asserting, Assume, Div, Exhale, Exp, ExtensionStmt, FieldAccess, FieldAccessPredicate, FieldAssign, Fold, FuncApp, Goto, If, Inhale, Label, LocalVarAssign, LocalVarDeclStmt, MapLookup, MethodCall, Mod, NewStmt, Package, Program, Quasihavoc, Quasihavocall, SeqIndex, Seqn, Stmt, Unfold, Unfolding, While} -import viper.silver.ast.utility.{Expressions, ViperStrategy} +import viper.silver.ast.{NoPerm, _} +import viper.silver.dependencyAnalysis.AssumptionType.AssumptionType +import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyType, FrontendDependencyAnalysisInfo} import java.util.concurrent.atomic.AtomicLong import scala.collection.mutable @@ -217,7 +216,7 @@ object DependencyAnalyzer { stopTimeMeasurementAndAddToTotal(startTime, timeForFunctionJoin) startTime = startTimeMeasurement() - val customInternalNodes = joinCandidateAssumptions.filter(_.assumptionType.equals(CustomInternal)).map(_.id) + val customInternalNodes = joinCandidateAssumptions.filter(_.assumptionType.equals(AssumptionType.CustomInternal)).map(_.id) // postconditions of methods assumed by every method call should depend on the assertions that justify them // hence, we add edges from assertions of method postconditions to assumptions of the same postcondition (at method calls) val relevantAssumptionNodes = joinCandidateAssumptions @@ -338,7 +337,7 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { override def registerExhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, labelNodeOpt: Option[LabelNode], analysisInfo: AnalysisInfo): CH = { val startTime = startTimeMeasurement() val labelNode = labelNodeOpt.get - val chunk = buildChunk(Ite(labelNode.term, perm, NoPerm)) + val chunk = buildChunk(Ite(labelNode.term, perm, NoPerm.asInstanceOf[Term])) val chunkNode = addPermissionExhaleNode(chunk, chunk.perm, analysisInfo.analysisInfoes.getSourceInfo, analysisInfo.analysisInfoes.getDependencyType.assertionType, labelNode) if(chunkNode.isDefined) addDependency(chunkNode, Some(labelNode.id)) @@ -350,7 +349,7 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { override def registerInhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, labelNodeOpt: Option[LabelNode], analysisInfo: AnalysisInfo): CH = { val startTime = startTimeMeasurement() val labelNode = labelNodeOpt.get - val chunk = buildChunk(Ite(labelNode.term, perm, NoPerm)) + val chunk = buildChunk(Ite((labelNode.term, perm, NoPerm.asInstanceOf[Term]))) val chunkNode = addPermissionInhaleNode(chunk, chunk.perm, analysisInfo.analysisInfoes.getSourceInfo, analysisInfo.analysisInfoes.getDependencyType.assumptionType, labelNode, isJoinNode=analysisInfo.analysisInfoes.getJoinInfo.exists(_.isJoin)) if(chunkNode.isDefined) addDependency(chunkNode, Some(labelNode.id)) @@ -406,7 +405,6 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { } override def addAssertionFailedNode(failedAssertion: Term, analysisInfoes: DependencyAnalysisInfoes): Option[Int] = { - val assumptionType = if(AssumptionType.postconditionTypes.contains(analysisInfoes.getDependencyType.assumptionType)) AssumptionType.ExplicitPostcondition else AssumptionType.Explicit val assumeNode = SimpleAssumptionNode(failedAssertion, None, analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assumptionType, analysisInfoes.getMergeInfo.isMerge, analysisInfoes.getJoinInfo.exists(_.isJoin)) val assertFailedNode = SimpleAssertionNode(failedAssertion, analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assumptionType, analysisInfoes.getMergeInfo.isMerge, hasFailed=true, isJoinNode=analysisInfoes.getJoinInfo.exists(_.isJoin)) dependencyGraph.addNode(assumeNode) diff --git a/src/main/scala/dependencyAnalysis/DependencyGraph.scala b/src/main/scala/dependencyAnalysis/DependencyGraph.scala index 37645f6ee..eb13dbbc8 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraph.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraph.scala @@ -1,6 +1,6 @@ package viper.silicon.dependencyAnalysis -import viper.silver.dependencyAnalysis.AbstractReadOnlyDependencyGraph +import viper.silver.dependencyAnalysis.{AbstractReadOnlyDependencyGraph, AnalysisSourceInfo, AssumptionType} import java.io.PrintWriter import java.nio.file.Paths diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala index e6e02fa6b..f16678cdd 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala @@ -8,6 +8,7 @@ import viper.silicon.state.terms.sorts.Bool import viper.silicon.state.terms.{NoPerm, Term, True, Var} import viper.silver.ast import viper.silver.ast._ +import viper.silver.dependencyAnalysis.{AssumptionType, StringAnalysisSourceInfo} import viper.silver.frontend.SilFrontend import java.nio.file.Paths diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index a3ba4df12..2094d4be1 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -8,7 +8,7 @@ import viper.silver.ast import viper.silver.ast.utility.ViperStrategy import viper.silver.ast.utility.rewriter.Traverse import viper.silver.ast.{If, Program, Stmt} -import viper.silver.dependencyAnalysis.AbstractDependencyGraphInterpreter +import viper.silver.dependencyAnalysis.{AbstractDependencyGraphInterpreter, AnalysisSourceInfo, AssumptionType} import java.io.PrintWriter import java.lang.Double.isNaN diff --git a/src/main/scala/dependencyAnalysis/FrontendDependencyAnalysisInfo.scala b/src/main/scala/dependencyAnalysis/FrontendDependencyAnalysisInfo.scala deleted file mode 100644 index e501a079b..000000000 --- a/src/main/scala/dependencyAnalysis/FrontendDependencyAnalysisInfo.scala +++ /dev/null @@ -1,51 +0,0 @@ -package viper.silicon.dependencyAnalysis - -import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType -import viper.silicon.state.terms.True -import viper.silver.ast.{Info, Position} - -abstract class FrontendDependencyAnalysisInfo extends Info { - override val comment = Nil - override val isCached = false - - val info: String - val pos: Position - val dependencyType: Option[DependencyType]=None - - def getAnalysisSourceInfo: AnalysisSourceInfo -} - -case class SimpleFrontendDependencyAnalysisInfo(sourceInfo: AnalysisSourceInfo, _dependencyType: DependencyType) extends FrontendDependencyAnalysisInfo { - - override val info: String = sourceInfo.toString - override val pos: Position = sourceInfo.getPosition - override val dependencyType: Option[DependencyType] = Some(_dependencyType) - - override def getAnalysisSourceInfo: AnalysisSourceInfo = sourceInfo -} - -/* - Viper statements / expressions with a DependencyAnalysisJoinNodeInfo will produce an assertion and assumption node with the given source info and dependency type, - and with isJoinNode set to true. - The idea is that these nodes can be used to join graphs without the need for an explicit method call. - We use this, for example, for Gobra interfaces and implementation proofs. - */ -case class DependencyAnalysisJoinNodeInfo(sourceInfo: AnalysisSourceInfo, _dependencyType: DependencyType) extends FrontendDependencyAnalysisInfo { - def getAssertionNode: GeneralAssertionNode = - SimpleAssertionNode(True, sourceInfo, AssumptionType.CustomInternal, isClosed=false, isJoinNode=true) - - def getAssertionNode(outerSourceInfo: AnalysisSourceInfo): GeneralAssertionNode = - SimpleAssertionNode(True, CompositeAnalysisSourceInfo(outerSourceInfo, sourceInfo), AssumptionType.CustomInternal, isClosed=false, isJoinNode=true) - - def getAssertionNode(outerSourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType): GeneralAssertionNode = - SimpleAssertionNode(True, CompositeAnalysisSourceInfo(outerSourceInfo, sourceInfo), assumptionType, isClosed=false, isJoinNode=true) - - def getAssumptionNode(outerSourceInfo: AnalysisSourceInfo): GeneralAssumptionNode = - SimpleAssumptionNode(True, None, CompositeAnalysisSourceInfo(outerSourceInfo, sourceInfo), AssumptionType.CustomInternal, isClosed=false, isJoinNode=true) - - override val info: String = sourceInfo.toString - override val pos: Position = sourceInfo.getPosition - override val dependencyType: Option[DependencyType] = Some(_dependencyType) - - override def getAnalysisSourceInfo: AnalysisSourceInfo = sourceInfo -} \ No newline at end of file diff --git a/src/main/scala/dependencyAnalysis/SourceInfoStack.scala b/src/main/scala/dependencyAnalysis/SourceInfoStack.scala deleted file mode 100644 index 2bbc94954..000000000 --- a/src/main/scala/dependencyAnalysis/SourceInfoStack.scala +++ /dev/null @@ -1,123 +0,0 @@ -package viper.silicon.dependencyAnalysis - -import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType -import viper.silicon.verifier.Verifier -import viper.silver.ast.NoPosition - -import java.util.concurrent.atomic.AtomicInteger - -object SourceInfoStackID { - private val idCounter: AtomicInteger = new AtomicInteger(0) - - def nextId(): Int = { - idCounter.getAndIncrement() - } -} - -trait SourceInfoStack { - - def getAnalysisSourceInfos: List[(AnalysisSourceInfo, DependencyType)] - - def getFullSourceInfo: AnalysisSourceInfo - - def getAssumptionType: AssumptionType - - def getAssertionType: AssumptionType - - def getDependencyType: DependencyType - - def addAnalysisSourceInfo(analysisSourceInfo: AnalysisSourceInfo, dependencyType: DependencyType): Unit - - def setAnalysisSourceInfo(analysisSourceInfo: List[(AnalysisSourceInfo, DependencyType)]): Unit - - def popAnalysisSourceInfo(analysisSourceInfo: AnalysisSourceInfo): Unit - - def getForcedSource: Option[(AnalysisSourceInfo, DependencyType)] - - def setUniqueForcedSource(description: String, dependencyType: DependencyType = DependencyType.Internal): Unit - - def setForcedSource(description: String, dependencyType: DependencyType = DependencyType.Internal): Unit - - def setForcedSource(source: AnalysisSourceInfo, dependencyType: DependencyType): Unit - - def setForcedSource(sourceOpt: Option[(AnalysisSourceInfo, DependencyType)]): Unit - - def removeForcedSource(): Unit -} - -case class AnalysisSourceInfoStack() extends SourceInfoStack { - private var sourceInfos: List[(AnalysisSourceInfo, DependencyType)] = List.empty - private var forcedMainSource: Option[(AnalysisSourceInfo, DependencyType)] = None - var isJoinRelevantNode: Boolean = false - - override def getAnalysisSourceInfos: List[(AnalysisSourceInfo, DependencyType)] = sourceInfos - - override def getFullSourceInfo: AnalysisSourceInfo = { - if(!Verifier.config.enableDependencyAnalysis()) return NoAnalysisSourceInfo() - if(forcedMainSource.isDefined) - return forcedMainSource.get._1 - if(sourceInfos.size <= 1){ - sourceInfos.headOption.map(_._1).getOrElse(NoAnalysisSourceInfo()) - } else{ - CompositeAnalysisSourceInfo(sourceInfos.last._1, sourceInfos.head._1) - } - } - - override def addAnalysisSourceInfo(analysisSourceInfo: AnalysisSourceInfo, dependencyType: DependencyType): Unit = { - if(!Verifier.config.enableDependencyAnalysis()) return - sourceInfos = (analysisSourceInfo, dependencyType) +: sourceInfos - } - - override def setAnalysisSourceInfo(analysisSourceInfo: List[(AnalysisSourceInfo, DependencyType)]): Unit = { - if(!Verifier.config.enableDependencyAnalysis()) return - sourceInfos = analysisSourceInfo - } - - override def popAnalysisSourceInfo(analysisSourceInfo: AnalysisSourceInfo): Unit = { - if(!Verifier.config.enableDependencyAnalysis()) return - - var currSourceInfo = sourceInfos - // popping just one source info might not be enough since infeasible branches might return without popping the source info - while(currSourceInfo.nonEmpty && !currSourceInfo.head._1.equals(analysisSourceInfo)) { - currSourceInfo = currSourceInfo.tail - } - if(currSourceInfo.isEmpty || !currSourceInfo.head._1.equals(analysisSourceInfo)) - throw new RuntimeException("unexpected source info") - sourceInfos = currSourceInfo.tail - } - - override def getForcedSource: Option[(AnalysisSourceInfo, DependencyType)] = forcedMainSource - - override def setForcedSource(description: String, dependencyType: DependencyType = DependencyType.Internal): Unit = { - forcedMainSource = Some(StringAnalysisSourceInfo(description, NoPosition), dependencyType) - } - - override def setUniqueForcedSource(description: String, dependencyType: DependencyType = DependencyType.Internal): Unit = { - forcedMainSource = Some(StringAnalysisSourceInfo(description + SourceInfoStackID.nextId(), NoPosition), dependencyType) - } - - override def setForcedSource(source: AnalysisSourceInfo, dependencyType: DependencyType): Unit = { - forcedMainSource = Some(source, dependencyType) - } - - override def setForcedSource(sourceOpt: Option[(AnalysisSourceInfo, DependencyType)]): Unit = { - forcedMainSource = sourceOpt - } - - override def removeForcedSource(): Unit = { - forcedMainSource = None - } - - override def getAssumptionType: AssumptionType = getDependencyType.assumptionType - - override def getAssertionType: AssumptionType = getDependencyType.assertionType - - override def getDependencyType: DependencyType = { - if(!Verifier.config.enableDependencyAnalysis()) return DependencyType.make(AssumptionType.Unknown) - forcedMainSource match { - case Some(value) => value._2 - case None => sourceInfos.lastOption.map(_._2).getOrElse(DependencyType.make(AssumptionType.Unknown)) - } - } - -} \ No newline at end of file diff --git a/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala b/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala index 515d6010e..15ad7564a 100644 --- a/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala +++ b/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala @@ -1,9 +1,10 @@ package dependencyAnalysis -import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyAnalysisNode, GeneralAssertionNode, GeneralAssumptionNode, StringAnalysisSourceInfo} -import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType +import viper.silicon.dependencyAnalysis._ import viper.silicon.state.terms.{And, Term} import viper.silver.ast.Position +import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, StringAnalysisSourceInfo} +import viper.silver.dependencyAnalysis.AssumptionType.AssumptionType object UserLevelDependencyAnalysisNode { diff --git a/src/main/scala/interfaces/decider/Prover.scala b/src/main/scala/interfaces/decider/Prover.scala index 985bd63f7..c535f4f9e 100644 --- a/src/main/scala/interfaces/decider/Prover.scala +++ b/src/main/scala/interfaces/decider/Prover.scala @@ -6,16 +6,17 @@ package viper.silicon.interfaces.decider -import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType -import viper.silicon.dependencyAnalysis._ import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.common.config.Version import viper.silicon.debugger.DebugAxiom +import viper.silicon.dependencyAnalysis._ import viper.silicon.state.terms._ import viper.silicon.verifier.Verifier import viper.silicon.{Config, Map} import viper.silver.ast import viper.silver.components.StatefulComponent +import viper.silver.dependencyAnalysis.AssumptionType.AssumptionType +import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, DependencyType} import viper.silver.verifier.Model sealed abstract class Result diff --git a/src/main/scala/interfaces/state/Chunks.scala b/src/main/scala/interfaces/state/Chunks.scala index d3fb81a6e..1bdd81e6e 100644 --- a/src/main/scala/interfaces/state/Chunks.scala +++ b/src/main/scala/interfaces/state/Chunks.scala @@ -6,11 +6,12 @@ package viper.silicon.interfaces.state -import viper.silicon.dependencyAnalysis.{AnalysisInfo, AssumptionType, DependencyType} import viper.silicon +import viper.silicon.dependencyAnalysis.AnalysisInfo import viper.silicon.resources.ResourceID import viper.silicon.state.terms.{Term, Var} import viper.silver.ast +import viper.silver.dependencyAnalysis.DependencyType import scala.annotation.unused diff --git a/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala b/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala index 96d0640f2..31d1b4077 100644 --- a/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala +++ b/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala @@ -7,13 +7,14 @@ package viper.silicon.resources import viper.silicon.Map -import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfoes, DependencyType} +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfoes import viper.silicon.interfaces.state._ import viper.silicon.state.terms.Term import viper.silicon.state.{QuantifiedBasicChunk, terms} import viper.silicon.utils.ast.{BigAnd, replaceVarsInExp} import viper.silicon.verifier.Verifier import viper.silver.ast +import viper.silver.dependencyAnalysis.DependencyType class NonQuantifiedPropertyInterpreter(heap: Iterable[Chunk], verifier: Verifier) extends PropertyInterpreter { diff --git a/src/main/scala/rules/Brancher.scala b/src/main/scala/rules/Brancher.scala index d4b0f0565..3abb6d67a 100644 --- a/src/main/scala/rules/Brancher.scala +++ b/src/main/scala/rules/Brancher.scala @@ -8,14 +8,15 @@ package viper.silicon.rules import viper.silicon.common.concurrency._ import viper.silicon.decider.PathConditionStack -import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfoes, DependencyAnalyzer, DependencyType} +import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfoes, DependencyAnalyzer} import viper.silicon.interfaces.{Unreachable, VerificationResult} -import viper.silicon.reporting.condenseToViperResult +import viper.silicon.reporting.{condenseToViperResult, convertToViperResult} import viper.silicon.state.State import viper.silicon.state.terms.{FunctionDecl, MacroDecl, Not, Term} import viper.silicon.verifier.Verifier import viper.silver.ast import viper.silver.ast.utility.Expressions +import viper.silver.dependencyAnalysis.DependencyType import viper.silver.reporter.BranchFailureMessage import viper.silver.verifier.Failure @@ -46,15 +47,14 @@ object brancher extends BranchingRules { : VerificationResult = { if(v.decider.isPathInfeasible()){ - val analysisSourceInfo = v.decider.pushAndGetAnalysisSourceInfo(conditionExp._1, Some(DependencyAnalyzer.extractDependencyTypeFromInfo(conditionExp._1.info).getOrElse(DependencyType.PathCondition))) + val analysisInfoes1 = analysisInfoes.addInfo(conditionExp._1.info, conditionExp._1) // FIXME ake: infeasible path // val assertionNodesForJoin = DependencyAnalyzer.extractAssertionsForJoin(conditionExp._1, s.program) // assertionNodesForJoin.foreach(n => v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, CompositeAnalysisSourceInfo(v.decider.analysisSourceInfoStack.getFullSourceInfo.getTopLevelSource, AnalysisSourceInfo.createAnalysisSourceInfo(n)), v.decider.analysisSourceInfoStack.getDependencyType, isJoinNode=true)) if(!Expressions.isKnownWellDefined(conditionExp._1, Some(s.program))){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfoes) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfoes1) } - v.decider.dependencyAnalyzer.addAssumption(condition, analysisInfoes) - v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(analysisSourceInfo) + v.decider.dependencyAnalyzer.addAssumption(condition, analysisInfoes1) return fThen(s, v).combine(fElse(s, v)) } @@ -73,19 +73,17 @@ object brancher extends BranchingRules { && s.quantifiedVariables.map(_._1).exists(condition.freeVariables.contains)) ) - val sourceInfo = v.decider.pushAndGetAnalysisSourceInfo(conditionExp._1, Some(DependencyType.get(conditionExp._1, DependencyType.PathCondition))) + val analysisInfoes1 = analysisInfoes.addInfo(conditionExp._1.info, conditionExp._1) /* True if the then-branch is to be explored */ val executeThenBranch = ( skipPathFeasibilityCheck - || !v.decider.check(negatedCondition, Verifier.config.checkTimeout(), analysisInfoes.withDependencyType(DependencyType.Internal))) + || !v.decider.check(negatedCondition, Verifier.config.checkTimeout(), analysisInfoes1.withDependencyType(DependencyType.Internal))) /* False if the then-branch is to be explored */ val executeElseBranch = ( !executeThenBranch /* Assumes that ast least one branch is feasible */ || skipPathFeasibilityCheck - || !v.decider.check(condition, Verifier.config.checkTimeout(), analysisInfoes.withDependencyType(DependencyType.Internal))) - - v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) + || !v.decider.check(condition, Verifier.config.checkTimeout(), analysisInfoes1.withDependencyType(DependencyType.Internal))) val parallelizeElseBranch = s.parallelizeBranches && executeThenBranch && executeElseBranch @@ -115,7 +113,6 @@ object brancher extends BranchingRules { var macrosOfElseBranchDecider: Seq[MacroDecl] = null var pcsForElseBranch: PathConditionStack = null var noOfErrors = 0 - val currentAnalysisSourceInfos = v.decider.analysisSourceInfoStack.getAnalysisSourceInfos val elseBranchVerificationTask: Verifier => VerificationResult = if (executeElseBranch || Verifier.config.disableInfeasibilityChecks()) { @@ -162,13 +159,10 @@ object brancher extends BranchingRules { } elseBranchVerifier = v0.uniqueId - v0.decider.analysisSourceInfoStack.setAnalysisSourceInfo(currentAnalysisSourceInfos) executionFlowController.locally(s, v0)((s1, v1) => { v1.decider.prover.comment(s"[else-branch: $cnt | $negatedCondition]") - val sourceInfo = v.decider.pushAndGetAnalysisSourceInfo(conditionExp._1, Some(DependencyType.get(conditionExp._1, DependencyType.PathCondition))) - v1.decider.setCurrentBranchCondition(negatedCondition, (negatedConditionExp, negatedConditionExpNew), analysisInfoes) - if(v.decider.isDependencyAnalysisEnabled && !executeElseBranch) v.decider.checkSmokeAndSetInfeasibilityNode(analysisInfoes) - v1.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) + v1.decider.setCurrentBranchCondition(negatedCondition, (negatedConditionExp, negatedConditionExpNew), analysisInfoes1) + if(v.decider.isDependencyAnalysisEnabled && !executeElseBranch) v.decider.checkSmokeAndSetInfeasibilityNode(analysisInfoes1) var functionsOfElseBranchdDeciderBefore: Set[FunctionDecl] = null var nMacrosOfElseBranchDeciderBefore: Int = 0 @@ -216,13 +210,10 @@ object brancher extends BranchingRules { val res = { val thenRes = if (executeThenBranch || Verifier.config.disableInfeasibilityChecks()) { v.symbExLog.markReachable(uidBranchPoint) - v.decider.analysisSourceInfoStack.setAnalysisSourceInfo(currentAnalysisSourceInfos) executionFlowController.locally(s, v)((s1, v1) => { v1.decider.prover.comment(s"[then-branch: $cnt | $condition]") - val sourceInfo = v.decider.pushAndGetAnalysisSourceInfo(conditionExp._1, Some(DependencyType.get(conditionExp._1, DependencyType.PathCondition))) - v1.decider.setCurrentBranchCondition(condition, conditionExp, analysisInfoes) - if(v.decider.isDependencyAnalysisEnabled && !executeThenBranch) v.decider.checkSmokeAndSetInfeasibilityNode(analysisInfoes) - v1.decider.analysisSourceInfoStack.popAnalysisSourceInfo(sourceInfo) + v1.decider.setCurrentBranchCondition(condition, conditionExp, analysisInfoes1) + if(v.decider.isDependencyAnalysisEnabled && !executeThenBranch) v.decider.checkSmokeAndSetInfeasibilityNode(analysisInfoes1) fThen(v1.stateConsolidator(s1).consolidateOptionally(s1, v1), v1) }) diff --git a/src/main/scala/rules/ChunkSupporter.scala b/src/main/scala/rules/ChunkSupporter.scala index 2fa35c20b..f05fe41ee 100644 --- a/src/main/scala/rules/ChunkSupporter.scala +++ b/src/main/scala/rules/ChunkSupporter.scala @@ -7,7 +7,7 @@ package viper.silicon.rules import viper.silicon.debugger.DebugExp -import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfoes, DependencyType, StringAnalysisSourceInfo} +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfoes import viper.silicon.interfaces.state._ import viper.silicon.interfaces.{Success, VerificationResult} import viper.silicon.resources.{NonQuantifiedPropertyInterpreter, Resources} @@ -17,6 +17,7 @@ import viper.silicon.state.terms.perms.IsPositive import viper.silicon.utils.ast.buildMinExp import viper.silicon.verifier.Verifier import viper.silver.ast +import viper.silver.dependencyAnalysis.{DependencyType, StringAnalysisSourceInfo} import viper.silver.parser.PUnknown import viper.silver.verifier.VerificationError diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index 56d4c6a34..e8c35b07c 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -16,6 +16,7 @@ import viper.silicon.utils.ast.BigAnd import viper.silicon.verifier.Verifier import viper.silver.ast import viper.silver.ast.utility.QuantifiedPermissions.QuantifiedPermissionAssertion +import viper.silver.dependencyAnalysis.{DependencyType, StringAnalysisSourceInfo} import viper.silver.verifier.PartialVerificationError import viper.silver.verifier.reasons._ diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index c0d4f5979..e3a1c92f0 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -10,7 +10,7 @@ import viper.silicon import viper.silicon.Config.JoinMode import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp -import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfoes, DependencyType} +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfoes import viper.silicon.interfaces._ import viper.silicon.interfaces.state.ChunkIdentifer import viper.silicon.logger.records.data.{CondExpRecord, EvaluateRecord, ImpliesRecord} @@ -25,6 +25,7 @@ import viper.silicon.{Map, TriggerSets} import viper.silver.ast import viper.silver.ast.utility.Expressions import viper.silver.ast.{AnnotationInfo, LocalVarWithVersion, WeightedQuantifier} +import viper.silver.dependencyAnalysis.DependencyType import viper.silver.reporter.{AnnotationWarning, WarningsDuringVerification} import viper.silver.utility.Common.Rational import viper.silver.verifier.errors.{ErrorWrapperWithExampleTransformer, PreconditionInAppFalse} @@ -597,7 +598,6 @@ object evaluator extends EvaluationRules { case fapp @ ast.FuncApp(funcName, eArgs) => val func = s.program.findFunction(funcName) - val dependencyType = v.decider.analysisSourceInfoStack.getDependencyType evals2(s, eArgs, Nil, _ => pve, v, analysisInfoes)((s1, tArgs, eArgsNew, v1) => { // bookkeeper.functionApplications += 1 val joinFunctionArgs = tArgs //++ c2a.quantifiedVariables.filterNot(tArgs.contains) @@ -671,10 +671,7 @@ object evaluator extends EvaluationRules { moreJoins = JoinMode.Off, assertReadAccessOnly = if (Verifier.config.respectFunctionPrePermAmounts()) s2.assertReadAccessOnly /* should currently always be false */ else true) - val oldIsRelevantJoinNode = v2.decider.analysisSourceInfoStack.isJoinRelevantNode - v2.decider.analysisSourceInfoStack.isJoinRelevantNode = true - consumes(s3, pres, true, _ => pvePre, v2, analysisInfoes)((s4, snap, v3) => { - v3.decider.analysisSourceInfoStack.isJoinRelevantNode = oldIsRelevantJoinNode + consumes(s3, pres, true, _ => pvePre, v2, analysisInfoes)((s4, snap, v3) => { // TODO ake: join! val snap1 = snap.get.convert(sorts.Snap) val preFApp = App(functionSupporter.preconditionVersion(v3.symbolConverter.toFunction(func)), snap1 :: tArgs) val preExp = Option.when(withExp)({ diff --git a/src/main/scala/rules/ExecutionFlowController.scala b/src/main/scala/rules/ExecutionFlowController.scala index e5d1862bd..96d4a4de7 100644 --- a/src/main/scala/rules/ExecutionFlowController.scala +++ b/src/main/scala/rules/ExecutionFlowController.scala @@ -107,9 +107,6 @@ object executionFlowController extends ExecutionFlowRules { var localActionSuccess = false - val analysisSourceInfoStackPrev = v.decider.analysisSourceInfoStack.getAnalysisSourceInfos - val analysisSourceInfoStackForcedSource = v.decider.analysisSourceInfoStack.getForcedSource - /* TODO: Consider how to handle situations where the action branches and the first branch * succeeds, i.e. localActionSuccess has been set to true, but the second fails. * Currently, the verification will fail without attempting to remedy the situation, @@ -143,8 +140,6 @@ object executionFlowController extends ExecutionFlowRules { Verifier.config.exhaleMode != ExhaleMode.Greedy } - v.decider.analysisSourceInfoStack.setAnalysisSourceInfo(analysisSourceInfoStackPrev) - v.decider.analysisSourceInfoStack.setForcedSource(analysisSourceInfoStackForcedSource) action(s0.copy(retrying = true, retryLevel = s.retryLevel, moreCompleteExhale = temporaryMCE), v, (s1, r, v1) => { v1.symbExLog.closeScope(sepIdentifier) Q(s1.copy(retrying = false, moreCompleteExhale = s0.moreCompleteExhale), r, v1) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 09c0671ed..a1828b9d7 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -11,7 +11,7 @@ import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.decider.RecordedPathConditions import viper.silicon.dependencyAnalysis.DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes -import viper.silicon.dependencyAnalysis.{AssumptionType, DependencyAnalysisInfoes, DependencyAnalyzer, DependencyType, StringAnalysisSourceInfo} +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfoes import viper.silicon.interfaces._ import viper.silicon.interfaces.state.{NonQuantifiedChunk, QuantifiedChunk} import viper.silicon.logger.records.data.{CommentRecord, ConditionalEdgeRecord, ExecuteRecord, MethodCallRecord} @@ -24,6 +24,7 @@ import viper.silver.ast.utility.Statements import viper.silver.cfg.silver.SilverCfg import viper.silver.cfg.silver.SilverCfg.{SilverBlock, SilverEdge} import viper.silver.cfg.{ConditionalEdge, StatementBlock} +import viper.silver.dependencyAnalysis.{DependencyType, StringAnalysisSourceInfo} import viper.silver.verifier.errors._ import viper.silver.verifier.reasons._ import viper.silver.verifier.{CounterexampleTransformer, NullPartialVerificationError, PartialVerificationError} @@ -568,8 +569,7 @@ object executor extends ExecutionRules { val s2 = s1.copy(g = Store(fargs.zip(argsWithExp)), recordVisited = true) - v1.decider.analysisSourceInfoStack.isJoinRelevantNode = true - consumes(s2, meth.pres, false, _ => pvePre, v1, analysisInfoes)((s3, _, v2) => { + consumes(s2, meth.pres, false, _ => pvePre, v1, analysisInfoes)((s3, _, v2) => { // TODO ake: join v2.symbExLog.closeScope(preCondId) val postCondLog = new CommentRecord("Postcondition", s3, v2.decider.pcs) val postCondId = v2.symbExLog.openScope(postCondLog) @@ -577,7 +577,6 @@ object executor extends ExecutionRules { val gOuts = Store(outs.map(x => (x, v2.decider.fresh(x))).toMap) val s4 = s3.copy(g = s3.g + gOuts, oldHeaps = s3.oldHeaps + (Verifier.PRE_STATE_LABEL -> magicWandSupporter.getEvalHeap(s1))) produces(s4, freshSnap, meth.posts, _ => pveCallTransformed, v2, analysisInfoes)((s5, v3) => { - v3.decider.analysisSourceInfoStack.isJoinRelevantNode = false v3.symbExLog.closeScope(postCondId) v3.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) val gLhs = Store(lhs.zip(outs) diff --git a/src/main/scala/rules/HeapSupporter.scala b/src/main/scala/rules/HeapSupporter.scala index 79d85b53d..c90986942 100644 --- a/src/main/scala/rules/HeapSupporter.scala +++ b/src/main/scala/rules/HeapSupporter.scala @@ -24,6 +24,7 @@ import viper.silicon.utils.ast.{BigAnd, replaceVarsInExp} import viper.silicon.utils.freshSnap import viper.silicon.verifier.Verifier import viper.silver.ast +import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, DependencyType} import viper.silver.parser.PUnknown import viper.silver.verifier.reasons.{InsufficientPermission, MagicWandChunkNotFound} import viper.silver.verifier.{ErrorReason, PartialVerificationError, VerificationError} diff --git a/src/main/scala/rules/Joiner.scala b/src/main/scala/rules/Joiner.scala index 496d975fc..9bc023266 100644 --- a/src/main/scala/rules/Joiner.scala +++ b/src/main/scala/rules/Joiner.scala @@ -9,7 +9,7 @@ package viper.silicon.rules import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.decider.RecordedPathConditions -import viper.silicon.dependencyAnalysis.{AnalysisInfo, AssumptionType, DependencyAnalysisInfoes, DependencyType, StringAnalysisSourceInfo} +import viper.silicon.dependencyAnalysis.{AnalysisInfo, DependencyAnalysisInfoes} import viper.silicon.interfaces.{Success, VerificationResult} import viper.silicon.logger.records.structural.JoiningRecord import viper.silicon.state.State @@ -18,6 +18,7 @@ import viper.silicon.utils.ast.{BigAnd, BigOr} import viper.silicon.verifier.Verifier import viper.silver.ast import viper.silver.ast.NoPosition +import viper.silver.dependencyAnalysis.{DependencyType, StringAnalysisSourceInfo} case class JoinDataEntry[D](s: State, data: D, pathConditions: RecordedPathConditions) { diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 594d327e4..f21ece46c 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -10,7 +10,7 @@ import viper.silicon._ import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.decider.RecordedPathConditions -import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfoes, DependencyType, ExpAnalysisSourceInfo, StringAnalysisSourceInfo} +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfoes import viper.silicon.interfaces._ import viper.silicon.interfaces.state._ import viper.silicon.state._ @@ -21,6 +21,7 @@ import viper.silver.ast import viper.silver.ast.{Exp, NoPosition, Stmt} import viper.silver.cfg.Edge import viper.silver.cfg.silver.SilverCfg.SilverBlock +import viper.silver.dependencyAnalysis.{DependencyType, ExpAnalysisSourceInfo, StringAnalysisSourceInfo} import viper.silver.parser.PUnknown import viper.silver.verifier.PartialVerificationError @@ -246,7 +247,6 @@ object magicWandSupporter extends SymbolicExecutionRules { (Q: (State, Chunk, Verifier) => VerificationResult) : VerificationResult = { - val prevAnalysisStack = v.decider.analysisSourceInfoStack.getAnalysisSourceInfos val s = if (state.exhaleExt) state else state.copy(reserveHeaps = v.heapSupporter.getEmptyHeap(state.program) :: state.h :: Nil) @@ -444,7 +444,6 @@ object magicWandSupporter extends SymbolicExecutionRules { // We execute the continuation Q in a new scope with all branch conditions and all conserved path conditions. executionFlowController.locally(s1, v)((s2, v1) => { - v1.decider.analysisSourceInfoStack.setAnalysisSourceInfo(prevAnalysisStack) val exp = viper.silicon.utils.ast.BigAnd(branchConditionsExp.map(_._1)) val expNew = Option.when(withExp)(viper.silicon.utils.ast.BigAnd(branchConditionsExp.map(_._2.get))) // Set the branch conditions diff --git a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala index 3c9997564..ca9405ce2 100644 --- a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala +++ b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala @@ -7,7 +7,7 @@ package viper.silicon.rules import viper.silicon.debugger.DebugExp -import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfoes, DependencyType} +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfoes import viper.silicon.interfaces.state._ import viper.silicon.interfaces.{Success, VerificationResult} import viper.silicon.resources.{FieldID, NonQuantifiedPropertyInterpreter, Resources} @@ -20,6 +20,7 @@ import viper.silicon.utils.ast._ import viper.silicon.verifier.Verifier import viper.silicon.{MList, MMap} import viper.silver.ast +import viper.silver.dependencyAnalysis.DependencyType import viper.silver.parser.PUnknown import viper.silver.verifier.VerificationError diff --git a/src/main/scala/rules/PredicateSupporter.scala b/src/main/scala/rules/PredicateSupporter.scala index b96b28d9d..123c5ae88 100644 --- a/src/main/scala/rules/PredicateSupporter.scala +++ b/src/main/scala/rules/PredicateSupporter.scala @@ -10,7 +10,7 @@ import viper.silicon import viper.silicon.Config.JoinMode import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp -import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfoes, DependencyType} +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfoes import viper.silicon.interfaces.VerificationResult import viper.silicon.interfaces.state.{ChunkIdentifer, GeneralChunk, NonQuantifiedChunk} import viper.silicon.resources.FieldID @@ -21,6 +21,7 @@ import viper.silicon.supporters.{PredicateBranchNode, PredicateContentsTree, Pre import viper.silicon.utils.toSf import viper.silicon.verifier.Verifier import viper.silver.ast +import viper.silver.dependencyAnalysis.DependencyType import viper.silver.verifier.PartialVerificationError trait PredicateSupportRules extends SymbolicExecutionRules { diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index e5785abb9..a69065bf6 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -8,7 +8,7 @@ package viper.silicon.rules import viper.silicon.Config.JoinMode import viper.silicon.debugger.DebugExp -import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfoes, DependencyType} +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfoes import viper.silicon.interfaces.{Unreachable, VerificationResult} import viper.silicon.logger.records.data.{CondExpRecord, ImpliesRecord, ProduceRecord} import viper.silicon.state._ diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 50ea6a1d0..2494c9eb0 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -27,7 +27,7 @@ import viper.silicon.utils.freshSnap import viper.silicon.utils.notNothing.NotNothing import viper.silicon.verifier.Verifier import viper.silver.ast -import viper.silver.ast.NoPosition +import viper.silver.dependencyAnalysis.DependencyType import viper.silver.parser.PUnknown import viper.silver.reporter.InternalWarningMessage import viper.silver.verifier.reasons.{InsufficientPermission, MagicWandChunkNotFound} @@ -794,8 +794,6 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optSmDomainDefinitionCondition: Option[Term] = None, optQVarsInstantiations: Option[Seq[Term]] = None) : (State, SnapshotMapDefinition, PermMapDefinition) = { - v.decider.analysisSourceInfoStack.setForcedSource("summarizing heap") - v.decider.dependencyAnalyzer.disableTransitiveEdges() val (smDef, smCache) = summarisingSnapshotMap( s, resource, codomainQVars, relevantChunks, v, optSmDomainDefinitionCondition, optQVarsInstantiations) @@ -807,8 +805,6 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { s1, resource, codomainQVars, relevantChunks, smDef, v) val s2 = s1.copy(pmCache = pmCache, functionRecorder = s1.functionRecorder.recordFvfAndDomain(smDef).recordPermMap(pmDef)) - v.decider.analysisSourceInfoStack.removeForcedSource() - v.decider.dependencyAnalyzer.enableTransitiveEdges() (s2, smDef, pmDef) } @@ -821,16 +817,12 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { relevantChunks: Seq[QuantifiedBasicChunk], v: Verifier) : (State, PermMapDefinition) = { - v.decider.analysisSourceInfoStack.setForcedSource("summarizing heap") - v.decider.dependencyAnalyzer.disableTransitiveEdges() val s1 = s val (pmDef, pmCache) = quantifiedChunkSupporter.summarisingPermissionMap( s1, resource, codomainQVars, relevantChunks, null, v) val s2 = s1.copy(pmCache = pmCache) - v.decider.analysisSourceInfoStack.removeForcedSource() - v.decider.dependencyAnalyzer.enableTransitiveEdges() (s2, pmDef) } @@ -1181,9 +1173,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val comment = "Nested auxiliary terms: globals" v.decider.prover.comment(comment) - v.decider.analysisSourceInfoStack.setForcedSource(comment) - v.decider.assume(auxGlobals, Option.when(withExp)(DebugExp.createInstance(description=comment, children=auxGlobalsExp.get)), enforceAssumption = false, analysisInfoes=analysisInfoes.withDependencyType(DependencyType.Internal)) - v.decider.analysisSourceInfoStack.removeForcedSource() + v.decider.assume(auxGlobals, Option.when(withExp)(DebugExp.createInstance(description=comment, children=auxGlobalsExp.get)), enforceAssumption = false, analysisInfoes=DependencyAnalysisInfoes.create(comment, DependencyType.Internal)) val comment2 = "Nested auxiliary terms: non-globals" v.decider.prover.comment(comment2) diff --git a/src/main/scala/rules/StateConsolidator.scala b/src/main/scala/rules/StateConsolidator.scala index 8ac332b47..4aceaf1ba 100644 --- a/src/main/scala/rules/StateConsolidator.scala +++ b/src/main/scala/rules/StateConsolidator.scala @@ -9,7 +9,6 @@ package viper.silicon.rules import viper.silicon.Config import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp -import viper.silicon.dependencyAnalysis.DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes import viper.silicon.dependencyAnalysis._ import viper.silicon.interfaces.state._ import viper.silicon.logger.records.data.{CommentRecord, SingleMergeRecord} @@ -21,6 +20,7 @@ import viper.silicon.state.terms.predef.`?r` import viper.silicon.supporters.functions.FunctionRecorder import viper.silicon.verifier.Verifier import viper.silver.ast +import viper.silver.dependencyAnalysis.DependencyType import scala.annotation.unused diff --git a/src/main/scala/supporters/Domains.scala b/src/main/scala/supporters/Domains.scala index 00036432f..abbb1df8e 100644 --- a/src/main/scala/supporters/Domains.scala +++ b/src/main/scala/supporters/Domains.scala @@ -6,17 +6,18 @@ package viper.silicon.supporters -import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType -import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, DependencyAnalyzer, AssumptionType, ExpAnalysisSourceInfo} import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.common.collections.immutable.MultiMap._ +import viper.silicon.dependencyAnalysis.DependencyAnalyzer import viper.silicon.interfaces.PreambleContributor import viper.silicon.interfaces.decider.ProverLike import viper.silicon.state.terms.{Distinct, DomainFun, Sort, Term} import viper.silicon.state.{FunctionPreconditionTransformer, SymbolConverter, terms} import viper.silicon.toMap import viper.silver.ast -import viper.silver.ast.{MakeInfoPair, NamedDomainAxiom} +import viper.silver.ast.NamedDomainAxiom +import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType} +import viper.silver.dependencyAnalysis.AssumptionType.AssumptionType trait DomainsContributor[SO, SY, AX, UA] extends PreambleContributor[SO, SY, AX] { def uniquenessAssumptionsAfterAnalysis: Iterable[UA] diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index 5f46ed36f..0bc197d45 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -9,7 +9,7 @@ package viper.silicon.supporters import com.typesafe.scalalogging.Logger import viper.silicon.Map import viper.silicon.decider.Decider -import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyAnalysisInfoes, DependencyAnalysisJoinNodeInfo, DependencyAnalyzer, DependencyGraphInterpreter, DependencyType, SimpleAssertionNode, SimpleAssumptionNode} +import viper.silicon.dependencyAnalysis._ import viper.silicon.interfaces._ import viper.silicon.logger.records.data.WellformednessCheckRecord import viper.silicon.rules.{consumer, executionFlowController, executor, producer} @@ -20,6 +20,7 @@ import viper.silicon.utils.freshSnap import viper.silicon.verifier.{Verifier, VerifierComponent} import viper.silver.ast import viper.silver.components.StatefulComponent +import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyAnalysisJoinNodeInfo} import viper.silver.verifier.errors._ /* TODO: Consider changing the DefaultMethodVerificationUnitProvider into a SymbolicExecutionRule */ @@ -83,22 +84,23 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif val daJoinNodeInfoOpt = method.info.getUniqueInfo[DependencyAnalysisJoinNodeInfo] - if(daJoinNodeInfoOpt.isDefined){ - val infodaJoinNodeInfo = daJoinNodeInfoOpt.get - // v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(infodaJoinNodeInfo.sourceInfo, DependencyType.make(AssumptionType.CustomInternal)) - val postCondNodes = (posts ++ pres).flatMap(_.topLevelConjuncts).map(pc => SimpleAssumptionNode(True, None, AnalysisSourceInfo.createAnalysisSourceInfo(pc), AssumptionType.ImplicitPostcondition, isClosed=false, isJoinNode=true)) - val postCondAssertNodes = (posts ++ pres).flatMap(_.topLevelConjuncts).map(pc => SimpleAssertionNode(True, AnalysisSourceInfo.createAnalysisSourceInfo(pc), AssumptionType.ImplicitPostcondition, isClosed=false, isJoinNode=true)) - val customJoinNode = infodaJoinNodeInfo.getAssertionNode - postCondNodes foreach v.decider.dependencyAnalyzer.addAssumptionNode - postCondAssertNodes foreach v.decider.dependencyAnalyzer.addAssertionNode - - v.decider.dependencyAnalyzer.addAssertionNode(customJoinNode) - - postCondNodes foreach (n => v.decider.dependencyAnalyzer.addDependency(Some(customJoinNode.id), Some(n.id))) - postCondNodes foreach (n => v.decider.dependencyAnalyzer.addDependency(Some(n.id), Some(customJoinNode.id))) - postCondAssertNodes foreach (n => v.decider.dependencyAnalyzer.addDependency(Some(customJoinNode.id), Some(n.id))) - postCondAssertNodes foreach (n => v.decider.dependencyAnalyzer.addDependency(Some(n.id), Some(customJoinNode.id))) - } + // TODO ake: frontend join +// if(daJoinNodeInfoOpt.isDefined){ +// val infodaJoinNodeInfo = daJoinNodeInfoOpt.get +// // v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(infodaJoinNodeInfo.sourceInfo, DependencyType.make(AssumptionType.CustomInternal)) +// val postCondNodes = (posts ++ pres).flatMap(_.topLevelConjuncts).map(pc => SimpleAssumptionNode(True, None, AnalysisSourceInfo.createAnalysisSourceInfo(pc), AssumptionType.ImplicitPostcondition, isClosed=false, isJoinNode=true)) +// val postCondAssertNodes = (posts ++ pres).flatMap(_.topLevelConjuncts).map(pc => SimpleAssertionNode(True, AnalysisSourceInfo.createAnalysisSourceInfo(pc), AssumptionType.ImplicitPostcondition, isClosed=false, isJoinNode=true)) +// val customJoinNode = infodaJoinNodeInfo.getAssertionNode +// postCondNodes foreach v.decider.dependencyAnalyzer.addAssumptionNode +// postCondAssertNodes foreach v.decider.dependencyAnalyzer.addAssertionNode +// +// v.decider.dependencyAnalyzer.addAssertionNode(customJoinNode) +// +// postCondNodes foreach (n => v.decider.dependencyAnalyzer.addDependency(Some(customJoinNode.id), Some(n.id))) +// postCondNodes foreach (n => v.decider.dependencyAnalyzer.addDependency(Some(n.id), Some(customJoinNode.id))) +// postCondAssertNodes foreach (n => v.decider.dependencyAnalyzer.addDependency(Some(customJoinNode.id), Some(n.id))) +// postCondAssertNodes foreach (n => v.decider.dependencyAnalyzer.addDependency(Some(n.id), Some(customJoinNode.id))) +// } errorsReportedSoFar.set(0) val result = diff --git a/src/main/scala/supporters/functions/FunctionData.scala b/src/main/scala/supporters/functions/FunctionData.scala index e8972813e..745443fa1 100644 --- a/src/main/scala/supporters/functions/FunctionData.scala +++ b/src/main/scala/supporters/functions/FunctionData.scala @@ -7,9 +7,8 @@ package viper.silicon.supporters.functions import com.typesafe.scalalogging.LazyLogging -import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType -import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyAnalyzer, ExpAnalysisSourceInfo, StringAnalysisSourceInfo} import viper.silicon.common.collections.immutable.InsertionOrderedSet +import viper.silicon.dependencyAnalysis.DependencyAnalyzer import viper.silicon.interfaces.FatalResult import viper.silicon.rules.{InverseFunctions, PermMapDefinition, SnapshotMapDefinition, functionSupporter} import viper.silicon.state.terms._ @@ -22,6 +21,8 @@ import viper.silicon.{Config, Map, toMap} import viper.silver.ast import viper.silver.ast.LocalVarWithVersion import viper.silver.ast.utility.Functions +import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, ExpAnalysisSourceInfo, StringAnalysisSourceInfo} +import viper.silver.dependencyAnalysis.AssumptionType.AssumptionType import viper.silver.parser.PUnknown import viper.silver.reporter.{InternalWarningMessage, Reporter} diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index 4007524ef..cc69fae8f 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -7,11 +7,10 @@ package viper.silicon.supporters.functions import com.typesafe.scalalogging.Logger -import viper.silicon.dependencyAnalysis.AssumptionType.{AssumptionType, ExplicitPostcondition, ImplicitPostcondition} -import viper.silicon.dependencyAnalysis._ import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.decider.Decider +import viper.silicon.dependencyAnalysis._ import viper.silicon.interfaces._ import viper.silicon.interfaces.decider.ProverLike import viper.silicon.rules.{consumer, evaluator, executionFlowController, producer} @@ -19,10 +18,6 @@ import viper.silicon.state.State.OldHeaps import viper.silicon.state._ import viper.silicon.state.terms._ import viper.silicon.state.terms.predef.`?s` -import viper.silicon.supporters.PredicateData -import viper.silicon.common.collections.immutable.InsertionOrderedSet -import viper.silicon.decider.Decider -import viper.silicon.rules.{consumer, evaluator, executionFlowController, producer} import viper.silicon.supporters.{AnnotationSupporter, PredicateData} import viper.silicon.utils.ast.{BigAnd, simplifyVariableName} import viper.silicon.utils.{freshSnap, toSf} @@ -32,6 +27,8 @@ import viper.silver.ast import viper.silver.ast.LocalVarWithVersion import viper.silver.ast.utility.Functions import viper.silver.components.StatefulComponent +import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyAnalysisJoinNodeInfo, DependencyType} +import viper.silver.dependencyAnalysis.AssumptionType.AssumptionType import viper.silver.parser.PType import viper.silver.verifier.errors.{ContractNotWellformed, FunctionNotWellformed, PostconditionViolated} @@ -284,22 +281,23 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver decider.dependencyAnalyzer.addNodes(v.decider.prover.getPreambleAnalysisNodes) val daJoinNodeInfoOpt = function.info.getUniqueInfo[DependencyAnalysisJoinNodeInfo] - if(daJoinNodeInfoOpt.isDefined){ - val infodaJoinNodeInfo = daJoinNodeInfoOpt.get -// v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(infodaJoinNodeInfo.sourceInfo, DependencyType.make(AssumptionType.CustomInternal)) - val postCondNodes = (posts ++ function.pres).flatMap(_.topLevelConjuncts).map(pc => SimpleAssumptionNode(True, None, AnalysisSourceInfo.createAnalysisSourceInfo(pc), AssumptionType.ImplicitPostcondition, isClosed=false, isJoinNode=true)) - val postCondAssertNodes = (posts ++ function.pres).flatMap(_.topLevelConjuncts).map(pc => SimpleAssertionNode(True, AnalysisSourceInfo.createAnalysisSourceInfo(pc), AssumptionType.ImplicitPostcondition, isClosed=false, isJoinNode=true)) - val customJoinNode = infodaJoinNodeInfo.getAssertionNode - - postCondNodes foreach v.decider.dependencyAnalyzer.addAssumptionNode - postCondAssertNodes foreach v.decider.dependencyAnalyzer.addAssertionNode - v.decider.dependencyAnalyzer.addAssertionNode(customJoinNode) - - postCondNodes foreach (n => v.decider.dependencyAnalyzer.addDependency(Some(customJoinNode.id), Some(n.id))) - postCondNodes foreach (n => v.decider.dependencyAnalyzer.addDependency(Some(n.id), Some(customJoinNode.id))) - postCondAssertNodes foreach (n => v.decider.dependencyAnalyzer.addDependency(Some(customJoinNode.id), Some(n.id))) - postCondAssertNodes foreach (n => v.decider.dependencyAnalyzer.addDependency(Some(n.id), Some(customJoinNode.id))) - } + // TODO ake: frontend join +// if(daJoinNodeInfoOpt.isDefined){ +// val infodaJoinNodeInfo = daJoinNodeInfoOpt.get +//// v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(infodaJoinNodeInfo.sourceInfo, DependencyType.make(AssumptionType.CustomInternal)) +// val postCondNodes = (posts ++ function.pres).flatMap(_.topLevelConjuncts).map(pc => SimpleAssumptionNode(True, None, AnalysisSourceInfo.createAnalysisSourceInfo(pc), AssumptionType.ImplicitPostcondition, isClosed=false, isJoinNode=true)) +// val postCondAssertNodes = (posts ++ function.pres).flatMap(_.topLevelConjuncts).map(pc => SimpleAssertionNode(True, AnalysisSourceInfo.createAnalysisSourceInfo(pc), AssumptionType.ImplicitPostcondition, isClosed=false, isJoinNode=true)) +// val customJoinNode = infodaJoinNodeInfo.getAssertionNode +// +// postCondNodes foreach v.decider.dependencyAnalyzer.addAssumptionNode +// postCondAssertNodes foreach v.decider.dependencyAnalyzer.addAssertionNode +// v.decider.dependencyAnalyzer.addAssertionNode(customJoinNode) +// +// postCondNodes foreach (n => v.decider.dependencyAnalyzer.addDependency(Some(customJoinNode.id), Some(n.id))) +// postCondNodes foreach (n => v.decider.dependencyAnalyzer.addDependency(Some(n.id), Some(customJoinNode.id))) +// postCondAssertNodes foreach (n => v.decider.dependencyAnalyzer.addDependency(Some(customJoinNode.id), Some(n.id))) +// postCondAssertNodes foreach (n => v.decider.dependencyAnalyzer.addDependency(Some(n.id), Some(customJoinNode.id))) +// } val result = phase1data.foldLeft(Success(): VerificationResult) { case (fatalResult: FatalResult, _) => fatalResult @@ -317,7 +315,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver val eNew = ast.EqCmp(ast.Result(function.typ)(), bodyNew.get)(function.pos, function.info, function.errT) Some(DebugExp.createInstance(e, eNew)) } else { None } - val bodyAnalysisSourceInfoes = DependencyAnalysisInfoes.create(AnalysisSourceInfo.createAnalysisSourceInfo(body), DependencyType.get(body,DependencyType.make(AssumptionType.FunctionBody))) + val bodyAnalysisSourceInfoes = DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes.addInfo(body.info, body) decider.assume(BuiltinEquals(data.formalResult, tBody), debugExp, bodyAnalysisSourceInfoes) consumes(s2, posts, false, postconditionViolated, v, DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes)((s3, _, _) => { recorders :+= s3.functionRecorder diff --git a/src/main/scala/verifier/VerificationPoolManager.scala b/src/main/scala/verifier/VerificationPoolManager.scala index e863be51d..cda3091bd 100644 --- a/src/main/scala/verifier/VerificationPoolManager.scala +++ b/src/main/scala/verifier/VerificationPoolManager.scala @@ -9,13 +9,13 @@ package viper.silicon.verifier import org.apache.commons.pool2.impl.{DefaultPooledObject, GenericObjectPool, GenericObjectPoolConfig} import org.apache.commons.pool2.{BasePooledObjectFactory, ObjectPool, PoolUtils, PooledObject} import viper.silicon.Config -import viper.silicon.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType} -import viper.silicon.dependencyAnalysis.AssumptionType.AssumptionType import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.interfaces.VerificationResult import viper.silicon.interfaces.decider.ProverLike import viper.silicon.state.terms.{Decl, Term} import viper.silver.components.StatefulComponent +import viper.silver.dependencyAnalysis.AnalysisSourceInfo +import viper.silver.dependencyAnalysis.AssumptionType.AssumptionType import java.util.concurrent._ From 206e32623bcc4c6faa2b908ba36f2fae172daf21 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 30 Mar 2026 18:04:45 +0200 Subject: [PATCH 403/474] refactor join info --- src/main/scala/decider/Decider.scala | 2 +- .../DependencyAnalysisInfo.scala | 15 ++++- .../DependencyAnalysisNode.scala | 24 ++++---- .../DependencyAnalyzer.scala | 59 ++++++++++--------- .../dependencyAnalysis/DependencyGraph.scala | 5 +- .../DependencyGraphImporter.scala | 14 ++--- .../DependencyGraphInterpreter.scala | 28 ++++----- .../UserLevelDependencyAnalysisNode.scala | 2 +- src/main/scala/rules/Brancher.scala | 2 +- src/main/scala/rules/Executor.scala | 6 +- .../scala/supporters/MethodSupporter.scala | 2 +- .../functions/FunctionVerificationUnit.scala | 3 +- .../DependencyAnalysisTestFramework.scala | 11 ++-- 13 files changed, 95 insertions(+), 78 deletions(-) diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index dd9e32694..f1571bdf5 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -376,7 +376,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => // if (info.getUniqueInfo[DependencyAnalysisJoinNodeInfo].isDefined) { // // add assertions and assumptions nodes that can be used for a graph join // val joinNodeInfo = info.getUniqueInfo[DependencyAnalysisJoinNodeInfo].get -// val currentTopLevelSource = analysisSourceInfoStack.getFullSourceInfo.getTopLevelSource +// val currentTopLevelSource = analysisSourceInfoStack.getFullSourceInfo // val assertionNode = joinNodeInfo.getAssertionNode(currentTopLevelSource, analysisSourceInfoStack.getDependencyType.assertionType) // val assumptionNode = joinNodeInfo.getAssumptionNode(currentTopLevelSource) // dependencyAnalyzer.addAssertionNode(assertionNode) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala index 817f8b635..f966edf87 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala @@ -45,10 +45,22 @@ case class DependencyAnalysisInfoes(sourceInfoes: List[AnalysisSourceInfo], depe def getMergeInfo: DependencyAnalysisMergeInfo = NoDependencyAnalysisMerge() // mergeInfoes.head // TODO - def getJoinInfo: List[DependencyAnalysisJoinInfo] = joinInfoes + def getJoinInfoes: List[DependencyAnalysisJoinInfo] = joinInfoes + + def getJoinInfo: List[SimpleDependencyAnalysisJoin] = { + if(joinInfoes.isEmpty) return List.empty + val h = joinInfoes.head match { + case EvalStackDependencyAnalysisJoin(joinType) => SimpleDependencyAnalysisJoin(sourceInfoes.last, joinType) + case a: SimpleDependencyAnalysisJoin => a + } + List(h) + } def withMergeInfo(mergeInfo: DependencyAnalysisMergeInfo): DependencyAnalysisInfoes = this.copy(mergeInfoes = mergeInfo +: mergeInfoes) + + def withJoinInfo(joinInfo: EvalStackDependencyAnalysisJoin): DependencyAnalysisInfoes = + this.copy(joinInfoes = joinInfo +: joinInfoes) } object DependencyAnalysisInfoes { @@ -80,6 +92,7 @@ trait DependencyAnalysisJoinInfo extends ast.Info { def isJoin: Boolean = true + override def comment: Seq[String] = Nil override def isCached: Boolean = false } diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala index ece8090e1..8a11169b7 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala @@ -37,7 +37,7 @@ trait DependencyAnalysisNode extends AbstractDependencyAnalysisNode { val isClosed: Boolean - val isJoinNode: Boolean + val joinInfoes: List[SimpleDependencyAnalysisJoin] /** * The assumes or asserted Silicon term. Currently, only used for debugging purposes. @@ -45,8 +45,8 @@ trait DependencyAnalysisNode extends AbstractDependencyAnalysisNode { val term: Term def getTerm: Term = term - def getUserLevelRepresentation: String = sourceInfo.getTopLevelSource.toString - def getSourceCodePosition: Position = sourceInfo.getTopLevelSource.getPosition + def getUserLevelRepresentation: String = sourceInfo.toString + def getSourceCodePosition: Position = sourceInfo.getPosition /* Some string representations, mainly used for debugging purposes. @@ -86,34 +86,34 @@ trait ChunkAnalysisInfo { val labelNode: LabelNode } -case class SimpleAssumptionNode(term: Term, description: Option[String], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean, isJoinNode: Boolean, _id: Option[Int]=None) extends GeneralAssumptionNode { +case class SimpleAssumptionNode(term: Term, description: Option[String], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean, joinInfoes: List[SimpleDependencyAnalysisJoin]=List.empty, _id: Option[Int]=None) extends GeneralAssumptionNode { override def getNodeString: String = "assume " + term.toString + description.map(" (" + _ + ")").getOrElse("") } -case class AxiomAssumptionNode(term: Term, description: Option[String], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean, isJoinNode: Boolean = false, _id: Option[Int]=None) extends GeneralAssumptionNode { +case class AxiomAssumptionNode(term: Term, description: Option[String], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean, joinInfoes: List[SimpleDependencyAnalysisJoin]=List.empty, _id: Option[Int]=None) extends GeneralAssumptionNode { override def getNodeString: String = "assume axiom " + term.toString + description.map(" (" + _ + ")").getOrElse("") override def getNodeType: String = "Axiom" } -case class SimpleAssertionNode(term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean, hasFailed: Boolean = false, isJoinNode: Boolean = false, _id: Option[Int]=None) extends GeneralAssertionNode { +case class SimpleAssertionNode(term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean, hasFailed: Boolean = false, joinInfoes: List[SimpleDependencyAnalysisJoin]=List.empty, _id: Option[Int]=None) extends GeneralAssertionNode { override def getNodeString: String = "assert " + term.toString - override def getAssertFailedNode(): GeneralAssertionNode = SimpleAssertionNode(term, sourceInfo, assumptionType, isClosed, hasFailed=true, isJoinNode=isJoinNode) + override def getAssertFailedNode(): GeneralAssertionNode = SimpleAssertionNode(term, sourceInfo, assumptionType, isClosed, hasFailed=true, joinInfoes=joinInfoes) } -case class SimpleCheckNode(term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean, hasFailed: Boolean = false, isJoinNode: Boolean = false, _id: Option[Int]=None) extends GeneralAssertionNode { +case class SimpleCheckNode(term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean, hasFailed: Boolean = false, joinInfoes: List[SimpleDependencyAnalysisJoin]=List.empty, _id: Option[Int]=None) extends GeneralAssertionNode { override def getNodeString: String = "check " + term override def getNodeType: String = "Check" override def getAssertFailedNode(): GeneralAssertionNode = SimpleCheckNode(term, sourceInfo, assumptionType, isClosed, hasFailed=true) } -case class PermissionInhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean, labelNode: LabelNode, isJoinNode: Boolean, _id: Option[Int]=None) extends GeneralAssumptionNode with ChunkAnalysisInfo { +case class PermissionInhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean, labelNode: LabelNode, joinInfoes: List[SimpleDependencyAnalysisJoin]=List.empty, _id: Option[Int]=None) extends GeneralAssumptionNode with ChunkAnalysisInfo { override def getNodeString: String = "inhale " + chunk.toString override def getNodeType: String = "Inhale" } -case class PermissionExhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean, labelNode: LabelNode, hasFailed: Boolean = false, isJoinNode: Boolean = false, _id: Option[Int]=None) extends GeneralAssertionNode with ChunkAnalysisInfo { +case class PermissionExhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean, labelNode: LabelNode, hasFailed: Boolean = false, joinInfoes: List[SimpleDependencyAnalysisJoin]=List.empty, _id: Option[Int]=None) extends GeneralAssertionNode with ChunkAnalysisInfo { override def getNodeType: String = "Exhale" override def getNodeString: String = "exhale " + chunk.toString @@ -129,7 +129,7 @@ case class LabelNode(term: Var, _id: Option[Int]=None) extends GeneralAssumption val assumptionType: AssumptionType = AssumptionType.Internal val isClosed: Boolean = true val description: String = term.toString - val isJoinNode: Boolean = false + val joinInfoes: List[SimpleDependencyAnalysisJoin] = List.empty override def getNodeType: String = "Label" override def getNodeString: String = "assume " + description } @@ -145,7 +145,7 @@ case class InfeasibilityNode(sourceInfo: AnalysisSourceInfo, assumptionType: Ass val term: Term = False val isClosed: Boolean = true val description: String = "False" - val isJoinNode: Boolean = false + val joinInfoes: List[SimpleDependencyAnalysisJoin] = List.empty override def getNodeType: String = "Infeasible" override def getNodeString: String = "infeasible" diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index 8b5cfe338..3a9dfa448 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -5,7 +5,7 @@ import viper.silicon.interfaces.state.{Chunk, GeneralChunk} import viper.silicon.state.terms._ import viper.silicon.verifier.Verifier import viper.silver.ast -import viper.silver.ast.{NoPerm, _} +import viper.silver.ast._ import viper.silver.dependencyAnalysis.AssumptionType.AssumptionType import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyType, FrontendDependencyAnalysisInfo} @@ -203,12 +203,12 @@ object DependencyAnalyzer { // axioms assumed by every method / function should depend on the assertions that justify them // hence, we add edges from function postconditions & bodies to the corresponding axioms val axiomAssertionNodes = (joinCandidateAssertions ++ joinCandidateAssumptions.filter(_.assumptionType.equals(AssumptionType.FunctionBody))) - .groupBy(_.sourceInfo.getTopLevelSource) + .groupBy(_.sourceInfo) .view.mapValues(_.map(_.id)) .toMap joinCandidateAxioms .groupBy(n => n.sourceInfo) - .map{case (sourceInfo, axiomNodes) => (axiomNodes.map(_.id), axiomAssertionNodes.getOrElse(sourceInfo.getTopLevelSource, Seq.empty))} + .map{case (sourceInfo, axiomNodes) => (axiomNodes.map(_.id), axiomAssertionNodes.getOrElse(sourceInfo, Seq.empty))} .foreach{case (axiomNodeIds, assertionNodeIds) => newGraph.addEdgesConnectingMethodsDownwards(assertionNodeIds, axiomNodeIds) // TODO ake: maybe we could merge the axiom nodes here since they represent the same axiom? } @@ -220,24 +220,24 @@ object DependencyAnalyzer { // postconditions of methods assumed by every method call should depend on the assertions that justify them // hence, we add edges from assertions of method postconditions to assumptions of the same postcondition (at method calls) val relevantAssumptionNodes = joinCandidateAssumptions - .filter(_.isJoinNode) - .groupBy(_.sourceInfo.getFineGrainedSource) + .filter(_.joinInfoes.nonEmpty) + .groupBy(_.joinInfoes.head.sourceInfo) .view.mapValues(_.map(_.id)) .toMap joinCandidateNodes.diff(joinCandidateAxioms.toSet) - .map(node => (node.id, relevantAssumptionNodes.getOrElse(node.sourceInfo.getTopLevelSource, Seq.empty))) + .map(node => (node.id, relevantAssumptionNodes.getOrElse(node.sourceInfo, Seq.empty))) .foreach { case (src, targets) => if (customInternalNodes.intersect(targets.toSet.union(Set(src))).isEmpty) newGraph.addEdgesConnectingMethodsDownwards(src, targets) else newGraph.addEdges(src, targets) } val relevantAssertionNodes = joinCandidateAssertions // method call pres assertions - .filter(_.isJoinNode) - .groupBy(_.sourceInfo.getFineGrainedSource) + .filter(_.joinInfoes.nonEmpty) + .groupBy(_.joinInfoes.head.sourceInfo) .view.mapValues(_.map(_.id)) .toMap joinCandidateNodes - .map(node => (node.id, relevantAssertionNodes.getOrElse(node.sourceInfo.getTopLevelSource, Seq.empty))) + .map(node => (node.id, relevantAssertionNodes.getOrElse(node.sourceInfo, Seq.empty))) .foreach { case (target, sources) => if (customInternalNodes.intersect(sources.toSet.union(Set(target))).isEmpty) newGraph.addEdgesConnectingMethodsUpwards(sources, target) else newGraph.addEdges(sources, target) @@ -323,13 +323,13 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { // adding assumption nodes override def addAssumption(assumption: Term, analysisInfoes: DependencyAnalysisInfoes, description: Option[String]): Option[Int] = { - val node = SimpleAssumptionNode(assumption, description, analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assumptionType, analysisInfoes.getMergeInfo.isMerge, analysisInfoes.getJoinInfo.exists(_.isJoin)) + val node = SimpleAssumptionNode(assumption, description, analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assumptionType, analysisInfoes.getMergeInfo.isMerge, analysisInfoes.getJoinInfo) addAssumptionNode(node) Some(node.id) } override def addAxiom(assumption: Term, analysisInfoes: DependencyAnalysisInfoes, description: Option[String]): Option[Int] = { - val node = AxiomAssumptionNode(assumption, description, analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assumptionType, analysisInfoes.getMergeInfo.isMerge, analysisInfoes.getJoinInfo.exists(_.isJoin)) + val node = AxiomAssumptionNode(assumption, description, analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assumptionType, analysisInfoes.getMergeInfo.isMerge, analysisInfoes.getJoinInfo) addAssumptionNode(node) Some(node.id) } @@ -337,7 +337,7 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { override def registerExhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, labelNodeOpt: Option[LabelNode], analysisInfo: AnalysisInfo): CH = { val startTime = startTimeMeasurement() val labelNode = labelNodeOpt.get - val chunk = buildChunk(Ite(labelNode.term, perm, NoPerm.asInstanceOf[Term])) + val chunk = buildChunk(Ite(labelNode.term, perm, NoPerm)) val chunkNode = addPermissionExhaleNode(chunk, chunk.perm, analysisInfo.analysisInfoes.getSourceInfo, analysisInfo.analysisInfoes.getDependencyType.assertionType, labelNode) if(chunkNode.isDefined) addDependency(chunkNode, Some(labelNode.id)) @@ -349,8 +349,8 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { override def registerInhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, labelNodeOpt: Option[LabelNode], analysisInfo: AnalysisInfo): CH = { val startTime = startTimeMeasurement() val labelNode = labelNodeOpt.get - val chunk = buildChunk(Ite((labelNode.term, perm, NoPerm.asInstanceOf[Term]))) - val chunkNode = addPermissionInhaleNode(chunk, chunk.perm, analysisInfo.analysisInfoes.getSourceInfo, analysisInfo.analysisInfoes.getDependencyType.assumptionType, labelNode, isJoinNode=analysisInfo.analysisInfoes.getJoinInfo.exists(_.isJoin)) + val chunk = buildChunk(Ite((labelNode.term, perm, NoPerm))) + val chunkNode = addPermissionInhaleNode(chunk, chunk.perm, analysisInfo.analysisInfoes.getSourceInfo, analysisInfo.analysisInfoes.getDependencyType.assumptionType, labelNode, analysisInfo.analysisInfoes.getJoinInfo) if(chunkNode.isDefined) addDependency(chunkNode, Some(labelNode.id)) // addPermissionDependencies(sourceChunks, Set(), chunkNode) TODO ake: can be removed @@ -358,8 +358,8 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { chunk } - private def addPermissionInhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, labelNode: LabelNode, isJoinNode: Boolean): Option[Int] = { - val node = PermissionInhaleNode(chunk, permAmount, sourceInfo, assumptionType, isClosed_, labelNode, isJoinNode) + private def addPermissionInhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, labelNode: LabelNode, joinInfoes: List[SimpleDependencyAnalysisJoin]): Option[Int] = { + val node = PermissionInhaleNode(chunk, permAmount, sourceInfo, assumptionType, isClosed_, labelNode, joinInfoes) addAssumptionNode(node) Some(node.id) } @@ -381,9 +381,9 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { // adding assertion nodes override def createAssertOrCheckNode(term: Term, analysisInfoes: DependencyAnalysisInfoes, isCheck: Boolean): Option[GeneralAssertionNode] = { if(isCheck) - Some(SimpleCheckNode(term, analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assumptionType, analysisInfoes.getMergeInfo.isMerge, analysisInfoes.getJoinInfo.exists(_.isJoin))) + Some(SimpleCheckNode(term, analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assumptionType, analysisInfoes.getMergeInfo.isMerge, joinInfoes=analysisInfoes.getJoinInfo)) else - Some(SimpleAssertionNode(term, analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assumptionType, analysisInfoes.getMergeInfo.isMerge, analysisInfoes.getJoinInfo.exists(_.isJoin))) + Some(SimpleAssertionNode(term, analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assumptionType, analysisInfoes.getMergeInfo.isMerge, joinInfoes=analysisInfoes.getJoinInfo)) } def addAssertNode(term: Term, analysisInfoes: DependencyAnalysisInfoes): Option[Int] = { @@ -405,8 +405,8 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { } override def addAssertionFailedNode(failedAssertion: Term, analysisInfoes: DependencyAnalysisInfoes): Option[Int] = { - val assumeNode = SimpleAssumptionNode(failedAssertion, None, analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assumptionType, analysisInfoes.getMergeInfo.isMerge, analysisInfoes.getJoinInfo.exists(_.isJoin)) - val assertFailedNode = SimpleAssertionNode(failedAssertion, analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assumptionType, analysisInfoes.getMergeInfo.isMerge, hasFailed=true, isJoinNode=analysisInfoes.getJoinInfo.exists(_.isJoin)) + val assumeNode = SimpleAssumptionNode(failedAssertion, None, analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assumptionType, analysisInfoes.getMergeInfo.isMerge, analysisInfoes.getJoinInfo) + val assertFailedNode = SimpleAssertionNode(failedAssertion, analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assumptionType, analysisInfoes.getMergeInfo.isMerge, hasFailed=true, joinInfoes=analysisInfoes.getJoinInfo) dependencyGraph.addNode(assumeNode) dependencyGraph.addNode(assertFailedNode) dependencyGraph.addEdges(Set(assumeNode.id), assertFailedNode.id) @@ -451,9 +451,10 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { // } override def addCustomTransitiveDependency(sourceSourceInfo: AnalysisSourceInfo, targetSourceInfo: AnalysisSourceInfo): Unit = { - val sourceNodes = dependencyGraph.getAssertionNodes filter (n => n.sourceInfo.getSourceForTransitiveEdges.equals(sourceSourceInfo.getSourceForTransitiveEdges)) - val targetNodes = dependencyGraph.getAssumptionNodes filter (n => n.sourceInfo.getSourceForTransitiveEdges.equals(targetSourceInfo.getSourceForTransitiveEdges)) - dependencyGraph.addEdges(sourceNodes map (_.id), targetNodes map (_.id)) + // TODO ake +// val sourceNodes = dependencyGraph.getAssertionNodes filter (n => n.sourceInfo.getSourceForTransitiveEdges.equals(sourceSourceInfo.getSourceForTransitiveEdges)) +// val targetNodes = dependencyGraph.getAssumptionNodes filter (n => n.sourceInfo.getSourceForTransitiveEdges.equals(targetSourceInfo.getSourceForTransitiveEdges)) +// dependencyGraph.addEdges(sourceNodes map (_.id), targetNodes map (_.id)) } override def addDependenciesForAbstractMembers(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp], analysisInfoes: DependencyAnalysisInfoes): Unit = { @@ -511,10 +512,10 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { nodeMap.put(n.id, n.id) mergedGraph.addAssumptionNode(n) } - val assumptionNodesBySource = dependencyGraph.getAssumptionNodes.filter(!keepNode(_)).groupBy(n => (n.sourceInfo, n.assumptionType, n.isJoinNode)) - assumptionNodesBySource foreach { case ((sourceInfo, assumptionType, isJoinNode), assumptionNodes) => + val assumptionNodesBySource = dependencyGraph.getAssumptionNodes.filter(!keepNode(_)).groupBy(n => (n.sourceInfo, n.assumptionType, n.joinInfoes)) // TODO ake: review joinInfoes + assumptionNodesBySource foreach { case ((sourceInfo, assumptionType, joinInfoes), assumptionNodes) => if (assumptionNodes.nonEmpty) { - val newNode = SimpleAssumptionNode(True, None, sourceInfo, assumptionType, isClosed = false, isJoinNode=isJoinNode) + val newNode = SimpleAssumptionNode(True, None, sourceInfo, assumptionType, isClosed = false, joinInfoes=joinInfoes) // TODO ake: review joinInfoes assumptionNodes foreach (n => nodeMap.put(n.id, newNode.id)) mergedGraph.addAssumptionNode(newNode) } @@ -524,10 +525,10 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { nodeMap.put(n.id, n.id) mergedGraph.addAssertionNode(n) } - val assertionNodesBySource = dependencyGraph.getAssertionNodes.filter(!keepNode(_)).groupBy(n => (n.sourceInfo, n.assumptionType, n.isJoinNode)) - assertionNodesBySource foreach { case ((sourceInfo, assumptionType, isJoinNode), assertionNodes) => + val assertionNodesBySource = dependencyGraph.getAssertionNodes.filter(!keepNode(_)).groupBy(n => (n.sourceInfo, n.assumptionType, n.joinInfoes)) // TODO ake: review joinInfoes + assertionNodesBySource foreach { case ((sourceInfo, assumptionType, joinInfoes), assertionNodes) => if (assertionNodes.nonEmpty) { - val newNode = SimpleAssertionNode(True, sourceInfo, assumptionType, isClosed = false, hasFailed=assertionNodes.exists(_.hasFailed), isJoinNode=isJoinNode) + val newNode = SimpleAssertionNode(True, sourceInfo, assumptionType, isClosed = false, hasFailed=assertionNodes.exists(_.hasFailed), joinInfoes=joinInfoes) // TODO ake: review joinInfoes assertionNodes foreach (n => nodeMap.put(n.id, newNode.id)) mergedGraph.addAssertionNode(newNode) } diff --git a/src/main/scala/dependencyAnalysis/DependencyGraph.scala b/src/main/scala/dependencyAnalysis/DependencyGraph.scala index eb13dbbc8..a5b0cf91e 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraph.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraph.scala @@ -204,7 +204,8 @@ class DependencyGraph extends ReadOnlyDependencyGraph { // TODO ake: maybe move to DependencyAnalyzer? private def getNodesPerTransitivitySourceInfo: Map[AnalysisSourceInfo, Seq[DependencyAnalysisNode]] = { - getNodes.groupBy(_.sourceInfo.getSourceForTransitiveEdges) +// getNodes.groupBy(_.sourceInfo.getSourceForTransitiveEdges) + getNodes.groupBy(_.sourceInfo) // TODO ake: fix merge } // TODO ake: maybe move to DependencyAnalyzer? @@ -265,7 +266,7 @@ class DependencyGraph extends ReadOnlyDependencyGraph { private def exportNodes(fileName: String): Unit = { val sep = "#" def getNodeExportString(node: DependencyAnalysisNode): String = { - val parts = mutable.Seq(node.id.toString, node.getNodeType, node.assumptionType.toString, node.getNodeString, node.sourceInfo.toString, node.sourceInfo.getPositionString, node.sourceInfo.getFineGrainedSource.toString, node.sourceInfo.getDescription) + val parts = mutable.Seq(node.id.toString, node.getNodeType, node.assumptionType.toString, node.getNodeString, node.sourceInfo.toString, node.sourceInfo.getPositionString, node.sourceInfo.toString /* TODO ake: merge info */, node.sourceInfo.getDescription) parts.map(_.replace("#", "@")).mkString(sep) } val headerParts = mutable.Seq("id", "node type", "assumption type", "node info", "source info", "position", "fine grained source", "description") diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala index f16678cdd..49d943135 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala @@ -92,17 +92,17 @@ object DependencyGraphImporter { val description: Option[String] = None val isClosed: Boolean = false val labelNode: LabelNode = dummyLabelNode - val isJoinNode: Boolean = false + val joinNodeInfoes: List[SimpleDependencyAnalysisJoin] = List.empty val nodeId = Some(nodeIdStr.toInt) // Create node based on type val node = nodeType match { - case "Assumption" => SimpleAssumptionNode(term, description, sourceInfo, assumptionType, isClosed, isJoinNode, _id=nodeId) - case "Axiom" => AxiomAssumptionNode(term, description, sourceInfo, assumptionType, isClosed, isJoinNode, _id=nodeId) - case "Assertion" => SimpleAssertionNode(term, sourceInfo, assumptionType, isClosed, hasFailed = false, isJoinNode, _id=nodeId) - case "Check" => SimpleCheckNode(term, sourceInfo, assumptionType, isClosed, hasFailed = false, isJoinNode, _id=nodeId) - case "Inhale" => PermissionInhaleNode(chunk, term, sourceInfo, assumptionType, isClosed, labelNode, isJoinNode, _id=nodeId) - case "Exhale" => PermissionExhaleNode(chunk, term, sourceInfo, assumptionType, isClosed, labelNode, hasFailed = false, isJoinNode, _id=nodeId) + case "Assumption" => SimpleAssumptionNode(term, description, sourceInfo, assumptionType, isClosed, joinNodeInfoes, _id=nodeId) + case "Axiom" => AxiomAssumptionNode(term, description, sourceInfo, assumptionType, isClosed, joinNodeInfoes, _id=nodeId) + case "Assertion" => SimpleAssertionNode(term, sourceInfo, assumptionType, isClosed, hasFailed = false, joinNodeInfoes, _id=nodeId) + case "Check" => SimpleCheckNode(term, sourceInfo, assumptionType, isClosed, hasFailed = false, joinNodeInfoes, _id=nodeId) + case "Inhale" => PermissionInhaleNode(chunk, term, sourceInfo, assumptionType, isClosed, labelNode, joinNodeInfoes, _id=nodeId) + case "Exhale" => PermissionExhaleNode(chunk, term, sourceInfo, assumptionType, isClosed, labelNode, hasFailed = false, joinNodeInfoes, _id=nodeId) case "Label" => LabelNode(dummyVar, _id=nodeId) case "Infeasible" => InfeasibilityNode(sourceInfo, assumptionType, _id=nodeId) case _ => throw new IllegalArgumentException(s"Unknown node type: $nodeType") diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 2094d4be1..4a951517c 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -41,7 +41,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen val joinAssertionNodes: Set[GeneralAssertionNode] = getJoinCandidateNodes(dependencyGraph.getAssertionNodes.toSet) val axiomNodes: Set[GeneralAssumptionNode] = dependencyGraph.getAssumptionNodes.filter(_.isInstanceOf[AxiomAssumptionNode]).toSet - def getJoinCandidateNodes[T <: DependencyAnalysisNode](nodes: Set[T]): Set[T] = nodes.filter(node => node.isJoinNode || node.isInstanceOf[AxiomAssumptionNode] || AssumptionType.joinConditionTypes.contains(node.assumptionType)) + def getJoinCandidateNodes[T <: DependencyAnalysisNode](nodes: Set[T]): Set[T] = nodes.filter(node => node.joinInfoes.nonEmpty || node.isInstanceOf[AxiomAssumptionNode] || AssumptionType.joinConditionTypes.contains(node.assumptionType)) private def toUserLevelNodes(nodes: Iterable[DependencyAnalysisNode]): Set[UserLevelDependencyAnalysisNode] = UserLevelDependencyAnalysisNode.from(nodes) @@ -98,7 +98,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen def getNonInternalAssumptionNodes(nodes: Set[DependencyAnalysisNode]): Set[DependencyAnalysisNode] = nodes filter (node => (node.isInstanceOf[GeneralAssumptionNode] && !AssumptionType.internalTypes.contains(node.assumptionType)) - || AssumptionType.postconditionTypes.contains(node.assumptionType) || node.isJoinNode // postconditions act as assumptions for callers + || AssumptionType.postconditionTypes.contains(node.assumptionType) || node.joinInfoes.nonEmpty // postconditions act as assumptions for callers ) def getExplicitAssumptionNodes: Set[DependencyAnalysisNode] = getNonInternalAssumptionNodes filter (node => @@ -112,7 +112,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen def getNonInternalAssertionNodes: Set[DependencyAnalysisNode] = getAssertionNodes filter (node => - !AssumptionType.internalTypes.contains(node.assumptionType) || node.isJoinNode) + !AssumptionType.internalTypes.contains(node.assumptionType) || node.joinInfoes.nonEmpty) def getExplicitAssertionNodes: Set[DependencyAnalysisNode] = getNonInternalAssertionNodes.filter(node => AssumptionType.explicitAssertionTypes.contains(node.assumptionType)) @@ -136,8 +136,8 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen } private def getNodesWithIdenticalSource(nodes: Set[DependencyAnalysisNode]): Set[DependencyAnalysisNode] = { - val sourceInfos = nodes map (_.sourceInfo.getTopLevelSource) - getNodes filter (node => sourceInfos.contains(node.sourceInfo.getTopLevelSource)) + val sourceInfos = nodes map (_.sourceInfo) + getNodes filter (node => sourceInfos.contains(node.sourceInfo)) } def computeProofCoverage(): (Double, Set[String]) = { @@ -170,7 +170,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen crucialNodesWithStmtInfo exists (n => n.getPositionString.equals(AnalysisSourceInfo.extractPositionString(stmt.pos))) } - val crucialNodeSourceInfos = crucialNodes map (_.sourceInfo.getTopLevelSource) + val crucialNodeSourceInfos = crucialNodes map (_.sourceInfo) var total = 0 var removed = 0 var nonDetermBoolCount = 0 @@ -261,7 +261,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen // var aggregationOfSummaryNodesRuntime: Long = 0L // var filteringNodesRuntime: Long = 0L - private lazy val sourceToAssertionNodes: Map[AnalysisSourceInfo, Set[DependencyAnalysisNode]] = getNonInternalAssertionNodes.groupBy(_.sourceInfo.getTopLevelSource) + private lazy val sourceToAssertionNodes: Map[AnalysisSourceInfo, Set[DependencyAnalysisNode]] = getNonInternalAssertionNodes.groupBy(_.sourceInfo) @@ -279,7 +279,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen val allNonInternalAssertions = sourceToAssertionNodes.getOrElse(currentNode, Set.empty) val intraMethodDependencyIds = dependencyGraph.getAllDependencies(allNonInternalAssertions.map(_.id), includeInfeasibilityNodes=true, includeUpwardEdges=false, includeDownwardEdges=false) - val intraMethodDependencies = intraMethodDependencyIds.flatMap(nonInternalAssumptionNodesMap.get).filterNot(_.sourceInfo.getTopLevelSource.equals(currentNode)) + val intraMethodDependencies = intraMethodDependencyIds.flatMap(nonInternalAssumptionNodesMap.get).filterNot(_.sourceInfo.equals(currentNode)) val postconditionNodeIds = intraMethodDependencyIds.flatMap(n => dependencyGraph.getEdgesConnectingMethodsDownwards.getOrElse(n, Set.empty)) val postconditionNodes = postconditionNodeIds.flatMap(nodesMap.get) @@ -287,8 +287,8 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen val preconditionNodeIds = intraMethodDependencyIds.flatMap(n => dependencyGraph.getEdgesConnectingMethodsUpwards.getOrElse(n, Set.empty)) val preconditionNodes = preconditionNodeIds.flatMap(nodesMap.get) - val transDepsDownwards = postconditionNodes.map(_.sourceInfo.getTopLevelSource).filterNot(_.equals(currentNode)).flatMap(node => computeDependencies(node, updatedVisited, DATraversalMode.Downwards)) - val transDepsUpwards = preconditionNodes.map(_.sourceInfo.getTopLevelSource).filterNot(_.equals(currentNode)).flatMap(node => computeDependencies(node, updatedVisited, DATraversalMode.Upwards)) + val transDepsDownwards = postconditionNodes.map(_.sourceInfo).filterNot(_.equals(currentNode)).flatMap(node => computeDependencies(node, updatedVisited, DATraversalMode.Downwards)) + val transDepsUpwards = preconditionNodes.map(_.sourceInfo).filterNot(_.equals(currentNode)).flatMap(node => computeDependencies(node, updatedVisited, DATraversalMode.Upwards)) val result = reduceCompactUserLevelNodes(toCompactUserLevelNodes(intraMethodDependencies ++ postconditionNodes) ++ transDepsDownwards ++ transDepsUpwards) @@ -324,7 +324,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen } private def toCompactUserLevelNodes(lowLevelNodes: Set[DependencyAnalysisNode]): Set[CompactUserLevelDependencyAnalysisNode] = { - lowLevelNodes.groupBy(_.sourceInfo.getTopLevelSource).map{case (source, nodes) => + lowLevelNodes.groupBy(_.sourceInfo).map{case (source, nodes) => val assertionNodes = nodes.filter(_.isInstanceOf[GeneralAssertionNode]) CompactUserLevelDependencyAnalysisNode(source, nodes.filter(_.isInstanceOf[GeneralAssumptionNode]).map(_.assumptionType), @@ -391,11 +391,11 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen val explicitAssertions = toCompactUserLevelNodes(getExplicitAssertionNodes) val nonSourceCodeAssumptionTypes = AssumptionType.explicitAssumptionTypes ++ AssumptionType.verificationAnnotationTypes - val allSourceCodeNodes = toCompactUserLevelNodes(getNonInternalAssumptionNodes).filter(n => nonSourceCodeAssumptionTypes.intersect(n.assumptionTypes).isEmpty).map(_.source.getTopLevelSource).diff(explicitAssertions.map(_.source.getTopLevelSource)) + val allSourceCodeNodes = toCompactUserLevelNodes(getNonInternalAssumptionNodes).filter(n => nonSourceCodeAssumptionTypes.intersect(n.assumptionTypes).isEmpty).map(_.source).diff(explicitAssertions.map(_.source)) if(allSourceCodeNodes.isEmpty) return 1.0 - val coveredSourceCodeNodes = coveredNodes.map(_.source.getTopLevelSource).intersect(allSourceCodeNodes) + val coveredSourceCodeNodes = coveredNodes.map(_.source).intersect(allSourceCodeNodes) // println(s"Covered Source Code:\n\t${coveredSourceCodeNodes.toList.sortBy(n => (n.getLineNumber, n.toString())).mkString("\n\t")}") // println(s"Uncovered Source Code:\n\t${allSourceCodeNodes.diff(coveredSourceCodeNodes).toList.sortBy(n => (n.getLineNumber, n.toString())).mkString("\n\t")}") println(s"Spec Quality = ${coveredSourceCodeNodes.size} / ${allSourceCodeNodes.size}") @@ -508,7 +508,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen val unverifiedAssertionImpacts = getAssertionsWithZeroQuality.map(assertion => (assertion, 1.0/numAssertions)).toList val totalImpacts1 = (assumptionImpacts ++ unverifiedAssertionImpacts).groupBy(_._1) - val totalImpacts = totalImpacts1.map{case (assumption, impacts) => (assumption.getTopLevelSource.toString, impacts.map(_._2).sum)}.toList + val totalImpacts = totalImpacts1.map{case (assumption, impacts) => (assumption.toString, impacts.map(_._2).sum)}.toList totalImpacts.sortBy(_._2).reverse } diff --git a/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala b/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala index 15ad7564a..52aaa937d 100644 --- a/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala +++ b/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala @@ -56,7 +56,7 @@ object UserLevelDependencyAnalysisNode { } def getSourceSet(): Set[AnalysisSourceInfo] = { - left.map(_.source.getTopLevelSource) + left.map(_.source) } } } diff --git a/src/main/scala/rules/Brancher.scala b/src/main/scala/rules/Brancher.scala index 3abb6d67a..7e1f2333f 100644 --- a/src/main/scala/rules/Brancher.scala +++ b/src/main/scala/rules/Brancher.scala @@ -50,7 +50,7 @@ object brancher extends BranchingRules { val analysisInfoes1 = analysisInfoes.addInfo(conditionExp._1.info, conditionExp._1) // FIXME ake: infeasible path // val assertionNodesForJoin = DependencyAnalyzer.extractAssertionsForJoin(conditionExp._1, s.program) -// assertionNodesForJoin.foreach(n => v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, CompositeAnalysisSourceInfo(v.decider.analysisSourceInfoStack.getFullSourceInfo.getTopLevelSource, AnalysisSourceInfo.createAnalysisSourceInfo(n)), v.decider.analysisSourceInfoStack.getDependencyType, isJoinNode=true)) +// assertionNodesForJoin.foreach(n => v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, CompositeAnalysisSourceInfo(v.decider.analysisSourceInfoStack.getFullSourceInfo, AnalysisSourceInfo.createAnalysisSourceInfo(n)), v.decider.analysisSourceInfoStack.getDependencyType, isJoinNode=true)) if(!Expressions.isKnownWellDefined(conditionExp._1, Some(s.program))){ v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfoes1) } diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index a1828b9d7..6ea661e1f 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -11,7 +11,7 @@ import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.decider.RecordedPathConditions import viper.silicon.dependencyAnalysis.DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes -import viper.silicon.dependencyAnalysis.DependencyAnalysisInfoes +import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfoes, EvalStackDependencyAnalysisJoin, JoinType} import viper.silicon.interfaces._ import viper.silicon.interfaces.state.{NonQuantifiedChunk, QuantifiedChunk} import viper.silicon.logger.records.data.{CommentRecord, ConditionalEdgeRecord, ExecuteRecord, MethodCallRecord} @@ -356,7 +356,7 @@ object executor extends ExecutionRules { if(v.decider.isPathInfeasible()){ // FIXME ake: infeasbile path // val assertionNodesForJoin = DependencyAnalyzer.extractAssertionsForJoin(stmt, state.program) -// assertionNodesForJoin.foreach(n => v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, CompositeAnalysisSourceInfo(v.decider.analysisSourceInfoStack.getFullSourceInfo.getTopLevelSource, AnalysisSourceInfo.createAnalysisSourceInfo(n)), v.decider.analysisSourceInfoStack.getDependencyType, isJoinNode=true)) +// assertionNodesForJoin.foreach(n => v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, CompositeAnalysisSourceInfo(v.decider.analysisSourceInfoStack.getFullSourceInfo, AnalysisSourceInfo.createAnalysisSourceInfo(n)), v.decider.analysisSourceInfoStack.getDependencyType, isJoinNode=true)) if(Statements.hasProofObligations(stmt, state.program)){ v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfoes) @@ -569,7 +569,7 @@ object executor extends ExecutionRules { val s2 = s1.copy(g = Store(fargs.zip(argsWithExp)), recordVisited = true) - consumes(s2, meth.pres, false, _ => pvePre, v1, analysisInfoes)((s3, _, v2) => { // TODO ake: join + consumes(s2, meth.pres, false, _ => pvePre, v1, analysisInfoes.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Source)))((s3, _, v2) => { v2.symbExLog.closeScope(preCondId) val postCondLog = new CommentRecord("Postcondition", s3, v2.decider.pcs) val postCondId = v2.symbExLog.openScope(postCondLog) diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index 0bc197d45..02d4074b3 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -79,7 +79,7 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif new java.io.File(s"${Verifier.config.tempDirectory()}/${method.name}.dot")) } - val presAssertionNodeForJoin = pres.flatMap(_.topLevelConjuncts).map(pc => SimpleAssertionNode(True, AnalysisSourceInfo.createAnalysisSourceInfo(pc), AssumptionType.Precondition, isClosed=false, isJoinNode=true)) + val presAssertionNodeForJoin = pres.flatMap(_.topLevelConjuncts).map(pc => SimpleAssertionNode(True, AnalysisSourceInfo.createAnalysisSourceInfo(pc), AssumptionType.Precondition, isClosed=false, joinInfoes = List(SimpleDependencyAnalysisJoin(AnalysisSourceInfo.createAnalysisSourceInfo(pc), JoinType.Sink)))) presAssertionNodeForJoin foreach v.decider.dependencyAnalyzer.addAssertionNode diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index cc69fae8f..e8222e2ca 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -7,6 +7,7 @@ package viper.silicon.supporters.functions import com.typesafe.scalalogging.Logger +import viper.silicon.Config.Sink import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.decider.Decider @@ -189,7 +190,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver assertReadAccessOnly = !Verifier.config.respectFunctionPrePermAmounts()) - val presAssertionNodeForJoin = function.pres.flatMap(_.topLevelConjuncts).map(pc => SimpleAssertionNode(True, AnalysisSourceInfo.createAnalysisSourceInfo(pc), AssumptionType.Precondition, isClosed=false, isJoinNode=true)) + val presAssertionNodeForJoin = function.pres.flatMap(_.topLevelConjuncts).map(pc => SimpleAssertionNode(True, AnalysisSourceInfo.createAnalysisSourceInfo(pc), AssumptionType.Precondition, isClosed=false, joinInfoes = List(SimpleDependencyAnalysisJoin(AnalysisSourceInfo.createAnalysisSourceInfo(pc), JoinType.Sink)))) presAssertionNodeForJoin foreach v.decider.dependencyAnalyzer.addAssertionNode /* Phase 1: Check well-definedness of the specifications */ diff --git a/src/test/scala/DependencyAnalysisTestFramework.scala b/src/test/scala/DependencyAnalysisTestFramework.scala index ac4b85969..fd59af739 100644 --- a/src/test/scala/DependencyAnalysisTestFramework.scala +++ b/src/test/scala/DependencyAnalysisTestFramework.scala @@ -1,16 +1,17 @@ package viper.silicon.tests import viper.silicon.SiliconFrontend -import viper.silicon.dependencyAnalysis.{DependencyGraphInterpreter, DependencyAnalysisNode, AssumptionType, DependencyAnalysisReporter} -import viper.silver.ast.{Infoed, Program} +import viper.silicon.dependencyAnalysis.{DependencyAnalysisNode, DependencyAnalysisReporter, DependencyGraphInterpreter} import viper.silver.ast.utility.ViperStrategy -import viper.silver.{ast, verifier} +import viper.silver.ast.{Infoed, Program} +import viper.silver.dependencyAnalysis.AssumptionType import viper.silver.verifier.VerificationResult -import scala.jdk.CollectionConverters.IterableHasAsScala +import viper.silver.{ast, verifier} import java.io.PrintWriter import java.nio.file.{Files, Path, Paths} import scala.annotation.unused +import scala.jdk.CollectionConverters.IterableHasAsScala trait DependencyAnalysisTestFramework { val irrelevantKeyword = "irrelevant" @@ -129,7 +130,7 @@ trait DependencyAnalysisTestFramework { protected def evaluatePrecision(relevantLines: Set[Int]): Unit = { val relevantNodes = relevantLines.flatMap(line => fullGraphInterpreter.getNodesByLine(line)) - val sourceInfoes = relevantNodes.groupBy(_.sourceInfo.getTopLevelSource).keySet + val sourceInfoes = relevantNodes.groupBy(_.sourceInfo).keySet println(s"Evaluating precision of\n\t${sourceInfoes.mkString("\n\t")}") val reportedDependencies = fullGraphInterpreter.getAllNonInternalDependencies(relevantNodes.map(_.id)).diff(relevantNodes) From df9c5159889ea1255b5e62694bc31297ac501250 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 30 Mar 2026 19:22:45 +0200 Subject: [PATCH 404/474] fix --- src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index 3a9dfa448..d8a75f312 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -2,7 +2,7 @@ package viper.silicon.dependencyAnalysis import viper.silicon.dependencyAnalysis.DependencyAnalyzer._ import viper.silicon.interfaces.state.{Chunk, GeneralChunk} -import viper.silicon.state.terms._ +import viper.silicon.state.terms.{NoPerm, _} import viper.silicon.verifier.Verifier import viper.silver.ast import viper.silver.ast._ From 2d96e5378de4aea8aeb9d83cc924c4f4432b24e9 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 30 Mar 2026 19:41:09 +0200 Subject: [PATCH 405/474] fix merge info refactor dependency type --- src/main/scala/debugger/DebugParser.scala | 5 +-- .../DependencyAnalysisInfo.scala | 17 ++++------ .../DependencyAnalysisNode.scala | 29 +++++++--------- .../DependencyAnalyzer.scala | 34 +++++++++---------- .../dependencyAnalysis/DependencyGraph.scala | 25 ++++---------- .../DependencyGraphImporter.scala | 14 ++++---- src/main/scala/rules/Executor.scala | 3 +- .../rules/MoreCompleteExhaleSupporter.scala | 4 +-- src/main/scala/rules/StateConsolidator.scala | 6 ++-- .../scala/supporters/MethodSupporter.scala | 2 +- .../functions/FunctionVerificationUnit.scala | 6 ++-- 11 files changed, 62 insertions(+), 83 deletions(-) diff --git a/src/main/scala/debugger/DebugParser.scala b/src/main/scala/debugger/DebugParser.scala index 3d2a792e2..9db8a9c7f 100644 --- a/src/main/scala/debugger/DebugParser.scala +++ b/src/main/scala/debugger/DebugParser.scala @@ -2,6 +2,7 @@ package viper.silicon.debugger import fastparse._ import viper.silver.ast._ +import viper.silver.dependencyAnalysis.DependencyType import viper.silver.parser._ import scala.collection.mutable @@ -36,7 +37,7 @@ class DebugParser extends FastParser { class DebugTranslator(p: PProgram, override val members: mutable.Map[String, Node]) extends Translator(p) { - override protected def expInternal(pexp: PExp, pos: PExp, info: Info): Exp = { + override protected def expInternal(pexp: PExp, pos: PExp, info: Info, dependencyType: Option[DependencyType]): Exp = { pexp match { case pviu@PVersionedIdnUseExp(_, _, _) => pexp.typ match { @@ -45,7 +46,7 @@ class DebugTranslator(p: PProgram, override val members: mutable.Map[String, Nod } case PDebugLabelledOldExp(_, lbl, e) => DebugLabelledOld(exp(e), lbl.versionedName)(pos, info) - case _ => super.expInternal(pexp, pos, info) + case _ => super.expInternal(pexp, pos, info, dependencyType) } } diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala index f966edf87..f02913518 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala @@ -2,7 +2,7 @@ package viper.silicon.dependencyAnalysis import viper.silicon.dependencyAnalysis.JoinType.JoinType import viper.silver.ast -import viper.silver.ast.{AbstractAssign, Apply, Assert, Assume, DependencyTypeInfo, Exhale, ExtensionStmt, Fold, Goto, If, Inhale, Label, LocalVarDeclStmt, MethodCall, NewStmt, NoPosition, Quasihavoc, Quasihavocall, Seqn, Unfold, While} +import viper.silver.ast.{DependencyTypeInfo, NoPosition} import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, DependencyType, StringAnalysisSourceInfo} @@ -42,10 +42,7 @@ case class DependencyAnalysisInfoes(sourceInfoes: List[AnalysisSourceInfo], depe def getDependencyType: DependencyType = dependencyTypes.head.dependencyType - def getMergeInfo: DependencyAnalysisMergeInfo = NoDependencyAnalysisMerge() -// mergeInfoes.head // TODO - - def getJoinInfoes: List[DependencyAnalysisJoinInfo] = joinInfoes + def getMergeInfo: DependencyAnalysisMergeInfo = mergeInfoes.headOption.getOrElse(SimpleDependencyAnalysisMerge(getSourceInfo)) def getJoinInfo: List[SimpleDependencyAnalysisJoin] = { if(joinInfoes.isEmpty) return List.empty @@ -89,10 +86,6 @@ object JoinType extends Enumeration { trait DependencyAnalysisJoinInfo extends ast.Info { - - def isJoin: Boolean = true - - override def comment: Seq[String] = Nil override def isCached: Boolean = false } @@ -104,7 +97,7 @@ case class SimpleDependencyAnalysisJoin(sourceInfo: AnalysisSourceInfo, joinType trait DependencyAnalysisMergeInfo extends ast.Info { - def isMerge: Boolean = true + def isMerge: Boolean override def comment: Seq[String] = Nil override def isCached: Boolean = false @@ -114,7 +107,9 @@ case class NoDependencyAnalysisMerge() extends DependencyAnalysisMergeInfo { override def isMerge: Boolean = false } -case class SimpleDependencyAnalysisMerge(sourceInfo: AnalysisSourceInfo) extends DependencyAnalysisMergeInfo +case class SimpleDependencyAnalysisMerge(sourceInfo: AnalysisSourceInfo) extends DependencyAnalysisMergeInfo { + override def isMerge: Boolean = true +} diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala index 8a11169b7..920b3d243 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala @@ -29,12 +29,7 @@ trait DependencyAnalysisNode extends AbstractDependencyAnalysisNode { */ val assumptionType: AssumptionType - /** - * A flag that, when set to true, indicates that the node should not receive any additional edges, unless explicitly added. - * This is useful to increase precision of the dependency analysis, for example, to ensure that an assumption does not - * depend on more assertions than necessary. - */ - val isClosed: Boolean + val mergeInfo: DependencyAnalysisMergeInfo val joinInfoes: List[SimpleDependencyAnalysisJoin] @@ -86,38 +81,38 @@ trait ChunkAnalysisInfo { val labelNode: LabelNode } -case class SimpleAssumptionNode(term: Term, description: Option[String], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean, joinInfoes: List[SimpleDependencyAnalysisJoin]=List.empty, _id: Option[Int]=None) extends GeneralAssumptionNode { +case class SimpleAssumptionNode(term: Term, description: Option[String], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, mergeInfo: DependencyAnalysisMergeInfo, joinInfoes: List[SimpleDependencyAnalysisJoin], _id: Option[Int]=None) extends GeneralAssumptionNode { override def getNodeString: String = "assume " + term.toString + description.map(" (" + _ + ")").getOrElse("") } -case class AxiomAssumptionNode(term: Term, description: Option[String], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean, joinInfoes: List[SimpleDependencyAnalysisJoin]=List.empty, _id: Option[Int]=None) extends GeneralAssumptionNode { +case class AxiomAssumptionNode(term: Term, description: Option[String], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, mergeInfo: DependencyAnalysisMergeInfo, joinInfoes: List[SimpleDependencyAnalysisJoin], _id: Option[Int]=None) extends GeneralAssumptionNode { override def getNodeString: String = "assume axiom " + term.toString + description.map(" (" + _ + ")").getOrElse("") override def getNodeType: String = "Axiom" } -case class SimpleAssertionNode(term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean, hasFailed: Boolean = false, joinInfoes: List[SimpleDependencyAnalysisJoin]=List.empty, _id: Option[Int]=None) extends GeneralAssertionNode { +case class SimpleAssertionNode(term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, mergeInfo: DependencyAnalysisMergeInfo, joinInfoes: List[SimpleDependencyAnalysisJoin], hasFailed: Boolean = false, _id: Option[Int]=None) extends GeneralAssertionNode { override def getNodeString: String = "assert " + term.toString - override def getAssertFailedNode(): GeneralAssertionNode = SimpleAssertionNode(term, sourceInfo, assumptionType, isClosed, hasFailed=true, joinInfoes=joinInfoes) + override def getAssertFailedNode(): GeneralAssertionNode = SimpleAssertionNode(term, sourceInfo, assumptionType, mergeInfo, hasFailed=true, joinInfoes=joinInfoes) } -case class SimpleCheckNode(term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean, hasFailed: Boolean = false, joinInfoes: List[SimpleDependencyAnalysisJoin]=List.empty, _id: Option[Int]=None) extends GeneralAssertionNode { +case class SimpleCheckNode(term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, mergeInfo: DependencyAnalysisMergeInfo, joinInfoes: List[SimpleDependencyAnalysisJoin], hasFailed: Boolean = false, _id: Option[Int]=None) extends GeneralAssertionNode { override def getNodeString: String = "check " + term override def getNodeType: String = "Check" - override def getAssertFailedNode(): GeneralAssertionNode = SimpleCheckNode(term, sourceInfo, assumptionType, isClosed, hasFailed=true) + override def getAssertFailedNode(): GeneralAssertionNode = SimpleCheckNode(term, sourceInfo, assumptionType, mergeInfo, joinInfoes, hasFailed=true) } -case class PermissionInhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean, labelNode: LabelNode, joinInfoes: List[SimpleDependencyAnalysisJoin]=List.empty, _id: Option[Int]=None) extends GeneralAssumptionNode with ChunkAnalysisInfo { +case class PermissionInhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, mergeInfo: DependencyAnalysisMergeInfo, labelNode: LabelNode, joinInfoes: List[SimpleDependencyAnalysisJoin], _id: Option[Int]=None) extends GeneralAssumptionNode with ChunkAnalysisInfo { override def getNodeString: String = "inhale " + chunk.toString override def getNodeType: String = "Inhale" } -case class PermissionExhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, isClosed: Boolean, labelNode: LabelNode, hasFailed: Boolean = false, joinInfoes: List[SimpleDependencyAnalysisJoin]=List.empty, _id: Option[Int]=None) extends GeneralAssertionNode with ChunkAnalysisInfo { +case class PermissionExhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, mergeInfo: DependencyAnalysisMergeInfo, labelNode: LabelNode, joinInfoes: List[SimpleDependencyAnalysisJoin], hasFailed: Boolean = false, _id: Option[Int]=None) extends GeneralAssertionNode with ChunkAnalysisInfo { override def getNodeType: String = "Exhale" override def getNodeString: String = "exhale " + chunk.toString - override def getAssertFailedNode(): GeneralAssertionNode = PermissionExhaleNode(chunk, term, sourceInfo, assumptionType, isClosed, labelNode, hasFailed=true, _id=_id) + override def getAssertFailedNode(): GeneralAssertionNode = PermissionExhaleNode(chunk, term, sourceInfo, assumptionType, mergeInfo, labelNode, joinInfoes, hasFailed=true, _id=_id) } /** @@ -127,7 +122,7 @@ case class PermissionExhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSo case class LabelNode(term: Var, _id: Option[Int]=None) extends GeneralAssumptionNode { val sourceInfo: AnalysisSourceInfo = NoAnalysisSourceInfo() val assumptionType: AssumptionType = AssumptionType.Internal - val isClosed: Boolean = true + val mergeInfo: DependencyAnalysisMergeInfo = NoDependencyAnalysisMerge() val description: String = term.toString val joinInfoes: List[SimpleDependencyAnalysisJoin] = List.empty override def getNodeType: String = "Label" @@ -143,7 +138,7 @@ case class LabelNode(term: Var, _id: Option[Int]=None) extends GeneralAssumption */ case class InfeasibilityNode(sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, _id: Option[Int]=None) extends GeneralAssumptionNode { val term: Term = False - val isClosed: Boolean = true + val mergeInfo: DependencyAnalysisMergeInfo = NoDependencyAnalysisMerge() val description: String = "False" val joinInfoes: List[SimpleDependencyAnalysisJoin] = List.empty diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index d8a75f312..b7fbc6573 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -323,13 +323,13 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { // adding assumption nodes override def addAssumption(assumption: Term, analysisInfoes: DependencyAnalysisInfoes, description: Option[String]): Option[Int] = { - val node = SimpleAssumptionNode(assumption, description, analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assumptionType, analysisInfoes.getMergeInfo.isMerge, analysisInfoes.getJoinInfo) + val node = SimpleAssumptionNode(assumption, description, analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assumptionType, analysisInfoes.getMergeInfo, analysisInfoes.getJoinInfo) addAssumptionNode(node) Some(node.id) } override def addAxiom(assumption: Term, analysisInfoes: DependencyAnalysisInfoes, description: Option[String]): Option[Int] = { - val node = AxiomAssumptionNode(assumption, description, analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assumptionType, analysisInfoes.getMergeInfo.isMerge, analysisInfoes.getJoinInfo) + val node = AxiomAssumptionNode(assumption, description, analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assumptionType, analysisInfoes.getMergeInfo, analysisInfoes.getJoinInfo) addAssumptionNode(node) Some(node.id) } @@ -338,7 +338,7 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { val startTime = startTimeMeasurement() val labelNode = labelNodeOpt.get val chunk = buildChunk(Ite(labelNode.term, perm, NoPerm)) - val chunkNode = addPermissionExhaleNode(chunk, chunk.perm, analysisInfo.analysisInfoes.getSourceInfo, analysisInfo.analysisInfoes.getDependencyType.assertionType, labelNode) + val chunkNode = addPermissionExhaleNode(chunk, chunk.perm, analysisInfo.analysisInfoes, labelNode) if(chunkNode.isDefined) addDependency(chunkNode, Some(labelNode.id)) // addPermissionDependencies(sourceChunks, Set(), chunkNode) TODO ake: can be removed @@ -350,7 +350,7 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { val startTime = startTimeMeasurement() val labelNode = labelNodeOpt.get val chunk = buildChunk(Ite((labelNode.term, perm, NoPerm))) - val chunkNode = addPermissionInhaleNode(chunk, chunk.perm, analysisInfo.analysisInfoes.getSourceInfo, analysisInfo.analysisInfoes.getDependencyType.assumptionType, labelNode, analysisInfo.analysisInfoes.getJoinInfo) + val chunkNode = addPermissionInhaleNode(chunk, chunk.perm, analysisInfo.analysisInfoes, labelNode) if(chunkNode.isDefined) addDependency(chunkNode, Some(labelNode.id)) // addPermissionDependencies(sourceChunks, Set(), chunkNode) TODO ake: can be removed @@ -358,14 +358,14 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { chunk } - private def addPermissionInhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, labelNode: LabelNode, joinInfoes: List[SimpleDependencyAnalysisJoin]): Option[Int] = { - val node = PermissionInhaleNode(chunk, permAmount, sourceInfo, assumptionType, isClosed_, labelNode, joinInfoes) + private def addPermissionInhaleNode(chunk: Chunk, permAmount: Term, analysisInfoes: DependencyAnalysisInfoes, labelNode: LabelNode): Option[Int] = { + val node = PermissionInhaleNode(chunk, permAmount, analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assumptionType, analysisInfoes.getMergeInfo, labelNode, analysisInfoes.getJoinInfo) addAssumptionNode(node) Some(node.id) } - private def addPermissionExhaleNode(chunk: Chunk, permAmount: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, labelNode: LabelNode): Option[Int] = { - val node = PermissionExhaleNode(chunk, permAmount, sourceInfo, assumptionType, isClosed_, labelNode) + private def addPermissionExhaleNode(chunk: Chunk, permAmount: Term, analysisInfoes: DependencyAnalysisInfoes, labelNode: LabelNode): Option[Int] = { + val node = PermissionExhaleNode(chunk, permAmount, analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assertionType, analysisInfoes.getMergeInfo, labelNode, analysisInfoes.getJoinInfo) addAssertionNode(node) // addPermissionDependencies(Set(chunk), Set(), Some(node.id)) TODO ake: can be removed Some(node.id) @@ -381,9 +381,9 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { // adding assertion nodes override def createAssertOrCheckNode(term: Term, analysisInfoes: DependencyAnalysisInfoes, isCheck: Boolean): Option[GeneralAssertionNode] = { if(isCheck) - Some(SimpleCheckNode(term, analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assumptionType, analysisInfoes.getMergeInfo.isMerge, joinInfoes=analysisInfoes.getJoinInfo)) + Some(SimpleCheckNode(term, analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assumptionType, analysisInfoes.getMergeInfo, analysisInfoes.getJoinInfo)) else - Some(SimpleAssertionNode(term, analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assumptionType, analysisInfoes.getMergeInfo.isMerge, joinInfoes=analysisInfoes.getJoinInfo)) + Some(SimpleAssertionNode(term, analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assumptionType, analysisInfoes.getMergeInfo, analysisInfoes.getJoinInfo)) } def addAssertNode(term: Term, analysisInfoes: DependencyAnalysisInfoes): Option[Int] = { @@ -405,8 +405,8 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { } override def addAssertionFailedNode(failedAssertion: Term, analysisInfoes: DependencyAnalysisInfoes): Option[Int] = { - val assumeNode = SimpleAssumptionNode(failedAssertion, None, analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assumptionType, analysisInfoes.getMergeInfo.isMerge, analysisInfoes.getJoinInfo) - val assertFailedNode = SimpleAssertionNode(failedAssertion, analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assumptionType, analysisInfoes.getMergeInfo.isMerge, hasFailed=true, joinInfoes=analysisInfoes.getJoinInfo) + val assumeNode = SimpleAssumptionNode(failedAssertion, None, analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assumptionType, analysisInfoes.getMergeInfo, analysisInfoes.getJoinInfo) + val assertFailedNode = SimpleAssertionNode(failedAssertion, analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assumptionType, analysisInfoes.getMergeInfo, analysisInfoes.getJoinInfo, hasFailed=true) dependencyGraph.addNode(assumeNode) dependencyGraph.addNode(assertFailedNode) dependencyGraph.addEdges(Set(assumeNode.id), assertFailedNode.id) @@ -458,8 +458,8 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { } override def addDependenciesForAbstractMembers(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp], analysisInfoes: DependencyAnalysisInfoes): Unit = { - val sourceNodeIds = sourceExps.flatMap(e => addAssumption(True, analysisInfoes)) - val targetNodes = targetExps.flatMap(e => addAssertNode(True, analysisInfoes)) + val sourceNodeIds = sourceExps.flatMap(e => addAssumption(True, analysisInfoes.addInfo(e.info, e))) + val targetNodes = targetExps.flatMap(e => addAssertNode(True, analysisInfoes.addInfo(e.info, e))) dependencyGraph.addEdges(sourceNodeIds, targetNodes) } @@ -503,7 +503,7 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { * the low-level graph containing all details. */ private def buildAndGetMergedGraph(): DependencyGraph = { - def keepNode(n: DependencyAnalysisNode): Boolean = n.isClosed || n.isInstanceOf[InfeasibilityNode] || n.isInstanceOf[AxiomAssumptionNode] + def keepNode(n: DependencyAnalysisNode): Boolean = !n.mergeInfo.isMerge || n.isInstanceOf[InfeasibilityNode] || n.isInstanceOf[AxiomAssumptionNode] val mergedGraph = new DependencyGraph val nodeMap = mutable.HashMap[Int, Int]() @@ -515,7 +515,7 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { val assumptionNodesBySource = dependencyGraph.getAssumptionNodes.filter(!keepNode(_)).groupBy(n => (n.sourceInfo, n.assumptionType, n.joinInfoes)) // TODO ake: review joinInfoes assumptionNodesBySource foreach { case ((sourceInfo, assumptionType, joinInfoes), assumptionNodes) => if (assumptionNodes.nonEmpty) { - val newNode = SimpleAssumptionNode(True, None, sourceInfo, assumptionType, isClosed = false, joinInfoes=joinInfoes) // TODO ake: review joinInfoes + val newNode = SimpleAssumptionNode(True, None, sourceInfo, assumptionType, SimpleDependencyAnalysisMerge(sourceInfo), joinInfoes) // TODO ake: review joinInfoes assumptionNodes foreach (n => nodeMap.put(n.id, newNode.id)) mergedGraph.addAssumptionNode(newNode) } @@ -528,7 +528,7 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { val assertionNodesBySource = dependencyGraph.getAssertionNodes.filter(!keepNode(_)).groupBy(n => (n.sourceInfo, n.assumptionType, n.joinInfoes)) // TODO ake: review joinInfoes assertionNodesBySource foreach { case ((sourceInfo, assumptionType, joinInfoes), assertionNodes) => if (assertionNodes.nonEmpty) { - val newNode = SimpleAssertionNode(True, sourceInfo, assumptionType, isClosed = false, hasFailed=assertionNodes.exists(_.hasFailed), joinInfoes=joinInfoes) // TODO ake: review joinInfoes + val newNode = SimpleAssertionNode(True, sourceInfo, assumptionType, SimpleDependencyAnalysisMerge(sourceInfo), hasFailed=assertionNodes.exists(_.hasFailed), joinInfoes=joinInfoes) // TODO ake: review joinInfoes assertionNodes foreach (n => nodeMap.put(n.id, newNode.id)) mergedGraph.addAssertionNode(newNode) } diff --git a/src/main/scala/dependencyAnalysis/DependencyGraph.scala b/src/main/scala/dependencyAnalysis/DependencyGraph.scala index a5b0cf91e..65878d91b 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraph.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraph.scala @@ -192,32 +192,21 @@ class DependencyGraph extends ReadOnlyDependencyGraph { visited } - private def addTransitiveEdges(sources: Iterable[DependencyAnalysisNode], target: DependencyAnalysisNode): Unit = { - val oldSources = edges.getOrElse(target.id, Set.empty) - val newSources = sources map(_.id) // filter(_ > target.id) does not work due to loop invariants - if(newSources.nonEmpty) edges.update(target.id, oldSources ++ newSources) - } - - private def addTransitiveEdges(sources: Iterable[DependencyAnalysisNode], targets: Iterable[DependencyAnalysisNode]): Unit = { - targets foreach (t => addTransitiveEdges(sources, t)) - } - // TODO ake: maybe move to DependencyAnalyzer? - private def getNodesPerTransitivitySourceInfo: Map[AnalysisSourceInfo, Seq[DependencyAnalysisNode]] = { -// getNodes.groupBy(_.sourceInfo.getSourceForTransitiveEdges) - getNodes.groupBy(_.sourceInfo) // TODO ake: fix merge + private def getNodesByMergeInfo: Map[DependencyAnalysisMergeInfo, Seq[DependencyAnalysisNode]] = { + getNodes.filter(_.mergeInfo.isMerge).groupBy(_.mergeInfo) } // TODO ake: maybe move to DependencyAnalyzer? def addTransitiveEdges(): Unit = { - val nodesPerSourceInfo = getNodesPerTransitivitySourceInfo + val nodesPerSourceInfo = getNodesByMergeInfo nodesPerSourceInfo foreach {case (_, nodes) => val asserts = nodes.filter(_.isInstanceOf[GeneralAssertionNode]) - val assumes = nodes.filter(n => !n.isClosed && n.isInstanceOf[GeneralAssumptionNode] && !n.isInstanceOf[LabelNode]) - addTransitiveEdges(asserts, assumes) + val assumes = nodes.filter(n => n.isInstanceOf[GeneralAssumptionNode] && !n.isInstanceOf[LabelNode]) + addEdges(asserts.map(_.id), assumes.map(_.id)) val checks = asserts.filter(_.isInstanceOf[SimpleCheckNode]) - val notChecks = nodes.filter(n => !n.isClosed && !n.isInstanceOf[SimpleCheckNode]) - addTransitiveEdges(checks, notChecks) + val notChecks = nodes.filter(n => !n.isInstanceOf[SimpleCheckNode]) + addEdges(checks.map(_.id), notChecks.map(_.id)) } } diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala index 49d943135..21af8f224 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala @@ -90,19 +90,19 @@ object DependencyGraphImporter { val term: Term = True val chunk: Chunk = DummyChunk() val description: Option[String] = None - val isClosed: Boolean = false + val mergeInfo: SimpleDependencyAnalysisMerge = SimpleDependencyAnalysisMerge(sourceInfo) val labelNode: LabelNode = dummyLabelNode val joinNodeInfoes: List[SimpleDependencyAnalysisJoin] = List.empty val nodeId = Some(nodeIdStr.toInt) // Create node based on type val node = nodeType match { - case "Assumption" => SimpleAssumptionNode(term, description, sourceInfo, assumptionType, isClosed, joinNodeInfoes, _id=nodeId) - case "Axiom" => AxiomAssumptionNode(term, description, sourceInfo, assumptionType, isClosed, joinNodeInfoes, _id=nodeId) - case "Assertion" => SimpleAssertionNode(term, sourceInfo, assumptionType, isClosed, hasFailed = false, joinNodeInfoes, _id=nodeId) - case "Check" => SimpleCheckNode(term, sourceInfo, assumptionType, isClosed, hasFailed = false, joinNodeInfoes, _id=nodeId) - case "Inhale" => PermissionInhaleNode(chunk, term, sourceInfo, assumptionType, isClosed, labelNode, joinNodeInfoes, _id=nodeId) - case "Exhale" => PermissionExhaleNode(chunk, term, sourceInfo, assumptionType, isClosed, labelNode, hasFailed = false, joinNodeInfoes, _id=nodeId) + case "Assumption" => SimpleAssumptionNode(term, description, sourceInfo, assumptionType, mergeInfo, joinNodeInfoes, _id=nodeId) + case "Axiom" => AxiomAssumptionNode(term, description, sourceInfo, assumptionType, mergeInfo, joinNodeInfoes, _id=nodeId) + case "Assertion" => SimpleAssertionNode(term, sourceInfo, assumptionType, mergeInfo, joinNodeInfoes, hasFailed = false, _id=nodeId) + case "Check" => SimpleCheckNode(term, sourceInfo, assumptionType, mergeInfo, joinNodeInfoes, hasFailed = false, _id=nodeId) + case "Inhale" => PermissionInhaleNode(chunk, term, sourceInfo, assumptionType, mergeInfo, labelNode, joinNodeInfoes, _id=nodeId) + case "Exhale" => PermissionExhaleNode(chunk, term, sourceInfo, assumptionType, mergeInfo, labelNode, joinNodeInfoes, hasFailed = false, _id=nodeId) case "Label" => LabelNode(dummyVar, _id=nodeId) case "Infeasible" => InfeasibilityNode(sourceInfo, assumptionType, _id=nodeId) case _ => throw new IllegalArgumentException(s"Unknown node type: $nodeType") diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 6ea661e1f..0452949bb 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -261,8 +261,7 @@ object executor extends ExecutionRules { val sBody = s.copy(g = gBody, h = v.heapSupporter.getEmptyHeap(s.program)) val analysisInfoesInv = DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes - val analysisInfoesLoopInternal = DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes - .withSource(StringAnalysisSourceInfo(s"Loop ${block.id}", ast.NoPosition)).withDependencyType(DependencyType.Internal) + val analysisInfoesLoopInternal = DependencyAnalysisInfoes.create(s"Loop ${block.id}\"", DependencyType.Internal) val edges = s.methodCfg.outEdges(block) val (outEdges, otherEdges) = edges partition(_.kind == cfg.Kind.Out) diff --git a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala index ca9405ce2..55719cd98 100644 --- a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala +++ b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala @@ -109,7 +109,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { Implies(And(argumentEqualities, IsPositive(ch.perm)), `?s` === ch.snap) }) - val analysisInfoes = DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes // TODO ake + val analysisInfoes = DependencyAnalysisInfoes.create("summarize", DependencyType.Internal) // TODO ake val taggedSummarisingSnapshot = summarisingSnapshotDefinitions @@ -168,7 +168,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { // query to check if the permission amount we have is sufficient to get the correct counterexample. If we perform // the query in two parts (one part here, one part in our caller to see if the permission amount is sufficient), // the counterexample might be wrong. - val analysisInfoes = DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes // TODO ake + val analysisInfoes = DependencyAnalysisInfoes.create("summarise", DependencyType.Internal) // TODO ake if (relevantChunks.size == 1 && !Verifier.config.counterexample.isDefined) { val chunk = relevantChunks.head diff --git a/src/main/scala/rules/StateConsolidator.scala b/src/main/scala/rules/StateConsolidator.scala index 4aceaf1ba..b64d500ac 100644 --- a/src/main/scala/rules/StateConsolidator.scala +++ b/src/main/scala/rules/StateConsolidator.scala @@ -138,15 +138,15 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol val mergeLog = new CommentRecord("Merge", null, v.decider.pcs) val sepIdentifier = v.symbExLog.openScope(mergeLog) - val (fr2, mergedChunks, newlyAddedChunks, snapEqs) = singleMerge(fr1, h.values.toSeq, newH.values.toSeq, s.functionRecorderQuantifiedVariables().map(_._1), v, analysisInfoes) + val (fr2, mergedChunks, newlyAddedChunks, snapEqs) = singleMerge(fr1, h.values.toSeq, newH.values.toSeq, s.functionRecorderQuantifiedVariables().map(_._1), v, analysisInfoes1) - v.decider.assume(snapEqs, Option.when(withExp)(DebugExp.createInstance("Snapshot", isInternal_ = true)), enforceAssumption = false, analysisInfoes.withDependencyType(DependencyType.Internal)) + v.decider.assume(snapEqs, Option.when(withExp)(DebugExp.createInstance("Snapshot", isInternal_ = true)), enforceAssumption = false, analysisInfoes1.withDependencyType(DependencyType.Internal)) val interpreter = new NonQuantifiedPropertyInterpreter(mergedChunks, v) newlyAddedChunks.filter(_.isInstanceOf[BasicChunk]) foreach { case ch: BasicChunk => val resource = Resources.resourceDescriptions(ch.resourceID) val pathCond = interpreter.buildPathConditionsForChunk(ch, resource.instanceProperties(s.mayAssumeUpperBounds)) - pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), analysisInfoes.withDependencyType(DependencyType.Internal))) + pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), analysisInfoes1.withDependencyType(DependencyType.Internal))) } v.symbExLog.closeScope(sepIdentifier) diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index 02d4074b3..1510f2925 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -79,7 +79,7 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif new java.io.File(s"${Verifier.config.tempDirectory()}/${method.name}.dot")) } - val presAssertionNodeForJoin = pres.flatMap(_.topLevelConjuncts).map(pc => SimpleAssertionNode(True, AnalysisSourceInfo.createAnalysisSourceInfo(pc), AssumptionType.Precondition, isClosed=false, joinInfoes = List(SimpleDependencyAnalysisJoin(AnalysisSourceInfo.createAnalysisSourceInfo(pc), JoinType.Sink)))) + val presAssertionNodeForJoin = pres.flatMap(_.topLevelConjuncts).map(pc => SimpleAssertionNode(True, AnalysisSourceInfo.createAnalysisSourceInfo(pc), AssumptionType.Precondition, SimpleDependencyAnalysisMerge(AnalysisSourceInfo.createAnalysisSourceInfo(pc)), joinInfoes = List(SimpleDependencyAnalysisJoin(AnalysisSourceInfo.createAnalysisSourceInfo(pc), JoinType.Sink)))) presAssertionNodeForJoin foreach v.decider.dependencyAnalyzer.addAssertionNode diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index e8222e2ca..f49b46b9d 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -190,7 +190,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver assertReadAccessOnly = !Verifier.config.respectFunctionPrePermAmounts()) - val presAssertionNodeForJoin = function.pres.flatMap(_.topLevelConjuncts).map(pc => SimpleAssertionNode(True, AnalysisSourceInfo.createAnalysisSourceInfo(pc), AssumptionType.Precondition, isClosed=false, joinInfoes = List(SimpleDependencyAnalysisJoin(AnalysisSourceInfo.createAnalysisSourceInfo(pc), JoinType.Sink)))) + val presAssertionNodeForJoin = function.pres.flatMap(_.topLevelConjuncts).map(pc => SimpleAssertionNode(True, AnalysisSourceInfo.createAnalysisSourceInfo(pc), AssumptionType.Precondition, SimpleDependencyAnalysisMerge(AnalysisSourceInfo.createAnalysisSourceInfo(pc)), List(SimpleDependencyAnalysisJoin(AnalysisSourceInfo.createAnalysisSourceInfo(pc), JoinType.Sink)))) presAssertionNodeForJoin foreach v.decider.dependencyAnalyzer.addAssertionNode /* Phase 1: Check well-definedness of the specifications */ @@ -305,12 +305,12 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver case (intermediateResult, Phase1Data(sPre, bcsPre, bcsPreExp, pcsPre, pcsPreExp)) => intermediateResult && executionFlowController.locally(sPre, v)((s1, _) => { val labelledBcsPre = And(bcsPre map (t => v.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t)))) - val precondAnalysisSourceInfoes = DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes /* TODO ake */ + val precondAnalysisSourceInfoes = DependencyAnalysisInfoes.create("preconditions", DependencyType.make(AssumptionType.Precondition)) /* TODO ake */ decider.setCurrentBranchCondition(labelledBcsPre, (BigAnd(bcsPreExp.map(_._1)), Option.when(wExp)(BigAnd(bcsPreExp.map(_._2.get)))), precondAnalysisSourceInfoes) val labelledPcsPre = pcsPre map (t => v.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t))) decider.assume(labelledPcsPre, pcsPreExp, s"precondition of ${function.name}", enforceAssumption=false, precondAnalysisSourceInfoes) v.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) - eval(s1, body, FunctionNotWellformed(function), v, DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes)((s2, tBody, bodyNew, _) => { + eval(s1, body, FunctionNotWellformed(function), v, DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes.addInfo(body.info, body))((s2, tBody, bodyNew, _) => { val debugExp = if (wExp) { val e = ast.EqCmp(ast.Result(function.typ)(), body)(function.pos, function.info, function.errT) val eNew = ast.EqCmp(ast.Result(function.typ)(), bodyNew.get)(function.pos, function.info, function.errT) From 6560e2cab0a737e54bd1dcdfaba3c3f4b0debfaa Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 31 Mar 2026 10:15:38 +0200 Subject: [PATCH 406/474] fix join --- silver | 2 +- .../DependencyAnalysisInfo.scala | 21 ++++- .../DependencyAnalyzer.scala | 90 +++++++------------ .../DependencyGraphInterpreter.scala | 6 +- src/main/scala/rules/Executor.scala | 10 +-- .../scala/supporters/MethodSupporter.scala | 13 +-- .../functions/FunctionVerificationUnit.scala | 25 ++++-- 7 files changed, 81 insertions(+), 86 deletions(-) diff --git a/silver b/silver index f0f273c6c..5bca3ee1a 160000 --- a/silver +++ b/silver @@ -1 +1 @@ -Subproject commit f0f273c6c92a00ec96be35ee9b224d16bebe2ac2 +Subproject commit 5bca3ee1a0fb2ae52869390a7b7e445351ef48f3 diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala index f02913518..09046894d 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala @@ -1,5 +1,6 @@ package viper.silicon.dependencyAnalysis +import viper.silicon.dependencyAnalysis.EdgeType.EdgeType import viper.silicon.dependencyAnalysis.JoinType.JoinType import viper.silver.ast import viper.silver.ast.{DependencyTypeInfo, NoPosition} @@ -47,7 +48,7 @@ case class DependencyAnalysisInfoes(sourceInfoes: List[AnalysisSourceInfo], depe def getJoinInfo: List[SimpleDependencyAnalysisJoin] = { if(joinInfoes.isEmpty) return List.empty val h = joinInfoes.head match { - case EvalStackDependencyAnalysisJoin(joinType) => SimpleDependencyAnalysisJoin(sourceInfoes.last, joinType) + case EvalStackDependencyAnalysisJoin(joinType, edgeType) => SimpleDependencyAnalysisJoin(sourceInfoes.last, joinType, edgeType) case a: SimpleDependencyAnalysisJoin => a } List(h) @@ -56,7 +57,7 @@ case class DependencyAnalysisInfoes(sourceInfoes: List[AnalysisSourceInfo], depe def withMergeInfo(mergeInfo: DependencyAnalysisMergeInfo): DependencyAnalysisInfoes = this.copy(mergeInfoes = mergeInfo +: mergeInfoes) - def withJoinInfo(joinInfo: EvalStackDependencyAnalysisJoin): DependencyAnalysisInfoes = + def withJoinInfo(joinInfo: DependencyAnalysisJoinInfo): DependencyAnalysisInfoes = this.copy(joinInfoes = joinInfo +: joinInfoes) } @@ -84,15 +85,27 @@ object JoinType extends Enumeration { val Source, Sink = Value } +object EdgeType extends Enumeration { + type EdgeType = Value + val Up, Down = Value +} + trait DependencyAnalysisJoinInfo extends ast.Info { override def comment: Seq[String] = Nil override def isCached: Boolean = false + + def matches(dependencyAnalysisJoinInfo: DependencyAnalysisJoinInfo) = { + (this, dependencyAnalysisJoinInfo) match { + case (SimpleDependencyAnalysisJoin(sourceInfo, joinType, edgeType), SimpleDependencyAnalysisJoin(sourceInfo1, joinType1, edgeType1)) => + sourceInfo.equals(sourceInfo1) && edgeType.equals(edgeType1) + } + } } -case class EvalStackDependencyAnalysisJoin(joinType: JoinType) extends DependencyAnalysisJoinInfo +case class EvalStackDependencyAnalysisJoin(joinType: JoinType, edgeType: EdgeType) extends DependencyAnalysisJoinInfo -case class SimpleDependencyAnalysisJoin(sourceInfo: AnalysisSourceInfo, joinType: JoinType) extends DependencyAnalysisJoinInfo +case class SimpleDependencyAnalysisJoin(sourceInfo: AnalysisSourceInfo, joinType: JoinType, edgeType: EdgeType) extends DependencyAnalysisJoinInfo trait DependencyAnalysisMergeInfo extends ast.Info { diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index b7fbc6573..b77be21b1 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -1,6 +1,7 @@ package viper.silicon.dependencyAnalysis import viper.silicon.dependencyAnalysis.DependencyAnalyzer._ +import viper.silicon.dependencyAnalysis.JoinType.JoinType import viper.silicon.interfaces.state.{Chunk, GeneralChunk} import viper.silicon.state.terms.{NoPerm, _} import viper.silicon.verifier.Verifier @@ -183,68 +184,49 @@ object DependencyAnalyzer { * of functions and methods. */ def joinGraphsAndGetInterpreter(name: String, dependencyGraphInterpreters: Set[DependencyGraphInterpreter]): DependencyGraphInterpreter = { - var startTime = startTimeMeasurement() val newGraph = new DependencyGraph newGraph.addAssumptionNodes(dependencyGraphInterpreters.flatMap (_.getGraph.getAssumptionNodes)) newGraph.addAssertionNodes(dependencyGraphInterpreters.flatMap (_.getGraph.getAssertionNodes)) - stopTimeMeasurementAndAddToTotal(startTime, timeToAddNodes) - startTime = startTimeMeasurement() + dependencyGraphInterpreters foreach (interpreter => interpreter.getGraph.getAllEdges foreach {case (t, deps) => newGraph.addEdges(deps, t)}) - stopTimeMeasurementAndAddToTotal(startTime, timeToAddEdges) - startTime = startTimeMeasurement() - val joinCandidateAssertions = dependencyGraphInterpreters flatMap(i => i.joinAssertionNodes) - val joinCandidateAssumptions = dependencyGraphInterpreters flatMap(i => i.joinAssumptionNodes) + + val joinSourceNodes = dependencyGraphInterpreters flatMap(i => i.joinSourceNodes) + val joinSinkNodes = dependencyGraphInterpreters flatMap(i => i.joinSinkNodes) val joinCandidateAxioms = dependencyGraphInterpreters flatMap(i => i.axiomNodes) - val joinCandidateNodes = joinCandidateAssumptions ++ joinCandidateAssertions - stopTimeMeasurementAndAddToTotal(startTime, timeToExtractCandidateNodes) - startTime = startTimeMeasurement() // axioms assumed by every method / function should depend on the assertions that justify them // hence, we add edges from function postconditions & bodies to the corresponding axioms - val axiomAssertionNodes = (joinCandidateAssertions ++ joinCandidateAssumptions.filter(_.assumptionType.equals(AssumptionType.FunctionBody))) + val axiomAssertionNodes = (joinSourceNodes ++ joinSinkNodes.filter(_.assumptionType.equals(AssumptionType.FunctionBody))) .groupBy(_.sourceInfo) .view.mapValues(_.map(_.id)) .toMap joinCandidateAxioms - .groupBy(n => n.sourceInfo) + .groupBy(n => n.sourceInfo) // TODO ake: add joinInfoes to the axiom nodes .map{case (sourceInfo, axiomNodes) => (axiomNodes.map(_.id), axiomAssertionNodes.getOrElse(sourceInfo, Seq.empty))} .foreach{case (axiomNodeIds, assertionNodeIds) => newGraph.addEdgesConnectingMethodsDownwards(assertionNodeIds, axiomNodeIds) // TODO ake: maybe we could merge the axiom nodes here since they represent the same axiom? } - stopTimeMeasurementAndAddToTotal(startTime, timeForFunctionJoin) - startTime = startTimeMeasurement() - - val customInternalNodes = joinCandidateAssumptions.filter(_.assumptionType.equals(AssumptionType.CustomInternal)).map(_.id) - // postconditions of methods assumed by every method call should depend on the assertions that justify them - // hence, we add edges from assertions of method postconditions to assumptions of the same postcondition (at method calls) - val relevantAssumptionNodes = joinCandidateAssumptions - .filter(_.joinInfoes.nonEmpty) - .groupBy(_.joinInfoes.head.sourceInfo) - .view.mapValues(_.map(_.id)) - .toMap - joinCandidateNodes.diff(joinCandidateAxioms.toSet) - .map(node => (node.id, relevantAssumptionNodes.getOrElse(node.sourceInfo, Seq.empty))) - .foreach { case (src, targets) => - if (customInternalNodes.intersect(targets.toSet.union(Set(src))).isEmpty) newGraph.addEdgesConnectingMethodsDownwards(src, targets) - else newGraph.addEdges(src, targets) - } + def getJoinNodesByJoinInfo(candidateNodes: Set[DependencyAnalysisNode], joinType: JoinType) = { + candidateNodes + .flatMap(node => node.joinInfoes.filter(_.joinType.equals(joinType)).map((_, node))) + .groupBy(_._1) + .view.mapValues(_.map(_._2)) + .toMap + } - val relevantAssertionNodes = joinCandidateAssertions // method call pres assertions - .filter(_.joinInfoes.nonEmpty) - .groupBy(_.joinInfoes.head.sourceInfo) - .view.mapValues(_.map(_.id)) - .toMap - joinCandidateNodes - .map(node => (node.id, relevantAssertionNodes.getOrElse(node.sourceInfo, Seq.empty))) - .foreach { case (target, sources) => - if (customInternalNodes.intersect(sources.toSet.union(Set(target))).isEmpty) newGraph.addEdgesConnectingMethodsUpwards(sources, target) - else newGraph.addEdges(sources, target) - } + val sourceNodesByJoinInfo = getJoinNodesByJoinInfo(joinSourceNodes, JoinType.Source) + val sinkNodesByJoinInfo = getJoinNodesByJoinInfo(joinSinkNodes, JoinType.Sink) + sinkNodesByJoinInfo.foreach{case (joinInfo, nodes) => + val matchingSourceNodes = sourceNodesByJoinInfo.filter{case (sourceJoinInfo, _) => sourceJoinInfo.matches(joinInfo)}.values.flatten + if(joinInfo.edgeType.equals(EdgeType.Up)) + newGraph.addEdgesConnectingMethodsUpwards(matchingSourceNodes.map(_.id), nodes.map(_.id)) + else + newGraph.addEdgesConnectingMethodsDownwards(matchingSourceNodes.map(_.id), nodes.map(_.id)) + } - stopTimeMeasurementAndAddToTotal(startTime, timeForMethodJoin) val newInterpreter = new DependencyGraphInterpreter(name, newGraph, dependencyGraphInterpreters.toList.flatMap(_.getErrors)) newInterpreter @@ -458,8 +440,8 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { } override def addDependenciesForAbstractMembers(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp], analysisInfoes: DependencyAnalysisInfoes): Unit = { - val sourceNodeIds = sourceExps.flatMap(e => addAssumption(True, analysisInfoes.addInfo(e.info, e))) - val targetNodes = targetExps.flatMap(e => addAssertNode(True, analysisInfoes.addInfo(e.info, e))) + val sourceNodeIds = sourceExps.flatMap(e => addAssumption(True, analysisInfoes.addInfo(e.info, e).withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Sink, EdgeType.Up)))) + val targetNodes = targetExps.flatMap(e => addAssertNode(True, analysisInfoes.addInfo(e.info, e).withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Source, EdgeType.Down)))) dependencyGraph.addEdges(sourceNodeIds, targetNodes) } @@ -471,18 +453,10 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { * Further, this operation removes unnecessary details from the graph by, for example, removing label nodes and merging identical nodes. */ override def buildFinalGraph(): Option[DependencyGraph] = { - val removingNodesStart = DependencyAnalyzer.startTimeMeasurement() dependencyGraph.removeLabelNodes() - DependencyAnalyzer.stopTimeMeasurementAndAddToTotal(removingNodesStart, timeToRemoveInternalNodes) - val mergingNodesStart = DependencyAnalyzer.startTimeMeasurement() val mergedGraph = if(Verifier.config.enableDependencyAnalysisDebugging()) dependencyGraph else buildAndGetMergedGraph() - DependencyAnalyzer.stopTimeMeasurementAndAddToTotal(mergingNodesStart, timeToMergeNodes) - val addingTransitiveEdgesStart = DependencyAnalyzer.startTimeMeasurement() mergedGraph.addTransitiveEdges() - DependencyAnalyzer.stopTimeMeasurementAndAddToTotal(addingTransitiveEdgesStart, timeToAddTransitiveEdges) - val removingNodesStart2 = DependencyAnalyzer.startTimeMeasurement() if(!Verifier.config.enableDependencyAnalysisDebugging()) mergedGraph.removeInternalNodes() - DependencyAnalyzer.stopTimeMeasurementAndAddToTotal(removingNodesStart2, timeToRemoveInternalNodes) Some(mergedGraph) } @@ -503,7 +477,7 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { * the low-level graph containing all details. */ private def buildAndGetMergedGraph(): DependencyGraph = { - def keepNode(n: DependencyAnalysisNode): Boolean = !n.mergeInfo.isMerge || n.isInstanceOf[InfeasibilityNode] || n.isInstanceOf[AxiomAssumptionNode] + def keepNode(n: DependencyAnalysisNode): Boolean = !n.mergeInfo.isMerge || n.joinInfoes.nonEmpty || n.isInstanceOf[InfeasibilityNode] || n.isInstanceOf[AxiomAssumptionNode] val mergedGraph = new DependencyGraph val nodeMap = mutable.HashMap[Int, Int]() @@ -512,10 +486,10 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { nodeMap.put(n.id, n.id) mergedGraph.addAssumptionNode(n) } - val assumptionNodesBySource = dependencyGraph.getAssumptionNodes.filter(!keepNode(_)).groupBy(n => (n.sourceInfo, n.assumptionType, n.joinInfoes)) // TODO ake: review joinInfoes - assumptionNodesBySource foreach { case ((sourceInfo, assumptionType, joinInfoes), assumptionNodes) => + val assumptionNodesBySource = dependencyGraph.getAssumptionNodes.filter(!keepNode(_)).groupBy(n => (n.sourceInfo, n.assumptionType, n.mergeInfo, n.joinInfoes)) // TODO ake: review joinInfoes + assumptionNodesBySource foreach { case ((sourceInfo, assumptionType, mergeInfo, joinInfoes), assumptionNodes) => if (assumptionNodes.nonEmpty) { - val newNode = SimpleAssumptionNode(True, None, sourceInfo, assumptionType, SimpleDependencyAnalysisMerge(sourceInfo), joinInfoes) // TODO ake: review joinInfoes + val newNode = SimpleAssumptionNode(True, None, sourceInfo, assumptionType, mergeInfo, joinInfoes) // TODO ake: review joinInfoes assumptionNodes foreach (n => nodeMap.put(n.id, newNode.id)) mergedGraph.addAssumptionNode(newNode) } @@ -525,10 +499,10 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { nodeMap.put(n.id, n.id) mergedGraph.addAssertionNode(n) } - val assertionNodesBySource = dependencyGraph.getAssertionNodes.filter(!keepNode(_)).groupBy(n => (n.sourceInfo, n.assumptionType, n.joinInfoes)) // TODO ake: review joinInfoes - assertionNodesBySource foreach { case ((sourceInfo, assumptionType, joinInfoes), assertionNodes) => + val assertionNodesBySource = dependencyGraph.getAssertionNodes.filter(!keepNode(_)).groupBy(n => (n.sourceInfo, n.assumptionType, n.mergeInfo, n.joinInfoes)) // TODO ake: review joinInfoes + assertionNodesBySource foreach { case ((sourceInfo, assumptionType, mergeInfo, joinInfoes), assertionNodes) => if (assertionNodes.nonEmpty) { - val newNode = SimpleAssertionNode(True, sourceInfo, assumptionType, SimpleDependencyAnalysisMerge(sourceInfo), hasFailed=assertionNodes.exists(_.hasFailed), joinInfoes=joinInfoes) // TODO ake: review joinInfoes + val newNode = SimpleAssertionNode(True, sourceInfo, assumptionType, mergeInfo, joinInfoes, hasFailed=assertionNodes.exists(_.hasFailed)) // TODO ake: review joinInfoes assertionNodes foreach (n => nodeMap.put(n.id, newNode.id)) mergedGraph.addAssertionNode(newNode) } diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 4a951517c..52dcaf916 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -37,11 +37,11 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen def getAssertionNodes: Set[DependencyAnalysisNode] = dependencyGraph.getAssertionNodes.toSet def getErrors: List[Failure] = errors - val joinAssumptionNodes: Set[GeneralAssumptionNode] = getJoinCandidateNodes(dependencyGraph.getAssumptionNodes.toSet) - val joinAssertionNodes: Set[GeneralAssertionNode] = getJoinCandidateNodes(dependencyGraph.getAssertionNodes.toSet) + val joinSinkNodes: Set[DependencyAnalysisNode] = getJoinCandidateNodes(getNodes).filter(_.joinInfoes.exists(_.joinType.equals(JoinType.Sink))) + val joinSourceNodes: Set[DependencyAnalysisNode] = getJoinCandidateNodes(getNodes).filter(_.joinInfoes.exists(_.joinType.equals(JoinType.Source))) val axiomNodes: Set[GeneralAssumptionNode] = dependencyGraph.getAssumptionNodes.filter(_.isInstanceOf[AxiomAssumptionNode]).toSet - def getJoinCandidateNodes[T <: DependencyAnalysisNode](nodes: Set[T]): Set[T] = nodes.filter(node => node.joinInfoes.nonEmpty || node.isInstanceOf[AxiomAssumptionNode] || AssumptionType.joinConditionTypes.contains(node.assumptionType)) + def getJoinCandidateNodes(nodes: Set[DependencyAnalysisNode]): Set[DependencyAnalysisNode] = nodes.filter(node => node.joinInfoes.nonEmpty) private def toUserLevelNodes(nodes: Iterable[DependencyAnalysisNode]): Set[UserLevelDependencyAnalysisNode] = UserLevelDependencyAnalysisNode.from(nodes) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 0452949bb..44f29a894 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -11,7 +11,7 @@ import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.decider.RecordedPathConditions import viper.silicon.dependencyAnalysis.DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes -import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfoes, EvalStackDependencyAnalysisJoin, JoinType} +import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfoes, EdgeType, EvalStackDependencyAnalysisJoin, JoinType} import viper.silicon.interfaces._ import viper.silicon.interfaces.state.{NonQuantifiedChunk, QuantifiedChunk} import viper.silicon.logger.records.data.{CommentRecord, ConditionalEdgeRecord, ExecuteRecord, MethodCallRecord} @@ -544,9 +544,7 @@ object executor extends ExecutionRules { val pveCall = CallFailed(call) val pveCallTransformed = pveCall.withReasonNodeTransformed(reasonTransformer) - // FIXME ake: why was this added? -// v.decider.dependencyAnalyzer.addAssumption(True, v.decider.analysisSourceInfoStack.getFullSourceInfo, v.decider.analysisSourceInfoStack.getAssumptionType, -// isJoinNode=false, None) + v.decider.dependencyAnalyzer.addAssumption(True, analysisInfoes, None) // make sure method calls are represented as a node, even if there are no postconditions val mcLog = new MethodCallRecord(call, s, v.decider.pcs) val sepIdentifier = v.symbExLog.openScope(mcLog) @@ -568,14 +566,14 @@ object executor extends ExecutionRules { val s2 = s1.copy(g = Store(fargs.zip(argsWithExp)), recordVisited = true) - consumes(s2, meth.pres, false, _ => pvePre, v1, analysisInfoes.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Source)))((s3, _, v2) => { + consumes(s2, meth.pres, false, _ => pvePre, v1, analysisInfoes.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Source, EdgeType.Up)))((s3, _, v2) => { v2.symbExLog.closeScope(preCondId) val postCondLog = new CommentRecord("Postcondition", s3, v2.decider.pcs) val postCondId = v2.symbExLog.openScope(postCondLog) val outs = meth.formalReturns.map(_.localVar) val gOuts = Store(outs.map(x => (x, v2.decider.fresh(x))).toMap) val s4 = s3.copy(g = s3.g + gOuts, oldHeaps = s3.oldHeaps + (Verifier.PRE_STATE_LABEL -> magicWandSupporter.getEvalHeap(s1))) - produces(s4, freshSnap, meth.posts, _ => pveCallTransformed, v2, analysisInfoes)((s5, v3) => { + produces(s4, freshSnap, meth.posts, _ => pveCallTransformed, v2, analysisInfoes.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Sink, EdgeType.Down)))((s5, v3) => { v3.symbExLog.closeScope(postCondId) v3.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) val gLhs = Store(lhs.zip(outs) diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index 1510f2925..5f9dad275 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -79,9 +79,12 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif new java.io.File(s"${Verifier.config.tempDirectory()}/${method.name}.dot")) } - val presAssertionNodeForJoin = pres.flatMap(_.topLevelConjuncts).map(pc => SimpleAssertionNode(True, AnalysisSourceInfo.createAnalysisSourceInfo(pc), AssumptionType.Precondition, SimpleDependencyAnalysisMerge(AnalysisSourceInfo.createAnalysisSourceInfo(pc)), joinInfoes = List(SimpleDependencyAnalysisJoin(AnalysisSourceInfo.createAnalysisSourceInfo(pc), JoinType.Sink)))) + val presAssertionNodeForJoin = pres.flatMap(_.topLevelConjuncts).map(pc => SimpleAssertionNode(True, AnalysisSourceInfo.createAnalysisSourceInfo(pc), AssumptionType.Precondition, SimpleDependencyAnalysisMerge(AnalysisSourceInfo.createAnalysisSourceInfo(pc)), joinInfoes = List(SimpleDependencyAnalysisJoin(AnalysisSourceInfo.createAnalysisSourceInfo(pc), JoinType.Sink, EdgeType.Up)))) presAssertionNodeForJoin foreach v.decider.dependencyAnalyzer.addAssertionNode + val analysisInfoesPrecondition = DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Sink, EdgeType.Up)) + val analysisInfoesPostcondition = DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Source, EdgeType.Down)) + val daJoinNodeInfoOpt = method.info.getUniqueInfo[DependencyAnalysisJoinNodeInfo] // TODO ake: frontend join @@ -108,14 +111,14 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif * rules in Smans' paper. */ executionFlowController.locally(s, v)((s1, v1) => { - produces(s1, freshSnap, pres, ContractNotWellformed, v1, DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes)((s2, v2) => { + produces(s1, freshSnap, pres, ContractNotWellformed, v1, analysisInfoesPrecondition)((s2, v2) => { v2.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) val s2a = s2.copy(oldHeaps = s2.oldHeaps + (Verifier.PRE_STATE_LABEL -> s2.h)) ( executionFlowController.locally(s2a, v2)((s3, v3) => { val s4 = s3.copy(h = v3.heapSupporter.getEmptyHeap(s3.program)) val impLog = new WellformednessCheckRecord(posts, s, v.decider.pcs) val sepIdentifier = symbExLog.openScope(impLog) - produces(s4, freshSnap, posts, ContractNotWellformed, v3, DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes)((_, _) => { + produces(s4, freshSnap, posts, ContractNotWellformed, v3, analysisInfoesPostcondition)((_, _) => { symbExLog.closeScope(sepIdentifier) Success()})}) && { @@ -124,7 +127,7 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif if(method.body.isEmpty) v3.decider.removeDependencyAnalyzer() exec(s3, body, v3)((s4, v4) => { if(method.body.isEmpty) v3.decider.dependencyAnalyzer = da - consumes(s4, posts, false, postViolated, v4, DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes)((_, _, _) => + consumes(s4, posts, false, postViolated, v4, analysisInfoesPostcondition)((_, _, _) => Success())})}) } )})}) if(method.body.isEmpty){ @@ -135,8 +138,6 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif result.dependencyGraphInterpreter = v.decider.dependencyAnalyzer.buildFinalGraph().map(new DependencyGraphInterpreter(method.name, _, allErrors, Some(method))) - // if(daJoinNodeInfoOpt.isDefined) v.decider.analysisSourceInfoStack.popAnalysisSourceInfo(daJoinNodeInfoOpt.get.sourceInfo) - v.decider.resetProverOptions() symbExLog.closeMemberScope() diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index f49b46b9d..0a04be432 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -190,9 +190,10 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver assertReadAccessOnly = !Verifier.config.respectFunctionPrePermAmounts()) - val presAssertionNodeForJoin = function.pres.flatMap(_.topLevelConjuncts).map(pc => SimpleAssertionNode(True, AnalysisSourceInfo.createAnalysisSourceInfo(pc), AssumptionType.Precondition, SimpleDependencyAnalysisMerge(AnalysisSourceInfo.createAnalysisSourceInfo(pc)), List(SimpleDependencyAnalysisJoin(AnalysisSourceInfo.createAnalysisSourceInfo(pc), JoinType.Sink)))) + val presAssertionNodeForJoin = function.pres.flatMap(_.topLevelConjuncts).map(pc => SimpleAssertionNode(True, AnalysisSourceInfo.createAnalysisSourceInfo(pc), AssumptionType.Precondition, SimpleDependencyAnalysisMerge(AnalysisSourceInfo.createAnalysisSourceInfo(pc)), List(SimpleDependencyAnalysisJoin(AnalysisSourceInfo.createAnalysisSourceInfo(pc), JoinType.Sink, EdgeType.Up)))) presAssertionNodeForJoin foreach v.decider.dependencyAnalyzer.addAssertionNode + /* Phase 1: Check well-definedness of the specifications */ checkSpecificationWelldefinedness(s, function) match { case (result1: FatalResult, _) => @@ -244,19 +245,22 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver val g = Store(argsStore + (function.result -> (data.formalResult, data.valFormalResultExp))) val s = sInit.copy(g = g, h = v.heapSupporter.getEmptyHeap(sInit.program), oldHeaps = OldHeaps()) + val analysisInfoesPrecondition = DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Sink, EdgeType.Up)) + val analysisInfoesPostcondition = DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Source, EdgeType.Down)) + var phase1Data: Seq[Phase1Data] = Vector.empty var recorders: Seq[FunctionRecorder] = Vector.empty val result = executionFlowController.locally(s, v)((s0, _) => { val preMark = decider.setPathConditionMark() - produces(s0, toSf(`?s`), pres, ContractNotWellformed, v, DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes)((s1, _) => { + produces(s0, toSf(`?s`), pres, ContractNotWellformed, v, analysisInfoesPrecondition)((s1, _) => { val relevantPathConditionStack = decider.pcs.after(preMark) phase1Data :+= Phase1Data(s1, relevantPathConditionStack.branchConditions, relevantPathConditionStack.branchConditionExps, relevantPathConditionStack.assumptions, Option.when(evaluator.withExp)(relevantPathConditionStack.assumptionExps)) // The postcondition must be produced with a fresh snapshot (different from `?s`) because // the postcondition's snapshot structure is most likely different than that of the // precondition - produces(s1, freshSnap, posts, ContractNotWellformed, v, DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes)((s2, _) => { + produces(s1, freshSnap, posts, ContractNotWellformed, v, analysisInfoesPostcondition)((s2, _) => { recorders :+= s2.functionRecorder Success()})})}) @@ -281,6 +285,11 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver val wExp = evaluator.withExp decider.dependencyAnalyzer.addNodes(v.decider.prover.getPreambleAnalysisNodes) + + val analysisInfoesPostcondition = DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Source, EdgeType.Down)) + val analysisInfoesBody = DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes.addInfo(body.info, body) + .withJoinInfo(SimpleDependencyAnalysisJoin(AnalysisSourceInfo.createAnalysisSourceInfo(body), JoinType.Source, EdgeType.Down)) + val daJoinNodeInfoOpt = function.info.getUniqueInfo[DependencyAnalysisJoinNodeInfo] // TODO ake: frontend join // if(daJoinNodeInfoOpt.isDefined){ @@ -300,25 +309,25 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver // postCondAssertNodes foreach (n => v.decider.dependencyAnalyzer.addDependency(Some(n.id), Some(customJoinNode.id))) // } + val result = phase1data.foldLeft(Success(): VerificationResult) { case (fatalResult: FatalResult, _) => fatalResult case (intermediateResult, Phase1Data(sPre, bcsPre, bcsPreExp, pcsPre, pcsPreExp)) => intermediateResult && executionFlowController.locally(sPre, v)((s1, _) => { val labelledBcsPre = And(bcsPre map (t => v.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t)))) - val precondAnalysisSourceInfoes = DependencyAnalysisInfoes.create("preconditions", DependencyType.make(AssumptionType.Precondition)) /* TODO ake */ + val precondAnalysisSourceInfoes = DependencyAnalysisInfoes.create("preconditions", DependencyType.Internal) decider.setCurrentBranchCondition(labelledBcsPre, (BigAnd(bcsPreExp.map(_._1)), Option.when(wExp)(BigAnd(bcsPreExp.map(_._2.get)))), precondAnalysisSourceInfoes) val labelledPcsPre = pcsPre map (t => v.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t))) decider.assume(labelledPcsPre, pcsPreExp, s"precondition of ${function.name}", enforceAssumption=false, precondAnalysisSourceInfoes) v.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) - eval(s1, body, FunctionNotWellformed(function), v, DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes.addInfo(body.info, body))((s2, tBody, bodyNew, _) => { + eval(s1, body, FunctionNotWellformed(function), v, analysisInfoesBody)((s2, tBody, bodyNew, _) => { val debugExp = if (wExp) { val e = ast.EqCmp(ast.Result(function.typ)(), body)(function.pos, function.info, function.errT) val eNew = ast.EqCmp(ast.Result(function.typ)(), bodyNew.get)(function.pos, function.info, function.errT) Some(DebugExp.createInstance(e, eNew)) } else { None } - val bodyAnalysisSourceInfoes = DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes.addInfo(body.info, body) - decider.assume(BuiltinEquals(data.formalResult, tBody), debugExp, bodyAnalysisSourceInfoes) - consumes(s2, posts, false, postconditionViolated, v, DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes)((s3, _, _) => { + decider.assume(BuiltinEquals(data.formalResult, tBody), debugExp, analysisInfoesBody) + consumes(s2, posts, false, postconditionViolated, v, analysisInfoesPostcondition)((s3, _, _) => { recorders :+= s3.functionRecorder Success()})})})} From 8c405ead91aa7557da7a9de8780a2d7854cefc9f Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 31 Mar 2026 10:38:51 +0200 Subject: [PATCH 407/474] refactoring --- src/main/scala/decider/Decider.scala | 43 +++++++------------ .../DependencyAnalyzer.scala | 14 +++++- .../dependencyAnalysis/DependencyGraph.scala | 18 -------- .../DependencyGraphInterpreter.scala | 8 ++-- src/main/scala/rules/Brancher.scala | 2 +- src/main/scala/rules/ChunkSupporter.scala | 4 +- src/main/scala/rules/Consumer.scala | 2 +- src/main/scala/rules/Evaluator.scala | 6 +-- src/main/scala/rules/Executor.scala | 10 +++-- src/main/scala/rules/HeapSupporter.scala | 10 ++--- src/main/scala/rules/Producer.scala | 2 +- src/main/scala/rules/StateConsolidator.scala | 4 +- 12 files changed, 53 insertions(+), 70 deletions(-) diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index f1571bdf5..7f5b7006f 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -22,7 +22,6 @@ import viper.silicon.verifier.{Verifier, VerifierComponent} import viper.silver.ast import viper.silver.ast.{LocalVarWithVersion, Member, NoPosition} import viper.silver.components.StatefulComponent -import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, DependencyAnalysisJoinNodeInfo, DependencyType} import viper.silver.parser.{PKw, PPrimitiv, PReserved, PType} import viper.silver.reporter.{ConfigurationConfirmation, InternalWarningMessage} import viper.silver.verifier.{DependencyNotFoundError, Model} @@ -65,7 +64,7 @@ trait Decider { def registerChunk[CH <: GeneralChunk](buildChunk: Term => CH, perm: Term, analysisInfo: AnalysisInfo, isExhale: Boolean): CH def registerDerivedChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, analysisInfo: AnalysisInfo, isExhale: Boolean, createLabel: Boolean=true): CH def wrapWithDependencyAnalysisLabel(term: Term, sourceChunks: Iterable[Chunk] = Set.empty, sourceTerms: Iterable[Term] = Set.empty): Term - def isPathInfeasible(): Boolean + def isPathInfeasible: Boolean def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp], analysisInfoes: DependencyAnalysisInfoes): Unit def assume(t: Term, debugExp: Option[DebugExp], analysisInfoes: DependencyAnalysisInfoes): Unit @@ -288,7 +287,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } def setCurrentBranchCondition(t: Term, te: (ast.Exp, Option[ast.Exp]), analysisInfoes: DependencyAnalysisInfoes): Unit = { - if(isPathInfeasible()) return + if(isPathInfeasible) return pathConditions.setCurrentBranchCondition(t, te) assume(t, Option.when(te._2.isDefined)(te._1), te._2, analysisInfoes) @@ -350,7 +349,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => labelNode.map(n => Implies(n.term, term)).getOrElse(term) } - def isPathInfeasible(): Boolean = Verifier.config.disableInfeasibilityChecks() && pcs.getCurrentInfeasibilityNode.isDefined + def isPathInfeasible: Boolean = Verifier.config.disableInfeasibilityChecks() && pcs.getCurrentInfeasibilityNode.isDefined def addDebugExp(e: DebugExp): Unit = { if (debugMode) { @@ -358,19 +357,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } } - def pushAndGetAnalysisSourceInfo(e: ast.Exp, dependencyType: Option[DependencyType]): AnalysisSourceInfo = { - val sourceInfo = AnalysisSourceInfo.createAnalysisSourceInfo(e) - pushAnalysisSourceInfo(sourceInfo, e.info, dependencyType) - sourceInfo - } - - def pushAndGetAnalysisSourceInfo(stmt: ast.Stmt, dependencyType: Option[DependencyType]): AnalysisSourceInfo = { - val sourceInfo = AnalysisSourceInfo.createAnalysisSourceInfo(stmt) - pushAnalysisSourceInfo(sourceInfo, stmt.info, dependencyType) - sourceInfo - } - - private def pushAnalysisSourceInfo(sourceInfo: AnalysisSourceInfo, info: ast.Info, dependencyType: Option[DependencyType]): Unit = { +// private def pushAnalysisSourceInfo(sourceInfo: AnalysisSourceInfo, info: ast.Info, dependencyType: Option[DependencyType]): Unit = { // analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo, dependencyType.getOrElse(analysisSourceInfoStack.getDependencyType)) // TODO ake: frontend join // if (info.getUniqueInfo[DependencyAnalysisJoinNodeInfo].isDefined) { @@ -383,27 +370,27 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => // dependencyAnalyzer.addAssumptionNode(assumptionNode) // dependencyAnalyzer.addDependency(Some(assumptionNode.id), Some(assertionNode.id)) // } - } +// } def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp], analysisInfoes: DependencyAnalysisInfoes): Unit = { if (finalExp.isDefined) { - assume(assumptions=InsertionOrderedSet((t, Some(DebugExp.createInstance(e.get, finalExp.get)))), analysisInfoes.getSourceInfo, enforceAssumption = false, isDefinition = false, analysisInfoes) + assume(assumptions=InsertionOrderedSet((t, Some(DebugExp.createInstance(e.get, finalExp.get)))), enforceAssumption = false, isDefinition = false, analysisInfoes) } else { - assume(assumptions=InsertionOrderedSet((t, None)), analysisInfoes.getSourceInfo, enforceAssumption = false, isDefinition = false, analysisInfoes) + assume(assumptions=InsertionOrderedSet((t, None)), enforceAssumption = false, isDefinition = false, analysisInfoes) } } def assume(t: Term, debugExp: Option[DebugExp], analysisInfoes: DependencyAnalysisInfoes): Unit = { - assume(InsertionOrderedSet(Seq((t, debugExp))), analysisInfoes.getSourceInfo, enforceAssumption = false, isDefinition = false, analysisInfoes) + assume(InsertionOrderedSet(Seq((t, debugExp))), enforceAssumption = false, isDefinition = false, analysisInfoes) } def assumeDefinition(t: Term, debugExp: Option[DebugExp], analysisInfoes: DependencyAnalysisInfoes): Unit = { - assume(InsertionOrderedSet(Seq((t, debugExp))), analysisInfoes.getSourceInfo, enforceAssumption=false, isDefinition=true, analysisInfoes) + assume(InsertionOrderedSet(Seq((t, debugExp))), enforceAssumption=false, isDefinition=true, analysisInfoes) } - def assume(assumptions: InsertionOrderedSet[(Term, Option[DebugExp])], analysisSourceInfo: AnalysisSourceInfo, enforceAssumption: Boolean, isDefinition: Boolean, analysisInfoes: DependencyAnalysisInfoes): Unit = { + def assume(assumptions: InsertionOrderedSet[(Term, Option[DebugExp])], enforceAssumption: Boolean, isDefinition: Boolean, analysisInfoes: DependencyAnalysisInfoes): Unit = { val filteredAssumptions = if (enforceAssumption) assumptions else assumptions filterNot (a => isKnownToBeTrue(a._1)) @@ -480,7 +467,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } private def assumeWithoutSmokeChecks(termsWithLabel: InsertionOrderedSet[(Term, String)], isDefinition: Boolean = false): None.type = { - if (isPathInfeasible()) return None + if (isPathInfeasible) return None val terms = termsWithLabel map (_._1) val assumeRecord = new DeciderAssumeRecord(terms) @@ -514,9 +501,9 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => val label = DependencyAnalyzer.createAssertionLabel(checkNode.map(_.id)) val timeout = if (isAssert) Verifier.config.assertTimeout.toOption else Verifier.config.checkTimeout.toOption - val result = isPathInfeasible() || prover.check(timeout, label) == Unsat + val result = isPathInfeasible || prover.check(timeout, label) == Unsat - if(isPathInfeasible()){ + if(isPathInfeasible){ checkNode foreach dependencyAnalyzer.addAssertionNode dependencyAnalyzer.addDependency(pcs.getCurrentInfeasibilityNode, checkNode.map(_.id)) }else if(result){ @@ -610,9 +597,9 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => val assertRecord = new ProverAssertRecord(t, timeout) val sepIdentifier = symbExLog.openScope(assertRecord) - val result = isPathInfeasible() || prover.assert(t, timeout, label) + val result = isPathInfeasible || prover.assert(t, timeout, label) - if(isPathInfeasible()) dependencyAnalyzer.addDependency(pcs.getCurrentInfeasibilityNode, Some(DependencyAnalyzer.getIdFromLabel(label))) + if(isPathInfeasible) dependencyAnalyzer.addDependency(pcs.getCurrentInfeasibilityNode, Some(DependencyAnalyzer.getIdFromLabel(label))) else if(result) dependencyAnalyzer.processUnsatCoreAndAddDependencies(prover.getLastUnsatCore, label) symbExLog.whenEnabled { diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index b77be21b1..e9c81c7a0 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -455,11 +455,23 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { override def buildFinalGraph(): Option[DependencyGraph] = { dependencyGraph.removeLabelNodes() val mergedGraph = if(Verifier.config.enableDependencyAnalysisDebugging()) dependencyGraph else buildAndGetMergedGraph() - mergedGraph.addTransitiveEdges() + addTransitiveEdges(mergedGraph) if(!Verifier.config.enableDependencyAnalysisDebugging()) mergedGraph.removeInternalNodes() Some(mergedGraph) } + private def addTransitiveEdges(mergedGraph: DependencyGraph): Unit = { + val nodesPerSourceInfo = mergedGraph.getNodes.filter(_.mergeInfo.isMerge).groupBy(_.mergeInfo) + nodesPerSourceInfo foreach {case (_, nodes) => + val asserts = nodes.filter(_.isInstanceOf[GeneralAssertionNode]) + val assumes = nodes.filter(n => n.isInstanceOf[GeneralAssumptionNode] && !n.isInstanceOf[LabelNode]) + mergedGraph.addEdges(asserts.map(_.id), assumes.map(_.id)) + val checks = asserts.filter(_.isInstanceOf[SimpleCheckNode]) + val notChecks = nodes.filter(n => !n.isInstanceOf[SimpleCheckNode]) + mergedGraph.addEdges(checks.map(_.id), notChecks.map(_.id)) // TODO ake: why do we need this? + } + } + override def addFunctionAxiomEdges(): Unit = { val axiomNodes = dependencyGraph.getAssumptionNodes.filter(_.isInstanceOf[AxiomAssumptionNode]) val postcondNodes = getNodes.filter(n => AssumptionType.postconditionTypes.contains(n.assumptionType)) diff --git a/src/main/scala/dependencyAnalysis/DependencyGraph.scala b/src/main/scala/dependencyAnalysis/DependencyGraph.scala index 65878d91b..5326613a4 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraph.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraph.scala @@ -192,24 +192,6 @@ class DependencyGraph extends ReadOnlyDependencyGraph { visited } - // TODO ake: maybe move to DependencyAnalyzer? - private def getNodesByMergeInfo: Map[DependencyAnalysisMergeInfo, Seq[DependencyAnalysisNode]] = { - getNodes.filter(_.mergeInfo.isMerge).groupBy(_.mergeInfo) - } - - // TODO ake: maybe move to DependencyAnalyzer? - def addTransitiveEdges(): Unit = { - val nodesPerSourceInfo = getNodesByMergeInfo - nodesPerSourceInfo foreach {case (_, nodes) => - val asserts = nodes.filter(_.isInstanceOf[GeneralAssertionNode]) - val assumes = nodes.filter(n => n.isInstanceOf[GeneralAssumptionNode] && !n.isInstanceOf[LabelNode]) - addEdges(asserts.map(_.id), assumes.map(_.id)) - val checks = asserts.filter(_.isInstanceOf[SimpleCheckNode]) - val notChecks = nodes.filter(n => !n.isInstanceOf[SimpleCheckNode]) - addEdges(checks.map(_.id), notChecks.map(_.id)) - } - } - private def removeAllEdgesForNode(node: DependencyAnalysisNode): Unit = { val id = node.id val predecessors = (edges filter { case (_, t) => t.contains(id) }).keys diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 52dcaf916..04f20a74c 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -204,7 +204,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen val nonDetermBool = getNextNonDetermBool ast.Seqn(Seq( ast.LocalVarDeclStmt(ast.LocalVarDecl(nonDetermBool, ast.Bool)())(), - ast.If(ast.LocalVar(nonDetermBool, ast.Bool)(), thenBody, elseBody)()) + ast.If(ast.LocalVar(nonDetermBool, ast.Bool)(cond.pos, cond.info, cond.errT), thenBody, elseBody)(ifStmt.pos, ifStmt.info, ifStmt.errT)) , Seq())(ifStmt.pos, ifStmt.info, ifStmt.errT) case ifStmt: If => total += 1 @@ -216,7 +216,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen val nonDetermBool = getNextNonDetermBool ast.Seqn(Seq( ast.LocalVarDeclStmt(ast.LocalVarDecl(nonDetermBool, ast.Bool)())(), - ast.While(ast.LocalVar(nonDetermBool, ast.Bool)(), newInvs, body)(whileStmt.pos, whileStmt.info, whileStmt.errT)) + ast.While(ast.LocalVar(nonDetermBool, ast.Bool)(cond.pos, cond.info, cond.errT), newInvs, body)(whileStmt.pos, whileStmt.info, whileStmt.errT)) , Seq())(whileStmt.pos, whileStmt.info, whileStmt.errT) case whileStmt@ast.While(cond, invs, body) => val newInvs = invs filter (isCrucialExp(_, crucialNodeSourceInfos)) @@ -231,11 +231,11 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen case s: ast.Package if !isCrucialStmt(s, crucialNodeSourceInfos) => total += 1 removed += 1 - ast.Inhale(ast.TrueLit()())() + ast.Inhale(ast.TrueLit()(s.pos, s.info, s.errT))(s.pos, s.info, s.errT) case s: Stmt if !isCrucialStmt(s, crucialNodeSourceInfos) => total += 1 removed += 1 - ast.Inhale(ast.TrueLit()())() + ast.Inhale(ast.TrueLit()(s.pos, s.info, s.errT))(s.pos, s.info, s.errT) case s: Stmt => total += 1 s diff --git a/src/main/scala/rules/Brancher.scala b/src/main/scala/rules/Brancher.scala index 7e1f2333f..a37c39b6b 100644 --- a/src/main/scala/rules/Brancher.scala +++ b/src/main/scala/rules/Brancher.scala @@ -46,7 +46,7 @@ object brancher extends BranchingRules { fElse: (State, Verifier) => VerificationResult) : VerificationResult = { - if(v.decider.isPathInfeasible()){ + if(v.decider.isPathInfeasible){ val analysisInfoes1 = analysisInfoes.addInfo(conditionExp._1.info, conditionExp._1) // FIXME ake: infeasible path // val assertionNodesForJoin = DependencyAnalyzer.extractAssertionsForJoin(conditionExp._1, s.program) diff --git a/src/main/scala/rules/ChunkSupporter.scala b/src/main/scala/rules/ChunkSupporter.scala index f05fe41ee..48f8e0474 100644 --- a/src/main/scala/rules/ChunkSupporter.scala +++ b/src/main/scala/rules/ChunkSupporter.scala @@ -84,7 +84,7 @@ object chunkSupporter extends ChunkSupportRules { analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { - if(v.decider.isPathInfeasible()){ + if(v.decider.isPathInfeasible){ v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfoes) return Q(s, h, Option.when(returnSnap)(Unit), v) } @@ -254,7 +254,7 @@ object chunkSupporter extends ChunkSupportRules { analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Heap, Term, Verifier) => VerificationResult) : VerificationResult = { - if(v.decider.isPathInfeasible()){ + if(v.decider.isPathInfeasible){ v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfoes) return Q(s, h, Unit, v) } diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index e8c35b07c..fc71cd867 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -199,7 +199,7 @@ object consumer extends ConsumptionRules { * time permissions have been consumed. */ - if(v.decider.isPathInfeasible()){ + if(v.decider.isPathInfeasible){ v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfoes) return Q(s, h, Option.when(returnSnap)(Unit), v) } diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index e3a1c92f0..53208dee3 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -10,7 +10,7 @@ import viper.silicon import viper.silicon.Config.JoinMode import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp -import viper.silicon.dependencyAnalysis.DependencyAnalysisInfoes +import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfoes, NoDependencyAnalysisMerge} import viper.silicon.interfaces._ import viper.silicon.interfaces.state.ChunkIdentifer import viper.silicon.logger.records.data.{CondExpRecord, EvaluateRecord, ImpliesRecord} @@ -100,7 +100,7 @@ object evaluator extends EvaluationRules { (Q: (State, Term, Option[ast.Exp], Verifier) => VerificationResult) : VerificationResult = { - if(v.decider.isPathInfeasible()){ + if(v.decider.isPathInfeasible){ // FIXME ake: infeasible paths // val assertionNodesForJoin = DependencyAnalyzer.extractAssertionsForJoin(e, s.program) // assertionNodesForJoin.foreach(n => v.decider.dependencyAnalyzer.addAssumption(True, CompositeAnalysisSourceInfo(v.decider.analysisSourceInfoStack.getFullSourceInfo, AnalysisSourceInfo.createAnalysisSourceInfo(n)), v.decider.analysisSourceInfoStack.getAssumptionType, isJoinNode=true)) @@ -577,7 +577,7 @@ object evaluator extends EvaluationRules { val exp = ast.Forall(eQuant.variables, eTriggers, body)(sourceQuant.pos, sourceQuant.info, sourceQuant.errT) DebugExp.createInstance(exp, expNew) }) - v1.decider.assume(Quantification(Forall, tVars, FunctionPreconditionTransformer.transform(tBody, s1.program), tTriggers, name, quantWeight), debugExp, analysisInfoes) + v1.decider.assume(Quantification(Forall, tVars, FunctionPreconditionTransformer.transform(tBody, s1.program), tTriggers, name, quantWeight), debugExp, analysisInfoes.withMergeInfo(NoDependencyAnalysisMerge())) } val tQuant = Quantification(qantOp, tVars, tBody, tTriggers, name, quantWeight) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 44f29a894..cfc023157 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -94,7 +94,7 @@ object executor extends ExecutionRules { def handleOutEdge(s: State, edge: SilverEdge, v: Verifier): State = { edge.kind match { - case cfg.Kind.Out if !v.decider.isPathInfeasible() => + case cfg.Kind.Out if !v.decider.isPathInfeasible => val analysisInfoes = DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes // TODO ake val (fr1, h1) = v.stateConsolidator(s).merge(s.functionRecorder, s, s.h, s.invariantContexts.head, v, analysisInfoes) val s1 = s.copy(functionRecorder = fr1, h = h1, @@ -134,9 +134,9 @@ object executor extends ExecutionRules { // as opposed to a loop head block. edge1.source.isInstanceOf[StatementBlock[ast.Stmt, ast.Exp]] && edge2.source.isInstanceOf[StatementBlock[ast.Stmt, ast.Exp]]) || - v.decider.isPathInfeasible() => + v.decider.isPathInfeasible => - val isPathInfeasibleBefore = v.decider.isPathInfeasible() + val isPathInfeasibleBefore = v.decider.isPathInfeasible assert(edge1.source == edge2.source) val cedge1 = edge1.asInstanceOf[ConditionalEdge[ast.Stmt, ast.Exp]] @@ -352,7 +352,7 @@ object executor extends ExecutionRules { (continuation: (State, Verifier) => VerificationResult) : VerificationResult = { - if(v.decider.isPathInfeasible()){ + if(v.decider.isPathInfeasible){ // FIXME ake: infeasbile path // val assertionNodesForJoin = DependencyAnalyzer.extractAssertionsForJoin(stmt, state.program) // assertionNodesForJoin.foreach(n => v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, CompositeAnalysisSourceInfo(v.decider.analysisSourceInfoStack.getFullSourceInfo, AnalysisSourceInfo.createAnalysisSourceInfo(n)), v.decider.analysisSourceInfoStack.getDependencyType, isJoinNode=true)) @@ -453,6 +453,8 @@ object executor extends ExecutionRules { case _: ast.FalseLit if !Verifier.config.disableInfeasibilityChecks() => /* We're done */ Success() + case _: ast.TrueLit => + Q(s, v) case _ => produce(s, freshSnap, a, InhaleFailed(inhale), v, analysisInfoes)((s1, v1) => { v1.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterInhale) diff --git a/src/main/scala/rules/HeapSupporter.scala b/src/main/scala/rules/HeapSupporter.scala index c90986942..29fa74023 100644 --- a/src/main/scala/rules/HeapSupporter.scala +++ b/src/main/scala/rules/HeapSupporter.scala @@ -190,7 +190,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { - if(v.decider.isPathInfeasible()){ + if(v.decider.isPathInfeasible){ v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfoes) v.decider.dependencyAnalyzer.addAssumption(False, analysisInfoes) return Q(s, v) @@ -273,7 +273,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Term, Verifier) => VerificationResult): VerificationResult = { - if(v.decider.isPathInfeasible()){ + if(v.decider.isPathInfeasible){ v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfoes) return Q(s, NoPerm, v) } @@ -341,7 +341,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Term, Verifier) => VerificationResult) : VerificationResult = { - if(v.decider.isPathInfeasible()){ + if(v.decider.isPathInfeasible){ v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfoes) val sort = v.symbolConverter.toSort(fa.field.typ) @@ -547,7 +547,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { v: Verifier, analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult): VerificationResult = { - if(v.decider.isPathInfeasible()){ + if(v.decider.isPathInfeasible){ v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfoes) return Q(s, h, Some(Unit), v) } @@ -663,7 +663,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { v: Verifier, analysisInfoes: DependencyAnalysisInfoes) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult): VerificationResult = { - if(v.decider.isPathInfeasible()){ + if(v.decider.isPathInfeasible){ v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfoes) return Q(s, h, Some(Unit), v) } diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index a69065bf6..72336f879 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -208,7 +208,7 @@ object producer extends ProductionRules { (Q: (State, Verifier) => VerificationResult) : VerificationResult = { - if(v.decider.isPathInfeasible()){ + if(v.decider.isPathInfeasible){ if(!Expressions.isKnownWellDefined(a, Some(s.program))){ v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfoes) } diff --git a/src/main/scala/rules/StateConsolidator.scala b/src/main/scala/rules/StateConsolidator.scala index b64d500ac..b1a359e0a 100644 --- a/src/main/scala/rules/StateConsolidator.scala +++ b/src/main/scala/rules/StateConsolidator.scala @@ -59,7 +59,7 @@ class MinimalStateConsolidator extends StateConsolidationRules { */ class DefaultStateConsolidator(protected val config: Config) extends StateConsolidationRules { def consolidate(s: State, v: Verifier): State = { - if(v.decider.isPathInfeasible()) return s + if(v.decider.isPathInfeasible) return s val comLog = new CommentRecord("state consolidation", s, v.decider.pcs) val sepIdentifier = v.symbExLog.openScope(comLog) @@ -132,7 +132,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol } def merge(fr1: FunctionRecorder, s: State, h: Heap, newH: Heap, v: Verifier, analysisInfoes: DependencyAnalysisInfoes): (FunctionRecorder, Heap) = { - if(v.decider.isPathInfeasible()) return (fr1, h) + if(v.decider.isPathInfeasible) return (fr1, h) val analysisInfoes1 = analysisInfoes.addInfo("merge", ast.NoPosition, DependencyType.Internal) From 2d4dd16ee7f023dfff2cc61c60e385b6e146f984 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 31 Mar 2026 14:57:06 +0200 Subject: [PATCH 408/474] fix axioms --- src/main/scala/decider/ProverStdIO.scala | 2 - .../DependencyAnalysisInfo.scala | 46 +----- .../DependencyAnalysisNode.scala | 2 +- .../DependencyAnalyzer.scala | 135 ++---------------- .../DependencyGraphInterpreter.scala | 2 +- .../scala/interfaces/decider/Prover.scala | 7 +- src/main/scala/rules/Evaluator.scala | 18 +-- src/main/scala/rules/Executor.scala | 5 +- src/main/scala/rules/HeapSupporter.scala | 5 +- .../scala/rules/QuantifiedChunkSupport.scala | 1 + src/main/scala/rules/StateConsolidator.scala | 1 + src/main/scala/supporters/Domains.scala | 8 +- .../scala/supporters/MethodSupporter.scala | 3 +- .../supporters/functions/FunctionData.scala | 49 ++++--- .../functions/FunctionVerificationUnit.scala | 14 +- .../scala/verifier/DefaultMainVerifier.scala | 10 -- .../verifier/VerificationPoolManager.scala | 3 +- src/test/scala/DependencyAnalysisTests.scala | 4 +- 18 files changed, 73 insertions(+), 242 deletions(-) diff --git a/src/main/scala/decider/ProverStdIO.scala b/src/main/scala/decider/ProverStdIO.scala index 3354c39ae..6c580cf90 100644 --- a/src/main/scala/decider/ProverStdIO.scala +++ b/src/main/scala/decider/ProverStdIO.scala @@ -308,11 +308,9 @@ abstract class ProverStdIO(uniqueId: String, } def extractUnsatCore(): String = { - val startTime = DependencyAnalyzer.startTimeMeasurement() writeLine("(get-unsat-core)") val unsatCore = input.readLine() comment("unsat core: " + unsatCore) - DependencyAnalyzer.stopTimeMeasurementAndAddToTotal(startTime, DependencyAnalyzer.timeToExtractUnsatCore) unsatCore } diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala index 09046894d..3361ed288 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala @@ -1,9 +1,7 @@ package viper.silicon.dependencyAnalysis -import viper.silicon.dependencyAnalysis.EdgeType.EdgeType -import viper.silicon.dependencyAnalysis.JoinType.JoinType import viper.silver.ast -import viper.silver.ast.{DependencyTypeInfo, NoPosition} +import viper.silver.ast.{DependencyAnalysisJoinInfo, DependencyAnalysisMergeInfo, DependencyTypeInfo, EvalStackDependencyAnalysisJoin, NoPosition, SimpleDependencyAnalysisJoin, SimpleDependencyAnalysisMerge} import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, DependencyType, StringAnalysisSourceInfo} @@ -80,49 +78,7 @@ object DependencyAnalysisInfoes { -object JoinType extends Enumeration { - type JoinType = Value - val Source, Sink = Value -} - -object EdgeType extends Enumeration { - type EdgeType = Value - val Up, Down = Value -} - - -trait DependencyAnalysisJoinInfo extends ast.Info { - override def comment: Seq[String] = Nil - override def isCached: Boolean = false - - def matches(dependencyAnalysisJoinInfo: DependencyAnalysisJoinInfo) = { - (this, dependencyAnalysisJoinInfo) match { - case (SimpleDependencyAnalysisJoin(sourceInfo, joinType, edgeType), SimpleDependencyAnalysisJoin(sourceInfo1, joinType1, edgeType1)) => - sourceInfo.equals(sourceInfo1) && edgeType.equals(edgeType1) - } - } -} - -case class EvalStackDependencyAnalysisJoin(joinType: JoinType, edgeType: EdgeType) extends DependencyAnalysisJoinInfo -case class SimpleDependencyAnalysisJoin(sourceInfo: AnalysisSourceInfo, joinType: JoinType, edgeType: EdgeType) extends DependencyAnalysisJoinInfo - - -trait DependencyAnalysisMergeInfo extends ast.Info { - - def isMerge: Boolean - - override def comment: Seq[String] = Nil - override def isCached: Boolean = false -} - -case class NoDependencyAnalysisMerge() extends DependencyAnalysisMergeInfo { - override def isMerge: Boolean = false -} - -case class SimpleDependencyAnalysisMerge(sourceInfo: AnalysisSourceInfo) extends DependencyAnalysisMergeInfo { - override def isMerge: Boolean = true -} diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala index 920b3d243..3d2e2d39a 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala @@ -2,7 +2,7 @@ package viper.silicon.dependencyAnalysis import viper.silicon.interfaces.state.Chunk import viper.silicon.state.terms.{False, Term, Var} -import viper.silver.ast.Position +import viper.silver.ast.{DependencyAnalysisMergeInfo, NoDependencyAnalysisMerge, Position, SimpleDependencyAnalysisJoin} import viper.silver.dependencyAnalysis.{AbstractDependencyAnalysisNode, AnalysisSourceInfo, AssumptionType, NoAnalysisSourceInfo} import viper.silver.dependencyAnalysis.AssumptionType.AssumptionType diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index e9c81c7a0..28be885bf 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -1,35 +1,22 @@ package viper.silicon.dependencyAnalysis -import viper.silicon.dependencyAnalysis.DependencyAnalyzer._ -import viper.silicon.dependencyAnalysis.JoinType.JoinType import viper.silicon.interfaces.state.{Chunk, GeneralChunk} import viper.silicon.state.terms.{NoPerm, _} import viper.silicon.verifier.Verifier import viper.silver.ast +import viper.silver.ast.JoinType.JoinType import viper.silver.ast._ -import viper.silver.dependencyAnalysis.AssumptionType.AssumptionType -import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyType, FrontendDependencyAnalysisInfo} +import viper.silver.dependencyAnalysis.{AssumptionType, DependencyType, FrontendDependencyAnalysisInfo} -import java.util.concurrent.atomic.AtomicLong import scala.collection.mutable trait DependencyAnalyzer { protected val dependencyGraph: DependencyGraph = new DependencyGraph() - protected var isClosed_ = false - - def disableTransitiveEdges(): Unit = { - isClosed_ = true - } - - def enableTransitiveEdges(): Unit = { - isClosed_ = false - } def getMember: Option[ast.Member] def getNodes: Iterable[DependencyAnalysisNode] -// def getChunkNode(chunk: Chunk): Option[ChunkAnalysisInfo] def addNodes(nodes: Iterable[DependencyAnalysisNode]): Unit def addAssertionNode(node: GeneralAssertionNode): Unit @@ -46,8 +33,6 @@ trait DependencyAnalyzer { def addDependency(source: Option[Int], dest: Option[Int]): Unit def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit -// def addPermissionDependencies(sourceChunks: Set[Chunk], sourceTerms: Set[Term], targetChunk: Chunk): Unit - def addCustomTransitiveDependency(sourceSourceInfo: AnalysisSourceInfo, targetSourceInfo: AnalysisSourceInfo): Unit /** * Adds dependencies between all pairs of sourceExps and targetExps, where sourceExps should be preconditions and @@ -55,11 +40,6 @@ trait DependencyAnalyzer { */ def addDependenciesForAbstractMembers(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp], analysisInfoes: DependencyAnalysisInfoes): Unit - /** - * Adds edges connecting nodes representing function postconditions with the corresponding axiom nodes. - */ - def addFunctionAxiomEdges(): Unit - /** * Adds an assertion and assumption node with the given analysis source info and dependencies to the current infeasibility node. */ @@ -77,52 +57,7 @@ object DependencyAnalyzer { val analysisLabelName: String = "$$analysisLabel$$" private val assumptionTypeAnnotationKey = "assumptionType" private val enableDependencyAnalysisAnnotationKey = "enableDependencyAnalysis" - val timeToVerifyAndCollectDependencies: AtomicLong = new AtomicLong(0) - val timeToVerifyAndBuildFinalGraph: AtomicLong = new AtomicLong(0) - val timeOfPostprocessing: AtomicLong = new AtomicLong(0) - val timeToAddNodes: AtomicLong = new AtomicLong(0) - val timeToAddEdges: AtomicLong = new AtomicLong(0) - val timeToExtractCandidateNodes: AtomicLong = new AtomicLong(0) - val timeForFunctionJoin: AtomicLong = new AtomicLong(0) - val timeForMethodJoin: AtomicLong = new AtomicLong(0) - val runtimeOverheadPermissionNodes: AtomicLong = new AtomicLong(0) - val timeToExtractUnsatCore: AtomicLong = new AtomicLong(0) - val timeToProcessUnsatCore: AtomicLong = new AtomicLong(0) - val timeToMergeNodes: AtomicLong = new AtomicLong(0) - val timeToRemoveInternalNodes: AtomicLong = new AtomicLong(0) - val timeToAddTransitiveEdges: AtomicLong = new AtomicLong(0) - - def startTimeMeasurement(): Long = { - if(!Verifier.config.enableDependencyAnalysisProfiling()) return 0 - System.nanoTime() - } - - def stopTimeMeasurementAndAddToTotal(startTime: Long, total: AtomicLong): Unit = { - if(!Verifier.config.enableDependencyAnalysisProfiling()) return - - val endTime = System.nanoTime() - total.addAndGet(endTime - startTime) - } - - // TODO ake: remove all profiling artifacts - def printProfilingResults(): Unit = { - if(!Verifier.config.enableDependencyAnalysisProfiling()) return - println(s"Overall runtime = time spent on verification and building the final graph: ${timeToVerifyAndBuildFinalGraph.get() / 1e6}ms") - println(s"This runtime can be categorized into the following, fine-grained measurements.") - println(s" Time spent on verification and collecting low-level dependencies: ${timeToVerifyAndCollectDependencies.get() / 1e6}ms") - println(s" Time spent on adding explicit permission nodes: ${runtimeOverheadPermissionNodes.get() / 1e6}ms") - println(s" Time spent on extracting the unsat core: ${timeToExtractUnsatCore.get() / 1e6}ms") - println(s" Time spent on processing the unsat core: ${timeToProcessUnsatCore.get() / 1e6}ms") - println(s" Time spent on merging lower-level nodes: ${timeToMergeNodes.get() / 1e6}ms") - println(s" Time spent on removing internal nodes: ${timeToRemoveInternalNodes.get() / 1e6}ms") - println(s" Time spent on adding transitive edges in lower-level graph: ${timeToAddTransitiveEdges.get() / 1e6}ms") - println(s" Postprocessing: ${timeOfPostprocessing.get() / 1e6}ms") - println(s" Time spent on adding nodes: ${timeToAddNodes.get() / 1e6}ms") - println(s" Time spent on adding edges: ${timeToAddEdges.get() / 1e6}ms") - println(s" Time spent on extracting candidate nodes: ${timeToExtractCandidateNodes.get() / 1e6}ms") - println(s" Time spent for joins over function calls: ${timeForFunctionJoin.get() / 1e6}ms") - println(s" Time spent for joins over method calls: ${timeForMethodJoin.get() / 1e6}ms") - } + private def extractAnnotationFromInfo(info: ast.Info, annotationKey: String): Option[Seq[String]] = { info.getAllInfos[ast.AnnotationInfo] @@ -130,10 +65,6 @@ object DependencyAnalyzer { .map(_.values(annotationKey)).headOption } - def extractAssumptionTypeFromInfo(info: ast.Info): Option[AssumptionType] = { - extractDependencyTypeFromInfo(info).map(_.assumptionType) - } - def extractDependencyTypeFromInfo(info: ast.Info): Option[DependencyType] = { val annotation = Some(List.empty) // TODO ake extractAnnotationFromInfo(info, assumptionTypeAnnotationKey) val dependencyAnalysisInfo = info.getUniqueInfo[FrontendDependencyAnalysisInfo] @@ -317,26 +248,18 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { } override def registerExhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, labelNodeOpt: Option[LabelNode], analysisInfo: AnalysisInfo): CH = { - val startTime = startTimeMeasurement() val labelNode = labelNodeOpt.get val chunk = buildChunk(Ite(labelNode.term, perm, NoPerm)) val chunkNode = addPermissionExhaleNode(chunk, chunk.perm, analysisInfo.analysisInfoes, labelNode) - if(chunkNode.isDefined) - addDependency(chunkNode, Some(labelNode.id)) -// addPermissionDependencies(sourceChunks, Set(), chunkNode) TODO ake: can be removed - stopTimeMeasurementAndAddToTotal(startTime, runtimeOverheadPermissionNodes) + if(chunkNode.isDefined) addDependency(chunkNode, Some(labelNode.id)) chunk } override def registerInhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, labelNodeOpt: Option[LabelNode], analysisInfo: AnalysisInfo): CH = { - val startTime = startTimeMeasurement() val labelNode = labelNodeOpt.get val chunk = buildChunk(Ite((labelNode.term, perm, NoPerm))) val chunkNode = addPermissionInhaleNode(chunk, chunk.perm, analysisInfo.analysisInfoes, labelNode) - if(chunkNode.isDefined) - addDependency(chunkNode, Some(labelNode.id)) -// addPermissionDependencies(sourceChunks, Set(), chunkNode) TODO ake: can be removed - stopTimeMeasurementAndAddToTotal(startTime, runtimeOverheadPermissionNodes) + if(chunkNode.isDefined) addDependency(chunkNode, Some(labelNode.id)) chunk } @@ -349,14 +272,13 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { private def addPermissionExhaleNode(chunk: Chunk, permAmount: Term, analysisInfoes: DependencyAnalysisInfoes, labelNode: LabelNode): Option[Int] = { val node = PermissionExhaleNode(chunk, permAmount, analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assertionType, analysisInfoes.getMergeInfo, labelNode, analysisInfoes.getJoinInfo) addAssertionNode(node) -// addPermissionDependencies(Set(chunk), Set(), Some(node.id)) TODO ake: can be removed Some(node.id) } override def createLabelNode(label: Var, sourceChunks: Iterable[Chunk], sourceTerms: Iterable[Term]): Option[LabelNode] = { val labelNode = LabelNode(label) addAssumptionNode(labelNode) - dependencyGraph.addEdges(/* getChunkNodeIds(sourceChunks.toSet) ++ TODO ake: can be removed */ getNodeIdsByTerm(sourceTerms.toSet), labelNode.id) + dependencyGraph.addEdges(getNodeIdsByTerm(sourceTerms.toSet), labelNode.id) Some(labelNode) } @@ -403,7 +325,6 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { } override def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit = { - val startTime = startTimeMeasurement() val assumptionLabels = dep.replace("(", "").replace(")", "").split(" ") val assumptionIds = assumptionLabels.filter(DependencyAnalyzer.isAssumptionLabel).map(DependencyAnalyzer.getIdFromLabel) val assertionIdsFromUnsatCore = assumptionLabels.filter(DependencyAnalyzer.isAssertionLabel).map(DependencyAnalyzer.getIdFromLabel) @@ -412,31 +333,6 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { dependencyGraph.addEdges(assumptionIds, assertionIds) val axiomIds = assumptionLabels.filter(DependencyAnalyzer.isAxiomLabel).map(DependencyAnalyzer.getIdFromLabel) dependencyGraph.addEdges(axiomIds, assertionIds) - stopTimeMeasurementAndAddToTotal(startTime, timeToProcessUnsatCore) - } - - // TODO ake: remove once we are sure this is not needed anymore -// private def addPermissionDependencies(sourceChunks: Set[Chunk], sourceTerms: Set[Term], newChunkNodeId: Option[Int]): Unit = { -// if(newChunkNodeId.isEmpty) return -// -// val sourceNodeIds = getChunkNodeIds(sourceChunks).filter(id => id != newChunkNodeId.get) ++ getNodeIdsByTerm(sourceTerms) -// dependencyGraph.addEdges(sourceNodeIds, newChunkNodeId.get) -// } -// -// override def addPermissionDependencies(sourceChunks: Set[Chunk], sourceTerms: Set[Term], newChunk: Chunk): Unit = { -// val startTime = startTimeMeasurement() -// val newChunkId = dependencyGraph.getNodes -// .filter(c => c.isInstanceOf[ChunkAnalysisInfo] && c.isInstanceOf[ChunkAnalysisInfo] && newChunk.equals(c.asInstanceOf[ChunkAnalysisInfo].getChunk)) -// .map(_.id).toSet -//// addPermissionDependencies(sourceChunks, sourceTerms, newChunkId.headOption) -// stopTimeMeasurementAndAddToTotal(startTime, runtimeOverheadPermissionNodes) -// } - - override def addCustomTransitiveDependency(sourceSourceInfo: AnalysisSourceInfo, targetSourceInfo: AnalysisSourceInfo): Unit = { - // TODO ake -// val sourceNodes = dependencyGraph.getAssertionNodes filter (n => n.sourceInfo.getSourceForTransitiveEdges.equals(sourceSourceInfo.getSourceForTransitiveEdges)) -// val targetNodes = dependencyGraph.getAssumptionNodes filter (n => n.sourceInfo.getSourceForTransitiveEdges.equals(targetSourceInfo.getSourceForTransitiveEdges)) -// dependencyGraph.addEdges(sourceNodes map (_.id), targetNodes map (_.id)) } override def addDependenciesForAbstractMembers(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp], analysisInfoes: DependencyAnalysisInfoes): Unit = { @@ -472,15 +368,6 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { } } - override def addFunctionAxiomEdges(): Unit = { - val axiomNodes = dependencyGraph.getAssumptionNodes.filter(_.isInstanceOf[AxiomAssumptionNode]) - val postcondNodes = getNodes.filter(n => AssumptionType.postconditionTypes.contains(n.assumptionType)) - axiomNodes foreach {aNode => - val pNodes = postcondNodes filter (_.sourceInfo.toString.equals(aNode.sourceInfo.toString)) map (_.id) - dependencyGraph.addEdges(pNodes, aNode.id) - } - } - /** * Creates a new graph where nodes that only differ in irrelevant information are merged into one node. * As a result, this operation removes some lower-level details from the graph. @@ -498,10 +385,10 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { nodeMap.put(n.id, n.id) mergedGraph.addAssumptionNode(n) } - val assumptionNodesBySource = dependencyGraph.getAssumptionNodes.filter(!keepNode(_)).groupBy(n => (n.sourceInfo, n.assumptionType, n.mergeInfo, n.joinInfoes)) // TODO ake: review joinInfoes + val assumptionNodesBySource = dependencyGraph.getAssumptionNodes.filter(!keepNode(_)).groupBy(n => (n.sourceInfo, n.assumptionType, n.mergeInfo, n.joinInfoes)) assumptionNodesBySource foreach { case ((sourceInfo, assumptionType, mergeInfo, joinInfoes), assumptionNodes) => if (assumptionNodes.nonEmpty) { - val newNode = SimpleAssumptionNode(True, None, sourceInfo, assumptionType, mergeInfo, joinInfoes) // TODO ake: review joinInfoes + val newNode = SimpleAssumptionNode(True, None, sourceInfo, assumptionType, mergeInfo, joinInfoes) assumptionNodes foreach (n => nodeMap.put(n.id, newNode.id)) mergedGraph.addAssumptionNode(newNode) } @@ -511,10 +398,10 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { nodeMap.put(n.id, n.id) mergedGraph.addAssertionNode(n) } - val assertionNodesBySource = dependencyGraph.getAssertionNodes.filter(!keepNode(_)).groupBy(n => (n.sourceInfo, n.assumptionType, n.mergeInfo, n.joinInfoes)) // TODO ake: review joinInfoes + val assertionNodesBySource = dependencyGraph.getAssertionNodes.filter(!keepNode(_)).groupBy(n => (n.sourceInfo, n.assumptionType, n.mergeInfo, n.joinInfoes)) assertionNodesBySource foreach { case ((sourceInfo, assumptionType, mergeInfo, joinInfoes), assertionNodes) => if (assertionNodes.nonEmpty) { - val newNode = SimpleAssertionNode(True, sourceInfo, assumptionType, mergeInfo, joinInfoes, hasFailed=assertionNodes.exists(_.hasFailed)) // TODO ake: review joinInfoes + val newNode = SimpleAssertionNode(True, sourceInfo, assumptionType, mergeInfo, joinInfoes, hasFailed=assertionNodes.exists(_.hasFailed)) assertionNodes foreach (n => nodeMap.put(n.id, newNode.id)) mergedGraph.addAssertionNode(newNode) } @@ -562,9 +449,7 @@ class NoDependencyAnalyzer extends DependencyAnalyzer { override def addDependency(source: Option[Int], dest: Option[Int]): Unit = {} override def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit = {} - override def addCustomTransitiveDependency(sourceSourceInfo: AnalysisSourceInfo, targetSourceInfo: AnalysisSourceInfo): Unit = {} override def addDependenciesForAbstractMembers(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp], analysisInfoes: DependencyAnalysisInfoes): Unit = {} - override def addFunctionAxiomEdges(): Unit = {} override def buildFinalGraph(): Option[DependencyGraph] = None diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 04f20a74c..49995ea0d 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -7,7 +7,7 @@ import viper.silicon.verifier.Verifier import viper.silver.ast import viper.silver.ast.utility.ViperStrategy import viper.silver.ast.utility.rewriter.Traverse -import viper.silver.ast.{If, Program, Stmt} +import viper.silver.ast.{If, JoinType, Program, Stmt} import viper.silver.dependencyAnalysis.{AbstractDependencyGraphInterpreter, AnalysisSourceInfo, AssumptionType} import java.io.PrintWriter diff --git a/src/main/scala/interfaces/decider/Prover.scala b/src/main/scala/interfaces/decider/Prover.scala index c535f4f9e..342502e4b 100644 --- a/src/main/scala/interfaces/decider/Prover.scala +++ b/src/main/scala/interfaces/decider/Prover.scala @@ -40,15 +40,14 @@ trait ProverLike { terms foreach assume } - def assumeAxiomsWithAnalysisInfo(axioms: InsertionOrderedSet[(Term, Option[(AnalysisSourceInfo, AssumptionType)])], description: String): Unit = { + def assumeAxiomsWithAnalysisInfo(axioms: InsertionOrderedSet[(Term, DependencyAnalysisInfoes)], description: String): Unit = { if (debugMode) preambleAssumptions :+= new DebugAxiom(description, axioms.map(_._1)) if(Verifier.config.enableDependencyAnalysis()){ axioms.foreach(axiom => { - val axiomInfo = axiom._2 - val analysisInfoes = axiomInfo.map(ai => DependencyAnalysisInfoes.create(ai._1, DependencyType.make(ai._2))) - val id = if(analysisInfoes.isDefined) preambleDependencyAnalyzer.addAxiom(axiom._1, analysisInfoes.get) else None + val analysisInfoes = axiom._2 + val id = preambleDependencyAnalyzer.addAxiom(axiom._1, analysisInfoes) assume(axiom._1, DependencyAnalyzer.createAxiomLabel(id)) }) } else{ diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 53208dee3..681c8b069 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -10,7 +10,7 @@ import viper.silicon import viper.silicon.Config.JoinMode import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp -import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfoes, NoDependencyAnalysisMerge} +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfoes import viper.silicon.interfaces._ import viper.silicon.interfaces.state.ChunkIdentifer import viper.silicon.logger.records.data.{CondExpRecord, EvaluateRecord, ImpliesRecord} @@ -24,7 +24,7 @@ import viper.silicon.verifier.Verifier import viper.silicon.{Map, TriggerSets} import viper.silver.ast import viper.silver.ast.utility.Expressions -import viper.silver.ast.{AnnotationInfo, LocalVarWithVersion, WeightedQuantifier} +import viper.silver.ast.{AnnotationInfo, EdgeType, EvalStackDependencyAnalysisJoin, JoinType, LocalVarWithVersion, NoDependencyAnalysisMerge, WeightedQuantifier} import viper.silver.dependencyAnalysis.DependencyType import viper.silver.reporter.{AnnotationWarning, WarningsDuringVerification} import viper.silver.utility.Common.Rational @@ -559,12 +559,11 @@ object evaluator extends EvaluationRules { val auxNonGlobalsExp = auxExps.map(_._2) val commentGlobal = "Nested auxiliary terms: globals (aux)" v1.decider.prover.comment(commentGlobal) - v1.decider.dependencyAnalyzer.disableTransitiveEdges() - v1.decider.assume(tAuxGlobal, Option.when(withExp)(DebugExp.createInstance(description=commentGlobal, children=auxGlobalsExp.get)), enforceAssumption = false, analysisInfoes.withDependencyType(DependencyType.Internal)) + val auxAnalysisInfoes = analysisInfoes.withDependencyType(DependencyType.Internal).withMergeInfo(NoDependencyAnalysisMerge()) + v1.decider.assume(tAuxGlobal, Option.when(withExp)(DebugExp.createInstance(description=commentGlobal, children=auxGlobalsExp.get)), enforceAssumption = false, auxAnalysisInfoes) val commentNonGlobals = "Nested auxiliary terms: non-globals (aux)" v1.decider.prover.comment(commentNonGlobals) - v1.decider.assume(tAuxHeapIndep/*tAux*/, Option.when(withExp)(DebugExp.createInstance(description=commentNonGlobals, children=auxNonGlobalsExp.get)), enforceAssumption = false, analysisInfoes.withDependencyType(DependencyType.Internal)) - v1.decider.dependencyAnalyzer.enableTransitiveEdges() + v1.decider.assume(tAuxHeapIndep/*tAux*/, Option.when(withExp)(DebugExp.createInstance(description=commentNonGlobals, children=auxNonGlobalsExp.get)), enforceAssumption = false, auxAnalysisInfoes) if (qantOp == Exists) { // For universal quantification, the non-global auxiliary assumptions will contain the information that // forall vars :: all function preconditions are fulfilled. @@ -577,7 +576,7 @@ object evaluator extends EvaluationRules { val exp = ast.Forall(eQuant.variables, eTriggers, body)(sourceQuant.pos, sourceQuant.info, sourceQuant.errT) DebugExp.createInstance(exp, expNew) }) - v1.decider.assume(Quantification(Forall, tVars, FunctionPreconditionTransformer.transform(tBody, s1.program), tTriggers, name, quantWeight), debugExp, analysisInfoes.withMergeInfo(NoDependencyAnalysisMerge())) + v1.decider.assume(Quantification(Forall, tVars, FunctionPreconditionTransformer.transform(tBody, s1.program), tTriggers, name, quantWeight), debugExp, analysisInfoes) } val tQuant = Quantification(qantOp, tVars, tBody, tTriggers, name, quantWeight) @@ -671,13 +670,14 @@ object evaluator extends EvaluationRules { moreJoins = JoinMode.Off, assertReadAccessOnly = if (Verifier.config.respectFunctionPrePermAmounts()) s2.assertReadAccessOnly /* should currently always be false */ else true) - consumes(s3, pres, true, _ => pvePre, v2, analysisInfoes)((s4, snap, v3) => { // TODO ake: join! + val precondAnalysisInfoes = analysisInfoes.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Source, EdgeType.Up)) + consumes(s3, pres, true, _ => pvePre, v2, precondAnalysisInfoes)((s4, snap, v3) => { val snap1 = snap.get.convert(sorts.Snap) val preFApp = App(functionSupporter.preconditionVersion(v3.symbolConverter.toFunction(func)), snap1 :: tArgs) val preExp = Option.when(withExp)({ DebugExp.createInstance(Some(s"precondition of ${func.name}(${eArgsNew.get.mkString(", ")}) holds"), None, None, InsertionOrderedSet.empty) }) - v3.decider.assume(preFApp, preExp, analysisInfoes) + v3.decider.assume(preFApp, preExp, precondAnalysisInfoes) val funcAnn = func.info.getUniqueInfo[AnnotationInfo] val tFApp = funcAnn match { case Some(a) if a.values.contains("opaque") => diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index cfc023157..2e2fa259d 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -10,8 +10,8 @@ import viper.silicon.Config.JoinMode import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.decider.RecordedPathConditions +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfoes import viper.silicon.dependencyAnalysis.DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes -import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfoes, EdgeType, EvalStackDependencyAnalysisJoin, JoinType} import viper.silicon.interfaces._ import viper.silicon.interfaces.state.{NonQuantifiedChunk, QuantifiedChunk} import viper.silicon.logger.records.data.{CommentRecord, ConditionalEdgeRecord, ExecuteRecord, MethodCallRecord} @@ -20,11 +20,12 @@ import viper.silicon.state.terms._ import viper.silicon.utils.ast.{BigAnd, extractPTypeFromExp, simplifyVariableName} import viper.silicon.utils.freshSnap import viper.silicon.verifier.Verifier +import viper.silver.ast.{EdgeType, EvalStackDependencyAnalysisJoin, JoinType} import viper.silver.ast.utility.Statements import viper.silver.cfg.silver.SilverCfg import viper.silver.cfg.silver.SilverCfg.{SilverBlock, SilverEdge} import viper.silver.cfg.{ConditionalEdge, StatementBlock} -import viper.silver.dependencyAnalysis.{DependencyType, StringAnalysisSourceInfo} +import viper.silver.dependencyAnalysis.DependencyType import viper.silver.verifier.errors._ import viper.silver.verifier.reasons._ import viper.silver.verifier.{CounterexampleTransformer, NullPartialVerificationError, PartialVerificationError} diff --git a/src/main/scala/rules/HeapSupporter.scala b/src/main/scala/rules/HeapSupporter.scala index 29fa74023..62254f897 100644 --- a/src/main/scala/rules/HeapSupporter.scala +++ b/src/main/scala/rules/HeapSupporter.scala @@ -24,6 +24,7 @@ import viper.silicon.utils.ast.{BigAnd, replaceVarsInExp} import viper.silicon.utils.freshSnap import viper.silicon.verifier.Verifier import viper.silver.ast +import viper.silver.ast.SimpleDependencyAnalysisMerge import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, DependencyType} import viper.silver.parser.PUnknown import viper.silver.verifier.reasons.{InsufficientPermission, MagicWandChunkNotFound} @@ -234,8 +235,6 @@ class DefaultHeapSupportRules extends HeapSupportRules { val debugExp2 = Option.when(withExp)(DebugExp.createInstance(s"FieldTrigger(${eRcvrNew.toString}.${field.name})")) v.decider.assume(FieldTrigger(field.name, sm, tRcvr), debugExp2, lhsSourceInfoes.withDependencyType(DependencyType.Trigger)) } - // TODO ake -// v.decider.dependencyAnalyzer.addCustomTransitiveDependency(lhsSourceInfoes.getMergeInfo, v.decider.analysisSourceInfoStack.getFullSourceInfo) val s4 = s3.copy(h = h3 + ch) val (debugHeapName, _) = v.getDebugOldLabel(s4, ass.lhs.pos) val s5 = if (withExp) s4.copy(oldHeaps = s4.oldHeaps + (debugHeapName -> magicWandSupporter.getEvalHeap(s4))) else s4 @@ -255,8 +254,6 @@ class DefaultHeapSupportRules extends HeapSupportRules { val s5 = s4.copy(h = h4) val (debugHeapName, _) = v4.getDebugOldLabel(s5, ass.lhs.pos) val s6 = if (withExp) s5.copy(oldHeaps = s5.oldHeaps + (debugHeapName -> magicWandSupporter.getEvalHeap(s5))) else s5 - // TODO ake -// v4.decider.dependencyAnalyzer.addCustomTransitiveDependency(lhsSourceInfo, v4.decider.analysisSourceInfoStack.getFullSourceInfo) Q(s6, v4) }) }) diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 2494c9eb0..d5e2cf0ff 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -27,6 +27,7 @@ import viper.silicon.utils.freshSnap import viper.silicon.utils.notNothing.NotNothing import viper.silicon.verifier.Verifier import viper.silver.ast +import viper.silver.ast.NoDependencyAnalysisMerge import viper.silver.dependencyAnalysis.DependencyType import viper.silver.parser.PUnknown import viper.silver.reporter.InternalWarningMessage diff --git a/src/main/scala/rules/StateConsolidator.scala b/src/main/scala/rules/StateConsolidator.scala index b1a359e0a..243080b9a 100644 --- a/src/main/scala/rules/StateConsolidator.scala +++ b/src/main/scala/rules/StateConsolidator.scala @@ -20,6 +20,7 @@ import viper.silicon.state.terms.predef.`?r` import viper.silicon.supporters.functions.FunctionRecorder import viper.silicon.verifier.Verifier import viper.silver.ast +import viper.silver.ast.NoDependencyAnalysisMerge import viper.silver.dependencyAnalysis.DependencyType import scala.annotation.unused diff --git a/src/main/scala/supporters/Domains.scala b/src/main/scala/supporters/Domains.scala index abbb1df8e..ce9b6d031 100644 --- a/src/main/scala/supporters/Domains.scala +++ b/src/main/scala/supporters/Domains.scala @@ -8,7 +8,7 @@ package viper.silicon.supporters import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.common.collections.immutable.MultiMap._ -import viper.silicon.dependencyAnalysis.DependencyAnalyzer +import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfoes, DependencyAnalyzer} import viper.silicon.interfaces.PreambleContributor import viper.silicon.interfaces.decider.ProverLike import viper.silicon.state.terms.{Distinct, DomainFun, Sort, Term} @@ -16,7 +16,7 @@ import viper.silicon.state.{FunctionPreconditionTransformer, SymbolConverter, te import viper.silicon.toMap import viper.silver.ast import viper.silver.ast.NamedDomainAxiom -import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType} +import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyType} import viper.silver.dependencyAnalysis.AssumptionType.AssumptionType trait DomainsContributor[SO, SY, AX, UA] extends PreambleContributor[SO, SY, AX] { @@ -30,7 +30,7 @@ class DefaultDomainsContributor(symbolConverter: SymbolConverter, private var collectedSorts = InsertionOrderedSet[Sort]() private var collectedFunctions = InsertionOrderedSet[terms.DomainFun]() - private var collectedAxioms = InsertionOrderedSet[(Term, Option[(AnalysisSourceInfo, AssumptionType)])]() + private var collectedAxioms = InsertionOrderedSet[(Term, DependencyAnalysisInfoes)]() private var uniqueSymbols = MultiMap.empty[Sort, DomainFun] /* Lifetime */ @@ -107,7 +107,7 @@ class DefaultDomainsContributor(symbolConverter: SymbolConverter, val tAx = domainTranslator.translateAxiom(axiom, symbolConverter.toSort) val tAxPres = FunctionPreconditionTransformer.transform(tAx, program) val enableAnalysis = DependencyAnalyzer.extractEnableAnalysisFromInfo(axiom.info).getOrElse(isAnalysisForDomainEnabled) - collectedAxioms = collectedAxioms.incl((terms.And(tAxPres, tAx), Option.when(enableAnalysis)((AnalysisSourceInfo.createAnalysisSourceInfo(axiom.exp), AssumptionType.DomainAxiom)))) + collectedAxioms = collectedAxioms.incl((terms.And(tAxPres, tAx), DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes.addInfo(axiom.exp.info, axiom.exp))) }) }) } diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index 5f9dad275..e4849173e 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -19,6 +19,7 @@ import viper.silicon.state.{State, Store} import viper.silicon.utils.freshSnap import viper.silicon.verifier.{Verifier, VerifierComponent} import viper.silver.ast +import viper.silver.ast._ import viper.silver.components.StatefulComponent import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyAnalysisJoinNodeInfo} import viper.silver.verifier.errors._ @@ -79,7 +80,7 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif new java.io.File(s"${Verifier.config.tempDirectory()}/${method.name}.dot")) } - val presAssertionNodeForJoin = pres.flatMap(_.topLevelConjuncts).map(pc => SimpleAssertionNode(True, AnalysisSourceInfo.createAnalysisSourceInfo(pc), AssumptionType.Precondition, SimpleDependencyAnalysisMerge(AnalysisSourceInfo.createAnalysisSourceInfo(pc)), joinInfoes = List(SimpleDependencyAnalysisJoin(AnalysisSourceInfo.createAnalysisSourceInfo(pc), JoinType.Sink, EdgeType.Up)))) + val presAssertionNodeForJoin = pres.flatMap(_.topLevelConjuncts).map(pc => SimpleAssertionNode(True, AnalysisSourceInfo.createAnalysisSourceInfo(pc), AssumptionType.Precondition, SimpleDependencyAnalysisMerge(AnalysisSourceInfo.createAnalysisSourceInfo(pc)), List(SimpleDependencyAnalysisJoin(AnalysisSourceInfo.createAnalysisSourceInfo(pc), JoinType.Sink, EdgeType.Up)))) presAssertionNodeForJoin foreach v.decider.dependencyAnalyzer.addAssertionNode val analysisInfoesPrecondition = DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Sink, EdgeType.Up)) diff --git a/src/main/scala/supporters/functions/FunctionData.scala b/src/main/scala/supporters/functions/FunctionData.scala index 745443fa1..f8405eaa3 100644 --- a/src/main/scala/supporters/functions/FunctionData.scala +++ b/src/main/scala/supporters/functions/FunctionData.scala @@ -8,7 +8,7 @@ package viper.silicon.supporters.functions import com.typesafe.scalalogging.LazyLogging import viper.silicon.common.collections.immutable.InsertionOrderedSet -import viper.silicon.dependencyAnalysis.DependencyAnalyzer +import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfoes, DependencyAnalyzer} import viper.silicon.interfaces.FatalResult import viper.silicon.rules.{InverseFunctions, PermMapDefinition, SnapshotMapDefinition, functionSupporter} import viper.silicon.state.terms._ @@ -19,10 +19,9 @@ import viper.silicon.utils.ast.simplifyVariableName import viper.silicon.verifier.Verifier import viper.silicon.{Config, Map, toMap} import viper.silver.ast -import viper.silver.ast.LocalVarWithVersion +import viper.silver.ast.{EdgeType, JoinType, LocalVarWithVersion, SimpleDependencyAnalysisJoin} import viper.silver.ast.utility.Functions -import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, ExpAnalysisSourceInfo, StringAnalysisSourceInfo} -import viper.silver.dependencyAnalysis.AssumptionType.AssumptionType +import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyType} import viper.silver.parser.PUnknown import viper.silver.reporter.{InternalWarningMessage, Reporter} @@ -140,20 +139,23 @@ class FunctionData(val programFunction: ast.Function, val triggerFunctionApplication = App(statelessFunction, formalArgs.values.toSeq) val preconditionFunctionApplication = App(preconditionFunction, `?s` +: formalArgs.values.toSeq) - val limitedAxiom: (Quantification, Option[(StringAnalysisSourceInfo, AssumptionType)]) = + + private val bodyAnalysisInfoes: DependencyAnalysisInfoes = if(programFunction.body.isDefined) + DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes.addInfo(programFunction.body.get.info, programFunction.body.get) + .withJoinInfo(SimpleDependencyAnalysisJoin(AnalysisSourceInfo.createAnalysisSourceInfo(programFunction.body.get), JoinType.Source, EdgeType.Down)) + else DependencyAnalysisInfoes.create("unverified function body", DependencyType.Internal) + + + val limitedAxiom: (Quantification, DependencyAnalysisInfoes) = (Forall(arguments, BuiltinEquals(limitedFunctionApplication, functionApplication), Trigger(functionApplication)), - Option.when(isAnalysisEnabled)((StringAnalysisSourceInfo("Limited Axiom", programFunction.pos), AssumptionType.Internal))) + DependencyAnalysisInfoes.create("Limited Axiom", DependencyType.Internal)) - val triggerAxiom: (Quantification, Option[(StringAnalysisSourceInfo, AssumptionType)]) = + val triggerAxiom: (Quantification, DependencyAnalysisInfoes) = (Forall(arguments, triggerFunctionApplication, Trigger(limitedFunctionApplication)), - Option.when(isAnalysisEnabled)((StringAnalysisSourceInfo("Trigger Axiom", programFunction.pos), AssumptionType.Trigger))) - - lazy val postconditionType: AssumptionType = DependencyAnalyzer.extractDependencyTypeFromInfo(programFunction.info).map(_.assertionType) - .getOrElse(if(programFunction.body.isDefined) AssumptionType.ImplicitPostcondition else AssumptionType.ExplicitPostcondition) + DependencyAnalysisInfoes.create("Trigger Axiom", DependencyType.Trigger)) - lazy val functionBodyType: AssumptionType = AssumptionType.FunctionBody // TODO ake: maybe extract from programFunction.info but make sure both ghost and non-ghost function bodies are treated as verification annotations /* * Data collected during phases 1 (well-definedness checking) and 2 (verification) @@ -233,7 +235,7 @@ class FunctionData(val programFunction: ast.Function, } } - lazy val postAxiom: Seq[(Term, Option[(AnalysisSourceInfo, AssumptionType)])] = { + lazy val postAxiom: Seq[(Term, DependencyAnalysisInfoes)] = { assert(phase == 1, s"Postcondition axiom must be generated in phase 1, current phase is $phase") if (programFunction.posts.nonEmpty) { @@ -241,16 +243,17 @@ class FunctionData(val programFunction: ast.Function, val bodyBindings: Map[Var, Term] = Map(formalResult -> limitedFunctionApplication) def wrapBody(body: Term): Term = Let(toMap(bodyBindings), body) + val analysisInfoes = DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes if(isAnalysisEnabled){ - (Forall(arguments, wrapBody(And(generateNestedDefinitionalAxioms)), Trigger(limitedFunctionApplication)), Option.empty[(AnalysisSourceInfo, AssumptionType)]) +: + (Forall(arguments, wrapBody(And(generateNestedDefinitionalAxioms)), Trigger(limitedFunctionApplication)), bodyAnalysisInfoes) +: programFunction.posts.flatMap(_.topLevelConjuncts).map({p => val terms = expressionTranslator.translatePostcondition(program, Seq(p), this) - (And(Forall(arguments, wrapBody(Implies(pre, And(terms))), Trigger(limitedFunctionApplication)), True), Some((AnalysisSourceInfo.createAnalysisSourceInfo(p), postconditionType))) + (And(Forall(arguments, wrapBody(Implies(pre, And(terms))), Trigger(limitedFunctionApplication)), True), analysisInfoes.addInfo(p.info, p).withJoinInfo(SimpleDependencyAnalysisJoin(AnalysisSourceInfo.createAnalysisSourceInfo(p), JoinType.Source, EdgeType.Down))) }) }else{ val innermostBody = And(generateNestedDefinitionalAxioms ++ List(Implies(pre, And(translatedPosts)))) - Seq((Forall(arguments, wrapBody(innermostBody), Trigger(limitedFunctionApplication)), None)) + Seq((Forall(arguments, wrapBody(innermostBody), Trigger(limitedFunctionApplication)), analysisInfoes)) } } else Seq.empty @@ -307,7 +310,7 @@ class FunctionData(val programFunction: ast.Function, expressionTranslator.translate(program, programFunction, this) } - lazy val definitionalAxiom: Option[(Term, Option[(AnalysisSourceInfo, AssumptionType)])] = { + lazy val definitionalAxiom: Option[(Term, DependencyAnalysisInfoes)] = { assert(phase == 2, s"Definitional axiom must be generated in phase 2, current phase is $phase") optBody.map(translatedBody => { @@ -322,27 +325,27 @@ class FunctionData(val programFunction: ast.Function, val allTriggers = ( Seq(Trigger(functionApplication)) ++ actualPredicateTriggers) - (Forall(arguments, body, allTriggers), Option.when(isAnalysisEnabled)((AnalysisSourceInfo.createAnalysisSourceInfo(programFunction.body.get), functionBodyType))) + (Forall(arguments, body, allTriggers), + bodyAnalysisInfoes) }) } - lazy val bodyPreconditionPropagationAxiom: Seq[(Term, Option[(AnalysisSourceInfo, AssumptionType)])] = { + lazy val bodyPreconditionPropagationAxiom: Seq[(Term, DependencyAnalysisInfoes)] = { val pre = preconditionFunctionApplication val bodyPreconditions = if (programFunction.body.isDefined) optBody.map(translatedBody => { val body = Implies(pre, FunctionPreconditionTransformer.transform(translatedBody, program)) - (Forall(arguments, body, Seq(Trigger(functionApplication))), - Option.when(isAnalysisEnabled)((ExpAnalysisSourceInfo(programFunction.body.get, programFunction.body.get.pos), functionBodyType))) + (Forall(arguments, body, Seq(Trigger(functionApplication))), bodyAnalysisInfoes) }) else None bodyPreconditions.toSeq } - lazy val postPreconditionPropagationAxiom: Seq[(Term, Option[(AnalysisSourceInfo, AssumptionType)])] = { + lazy val postPreconditionPropagationAxiom: Seq[(Term, DependencyAnalysisInfoes)] = { val pre = preconditionFunctionApplication val postPreconditions = if (programFunction.posts.nonEmpty) { val bodyBindings: Map[Var, Term] = Map(formalResult -> limitedFunctionApplication) val bodies = translatedPosts.map(tPost => Let(bodyBindings, Implies(pre, FunctionPreconditionTransformer.transform(tPost, program)))) bodies.map(b => (Forall(arguments, b, Seq(Trigger(limitedFunctionApplication))), - Option.when(isAnalysisEnabled)((StringAnalysisSourceInfo("postPreconditionPropagationAxiom", programFunction.pos), AssumptionType.ImplicitPostcondition)))) + DependencyAnalysisInfoes.create("postPreconditionPropagationAxiom", DependencyType.Internal))) // TODO ake } else Seq() postPreconditions } diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index 0a04be432..508883930 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -7,7 +7,6 @@ package viper.silicon.supporters.functions import com.typesafe.scalalogging.Logger -import viper.silicon.Config.Sink import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.decider.Decider @@ -25,11 +24,10 @@ import viper.silicon.utils.{freshSnap, toSf} import viper.silicon.verifier.{Verifier, VerifierComponent} import viper.silicon.{Map, Stack, toMap} import viper.silver.ast -import viper.silver.ast.LocalVarWithVersion import viper.silver.ast.utility.Functions +import viper.silver.ast.{And, _} import viper.silver.components.StatefulComponent import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyAnalysisJoinNodeInfo, DependencyType} -import viper.silver.dependencyAnalysis.AssumptionType.AssumptionType import viper.silver.parser.PType import viper.silver.verifier.errors.{ContractNotWellformed, FunctionNotWellformed, PostconditionViolated} @@ -55,9 +53,9 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver @unused private var program: ast.Program = _ /*private*/ var functionData: Map[String, FunctionData] = Map.empty - private var emittedFunctionAxioms: Vector[(Term, Option[(AnalysisSourceInfo, AssumptionType)])] = Vector.empty + private var emittedFunctionAxioms: Vector[(Term, DependencyAnalysisInfoes)] = Vector.empty private var freshVars: Vector[Var] = Vector.empty - private var postConditionAxioms: Vector[(Term, Option[(AnalysisSourceInfo, AssumptionType)])] = Vector.empty + private var postConditionAxioms: Vector[(Term, DependencyAnalysisInfoes)] = Vector.empty private val expressionTranslator = { def resolutionFailureMessage(exp: ast.Positioned, data: FunctionData): String = ( @@ -173,8 +171,6 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver v.decider.resetProverOptions() symbExLog.closeMemberScope() - v.decider.dependencyAnalyzer.addFunctionAxiomEdges() - val allErrors = (res :: res.previous.toList).filter(_.isInstanceOf[Failure]).map(_.asInstanceOf[Failure]) res.dependencyGraphInterpreter = v.decider.dependencyAnalyzer.buildFinalGraph().map(new DependencyGraphInterpreter(function.name, _, @@ -314,7 +310,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver case (fatalResult: FatalResult, _) => fatalResult case (intermediateResult, Phase1Data(sPre, bcsPre, bcsPreExp, pcsPre, pcsPreExp)) => intermediateResult && executionFlowController.locally(sPre, v)((s1, _) => { - val labelledBcsPre = And(bcsPre map (t => v.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t)))) + val labelledBcsPre = terms.And(bcsPre map (t => v.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t)))) val precondAnalysisSourceInfoes = DependencyAnalysisInfoes.create("preconditions", DependencyType.Internal) decider.setCurrentBranchCondition(labelledBcsPre, (BigAnd(bcsPreExp.map(_._1)), Option.when(wExp)(BigAnd(bcsPreExp.map(_._2.get)))), precondAnalysisSourceInfoes) val labelledPcsPre = pcsPre map (t => v.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t))) @@ -336,7 +332,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver result } - private def emitAndRecordFunctionAxioms(axiom: (Term, Option[(AnalysisSourceInfo, AssumptionType)])*): Unit = { + private def emitAndRecordFunctionAxioms(axiom: (Term, DependencyAnalysisInfoes)*): Unit = { val cleanAxiom = if(!Verifier.config.enableDependencyAnalysis()) axiom else axiom.map(a => (a._1.transform{ diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index 5c2d6bd9b..571574ef0 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -210,7 +210,6 @@ class DefaultMainVerifier(config: Config, predSnapGenerator.setup(program) // TODO: Why did Nadja put this here? - val verificationStartTime = DependencyAnalyzer.startTimeMeasurement() allProvers.comment("Started: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(System.currentTimeMillis()) /*bookkeeper.formattedStartTime*/) allProvers.comment("Silicon.version: " + Silicon.version) allProvers.comment(s"Input file: ${inputFile.getOrElse("")}") @@ -345,11 +344,7 @@ class DefaultMainVerifier(config: Config, ++ predicateVerificationResults ++ methodVerificationResults) - DependencyAnalyzer.stopTimeMeasurementAndAddToTotal(verificationStartTime, DependencyAnalyzer.timeToVerifyAndCollectDependencies) runDependencyAnalysisWorkflow(verificationResults, program, inputFile) - DependencyAnalyzer.stopTimeMeasurementAndAddToTotal(verificationStartTime, DependencyAnalyzer.timeToVerifyAndBuildFinalGraph) - - DependencyAnalyzer.printProfilingResults() if (Verifier.config.startDebuggerAutomatically()){ val debugger = new SiliconDebugger(verificationResults, identifierFactory, reporter, FrontendStateCache.resolver, FrontendStateCache.pprogram, FrontendStateCache.translator, this) @@ -660,8 +655,6 @@ class DefaultMainVerifier(config: Config, def runDependencyAnalysisWorkflow(verificationResults: List[VerificationResult], program: ast.Program, inputFile: Option[String]): Unit = { if(!Verifier.config.enableDependencyAnalysis()) return - val postProcessingStartTime = DependencyAnalyzer.startTimeMeasurement() - val dependencyGraphInterpreters = verificationResults.filter(_.dependencyGraphInterpreter.isDefined).map(_.dependencyGraphInterpreter.get) val verificationErrors: List[Failure] = (verificationResults filter (_.isInstanceOf[Failure])) map (_.asInstanceOf[Failure]) @@ -670,12 +663,9 @@ class DefaultMainVerifier(config: Config, dependencyAnalysisResult = Some(result) if (Verifier.config.dependencyAnalysisExportPath.isDefined) { -// result.dependencyGraphInterpreters foreach (_.exportGraph(program)) // comment this in to get the individual methods' graphs result.getFullDependencyGraphInterpreter.exportGraph(program) } - DependencyAnalyzer.stopTimeMeasurementAndAddToTotal(postProcessingStartTime, DependencyAnalyzer.timeOfPostprocessing) - if (Verifier.config.startDependencyAnalysisTool()) { val commandLineTool = new DependencyAnalysisUserTool(result.getFullDependencyGraphInterpreter, dependencyGraphInterpreters, program, verificationErrors) commandLineTool.run() diff --git a/src/main/scala/verifier/VerificationPoolManager.scala b/src/main/scala/verifier/VerificationPoolManager.scala index cda3091bd..d3db0d76a 100644 --- a/src/main/scala/verifier/VerificationPoolManager.scala +++ b/src/main/scala/verifier/VerificationPoolManager.scala @@ -10,6 +10,7 @@ import org.apache.commons.pool2.impl.{DefaultPooledObject, GenericObjectPool, Ge import org.apache.commons.pool2.{BasePooledObjectFactory, ObjectPool, PoolUtils, PooledObject} import viper.silicon.Config import viper.silicon.common.collections.immutable.InsertionOrderedSet +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfoes import viper.silicon.interfaces.VerificationResult import viper.silicon.interfaces.decider.ProverLike import viper.silicon.state.terms.{Decl, Term} @@ -32,7 +33,7 @@ class VerificationPoolManager(mainVerifier: MainVerifier) extends StatefulCompon def assume(term: Term): Unit = workerVerifiers foreach (_.decider.prover.assume(term)) def assume(term: Term, label: String): Unit = workerVerifiers foreach (_.decider.prover.assume(term, label)) override def assumeAxioms(terms: InsertionOrderedSet[Term], description: String): Unit = workerVerifiers foreach (_.decider.prover.assumeAxioms(terms, description)) - override def assumeAxiomsWithAnalysisInfo(axioms: InsertionOrderedSet[(Term, Option[(AnalysisSourceInfo, AssumptionType)])], description: String): Unit = workerVerifiers foreach (_.decider.prover.assumeAxiomsWithAnalysisInfo(axioms, description)) + override def assumeAxiomsWithAnalysisInfo(axioms: InsertionOrderedSet[(Term, DependencyAnalysisInfoes)], description: String): Unit = workerVerifiers foreach (_.decider.prover.assumeAxiomsWithAnalysisInfo(axioms, description)) def declare(decl: Decl): Unit = workerVerifiers foreach (_.decider.prover.declare(decl)) def comment(content: String): Unit = workerVerifiers foreach (_.decider.prover.comment(content)) diff --git a/src/test/scala/DependencyAnalysisTests.scala b/src/test/scala/DependencyAnalysisTests.scala index 87146ffd9..08112904f 100644 --- a/src/test/scala/DependencyAnalysisTests.scala +++ b/src/test/scala/DependencyAnalysisTests.scala @@ -65,7 +65,9 @@ class DependencyAnalysisTests extends AnyFunSuite with DependencyAnalysisTestFra resetFrontend() executeTest(dirName + "/", fileName, frontend) }catch{ - case t: Throwable => fail(t) + case t: Throwable => + println(t.getMessage) + fail(t) } } } From 900af54cd94764645e44382519e4fd3f6848d59e Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 31 Mar 2026 15:22:13 +0200 Subject: [PATCH 409/474] cleanup --- .../DependencyAnalyzer.scala | 97 ++----------------- .../dependencyAnalysis/DependencyGraph.scala | 10 +- .../DependencyGraphInterpreter.scala | 1 - .../supporters/functions/FunctionData.scala | 4 +- .../functions/FunctionVerificationUnit.scala | 3 +- 5 files changed, 18 insertions(+), 97 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index 28be885bf..67665857e 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -55,24 +55,14 @@ trait DependencyAnalyzer { object DependencyAnalyzer { val analysisLabelName: String = "$$analysisLabel$$" - private val assumptionTypeAnnotationKey = "assumptionType" private val enableDependencyAnalysisAnnotationKey = "enableDependencyAnalysis" - private def extractAnnotationFromInfo(info: ast.Info, annotationKey: String): Option[Seq[String]] = { info.getAllInfos[ast.AnnotationInfo] .filter(_.values.contains(annotationKey)) .map(_.values(annotationKey)).headOption } - def extractDependencyTypeFromInfo(info: ast.Info): Option[DependencyType] = { - val annotation = Some(List.empty) // TODO ake extractAnnotationFromInfo(info, assumptionTypeAnnotationKey) - val dependencyAnalysisInfo = info.getUniqueInfo[FrontendDependencyAnalysisInfo] - if(annotation.isDefined && annotation.get.nonEmpty) AssumptionType.fromString(annotation.get.head).map(DependencyType.make) // TODO ake: assumption and assertion type might not be the same! - else if(dependencyAnalysisInfo.isDefined) dependencyAnalysisInfo.get.dependencyType - else None - } - def extractEnableAnalysisFromInfo(info: ast.Info): Option[Boolean] = { val annotation = extractAnnotationFromInfo(info, enableDependencyAnalysisAnnotationKey) if(annotation.isDefined && annotation.get.nonEmpty) annotation.get.head.toBooleanOption else None @@ -99,12 +89,6 @@ object DependencyAnalyzer { label.split("_")(1).toInt } - def isAssertionLabel(label: String): Boolean = label.startsWith("assertion_") - - def isAssumptionLabel(label: String): Boolean = label.startsWith("assumption_") - - def isAxiomLabel(label: String): Boolean = label.startsWith("axiom_") - /** * * @param name Optional name for the result graph. @@ -119,25 +103,10 @@ object DependencyAnalyzer { newGraph.addAssumptionNodes(dependencyGraphInterpreters.flatMap (_.getGraph.getAssumptionNodes)) newGraph.addAssertionNodes(dependencyGraphInterpreters.flatMap (_.getGraph.getAssertionNodes)) - dependencyGraphInterpreters foreach (interpreter => interpreter.getGraph.getAllEdges foreach {case (t, deps) => newGraph.addEdges(deps, t)}) val joinSourceNodes = dependencyGraphInterpreters flatMap(i => i.joinSourceNodes) - val joinSinkNodes = dependencyGraphInterpreters flatMap(i => i.joinSinkNodes) - val joinCandidateAxioms = dependencyGraphInterpreters flatMap(i => i.axiomNodes) - - // axioms assumed by every method / function should depend on the assertions that justify them - // hence, we add edges from function postconditions & bodies to the corresponding axioms - val axiomAssertionNodes = (joinSourceNodes ++ joinSinkNodes.filter(_.assumptionType.equals(AssumptionType.FunctionBody))) - .groupBy(_.sourceInfo) - .view.mapValues(_.map(_.id)) - .toMap - joinCandidateAxioms - .groupBy(n => n.sourceInfo) // TODO ake: add joinInfoes to the axiom nodes - .map{case (sourceInfo, axiomNodes) => (axiomNodes.map(_.id), axiomAssertionNodes.getOrElse(sourceInfo, Seq.empty))} - .foreach{case (axiomNodeIds, assertionNodeIds) => - newGraph.addEdgesConnectingMethodsDownwards(assertionNodeIds, axiomNodeIds) // TODO ake: maybe we could merge the axiom nodes here since they represent the same axiom? - } + val joinSinkNodes = dependencyGraphInterpreters flatMap(i => i.joinSinkNodes) def getJoinNodesByJoinInfo(candidateNodes: Set[DependencyAnalysisNode], joinType: JoinType) = { candidateNodes @@ -158,42 +127,9 @@ object DependencyAnalyzer { newGraph.addEdgesConnectingMethodsDownwards(matchingSourceNodes.map(_.id), nodes.map(_.id)) } - val newInterpreter = new DependencyGraphInterpreter(name, newGraph, dependencyGraphInterpreters.toList.flatMap(_.getErrors)) newInterpreter } - - def extractAssertionsForJoin(exp: ast.Exp, program: ast.Program): Seq[ast.Exp] = { - exp match { - case FieldAccessPredicate(FieldAccess(rcv, _), prm) => - // Extra case for field access predicates because the contained field access does NOT require already having the field permission. - extractAssertionsForJoin(rcv, program) ++ extractAssertionsForJoin(prm.get, program) - case f: FuncApp => - program.findFunction(f.funcname).pres - case other => other.subExps.flatMap(extractAssertionsForJoin(_, program)) - } - } - - def extractAssertionsForJoin(s: Stmt, p: Program): Seq[ast.Exp] = { - def goE(exp: Exp): Seq[ast.Exp] = extractAssertionsForJoin(exp, p) - - def goEs(exps: Seq[Exp]): Seq[ast.Exp] = exps flatMap goE - - s match { - case NewStmt(lhs, _) => goE(lhs) - case LocalVarAssign(lhs, rhs) => goE(lhs) ++ goE(rhs) - case MethodCall(methodName, args, targets) => - p.findMethod(methodName).pres.flatMap(_.topLevelConjuncts) ++ goEs(args) ++ goEs(targets) - case Inhale(exp) => goE(exp) - case Assume(exp) => goE(exp) - case Seqn(ss, _) => ss flatMap (extractAssertionsForJoin(_, p)) - case If(cond, thn, els) => goE(cond) ++ extractAssertionsForJoin(thn, p) ++ extractAssertionsForJoin(els, p) - case While(cond, invs, body) => goEs(invs) ++ goE(cond) ++ extractAssertionsForJoin(body, p) - case Label(_, invs) => goEs(invs) - case _ => goEs(s.subnodes.filter(_.isInstanceOf[ast.Exp]).map( - _.asInstanceOf[ast.Exp])) - } - } } class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { @@ -203,22 +139,6 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { override def getNodes: Iterable[DependencyAnalysisNode] = dependencyGraph.getNodes - // TODO ake: remove once we are sure this is not needed anymore -// override def getChunkNode(chunk: Chunk): Option[ChunkAnalysisInfo] = { -// val chunkNode = dependencyGraph.getNodes -// .filter(c => c.isInstanceOf[ChunkAnalysisInfo] && chunk.equals(c.asInstanceOf[ChunkAnalysisInfo].getChunk)) -// .map(_.asInstanceOf[ChunkAnalysisInfo]) -// assert(chunkNode.size == 1) -// chunkNode.headOption -// } -// -// private def getChunkNodeIds(oldChunks: Set[Chunk]): Set[Int] = { -// Set.empty -// dependencyGraph.getNodes -// .filter(c => c.isInstanceOf[ChunkAnalysisInfo] && oldChunks.contains(c.asInstanceOf[ChunkAnalysisInfo].getChunk)) -// .map(_.id).toSet -// } - private def getNodeIdsByTerm(terms: Set[Term]): Set[Int] = { dependencyGraph.getNodes .filter(t => terms.contains(t.getTerm)) @@ -234,7 +154,6 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { override def addAssertionNode(node: GeneralAssertionNode): Unit = dependencyGraph.addAssertionNode(node) - // adding assumption nodes override def addAssumption(assumption: Term, analysisInfoes: DependencyAnalysisInfoes, description: Option[String]): Option[Int] = { val node = SimpleAssumptionNode(assumption, description, analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assumptionType, analysisInfoes.getMergeInfo, analysisInfoes.getJoinInfo) addAssumptionNode(node) @@ -282,7 +201,6 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { Some(labelNode) } - // adding assertion nodes override def createAssertOrCheckNode(term: Term, analysisInfoes: DependencyAnalysisInfoes, isCheck: Boolean): Option[GeneralAssertionNode] = { if(isCheck) Some(SimpleCheckNode(term, analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assumptionType, analysisInfoes.getMergeInfo, analysisInfoes.getJoinInfo)) @@ -318,7 +236,6 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { } - // adding dependencies override def addDependency(source: Option[Int], dest: Option[Int]): Unit = { if(source.isDefined && dest.isDefined) dependencyGraph.addEdges(source.get, Set(dest.get)) @@ -326,13 +243,11 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { override def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit = { val assumptionLabels = dep.replace("(", "").replace(")", "").split(" ") - val assumptionIds = assumptionLabels.filter(DependencyAnalyzer.isAssumptionLabel).map(DependencyAnalyzer.getIdFromLabel) - val assertionIdsFromUnsatCore = assumptionLabels.filter(DependencyAnalyzer.isAssertionLabel).map(DependencyAnalyzer.getIdFromLabel) - val assertionIdFromLabel = DependencyAnalyzer.getIdFromLabel(assertionLabel) - val assertionIds = assertionIdFromLabel +: assertionIdsFromUnsatCore - dependencyGraph.addEdges(assumptionIds, assertionIds) - val axiomIds = assumptionLabels.filter(DependencyAnalyzer.isAxiomLabel).map(DependencyAnalyzer.getIdFromLabel) - dependencyGraph.addEdges(axiomIds, assertionIds) + val assertionId = DependencyAnalyzer.getIdFromLabel(assertionLabel) + val assumptionIds = assumptionLabels.map(DependencyAnalyzer.getIdFromLabel).toSet + if(!assumptionIds.contains(assertionId)) + dependencyGraph.addVacuousProof(assertionId) + dependencyGraph.addEdges(assumptionIds.diff(Set(assertionId)), assertionId) } override def addDependenciesForAbstractMembers(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp], analysisInfoes: DependencyAnalysisInfoes): Unit = { diff --git a/src/main/scala/dependencyAnalysis/DependencyGraph.scala b/src/main/scala/dependencyAnalysis/DependencyGraph.scala index 5326613a4..f363f85a2 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraph.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraph.scala @@ -1,6 +1,6 @@ package viper.silicon.dependencyAnalysis -import viper.silver.dependencyAnalysis.{AbstractReadOnlyDependencyGraph, AnalysisSourceInfo, AssumptionType} +import viper.silver.dependencyAnalysis.{AbstractReadOnlyDependencyGraph, AssumptionType} import java.io.PrintWriter import java.nio.file.Paths @@ -40,6 +40,7 @@ class DependencyGraph extends ReadOnlyDependencyGraph { private val edges: mutable.Map[Int, Set[Int]] = mutable.Map.empty private val edgesConnectingMethodsDownwards: mutable.Map[Int, Set[Int]] = mutable.Map.empty // keep this, it's relevant for computing verification progress private val edgesConnectingMethodsUpwards: mutable.Map[Int, Set[Int]] = mutable.Map.empty // keep this, it's relevant for computing verification progress + private var vacuousProofs: mutable.Seq[Int] = mutable.Seq() def getNodes: Seq[DependencyAnalysisNode] = getAssumptionNodes ++ getAssertionNodes def getAssumptionNodes: Seq[GeneralAssumptionNode] = assumptionNodes.toSeq @@ -79,6 +80,8 @@ class DependencyGraph extends ReadOnlyDependencyGraph { allEdges.toMap } + def getVacuousProofs: Set[Int] = vacuousProofs.toSet // TODO ake: what to do with these? + def addAssumptionNode(node: GeneralAssumptionNode): Unit = { assumptionNodes = assumptionNodes :+ node } @@ -148,6 +151,11 @@ class DependencyGraph extends ReadOnlyDependencyGraph { addEdgesConnectingMethodsUpwards(Set(source), targets) } + + def addVacuousProof(assertionId: Int) = { + vacuousProofs = assertionId +: vacuousProofs + } + @deprecated // needs to be adapted to the notion of upward and downward edges def existsAnyDependency(sources: Set[Int], targets: Set[Int], includeInfeasibilityNodes: Boolean): Boolean = { val infeasibilityNodeIds: Set[Int] = if(includeInfeasibilityNodes) Set.empty else (getAssumptionNodes filter (_.isInstanceOf[InfeasibilityNode]) map (_.id)).toSet diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 49995ea0d..b6c5694e6 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -39,7 +39,6 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen val joinSinkNodes: Set[DependencyAnalysisNode] = getJoinCandidateNodes(getNodes).filter(_.joinInfoes.exists(_.joinType.equals(JoinType.Sink))) val joinSourceNodes: Set[DependencyAnalysisNode] = getJoinCandidateNodes(getNodes).filter(_.joinInfoes.exists(_.joinType.equals(JoinType.Source))) - val axiomNodes: Set[GeneralAssumptionNode] = dependencyGraph.getAssumptionNodes.filter(_.isInstanceOf[AxiomAssumptionNode]).toSet def getJoinCandidateNodes(nodes: Set[DependencyAnalysisNode]): Set[DependencyAnalysisNode] = nodes.filter(node => node.joinInfoes.nonEmpty) diff --git a/src/main/scala/supporters/functions/FunctionData.scala b/src/main/scala/supporters/functions/FunctionData.scala index f8405eaa3..464b62373 100644 --- a/src/main/scala/supporters/functions/FunctionData.scala +++ b/src/main/scala/supporters/functions/FunctionData.scala @@ -142,7 +142,7 @@ class FunctionData(val programFunction: ast.Function, private val bodyAnalysisInfoes: DependencyAnalysisInfoes = if(programFunction.body.isDefined) DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes.addInfo(programFunction.body.get.info, programFunction.body.get) - .withJoinInfo(SimpleDependencyAnalysisJoin(AnalysisSourceInfo.createAnalysisSourceInfo(programFunction.body.get), JoinType.Source, EdgeType.Down)) + .withJoinInfo(SimpleDependencyAnalysisJoin(AnalysisSourceInfo.createAnalysisSourceInfo(programFunction.body.get), JoinType.Sink, EdgeType.Down)) else DependencyAnalysisInfoes.create("unverified function body", DependencyType.Internal) @@ -249,7 +249,7 @@ class FunctionData(val programFunction: ast.Function, (Forall(arguments, wrapBody(And(generateNestedDefinitionalAxioms)), Trigger(limitedFunctionApplication)), bodyAnalysisInfoes) +: programFunction.posts.flatMap(_.topLevelConjuncts).map({p => val terms = expressionTranslator.translatePostcondition(program, Seq(p), this) - (And(Forall(arguments, wrapBody(Implies(pre, And(terms))), Trigger(limitedFunctionApplication)), True), analysisInfoes.addInfo(p.info, p).withJoinInfo(SimpleDependencyAnalysisJoin(AnalysisSourceInfo.createAnalysisSourceInfo(p), JoinType.Source, EdgeType.Down))) + (And(Forall(arguments, wrapBody(Implies(pre, And(terms))), Trigger(limitedFunctionApplication)), True), analysisInfoes.addInfo(p.info, p).withJoinInfo(SimpleDependencyAnalysisJoin(AnalysisSourceInfo.createAnalysisSourceInfo(p), JoinType.Sink, EdgeType.Down))) }) }else{ val innermostBody = And(generateNestedDefinitionalAxioms ++ List(Implies(pre, And(translatedPosts)))) diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index 508883930..557146401 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -281,7 +281,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver val wExp = evaluator.withExp decider.dependencyAnalyzer.addNodes(v.decider.prover.getPreambleAnalysisNodes) - + val precondAnalysisSourceInfoes = DependencyAnalysisInfoes.create("preconditions", DependencyType.Internal) val analysisInfoesPostcondition = DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Source, EdgeType.Down)) val analysisInfoesBody = DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes.addInfo(body.info, body) .withJoinInfo(SimpleDependencyAnalysisJoin(AnalysisSourceInfo.createAnalysisSourceInfo(body), JoinType.Source, EdgeType.Down)) @@ -311,7 +311,6 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver case (intermediateResult, Phase1Data(sPre, bcsPre, bcsPreExp, pcsPre, pcsPreExp)) => intermediateResult && executionFlowController.locally(sPre, v)((s1, _) => { val labelledBcsPre = terms.And(bcsPre map (t => v.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t)))) - val precondAnalysisSourceInfoes = DependencyAnalysisInfoes.create("preconditions", DependencyType.Internal) decider.setCurrentBranchCondition(labelledBcsPre, (BigAnd(bcsPreExp.map(_._1)), Option.when(wExp)(BigAnd(bcsPreExp.map(_._2.get)))), precondAnalysisSourceInfoes) val labelledPcsPre = pcsPre map (t => v.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t))) decider.assume(labelledPcsPre, pcsPreExp, s"precondition of ${function.name}", enforceAssumption=false, precondAnalysisSourceInfoes) From 620003d7ef9793dd9ab5536c4e885350c43831d4 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 31 Mar 2026 15:26:31 +0200 Subject: [PATCH 410/474] renaming --- src/main/scala/debugger/SiliconDebugger.scala | 6 +- src/main/scala/decider/Decider.scala | 106 ++--- .../dependencyAnalysis/AnalysisInfo.scala | 4 +- .../DependencyAnalysisInfo.scala | 72 +-- .../DependencyAnalysisNode.scala | 24 +- .../DependencyAnalyzer.scala | 100 ++--- .../DependencyGraphImporter.scala | 14 +- .../DependencyGraphInterpreter.scala | 10 +- .../scala/interfaces/decider/Prover.scala | 6 +- .../NonQuantifiedPropertyInterpreter.scala | 4 +- src/main/scala/rules/Brancher.scala | 26 +- src/main/scala/rules/ChunkSupporter.scala | 82 ++-- src/main/scala/rules/Consumer.scala | 100 ++--- src/main/scala/rules/ConsumptionResult.scala | 6 +- src/main/scala/rules/Evaluator.scala | 412 +++++++++--------- src/main/scala/rules/Executor.scala | 132 +++--- src/main/scala/rules/HavocSupporter.scala | 24 +- src/main/scala/rules/HeapSupporter.scala | 124 +++--- src/main/scala/rules/Joiner.scala | 16 +- src/main/scala/rules/LetSupporter.scala | 20 +- src/main/scala/rules/MagicWandSupporter.scala | 76 ++-- .../rules/MoreCompleteExhaleSupporter.scala | 92 ++-- .../scala/rules/PermissionSupporter.scala | 14 +- src/main/scala/rules/PredicateSupporter.scala | 58 +-- src/main/scala/rules/Producer.scala | 100 ++--- .../scala/rules/QuantifiedChunkSupport.scala | 178 ++++---- src/main/scala/rules/StateConsolidator.scala | 92 ++-- src/main/scala/supporters/Domains.scala | 6 +- .../scala/supporters/MethodSupporter.scala | 12 +- .../PredicateVerificationUnit.scala | 4 +- .../scala/supporters/SnapshotSupporter.scala | 12 +- .../supporters/functions/FunctionData.scala | 38 +- .../functions/FunctionVerificationUnit.scala | 32 +- .../verifier/VerificationPoolManager.scala | 4 +- .../DependencyAnalysisTestFramework.scala | 4 +- 35 files changed, 1005 insertions(+), 1005 deletions(-) diff --git a/src/main/scala/debugger/SiliconDebugger.scala b/src/main/scala/debugger/SiliconDebugger.scala index a3b30f122..d92eba389 100644 --- a/src/main/scala/debugger/SiliconDebugger.scala +++ b/src/main/scala/debugger/SiliconDebugger.scala @@ -2,7 +2,7 @@ package viper.silicon.debugger import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.decider.{Cvc5ProverStdIO, RecordedPathConditions, Z3ProverStdIO} -import viper.silicon.dependencyAnalysis.DependencyAnalysisInfoes +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfos import viper.silicon.interfaces.state.Chunk import viper.silicon.interfaces.{Failure, SiliconDebuggingFailureContext, Success, VerificationResult} import viper.silicon.resources.{FieldID, PredicateID} @@ -422,7 +422,7 @@ class SiliconDebugger(verificationResults: List[VerificationResult], var resE: ast.Exp = null var resV: Verifier = null val pve: PartialVerificationError = PartialVerificationError(r => ContractNotWellformed(assertionE, r)) - val verificationResult = evaluator.eval3(obl.s, assertionE, pve, obl.v, DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes)((_, t, newE, newV) => { + val verificationResult = evaluator.eval3(obl.s, assertionE, pve, obl.v, DependencyAnalysisInfos.DefaultDependencyAnalysisInfos)((_, t, newE, newV) => { resT = t resE = newE.get resV = newV @@ -489,7 +489,7 @@ class SiliconDebugger(verificationResults: List[VerificationResult], var evalPcs: RecordedPathConditions = null val pve: PartialVerificationError = PartialVerificationError(r => ContractNotWellformed(e, r)) val beforeEval = v.decider.setPathConditionMark() - val verificationResult = evaluator.eval3(obl.s, e, pve, v, DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes)((newS, t, newE, newV) => { + val verificationResult = evaluator.eval3(obl.s, e, pve, v, DependencyAnalysisInfos.DefaultDependencyAnalysisInfos)((newS, t, newE, newV) => { resS = newS resT = t resE = newE.get diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 7f5b7006f..6f664ba6a 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -51,10 +51,10 @@ trait Decider { def pushScope(): Unit def popScope(): Unit - def checkSmoke(analysisInfoes: DependencyAnalysisInfoes): Boolean - def checkSmoke(analysisInfoes: DependencyAnalysisInfoes, isAssert: Boolean = false): Boolean + def checkSmoke(analysisInfos: DependencyAnalysisInfos): Boolean + def checkSmoke(analysisInfos: DependencyAnalysisInfos, isAssert: Boolean = false): Boolean - def setCurrentBranchCondition(t: Term, te: (ast.Exp, Option[ast.Exp]), analysisInfoes: DependencyAnalysisInfoes): Unit + def setCurrentBranchCondition(t: Term, te: (ast.Exp, Option[ast.Exp]), analysisInfos: DependencyAnalysisInfos): Unit def setPathConditionMark(): Mark def finishDebugSubExp(description : String): Unit @@ -66,24 +66,24 @@ trait Decider { def wrapWithDependencyAnalysisLabel(term: Term, sourceChunks: Iterable[Chunk] = Set.empty, sourceTerms: Iterable[Term] = Set.empty): Term def isPathInfeasible: Boolean - def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp], analysisInfoes: DependencyAnalysisInfoes): Unit - def assume(t: Term, debugExp: Option[DebugExp], analysisInfoes: DependencyAnalysisInfoes): Unit - def assume(assumptions: Iterable[Term], debugExps: Option[Iterable[DebugExp]], description: String, enforceAssumption: Boolean, analysisInfoes: DependencyAnalysisInfoes): Unit - def assume(terms: Seq[Term], debugExps: Option[Seq[DebugExp]], analysisInfoes: DependencyAnalysisInfoes): Unit - def assumeDefinition(t: Term, debugExp: Option[DebugExp], analysisInfoes: DependencyAnalysisInfoes): Unit - def assume(terms: Iterable[Term], debugExp: Option[DebugExp], enforceAssumption: Boolean, analysisInfoes: DependencyAnalysisInfoes): Unit + def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp], analysisInfos: DependencyAnalysisInfos): Unit + def assume(t: Term, debugExp: Option[DebugExp], analysisInfos: DependencyAnalysisInfos): Unit + def assume(assumptions: Iterable[Term], debugExps: Option[Iterable[DebugExp]], description: String, enforceAssumption: Boolean, analysisInfos: DependencyAnalysisInfos): Unit + def assume(terms: Seq[Term], debugExps: Option[Seq[DebugExp]], analysisInfos: DependencyAnalysisInfos): Unit + def assumeDefinition(t: Term, debugExp: Option[DebugExp], analysisInfos: DependencyAnalysisInfos): Unit + def assume(terms: Iterable[Term], debugExp: Option[DebugExp], enforceAssumption: Boolean, analysisInfos: DependencyAnalysisInfos): Unit def assumeLabel(term: Term, assumptionLabel: String): Unit - def check(t: Term, timeout: Int, analysisInfoes: DependencyAnalysisInfoes): Boolean - def checkSmokeAndSetInfeasibilityNode(analysisInfoes: DependencyAnalysisInfoes): Unit + def check(t: Term, timeout: Int, analysisInfos: DependencyAnalysisInfos): Boolean + def checkSmokeAndSetInfeasibilityNode(analysisInfos: DependencyAnalysisInfos): Unit /* TODO: Consider changing assert such that * 1. It passes State and Operations to the continuation * 2. The implementation reacts to a failing assertion by e.g. a state consolidation */ - def assert(t: Term, analysisInfoes: DependencyAnalysisInfoes)(Q: Boolean => VerificationResult): VerificationResult - def assert(t: Term, analysisInfoes: DependencyAnalysisInfoes, timeout: Option[Int])(Q: Boolean => VerificationResult): VerificationResult + def assert(t: Term, analysisInfos: DependencyAnalysisInfos)(Q: Boolean => VerificationResult): VerificationResult + def assert(t: Term, analysisInfos: DependencyAnalysisInfos, timeout: Option[Int])(Q: Boolean => VerificationResult): VerificationResult def fresh(id: String, sort: Sort, ptype: Option[PType]): Var def fresh(id: String, argSorts: Seq[Sort], resultSort: Sort): Function @@ -115,9 +115,9 @@ trait Decider { var dependencyAnalyzer: DependencyAnalyzer def initDependencyAnalyzer(member: Member, preambleNodes: Iterable[DependencyAnalysisNode]): Unit def removeDependencyAnalyzer(): Unit - def getAnalysisInfo(daInfoes: DependencyAnalysisInfoes): AnalysisInfo + def getAnalysisInfo(daInfos: DependencyAnalysisInfos): AnalysisInfo def isDependencyAnalysisEnabled: Boolean - def handleFailedAssertionForDependencyAnalysis(failedAssertion: Term, analysisInfoes: DependencyAnalysisInfoes, assumeFailedAssertion: Boolean): Unit + def handleFailedAssertionForDependencyAnalysis(failedAssertion: Term, analysisInfos: DependencyAnalysisInfos, assumeFailedAssertion: Boolean): Unit } /* @@ -165,7 +165,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => dependencyAnalyzer = new NoDependencyAnalyzer } - def getAnalysisInfo(analysisInfoes: DependencyAnalysisInfoes): AnalysisInfo = AnalysisInfo(this, dependencyAnalyzer, analysisInfoes) + def getAnalysisInfo(analysisInfos: DependencyAnalysisInfos): AnalysisInfo = AnalysisInfo(this, dependencyAnalyzer, analysisInfos) def functionDecls: Set[FunctionDecl] = _declaredFreshFunctions def macroDecls: Vector[MacroDecl] = _declaredFreshMacros @@ -286,11 +286,11 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => //symbExLog.closeScope(sepIdentifier) } - def setCurrentBranchCondition(t: Term, te: (ast.Exp, Option[ast.Exp]), analysisInfoes: DependencyAnalysisInfoes): Unit = { + def setCurrentBranchCondition(t: Term, te: (ast.Exp, Option[ast.Exp]), analysisInfos: DependencyAnalysisInfos): Unit = { if(isPathInfeasible) return pathConditions.setCurrentBranchCondition(t, te) - assume(t, Option.when(te._2.isDefined)(te._1), te._2, analysisInfoes) + assume(t, Option.when(te._2.isDefined)(te._1), te._2, analysisInfos) } def setPathConditionMark(): Mark = pathConditions.mark() @@ -374,23 +374,23 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => - def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp], analysisInfoes: DependencyAnalysisInfoes): Unit = { + def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp], analysisInfos: DependencyAnalysisInfos): Unit = { if (finalExp.isDefined) { - assume(assumptions=InsertionOrderedSet((t, Some(DebugExp.createInstance(e.get, finalExp.get)))), enforceAssumption = false, isDefinition = false, analysisInfoes) + assume(assumptions=InsertionOrderedSet((t, Some(DebugExp.createInstance(e.get, finalExp.get)))), enforceAssumption = false, isDefinition = false, analysisInfos) } else { - assume(assumptions=InsertionOrderedSet((t, None)), enforceAssumption = false, isDefinition = false, analysisInfoes) + assume(assumptions=InsertionOrderedSet((t, None)), enforceAssumption = false, isDefinition = false, analysisInfos) } } - def assume(t: Term, debugExp: Option[DebugExp], analysisInfoes: DependencyAnalysisInfoes): Unit = { - assume(InsertionOrderedSet(Seq((t, debugExp))), enforceAssumption = false, isDefinition = false, analysisInfoes) + def assume(t: Term, debugExp: Option[DebugExp], analysisInfos: DependencyAnalysisInfos): Unit = { + assume(InsertionOrderedSet(Seq((t, debugExp))), enforceAssumption = false, isDefinition = false, analysisInfos) } - def assumeDefinition(t: Term, debugExp: Option[DebugExp], analysisInfoes: DependencyAnalysisInfoes): Unit = { - assume(InsertionOrderedSet(Seq((t, debugExp))), enforceAssumption=false, isDefinition=true, analysisInfoes) + def assumeDefinition(t: Term, debugExp: Option[DebugExp], analysisInfos: DependencyAnalysisInfos): Unit = { + assume(InsertionOrderedSet(Seq((t, debugExp))), enforceAssumption=false, isDefinition=true, analysisInfos) } - def assume(assumptions: InsertionOrderedSet[(Term, Option[DebugExp])], enforceAssumption: Boolean, isDefinition: Boolean, analysisInfoes: DependencyAnalysisInfoes): Unit = { + def assume(assumptions: InsertionOrderedSet[(Term, Option[DebugExp])], enforceAssumption: Boolean, isDefinition: Boolean, analysisInfos: DependencyAnalysisInfos): Unit = { val filteredAssumptions = if (enforceAssumption) assumptions else assumptions filterNot (a => isKnownToBeTrue(a._1)) @@ -401,15 +401,15 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } val filteredAssumptionsWithLabels = filteredAssumptions map{case (t, _) => - val assumptionId: Option[Int] = dependencyAnalyzer.addAssumption(t, analysisInfoes) + val assumptionId: Option[Int] = dependencyAnalyzer.addAssumption(t, analysisInfos) (t, DependencyAnalyzer.createAssumptionLabel(assumptionId)) } if (filteredAssumptions.nonEmpty) assumeWithoutSmokeChecks(filteredAssumptionsWithLabels, isDefinition=isDefinition) } - def assume(assumptions: Seq[Term], debugExps: Option[Seq[DebugExp]], analysisInfoes: DependencyAnalysisInfoes): Unit = { - val assumptionsWithLabels = addAssumptionLabels(assumptions, analysisInfoes) + def assume(assumptions: Seq[Term], debugExps: Option[Seq[DebugExp]], analysisInfos: DependencyAnalysisInfos): Unit = { + val assumptionsWithLabels = addAssumptionLabels(assumptions, analysisInfos) assumeWithoutSmokeChecks(InsertionOrderedSet(assumptionsWithLabels)) if (debugMode) { @@ -417,7 +417,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } } - def assume(assumptions: Iterable[Term], debugExps: Option[Iterable[DebugExp]], description: String, enforceAssumption: Boolean, analysisInfoes: DependencyAnalysisInfoes): Unit = { + def assume(assumptions: Iterable[Term], debugExps: Option[Iterable[DebugExp]], description: String, enforceAssumption: Boolean, analysisInfos: DependencyAnalysisInfos): Unit = { val debugExp = Option.when(debugExps.isDefined)(DebugExp.createInstance(description, InsertionOrderedSet(debugExps.get))) val filteredTerms = @@ -426,7 +426,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => if(filteredTerms.isEmpty) return - val assumptionsWithLabels = addAssumptionLabels(filteredTerms, analysisInfoes) + val assumptionsWithLabels = addAssumptionLabels(filteredTerms, analysisInfos) assumeWithoutSmokeChecks(InsertionOrderedSet(assumptionsWithLabels)) @@ -435,14 +435,14 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } } - private def addAssumptionLabels(filteredTerms: Iterable[Term], analysisInfoes: DependencyAnalysisInfoes) = { + private def addAssumptionLabels(filteredTerms: Iterable[Term], analysisInfos: DependencyAnalysisInfos) = { filteredTerms map (t => { - val assumptionIds = dependencyAnalyzer.addAssumption(t, analysisInfoes) + val assumptionIds = dependencyAnalyzer.addAssumption(t, analysisInfos) (t, DependencyAnalyzer.createAssumptionLabel(assumptionIds)) }) } - def assume(terms: Iterable[Term], debugExp: Option[DebugExp], enforceAssumption: Boolean, analysisInfoes: DependencyAnalysisInfoes): Unit = { + def assume(terms: Iterable[Term], debugExp: Option[DebugExp], enforceAssumption: Boolean, analysisInfos: DependencyAnalysisInfos): Unit = { val filteredTerms = if (enforceAssumption) terms else terms filterNot isKnownToBeTrue @@ -452,7 +452,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => if (debugMode) { addDebugExp(debugExp.get.withTerm(And(filteredTerms))) } - val termsWithLabel = addAssumptionLabels(filteredTerms, analysisInfoes) + val termsWithLabel = addAssumptionLabels(filteredTerms, analysisInfos) assumeWithoutSmokeChecks(InsertionOrderedSet(termsWithLabel)) } @@ -494,10 +494,10 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => /* Asserting facts */ - def checkSmoke(analysisInfoes: DependencyAnalysisInfoes): Boolean = checkSmoke(analysisInfoes, isAssert = false) + def checkSmoke(analysisInfos: DependencyAnalysisInfos): Boolean = checkSmoke(analysisInfos, isAssert = false) - def checkSmoke(analysisInfoes: DependencyAnalysisInfoes, isAssert: Boolean=false): Boolean = { - val checkNode = dependencyAnalyzer.createAssertOrCheckNode(False, analysisInfoes, !isAssert) + def checkSmoke(analysisInfos: DependencyAnalysisInfos, isAssert: Boolean=false): Boolean = { + val checkNode = dependencyAnalyzer.createAssertOrCheckNode(False, analysisInfos, !isAssert) val label = DependencyAnalyzer.createAssertionLabel(checkNode.map(_.id)) val timeout = if (isAssert) Verifier.config.assertTimeout.toOption else Verifier.config.checkTimeout.toOption @@ -509,7 +509,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => }else if(result){ checkNode foreach dependencyAnalyzer.addAssertionNode dependencyAnalyzer.processUnsatCoreAndAddDependencies(prover.getLastUnsatCore, label) - val infeasibleNodeId = dependencyAnalyzer.addInfeasibilityNode(!isAssert, analysisInfoes) + val infeasibleNodeId = dependencyAnalyzer.addInfeasibilityNode(!isAssert, analysisInfos) // THIS WOULD BE UNSOUND! Unsoundness is introduced when infeasibility is introduced while executing a package statements and pontentially in other cases as well. // assumeWithoutSmokeChecks(InsertionOrderedSet((False, DependencyAnalyzer.createAssumptionLabel(infeasibleNodeId)))) dependencyAnalyzer.addDependency(checkNode.map(_.id), infeasibleNodeId) @@ -518,38 +518,38 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => result } - def checkSmokeAndSetInfeasibilityNode(analysisInfoes: DependencyAnalysisInfoes): Unit = { + def checkSmokeAndSetInfeasibilityNode(analysisInfos: DependencyAnalysisInfos): Unit = { var infeasibilityNodeId: Option[Int] = pcs.getCurrentInfeasibilityNode if(infeasibilityNodeId.isDefined) return - val (success, checkNode) = deciderAssert(False, analysisInfoes, Some(Verifier.config.checkTimeout()), isCheck=true) + val (success, checkNode) = deciderAssert(False, analysisInfos, Some(Verifier.config.checkTimeout()), isCheck=true) if(success){ - infeasibilityNodeId = dependencyAnalyzer.addInfeasibilityNode(isCheck = true, analysisInfoes) + infeasibilityNodeId = dependencyAnalyzer.addInfeasibilityNode(isCheck = true, analysisInfos) dependencyAnalyzer.addDependency(checkNode.map(_.id), infeasibilityNodeId) pcs.setCurrentInfeasibilityNode(infeasibilityNodeId) } } - override def handleFailedAssertionForDependencyAnalysis(failedAssertion: Term, analysisInfoes: DependencyAnalysisInfoes, assumeFailedAssertion: Boolean): Unit = { - dependencyAnalyzer.addAssertionFailedNode(failedAssertion, analysisInfoes) + override def handleFailedAssertionForDependencyAnalysis(failedAssertion: Term, analysisInfos: DependencyAnalysisInfos, assumeFailedAssertion: Boolean): Unit = { + dependencyAnalyzer.addAssertionFailedNode(failedAssertion, analysisInfos) if(assumeFailedAssertion){ - assume(failedAssertion, None, None, analysisInfoes) + assume(failedAssertion, None, None, analysisInfos) failedAssertion match { - case False => checkSmokeAndSetInfeasibilityNode(analysisInfoes) + case False => checkSmokeAndSetInfeasibilityNode(analysisInfos) case _ => } } } - def check(t: Term, timeout: Int, analysisInfoes: DependencyAnalysisInfoes): Boolean = { - deciderAssert(t, analysisInfoes, Some(timeout), isCheck=true)._1 + def check(t: Term, timeout: Int, analysisInfos: DependencyAnalysisInfos): Boolean = { + deciderAssert(t, analysisInfos, Some(timeout), isCheck=true)._1 } - def assert(t: Term, analysisInfoes: DependencyAnalysisInfoes)(Q: Boolean => VerificationResult): VerificationResult = assert(t, analysisInfoes, timeout=Verifier.config.assertTimeout.toOption)(Q) + def assert(t: Term, analysisInfos: DependencyAnalysisInfos)(Q: Boolean => VerificationResult): VerificationResult = assert(t, analysisInfos, timeout=Verifier.config.assertTimeout.toOption)(Q) - def assert(t: Term, analysisInfoes: DependencyAnalysisInfoes, timeout: Option[Int])(Q: Boolean => VerificationResult): VerificationResult = { - val (success, _) = deciderAssert(t, analysisInfoes, timeout) + def assert(t: Term, analysisInfos: DependencyAnalysisInfos, timeout: Option[Int])(Q: Boolean => VerificationResult): VerificationResult = { + val (success, _) = deciderAssert(t, analysisInfos, timeout) // If the SMT query was not successful, store it (possibly "overwriting" // any previously saved query), otherwise discard any query we had saved @@ -563,13 +563,13 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => Q(success) } - private def deciderAssert(t: Term, analysisInfoes: DependencyAnalysisInfoes, timeout: Option[Int], isCheck: Boolean=false) = { + private def deciderAssert(t: Term, analysisInfos: DependencyAnalysisInfos, timeout: Option[Int], isCheck: Boolean=false) = { val assertRecord = new DeciderAssertRecord(t, timeout) val sepIdentifier = symbExLog.openScope(assertRecord) val asserted = if(isDependencyAnalysisEnabled) t.equals(True) else isKnownToBeTrue(t) - val assertNode = if(!asserted) dependencyAnalyzer.createAssertOrCheckNode(t, analysisInfoes, isCheck) else None + val assertNode = if(!asserted) dependencyAnalyzer.createAssertOrCheckNode(t, analysisInfos, isCheck) else None val result = asserted || proverAssert(t, timeout, DependencyAnalyzer.createAssertionLabel(assertNode map (_.id))) diff --git a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala index 8603fa10e..2d5e353c9 100644 --- a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala +++ b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala @@ -4,7 +4,7 @@ package viper.silicon.dependencyAnalysis import viper.silicon.decider.Decider import viper.silver.dependencyAnalysis.DependencyType -case class AnalysisInfo(decider: Decider, dependencyAnalyzer: DependencyAnalyzer, analysisInfoes: DependencyAnalysisInfoes) { - def withDependencyType(dependencyType: DependencyType) = this.copy(analysisInfoes=analysisInfoes.withDependencyType(dependencyType)) +case class AnalysisInfo(decider: Decider, dependencyAnalyzer: DependencyAnalyzer, analysisInfos: DependencyAnalysisInfos) { + def withDependencyType(dependencyType: DependencyType) = this.copy(analysisInfos=analysisInfos.withDependencyType(dependencyType)) } diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala index 3361ed288..15afc86f0 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala @@ -5,73 +5,73 @@ import viper.silver.ast.{DependencyAnalysisJoinInfo, DependencyAnalysisMergeInfo import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, DependencyType, StringAnalysisSourceInfo} -trait AnalysisInfoes { +trait AnalysisInfos { } -case class DependencyAnalysisInfoes(sourceInfoes: List[AnalysisSourceInfo], dependencyTypes: List[DependencyTypeInfo], mergeInfoes: List[DependencyAnalysisMergeInfo], joinInfoes: List[DependencyAnalysisJoinInfo], nodes: List[ast.Node]) extends AnalysisInfoes { +case class DependencyAnalysisInfos(sourceInfos: List[AnalysisSourceInfo], dependencyTypes: List[DependencyTypeInfo], mergeInfos: List[DependencyAnalysisMergeInfo], joinInfos: List[DependencyAnalysisJoinInfo], nodes: List[ast.Node]) extends AnalysisInfos { - def addInfo(info: ast.Info, node: ast.Node): DependencyAnalysisInfoes = { - val newSourceInfoes = sourceInfoes ++ info.getUniqueInfo[AnalysisSourceInfo].toList - val newDependencyInfoes = dependencyTypes ++ info.getUniqueInfo[DependencyTypeInfo].toList - val newMergeInfoes = mergeInfoes ++ info.getUniqueInfo[DependencyAnalysisMergeInfo].toList - val newJoinInfoes = joinInfoes ++ info.getUniqueInfo[DependencyAnalysisJoinInfo].toList - DependencyAnalysisInfoes(newSourceInfoes, newDependencyInfoes, newMergeInfoes, newJoinInfoes, nodes ++ List(node)) + def addInfo(info: ast.Info, node: ast.Node): DependencyAnalysisInfos = { + val newSourceInfos = sourceInfos ++ info.getUniqueInfo[AnalysisSourceInfo].toList + val newDependencyInfos = dependencyTypes ++ info.getUniqueInfo[DependencyTypeInfo].toList + val newMergeInfos = mergeInfos ++ info.getUniqueInfo[DependencyAnalysisMergeInfo].toList + val newJoinInfos = joinInfos ++ info.getUniqueInfo[DependencyAnalysisJoinInfo].toList + DependencyAnalysisInfos(newSourceInfos, newDependencyInfos, newMergeInfos, newJoinInfos, nodes ++ List(node)) } - def addInfo(info: ast.Info): DependencyAnalysisInfoes = { - val newSourceInfoes = sourceInfoes ++ info.getUniqueInfo[AnalysisSourceInfo].toList - val newDependencyInfoes = dependencyTypes ++ info.getUniqueInfo[DependencyTypeInfo].toList - val newMergeInfoes = mergeInfoes ++ info.getUniqueInfo[DependencyAnalysisMergeInfo].toList - val newJoinInfoes = joinInfoes ++ info.getUniqueInfo[DependencyAnalysisJoinInfo].toList - DependencyAnalysisInfoes(newSourceInfoes, newDependencyInfoes, newMergeInfoes, newJoinInfoes, nodes) + def addInfo(info: ast.Info): DependencyAnalysisInfos = { + val newSourceInfos = sourceInfos ++ info.getUniqueInfo[AnalysisSourceInfo].toList + val newDependencyInfos = dependencyTypes ++ info.getUniqueInfo[DependencyTypeInfo].toList + val newMergeInfos = mergeInfos ++ info.getUniqueInfo[DependencyAnalysisMergeInfo].toList + val newJoinInfos = joinInfos ++ info.getUniqueInfo[DependencyAnalysisJoinInfo].toList + DependencyAnalysisInfos(newSourceInfos, newDependencyInfos, newMergeInfos, newJoinInfos, nodes) } - def addInfo(infoString: String, pos: ast.Position, dependencyType: DependencyType): DependencyAnalysisInfoes = - this.copy(sourceInfoes = sourceInfoes ++ List(StringAnalysisSourceInfo(infoString, pos)), dependencyTypes = dependencyTypes ++ List(DependencyTypeInfo(dependencyType))) + def addInfo(infoString: String, pos: ast.Position, dependencyType: DependencyType): DependencyAnalysisInfos = + this.copy(sourceInfos = sourceInfos ++ List(StringAnalysisSourceInfo(infoString, pos)), dependencyTypes = dependencyTypes ++ List(DependencyTypeInfo(dependencyType))) - def withDependencyType(dependencyType: DependencyType): DependencyAnalysisInfoes = { + def withDependencyType(dependencyType: DependencyType): DependencyAnalysisInfos = { this.copy(dependencyTypes = DependencyTypeInfo(dependencyType) +: dependencyTypes) } - def withSource(source: AnalysisSourceInfo): DependencyAnalysisInfoes = { - this.copy(sourceInfoes = source +: sourceInfoes) + def withSource(source: AnalysisSourceInfo): DependencyAnalysisInfos = { + this.copy(sourceInfos = source +: sourceInfos) } - def getSourceInfo: AnalysisSourceInfo = sourceInfoes.head + def getSourceInfo: AnalysisSourceInfo = sourceInfos.head def getDependencyType: DependencyType = dependencyTypes.head.dependencyType - def getMergeInfo: DependencyAnalysisMergeInfo = mergeInfoes.headOption.getOrElse(SimpleDependencyAnalysisMerge(getSourceInfo)) + def getMergeInfo: DependencyAnalysisMergeInfo = mergeInfos.headOption.getOrElse(SimpleDependencyAnalysisMerge(getSourceInfo)) def getJoinInfo: List[SimpleDependencyAnalysisJoin] = { - if(joinInfoes.isEmpty) return List.empty - val h = joinInfoes.head match { - case EvalStackDependencyAnalysisJoin(joinType, edgeType) => SimpleDependencyAnalysisJoin(sourceInfoes.last, joinType, edgeType) + if(joinInfos.isEmpty) return List.empty + val h = joinInfos.head match { + case EvalStackDependencyAnalysisJoin(joinType, edgeType) => SimpleDependencyAnalysisJoin(sourceInfos.last, joinType, edgeType) case a: SimpleDependencyAnalysisJoin => a } List(h) } - def withMergeInfo(mergeInfo: DependencyAnalysisMergeInfo): DependencyAnalysisInfoes = - this.copy(mergeInfoes = mergeInfo +: mergeInfoes) + def withMergeInfo(mergeInfo: DependencyAnalysisMergeInfo): DependencyAnalysisInfos = + this.copy(mergeInfos = mergeInfo +: mergeInfos) - def withJoinInfo(joinInfo: DependencyAnalysisJoinInfo): DependencyAnalysisInfoes = - this.copy(joinInfoes = joinInfo +: joinInfoes) + def withJoinInfo(joinInfo: DependencyAnalysisJoinInfo): DependencyAnalysisInfos = + this.copy(joinInfos = joinInfo +: joinInfos) } -object DependencyAnalysisInfoes { - val DefaultDependencyAnalysisInfoes = DependencyAnalysisInfoes(List.empty, List.empty, List.empty, List.empty, List.empty) +object DependencyAnalysisInfos { + val DefaultDependencyAnalysisInfos = DependencyAnalysisInfos(List.empty, List.empty, List.empty, List.empty, List.empty) - def create(sourceInfo: AnalysisSourceInfo, dependencyType: DependencyType, mergeInfo: DependencyAnalysisMergeInfo): DependencyAnalysisInfoes = - DependencyAnalysisInfoes(List(sourceInfo), List(DependencyTypeInfo(dependencyType)), List(mergeInfo), List.empty, List.empty) + def create(sourceInfo: AnalysisSourceInfo, dependencyType: DependencyType, mergeInfo: DependencyAnalysisMergeInfo): DependencyAnalysisInfos = + DependencyAnalysisInfos(List(sourceInfo), List(DependencyTypeInfo(dependencyType)), List(mergeInfo), List.empty, List.empty) - def create(sourceInfo: AnalysisSourceInfo, dependencyType: DependencyType): DependencyAnalysisInfoes = - DependencyAnalysisInfoes(List(sourceInfo), List(DependencyTypeInfo(dependencyType)), List.empty, List.empty, List.empty) + def create(sourceInfo: AnalysisSourceInfo, dependencyType: DependencyType): DependencyAnalysisInfos = + DependencyAnalysisInfos(List(sourceInfo), List(DependencyTypeInfo(dependencyType)), List.empty, List.empty, List.empty) - def create(infoString: String, dependencyType: DependencyType, mergeInfo: DependencyAnalysisMergeInfo): DependencyAnalysisInfoes = + def create(infoString: String, dependencyType: DependencyType, mergeInfo: DependencyAnalysisMergeInfo): DependencyAnalysisInfos = create(StringAnalysisSourceInfo(infoString, NoPosition), dependencyType, mergeInfo) - def create(infoString: String, dependencyType: DependencyType): DependencyAnalysisInfoes = + def create(infoString: String, dependencyType: DependencyType): DependencyAnalysisInfos = create(StringAnalysisSourceInfo(infoString, NoPosition), dependencyType) } diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala index 3d2e2d39a..276f5ceb3 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala @@ -32,7 +32,7 @@ trait DependencyAnalysisNode extends AbstractDependencyAnalysisNode { val mergeInfo: DependencyAnalysisMergeInfo - val joinInfoes: List[SimpleDependencyAnalysisJoin] + val joinInfos: List[SimpleDependencyAnalysisJoin] /** * The assumes or asserted Silicon term. Currently, only used for debugging purposes. @@ -81,38 +81,38 @@ trait ChunkAnalysisInfo { val labelNode: LabelNode } -case class SimpleAssumptionNode(term: Term, description: Option[String], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, mergeInfo: DependencyAnalysisMergeInfo, joinInfoes: List[SimpleDependencyAnalysisJoin], _id: Option[Int]=None) extends GeneralAssumptionNode { +case class SimpleAssumptionNode(term: Term, description: Option[String], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, mergeInfo: DependencyAnalysisMergeInfo, joinInfos: List[SimpleDependencyAnalysisJoin], _id: Option[Int]=None) extends GeneralAssumptionNode { override def getNodeString: String = "assume " + term.toString + description.map(" (" + _ + ")").getOrElse("") } -case class AxiomAssumptionNode(term: Term, description: Option[String], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, mergeInfo: DependencyAnalysisMergeInfo, joinInfoes: List[SimpleDependencyAnalysisJoin], _id: Option[Int]=None) extends GeneralAssumptionNode { +case class AxiomAssumptionNode(term: Term, description: Option[String], sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, mergeInfo: DependencyAnalysisMergeInfo, joinInfos: List[SimpleDependencyAnalysisJoin], _id: Option[Int]=None) extends GeneralAssumptionNode { override def getNodeString: String = "assume axiom " + term.toString + description.map(" (" + _ + ")").getOrElse("") override def getNodeType: String = "Axiom" } -case class SimpleAssertionNode(term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, mergeInfo: DependencyAnalysisMergeInfo, joinInfoes: List[SimpleDependencyAnalysisJoin], hasFailed: Boolean = false, _id: Option[Int]=None) extends GeneralAssertionNode { +case class SimpleAssertionNode(term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, mergeInfo: DependencyAnalysisMergeInfo, joinInfos: List[SimpleDependencyAnalysisJoin], hasFailed: Boolean = false, _id: Option[Int]=None) extends GeneralAssertionNode { override def getNodeString: String = "assert " + term.toString - override def getAssertFailedNode(): GeneralAssertionNode = SimpleAssertionNode(term, sourceInfo, assumptionType, mergeInfo, hasFailed=true, joinInfoes=joinInfoes) + override def getAssertFailedNode(): GeneralAssertionNode = SimpleAssertionNode(term, sourceInfo, assumptionType, mergeInfo, hasFailed=true, joinInfos=joinInfos) } -case class SimpleCheckNode(term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, mergeInfo: DependencyAnalysisMergeInfo, joinInfoes: List[SimpleDependencyAnalysisJoin], hasFailed: Boolean = false, _id: Option[Int]=None) extends GeneralAssertionNode { +case class SimpleCheckNode(term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, mergeInfo: DependencyAnalysisMergeInfo, joinInfos: List[SimpleDependencyAnalysisJoin], hasFailed: Boolean = false, _id: Option[Int]=None) extends GeneralAssertionNode { override def getNodeString: String = "check " + term override def getNodeType: String = "Check" - override def getAssertFailedNode(): GeneralAssertionNode = SimpleCheckNode(term, sourceInfo, assumptionType, mergeInfo, joinInfoes, hasFailed=true) + override def getAssertFailedNode(): GeneralAssertionNode = SimpleCheckNode(term, sourceInfo, assumptionType, mergeInfo, joinInfos, hasFailed=true) } -case class PermissionInhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, mergeInfo: DependencyAnalysisMergeInfo, labelNode: LabelNode, joinInfoes: List[SimpleDependencyAnalysisJoin], _id: Option[Int]=None) extends GeneralAssumptionNode with ChunkAnalysisInfo { +case class PermissionInhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, mergeInfo: DependencyAnalysisMergeInfo, labelNode: LabelNode, joinInfos: List[SimpleDependencyAnalysisJoin], _id: Option[Int]=None) extends GeneralAssumptionNode with ChunkAnalysisInfo { override def getNodeString: String = "inhale " + chunk.toString override def getNodeType: String = "Inhale" } -case class PermissionExhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, mergeInfo: DependencyAnalysisMergeInfo, labelNode: LabelNode, joinInfoes: List[SimpleDependencyAnalysisJoin], hasFailed: Boolean = false, _id: Option[Int]=None) extends GeneralAssertionNode with ChunkAnalysisInfo { +case class PermissionExhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, mergeInfo: DependencyAnalysisMergeInfo, labelNode: LabelNode, joinInfos: List[SimpleDependencyAnalysisJoin], hasFailed: Boolean = false, _id: Option[Int]=None) extends GeneralAssertionNode with ChunkAnalysisInfo { override def getNodeType: String = "Exhale" override def getNodeString: String = "exhale " + chunk.toString - override def getAssertFailedNode(): GeneralAssertionNode = PermissionExhaleNode(chunk, term, sourceInfo, assumptionType, mergeInfo, labelNode, joinInfoes, hasFailed=true, _id=_id) + override def getAssertFailedNode(): GeneralAssertionNode = PermissionExhaleNode(chunk, term, sourceInfo, assumptionType, mergeInfo, labelNode, joinInfos, hasFailed=true, _id=_id) } /** @@ -124,7 +124,7 @@ case class LabelNode(term: Var, _id: Option[Int]=None) extends GeneralAssumption val assumptionType: AssumptionType = AssumptionType.Internal val mergeInfo: DependencyAnalysisMergeInfo = NoDependencyAnalysisMerge() val description: String = term.toString - val joinInfoes: List[SimpleDependencyAnalysisJoin] = List.empty + val joinInfos: List[SimpleDependencyAnalysisJoin] = List.empty override def getNodeType: String = "Label" override def getNodeString: String = "assume " + description } @@ -140,7 +140,7 @@ case class InfeasibilityNode(sourceInfo: AnalysisSourceInfo, assumptionType: Ass val term: Term = False val mergeInfo: DependencyAnalysisMergeInfo = NoDependencyAnalysisMerge() val description: String = "False" - val joinInfoes: List[SimpleDependencyAnalysisJoin] = List.empty + val joinInfos: List[SimpleDependencyAnalysisJoin] = List.empty override def getNodeType: String = "Infeasible" override def getNodeString: String = "infeasible" diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index 67665857e..5a1b9fb4e 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -21,15 +21,15 @@ trait DependencyAnalyzer { def addNodes(nodes: Iterable[DependencyAnalysisNode]): Unit def addAssertionNode(node: GeneralAssertionNode): Unit def addAssumptionNode(node: GeneralAssumptionNode): Unit - def addAssumption(assumption: Term, analysisInfoes: DependencyAnalysisInfoes, description: Option[String] = None): Option[Int] - def addAxiom(assumption: Term, analysisInfoes: DependencyAnalysisInfoes, description: Option[String] = None): Option[Int] + def addAssumption(assumption: Term, analysisInfos: DependencyAnalysisInfos, description: Option[String] = None): Option[Int] + def addAxiom(assumption: Term, analysisInfos: DependencyAnalysisInfos, description: Option[String] = None): Option[Int] def registerInhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, labelNode: Option[LabelNode], analysisInfo: AnalysisInfo): CH = buildChunk(perm) def registerExhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, labelNodeOpt: Option[LabelNode], analysisInfo: AnalysisInfo): CH = buildChunk(perm) def createLabelNode(label: Var, sourceChunks: Iterable[Chunk], sourceTerms: Iterable[Term]): Option[LabelNode] - def createAssertOrCheckNode(term: Term, analysisInfoes: DependencyAnalysisInfoes, isCheck: Boolean): Option[GeneralAssertionNode] - def addAssertFalseNode(isCheck: Boolean, analysisInfoes: DependencyAnalysisInfoes): Option[Int] - def addInfeasibilityNode(isCheck: Boolean, analysisInfoes: DependencyAnalysisInfoes): Option[Int] + def createAssertOrCheckNode(term: Term, analysisInfos: DependencyAnalysisInfos, isCheck: Boolean): Option[GeneralAssertionNode] + def addAssertFalseNode(isCheck: Boolean, analysisInfos: DependencyAnalysisInfos): Option[Int] + def addInfeasibilityNode(isCheck: Boolean, analysisInfos: DependencyAnalysisInfos): Option[Int] def addDependency(source: Option[Int], dest: Option[Int]): Unit def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit @@ -38,19 +38,19 @@ trait DependencyAnalyzer { * Adds dependencies between all pairs of sourceExps and targetExps, where sourceExps should be preconditions and * targetExps should be postconditions of an abstract function or method. */ - def addDependenciesForAbstractMembers(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp], analysisInfoes: DependencyAnalysisInfoes): Unit + def addDependenciesForAbstractMembers(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp], analysisInfos: DependencyAnalysisInfos): Unit /** * Adds an assertion and assumption node with the given analysis source info and dependencies to the current infeasibility node. */ - def addAssertionWithDepToInfeasNode(infeasNodeId: Option[Int], analysisInfoes: DependencyAnalysisInfoes): Unit = {} + def addAssertionWithDepToInfeasNode(infeasNodeId: Option[Int], analysisInfos: DependencyAnalysisInfos): Unit = {} /** * @return the final dependency graph representing all direct and transitive dependencies */ def buildFinalGraph(): Option[DependencyGraph] - def addAssertionFailedNode(failedAssertion: Term, analysisInfoes: DependencyAnalysisInfoes): Option[Int] + def addAssertionFailedNode(failedAssertion: Term, analysisInfos: DependencyAnalysisInfos): Option[Int] } object DependencyAnalyzer { @@ -110,7 +110,7 @@ object DependencyAnalyzer { def getJoinNodesByJoinInfo(candidateNodes: Set[DependencyAnalysisNode], joinType: JoinType) = { candidateNodes - .flatMap(node => node.joinInfoes.filter(_.joinType.equals(joinType)).map((_, node))) + .flatMap(node => node.joinInfos.filter(_.joinType.equals(joinType)).map((_, node))) .groupBy(_._1) .view.mapValues(_.map(_._2)) .toMap @@ -154,14 +154,14 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { override def addAssertionNode(node: GeneralAssertionNode): Unit = dependencyGraph.addAssertionNode(node) - override def addAssumption(assumption: Term, analysisInfoes: DependencyAnalysisInfoes, description: Option[String]): Option[Int] = { - val node = SimpleAssumptionNode(assumption, description, analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assumptionType, analysisInfoes.getMergeInfo, analysisInfoes.getJoinInfo) + override def addAssumption(assumption: Term, analysisInfos: DependencyAnalysisInfos, description: Option[String]): Option[Int] = { + val node = SimpleAssumptionNode(assumption, description, analysisInfos.getSourceInfo, analysisInfos.getDependencyType.assumptionType, analysisInfos.getMergeInfo, analysisInfos.getJoinInfo) addAssumptionNode(node) Some(node.id) } - override def addAxiom(assumption: Term, analysisInfoes: DependencyAnalysisInfoes, description: Option[String]): Option[Int] = { - val node = AxiomAssumptionNode(assumption, description, analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assumptionType, analysisInfoes.getMergeInfo, analysisInfoes.getJoinInfo) + override def addAxiom(assumption: Term, analysisInfos: DependencyAnalysisInfos, description: Option[String]): Option[Int] = { + val node = AxiomAssumptionNode(assumption, description, analysisInfos.getSourceInfo, analysisInfos.getDependencyType.assumptionType, analysisInfos.getMergeInfo, analysisInfos.getJoinInfo) addAssumptionNode(node) Some(node.id) } @@ -169,7 +169,7 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { override def registerExhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, labelNodeOpt: Option[LabelNode], analysisInfo: AnalysisInfo): CH = { val labelNode = labelNodeOpt.get val chunk = buildChunk(Ite(labelNode.term, perm, NoPerm)) - val chunkNode = addPermissionExhaleNode(chunk, chunk.perm, analysisInfo.analysisInfoes, labelNode) + val chunkNode = addPermissionExhaleNode(chunk, chunk.perm, analysisInfo.analysisInfos, labelNode) if(chunkNode.isDefined) addDependency(chunkNode, Some(labelNode.id)) chunk } @@ -177,19 +177,19 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { override def registerInhaleChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, labelNodeOpt: Option[LabelNode], analysisInfo: AnalysisInfo): CH = { val labelNode = labelNodeOpt.get val chunk = buildChunk(Ite((labelNode.term, perm, NoPerm))) - val chunkNode = addPermissionInhaleNode(chunk, chunk.perm, analysisInfo.analysisInfoes, labelNode) + val chunkNode = addPermissionInhaleNode(chunk, chunk.perm, analysisInfo.analysisInfos, labelNode) if(chunkNode.isDefined) addDependency(chunkNode, Some(labelNode.id)) chunk } - private def addPermissionInhaleNode(chunk: Chunk, permAmount: Term, analysisInfoes: DependencyAnalysisInfoes, labelNode: LabelNode): Option[Int] = { - val node = PermissionInhaleNode(chunk, permAmount, analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assumptionType, analysisInfoes.getMergeInfo, labelNode, analysisInfoes.getJoinInfo) + private def addPermissionInhaleNode(chunk: Chunk, permAmount: Term, analysisInfos: DependencyAnalysisInfos, labelNode: LabelNode): Option[Int] = { + val node = PermissionInhaleNode(chunk, permAmount, analysisInfos.getSourceInfo, analysisInfos.getDependencyType.assumptionType, analysisInfos.getMergeInfo, labelNode, analysisInfos.getJoinInfo) addAssumptionNode(node) Some(node.id) } - private def addPermissionExhaleNode(chunk: Chunk, permAmount: Term, analysisInfoes: DependencyAnalysisInfoes, labelNode: LabelNode): Option[Int] = { - val node = PermissionExhaleNode(chunk, permAmount, analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assertionType, analysisInfoes.getMergeInfo, labelNode, analysisInfoes.getJoinInfo) + private def addPermissionExhaleNode(chunk: Chunk, permAmount: Term, analysisInfos: DependencyAnalysisInfos, labelNode: LabelNode): Option[Int] = { + val node = PermissionExhaleNode(chunk, permAmount, analysisInfos.getSourceInfo, analysisInfos.getDependencyType.assertionType, analysisInfos.getMergeInfo, labelNode, analysisInfos.getJoinInfo) addAssertionNode(node) Some(node.id) } @@ -201,34 +201,34 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { Some(labelNode) } - override def createAssertOrCheckNode(term: Term, analysisInfoes: DependencyAnalysisInfoes, isCheck: Boolean): Option[GeneralAssertionNode] = { + override def createAssertOrCheckNode(term: Term, analysisInfos: DependencyAnalysisInfos, isCheck: Boolean): Option[GeneralAssertionNode] = { if(isCheck) - Some(SimpleCheckNode(term, analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assumptionType, analysisInfoes.getMergeInfo, analysisInfoes.getJoinInfo)) + Some(SimpleCheckNode(term, analysisInfos.getSourceInfo, analysisInfos.getDependencyType.assumptionType, analysisInfos.getMergeInfo, analysisInfos.getJoinInfo)) else - Some(SimpleAssertionNode(term, analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assumptionType, analysisInfoes.getMergeInfo, analysisInfoes.getJoinInfo)) + Some(SimpleAssertionNode(term, analysisInfos.getSourceInfo, analysisInfos.getDependencyType.assumptionType, analysisInfos.getMergeInfo, analysisInfos.getJoinInfo)) } - def addAssertNode(term: Term, analysisInfoes: DependencyAnalysisInfoes): Option[Int] = { - val node = createAssertOrCheckNode(term, analysisInfoes, isCheck=false) + def addAssertNode(term: Term, analysisInfos: DependencyAnalysisInfos): Option[Int] = { + val node = createAssertOrCheckNode(term, analysisInfos, isCheck=false) node foreach addAssertionNode node map (_.id) } - override def addAssertFalseNode(isCheck: Boolean, analysisInfoes: DependencyAnalysisInfoes): Option[Int] = { - val node = createAssertOrCheckNode(False, analysisInfoes, isCheck) + override def addAssertFalseNode(isCheck: Boolean, analysisInfos: DependencyAnalysisInfos): Option[Int] = { + val node = createAssertOrCheckNode(False, analysisInfos, isCheck) addAssertionNode(node.get) node.map(_.id) } - override def addInfeasibilityNode(isCheck: Boolean, analysisInfoes: DependencyAnalysisInfoes): Option[Int] = { - val node = InfeasibilityNode(analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assumptionType) + override def addInfeasibilityNode(isCheck: Boolean, analysisInfos: DependencyAnalysisInfos): Option[Int] = { + val node = InfeasibilityNode(analysisInfos.getSourceInfo, analysisInfos.getDependencyType.assumptionType) addAssumptionNode(node) Some(node.id) } - override def addAssertionFailedNode(failedAssertion: Term, analysisInfoes: DependencyAnalysisInfoes): Option[Int] = { - val assumeNode = SimpleAssumptionNode(failedAssertion, None, analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assumptionType, analysisInfoes.getMergeInfo, analysisInfoes.getJoinInfo) - val assertFailedNode = SimpleAssertionNode(failedAssertion, analysisInfoes.getSourceInfo, analysisInfoes.getDependencyType.assumptionType, analysisInfoes.getMergeInfo, analysisInfoes.getJoinInfo, hasFailed=true) + override def addAssertionFailedNode(failedAssertion: Term, analysisInfos: DependencyAnalysisInfos): Option[Int] = { + val assumeNode = SimpleAssumptionNode(failedAssertion, None, analysisInfos.getSourceInfo, analysisInfos.getDependencyType.assumptionType, analysisInfos.getMergeInfo, analysisInfos.getJoinInfo) + val assertFailedNode = SimpleAssertionNode(failedAssertion, analysisInfos.getSourceInfo, analysisInfos.getDependencyType.assumptionType, analysisInfos.getMergeInfo, analysisInfos.getJoinInfo, hasFailed=true) dependencyGraph.addNode(assumeNode) dependencyGraph.addNode(assertFailedNode) dependencyGraph.addEdges(Set(assumeNode.id), assertFailedNode.id) @@ -250,9 +250,9 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { dependencyGraph.addEdges(assumptionIds.diff(Set(assertionId)), assertionId) } - override def addDependenciesForAbstractMembers(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp], analysisInfoes: DependencyAnalysisInfoes): Unit = { - val sourceNodeIds = sourceExps.flatMap(e => addAssumption(True, analysisInfoes.addInfo(e.info, e).withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Sink, EdgeType.Up)))) - val targetNodes = targetExps.flatMap(e => addAssertNode(True, analysisInfoes.addInfo(e.info, e).withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Source, EdgeType.Down)))) + override def addDependenciesForAbstractMembers(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp], analysisInfos: DependencyAnalysisInfos): Unit = { + val sourceNodeIds = sourceExps.flatMap(e => addAssumption(True, analysisInfos.addInfo(e.info, e).withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Sink, EdgeType.Up)))) + val targetNodes = targetExps.flatMap(e => addAssertNode(True, analysisInfos.addInfo(e.info, e).withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Source, EdgeType.Down)))) dependencyGraph.addEdges(sourceNodeIds, targetNodes) } @@ -291,7 +291,7 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { * the low-level graph containing all details. */ private def buildAndGetMergedGraph(): DependencyGraph = { - def keepNode(n: DependencyAnalysisNode): Boolean = !n.mergeInfo.isMerge || n.joinInfoes.nonEmpty || n.isInstanceOf[InfeasibilityNode] || n.isInstanceOf[AxiomAssumptionNode] + def keepNode(n: DependencyAnalysisNode): Boolean = !n.mergeInfo.isMerge || n.joinInfos.nonEmpty || n.isInstanceOf[InfeasibilityNode] || n.isInstanceOf[AxiomAssumptionNode] val mergedGraph = new DependencyGraph val nodeMap = mutable.HashMap[Int, Int]() @@ -300,10 +300,10 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { nodeMap.put(n.id, n.id) mergedGraph.addAssumptionNode(n) } - val assumptionNodesBySource = dependencyGraph.getAssumptionNodes.filter(!keepNode(_)).groupBy(n => (n.sourceInfo, n.assumptionType, n.mergeInfo, n.joinInfoes)) - assumptionNodesBySource foreach { case ((sourceInfo, assumptionType, mergeInfo, joinInfoes), assumptionNodes) => + val assumptionNodesBySource = dependencyGraph.getAssumptionNodes.filter(!keepNode(_)).groupBy(n => (n.sourceInfo, n.assumptionType, n.mergeInfo, n.joinInfos)) + assumptionNodesBySource foreach { case ((sourceInfo, assumptionType, mergeInfo, joinInfos), assumptionNodes) => if (assumptionNodes.nonEmpty) { - val newNode = SimpleAssumptionNode(True, None, sourceInfo, assumptionType, mergeInfo, joinInfoes) + val newNode = SimpleAssumptionNode(True, None, sourceInfo, assumptionType, mergeInfo, joinInfos) assumptionNodes foreach (n => nodeMap.put(n.id, newNode.id)) mergedGraph.addAssumptionNode(newNode) } @@ -313,10 +313,10 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { nodeMap.put(n.id, n.id) mergedGraph.addAssertionNode(n) } - val assertionNodesBySource = dependencyGraph.getAssertionNodes.filter(!keepNode(_)).groupBy(n => (n.sourceInfo, n.assumptionType, n.mergeInfo, n.joinInfoes)) - assertionNodesBySource foreach { case ((sourceInfo, assumptionType, mergeInfo, joinInfoes), assertionNodes) => + val assertionNodesBySource = dependencyGraph.getAssertionNodes.filter(!keepNode(_)).groupBy(n => (n.sourceInfo, n.assumptionType, n.mergeInfo, n.joinInfos)) + assertionNodesBySource foreach { case ((sourceInfo, assumptionType, mergeInfo, joinInfos), assertionNodes) => if (assertionNodes.nonEmpty) { - val newNode = SimpleAssertionNode(True, sourceInfo, assumptionType, mergeInfo, joinInfoes, hasFailed=assertionNodes.exists(_.hasFailed)) + val newNode = SimpleAssertionNode(True, sourceInfo, assumptionType, mergeInfo, joinInfos, hasFailed=assertionNodes.exists(_.hasFailed)) assertionNodes foreach (n => nodeMap.put(n.id, newNode.id)) mergedGraph.addAssertionNode(newNode) } @@ -334,8 +334,8 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { * Adds an assertion node with the given analysis source info and dependencies to the current infeasibility node. * The resulting assertion node is required to detect dependencies of the source statement/expression on infeasible paths. */ - override def addAssertionWithDepToInfeasNode(infeasNodeId: Option[Int], analysisInfoes: DependencyAnalysisInfoes): Unit = { - val newAssertionNodeId = addAssertNode(False, analysisInfoes) + override def addAssertionWithDepToInfeasNode(infeasNodeId: Option[Int], analysisInfos: DependencyAnalysisInfos): Unit = { + val newAssertionNodeId = addAssertNode(False, analysisInfos) addDependency(infeasNodeId, newAssertionNodeId) } @@ -353,18 +353,18 @@ class NoDependencyAnalyzer extends DependencyAnalyzer { override def addNodes(nodes: Iterable[DependencyAnalysisNode]): Unit = {} override def addAssertionNode(node: GeneralAssertionNode): Unit = {} override def addAssumptionNode(node: GeneralAssumptionNode): Unit = {} - override def addAssumption(assumption: Term, analysisInfoes: DependencyAnalysisInfoes, description: Option[String] = None): Option[Int] = None - override def addAxiom(assumption: Term, analysisInfoes: DependencyAnalysisInfoes, description: Option[String]): Option[Int] = None + override def addAssumption(assumption: Term, analysisInfos: DependencyAnalysisInfos, description: Option[String] = None): Option[Int] = None + override def addAxiom(assumption: Term, analysisInfos: DependencyAnalysisInfos, description: Option[String]): Option[Int] = None override def createLabelNode(labelTerm: Var, sourceChunks: Iterable[Chunk], sourceTerms: Iterable[Term]): Option[LabelNode] = None - override def createAssertOrCheckNode(term: Term, analysisInfoes: DependencyAnalysisInfoes, isCheck: Boolean): Option[GeneralAssertionNode] = None - override def addAssertFalseNode(isCheck: Boolean, analysisInfoes: DependencyAnalysisInfoes): Option[Int] = None - override def addInfeasibilityNode(isCheck: Boolean, analysisInfoes: DependencyAnalysisInfoes): Option[Int] = None - override def addAssertionFailedNode(failedAssertion: Term, analysisInfoes: DependencyAnalysisInfoes): Option[Int] = None + override def createAssertOrCheckNode(term: Term, analysisInfos: DependencyAnalysisInfos, isCheck: Boolean): Option[GeneralAssertionNode] = None + override def addAssertFalseNode(isCheck: Boolean, analysisInfos: DependencyAnalysisInfos): Option[Int] = None + override def addInfeasibilityNode(isCheck: Boolean, analysisInfos: DependencyAnalysisInfos): Option[Int] = None + override def addAssertionFailedNode(failedAssertion: Term, analysisInfos: DependencyAnalysisInfos): Option[Int] = None override def addDependency(source: Option[Int], dest: Option[Int]): Unit = {} override def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit = {} - override def addDependenciesForAbstractMembers(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp], analysisInfoes: DependencyAnalysisInfoes): Unit = {} + override def addDependenciesForAbstractMembers(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp], analysisInfos: DependencyAnalysisInfos): Unit = {} override def buildFinalGraph(): Option[DependencyGraph] = None diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala index 21af8f224..08c3203c7 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala @@ -92,17 +92,17 @@ object DependencyGraphImporter { val description: Option[String] = None val mergeInfo: SimpleDependencyAnalysisMerge = SimpleDependencyAnalysisMerge(sourceInfo) val labelNode: LabelNode = dummyLabelNode - val joinNodeInfoes: List[SimpleDependencyAnalysisJoin] = List.empty + val joinNodeInfos: List[SimpleDependencyAnalysisJoin] = List.empty val nodeId = Some(nodeIdStr.toInt) // Create node based on type val node = nodeType match { - case "Assumption" => SimpleAssumptionNode(term, description, sourceInfo, assumptionType, mergeInfo, joinNodeInfoes, _id=nodeId) - case "Axiom" => AxiomAssumptionNode(term, description, sourceInfo, assumptionType, mergeInfo, joinNodeInfoes, _id=nodeId) - case "Assertion" => SimpleAssertionNode(term, sourceInfo, assumptionType, mergeInfo, joinNodeInfoes, hasFailed = false, _id=nodeId) - case "Check" => SimpleCheckNode(term, sourceInfo, assumptionType, mergeInfo, joinNodeInfoes, hasFailed = false, _id=nodeId) - case "Inhale" => PermissionInhaleNode(chunk, term, sourceInfo, assumptionType, mergeInfo, labelNode, joinNodeInfoes, _id=nodeId) - case "Exhale" => PermissionExhaleNode(chunk, term, sourceInfo, assumptionType, mergeInfo, labelNode, joinNodeInfoes, hasFailed = false, _id=nodeId) + case "Assumption" => SimpleAssumptionNode(term, description, sourceInfo, assumptionType, mergeInfo, joinNodeInfos, _id=nodeId) + case "Axiom" => AxiomAssumptionNode(term, description, sourceInfo, assumptionType, mergeInfo, joinNodeInfos, _id=nodeId) + case "Assertion" => SimpleAssertionNode(term, sourceInfo, assumptionType, mergeInfo, joinNodeInfos, hasFailed = false, _id=nodeId) + case "Check" => SimpleCheckNode(term, sourceInfo, assumptionType, mergeInfo, joinNodeInfos, hasFailed = false, _id=nodeId) + case "Inhale" => PermissionInhaleNode(chunk, term, sourceInfo, assumptionType, mergeInfo, labelNode, joinNodeInfos, _id=nodeId) + case "Exhale" => PermissionExhaleNode(chunk, term, sourceInfo, assumptionType, mergeInfo, labelNode, joinNodeInfos, hasFailed = false, _id=nodeId) case "Label" => LabelNode(dummyVar, _id=nodeId) case "Infeasible" => InfeasibilityNode(sourceInfo, assumptionType, _id=nodeId) case _ => throw new IllegalArgumentException(s"Unknown node type: $nodeType") diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index b6c5694e6..78dfbff6b 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -37,10 +37,10 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen def getAssertionNodes: Set[DependencyAnalysisNode] = dependencyGraph.getAssertionNodes.toSet def getErrors: List[Failure] = errors - val joinSinkNodes: Set[DependencyAnalysisNode] = getJoinCandidateNodes(getNodes).filter(_.joinInfoes.exists(_.joinType.equals(JoinType.Sink))) - val joinSourceNodes: Set[DependencyAnalysisNode] = getJoinCandidateNodes(getNodes).filter(_.joinInfoes.exists(_.joinType.equals(JoinType.Source))) + val joinSinkNodes: Set[DependencyAnalysisNode] = getJoinCandidateNodes(getNodes).filter(_.joinInfos.exists(_.joinType.equals(JoinType.Sink))) + val joinSourceNodes: Set[DependencyAnalysisNode] = getJoinCandidateNodes(getNodes).filter(_.joinInfos.exists(_.joinType.equals(JoinType.Source))) - def getJoinCandidateNodes(nodes: Set[DependencyAnalysisNode]): Set[DependencyAnalysisNode] = nodes.filter(node => node.joinInfoes.nonEmpty) + def getJoinCandidateNodes(nodes: Set[DependencyAnalysisNode]): Set[DependencyAnalysisNode] = nodes.filter(node => node.joinInfos.nonEmpty) private def toUserLevelNodes(nodes: Iterable[DependencyAnalysisNode]): Set[UserLevelDependencyAnalysisNode] = UserLevelDependencyAnalysisNode.from(nodes) @@ -97,7 +97,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen def getNonInternalAssumptionNodes(nodes: Set[DependencyAnalysisNode]): Set[DependencyAnalysisNode] = nodes filter (node => (node.isInstanceOf[GeneralAssumptionNode] && !AssumptionType.internalTypes.contains(node.assumptionType)) - || AssumptionType.postconditionTypes.contains(node.assumptionType) || node.joinInfoes.nonEmpty // postconditions act as assumptions for callers + || AssumptionType.postconditionTypes.contains(node.assumptionType) || node.joinInfos.nonEmpty // postconditions act as assumptions for callers ) def getExplicitAssumptionNodes: Set[DependencyAnalysisNode] = getNonInternalAssumptionNodes filter (node => @@ -111,7 +111,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen def getNonInternalAssertionNodes: Set[DependencyAnalysisNode] = getAssertionNodes filter (node => - !AssumptionType.internalTypes.contains(node.assumptionType) || node.joinInfoes.nonEmpty) + !AssumptionType.internalTypes.contains(node.assumptionType) || node.joinInfos.nonEmpty) def getExplicitAssertionNodes: Set[DependencyAnalysisNode] = getNonInternalAssertionNodes.filter(node => AssumptionType.explicitAssertionTypes.contains(node.assumptionType)) diff --git a/src/main/scala/interfaces/decider/Prover.scala b/src/main/scala/interfaces/decider/Prover.scala index 342502e4b..5ef06b68e 100644 --- a/src/main/scala/interfaces/decider/Prover.scala +++ b/src/main/scala/interfaces/decider/Prover.scala @@ -40,14 +40,14 @@ trait ProverLike { terms foreach assume } - def assumeAxiomsWithAnalysisInfo(axioms: InsertionOrderedSet[(Term, DependencyAnalysisInfoes)], description: String): Unit = { + def assumeAxiomsWithAnalysisInfo(axioms: InsertionOrderedSet[(Term, DependencyAnalysisInfos)], description: String): Unit = { if (debugMode) preambleAssumptions :+= new DebugAxiom(description, axioms.map(_._1)) if(Verifier.config.enableDependencyAnalysis()){ axioms.foreach(axiom => { - val analysisInfoes = axiom._2 - val id = preambleDependencyAnalyzer.addAxiom(axiom._1, analysisInfoes) + val analysisInfos = axiom._2 + val id = preambleDependencyAnalyzer.addAxiom(axiom._1, analysisInfos) assume(axiom._1, DependencyAnalyzer.createAxiomLabel(id)) }) } else{ diff --git a/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala b/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala index 31d1b4077..8290a3f40 100644 --- a/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala +++ b/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala @@ -7,7 +7,7 @@ package viper.silicon.resources import viper.silicon.Map -import viper.silicon.dependencyAnalysis.DependencyAnalysisInfoes +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfos import viper.silicon.interfaces.state._ import viper.silicon.state.terms.Term import viper.silicon.state.{QuantifiedBasicChunk, terms} @@ -122,7 +122,7 @@ class NonQuantifiedPropertyInterpreter(heap: Iterable[Chunk], verifier: Verifier otherwise: PropertyExpression[K], info: Info): (Term, Option[ast.Exp]) = { val conditionTerm = buildPathCondition(condition, info)._1 - if (verifier.decider.check(conditionTerm, Verifier.config.checkTimeout(), DependencyAnalysisInfoes.create("property interpreter", DependencyType.Internal) /* TODO ake */)) { + if (verifier.decider.check(conditionTerm, Verifier.config.checkTimeout(), DependencyAnalysisInfos.create("property interpreter", DependencyType.Internal) /* TODO ake */)) { val (t, e) = buildPathCondition(thenDo, info) (verifier.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(conditionTerm)), e) // TODO ake: causes imprecision! } else { diff --git a/src/main/scala/rules/Brancher.scala b/src/main/scala/rules/Brancher.scala index a37c39b6b..8c0290e33 100644 --- a/src/main/scala/rules/Brancher.scala +++ b/src/main/scala/rules/Brancher.scala @@ -8,7 +8,7 @@ package viper.silicon.rules import viper.silicon.common.concurrency._ import viper.silicon.decider.PathConditionStack -import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfoes, DependencyAnalyzer} +import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfos, DependencyAnalyzer} import viper.silicon.interfaces.{Unreachable, VerificationResult} import viper.silicon.reporting.{condenseToViperResult, convertToViperResult} import viper.silicon.state.State @@ -28,7 +28,7 @@ trait BranchingRules extends SymbolicExecutionRules { condition: Term, conditionExp: (ast.Exp, Option[ast.Exp]), v: Verifier, - analysisInfoes: DependencyAnalysisInfoes, + analysisInfos: DependencyAnalysisInfos, fromShortCircuitingAnd: Boolean = false) (fTrue: (State, Verifier) => VerificationResult, fFalse: (State, Verifier) => VerificationResult) @@ -40,21 +40,21 @@ object brancher extends BranchingRules { condition: Term, conditionExp: (ast.Exp, Option[ast.Exp]), v: Verifier, - analysisInfoes: DependencyAnalysisInfoes, + analysisInfos: DependencyAnalysisInfos, fromShortCircuitingAnd: Boolean = false) (fThen: (State, Verifier) => VerificationResult, fElse: (State, Verifier) => VerificationResult) : VerificationResult = { if(v.decider.isPathInfeasible){ - val analysisInfoes1 = analysisInfoes.addInfo(conditionExp._1.info, conditionExp._1) + val analysisInfos1 = analysisInfos.addInfo(conditionExp._1.info, conditionExp._1) // FIXME ake: infeasible path // val assertionNodesForJoin = DependencyAnalyzer.extractAssertionsForJoin(conditionExp._1, s.program) // assertionNodesForJoin.foreach(n => v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, CompositeAnalysisSourceInfo(v.decider.analysisSourceInfoStack.getFullSourceInfo, AnalysisSourceInfo.createAnalysisSourceInfo(n)), v.decider.analysisSourceInfoStack.getDependencyType, isJoinNode=true)) if(!Expressions.isKnownWellDefined(conditionExp._1, Some(s.program))){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfoes1) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfos1) } - v.decider.dependencyAnalyzer.addAssumption(condition, analysisInfoes1) + v.decider.dependencyAnalyzer.addAssumption(condition, analysisInfos1) return fThen(s, v).combine(fElse(s, v)) } @@ -73,17 +73,17 @@ object brancher extends BranchingRules { && s.quantifiedVariables.map(_._1).exists(condition.freeVariables.contains)) ) - val analysisInfoes1 = analysisInfoes.addInfo(conditionExp._1.info, conditionExp._1) + val analysisInfos1 = analysisInfos.addInfo(conditionExp._1.info, conditionExp._1) /* True if the then-branch is to be explored */ val executeThenBranch = ( skipPathFeasibilityCheck - || !v.decider.check(negatedCondition, Verifier.config.checkTimeout(), analysisInfoes1.withDependencyType(DependencyType.Internal))) + || !v.decider.check(negatedCondition, Verifier.config.checkTimeout(), analysisInfos1.withDependencyType(DependencyType.Internal))) /* False if the then-branch is to be explored */ val executeElseBranch = ( !executeThenBranch /* Assumes that ast least one branch is feasible */ || skipPathFeasibilityCheck - || !v.decider.check(condition, Verifier.config.checkTimeout(), analysisInfoes1.withDependencyType(DependencyType.Internal))) + || !v.decider.check(condition, Verifier.config.checkTimeout(), analysisInfos1.withDependencyType(DependencyType.Internal))) val parallelizeElseBranch = s.parallelizeBranches && executeThenBranch && executeElseBranch @@ -161,8 +161,8 @@ object brancher extends BranchingRules { executionFlowController.locally(s, v0)((s1, v1) => { v1.decider.prover.comment(s"[else-branch: $cnt | $negatedCondition]") - v1.decider.setCurrentBranchCondition(negatedCondition, (negatedConditionExp, negatedConditionExpNew), analysisInfoes1) - if(v.decider.isDependencyAnalysisEnabled && !executeElseBranch) v.decider.checkSmokeAndSetInfeasibilityNode(analysisInfoes1) + v1.decider.setCurrentBranchCondition(negatedCondition, (negatedConditionExp, negatedConditionExpNew), analysisInfos1) + if(v.decider.isDependencyAnalysisEnabled && !executeElseBranch) v.decider.checkSmokeAndSetInfeasibilityNode(analysisInfos1) var functionsOfElseBranchdDeciderBefore: Set[FunctionDecl] = null var nMacrosOfElseBranchDeciderBefore: Int = 0 @@ -212,8 +212,8 @@ object brancher extends BranchingRules { v.symbExLog.markReachable(uidBranchPoint) executionFlowController.locally(s, v)((s1, v1) => { v1.decider.prover.comment(s"[then-branch: $cnt | $condition]") - v1.decider.setCurrentBranchCondition(condition, conditionExp, analysisInfoes1) - if(v.decider.isDependencyAnalysisEnabled && !executeThenBranch) v.decider.checkSmokeAndSetInfeasibilityNode(analysisInfoes1) + v1.decider.setCurrentBranchCondition(condition, conditionExp, analysisInfos1) + if(v.decider.isDependencyAnalysisEnabled && !executeThenBranch) v.decider.checkSmokeAndSetInfeasibilityNode(analysisInfos1) fThen(v1.stateConsolidator(s1).consolidateOptionally(s1, v1), v1) }) diff --git a/src/main/scala/rules/ChunkSupporter.scala b/src/main/scala/rules/ChunkSupporter.scala index 48f8e0474..938b00968 100644 --- a/src/main/scala/rules/ChunkSupporter.scala +++ b/src/main/scala/rules/ChunkSupporter.scala @@ -7,7 +7,7 @@ package viper.silicon.rules import viper.silicon.debugger.DebugExp -import viper.silicon.dependencyAnalysis.DependencyAnalysisInfoes +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfos import viper.silicon.interfaces.state._ import viper.silicon.interfaces.{Success, VerificationResult} import viper.silicon.resources.{NonQuantifiedPropertyInterpreter, Resources} @@ -35,7 +35,7 @@ trait ChunkSupportRules extends SymbolicExecutionRules { ve: VerificationError, v: Verifier, description: String, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult @@ -50,7 +50,7 @@ trait ChunkSupportRules extends SymbolicExecutionRules { argsExp: Option[Seq[ast.Exp]], ve: VerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Heap, Term, Verifier) => VerificationResult) : VerificationResult @@ -59,7 +59,7 @@ trait ChunkSupportRules extends SymbolicExecutionRules { id: ChunkIdentifer, args: Iterable[Term], v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) : Option[CH] def findChunksWithID[CH <: NonQuantifiedChunk: ClassTag] @@ -81,15 +81,15 @@ object chunkSupporter extends ChunkSupportRules { ve: VerificationError, v: Verifier, description: String, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { if(v.decider.isPathInfeasible){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfoes) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfos) return Q(s, h, Option.when(returnSnap)(Unit), v) } - consume2(s, h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, analysisInfoes)((s2, h2, optSnap, v2) => + consume2(s, h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, analysisInfos)((s2, h2, optSnap, v2) => optSnap match { case Some(snap) => Q(s2, h2, Some(snap.convert(sorts.Snap)), v2) @@ -118,14 +118,14 @@ object chunkSupporter extends ChunkSupportRules { returnSnap: Boolean, ve: VerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { val id = ChunkIdentifier(resource, s.program) if (s.exhaleExt) { val failure = createFailure(ve, v, s, "chunk consume in package") - magicWandSupporter.transfer(s, perms, permsExp, failure, Seq(), v, analysisInfoes)(consumeGreedy(_, _, id, args, _, _, _, analysisInfoes))((s1, optCh, v1) => + magicWandSupporter.transfer(s, perms, permsExp, failure, Seq(), v, analysisInfos)(consumeGreedy(_, _, id, args, _, _, _, analysisInfos))((s1, optCh, v1) => if (returnSnap){ Q(s1, h, optCh.flatMap(ch => Some(ch.snap)), v1) } else { @@ -134,15 +134,15 @@ object chunkSupporter extends ChunkSupportRules { } else { executionFlowController.tryOrFail2[Heap, Option[Term]](s.copy(h = h), v)((s1, v1, QS) => if (s1.moreCompleteExhale) { - moreCompleteExhaleSupporter.consumeComplete(s1, s1.h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v1, analysisInfoes)((s2, h2, snap2, v2) => { + moreCompleteExhaleSupporter.consumeComplete(s1, s1.h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v1, analysisInfos)((s2, h2, snap2, v2) => { QS(s2.copy(h = s.h), h2, snap2, v2) }) } else { - consumeGreedy(s1, s1.h, id, args, perms, permsExp, v1, analysisInfoes) match { + consumeGreedy(s1, s1.h, id, args, perms, permsExp, v1, analysisInfos) match { case (Complete(), s2, h2, optCh2) => val snap = optCh2 match { case Some(ch) if returnSnap => - if (v1.decider.check(IsPositive(perms), Verifier.config.checkTimeout(), analysisInfoes)) { + if (v1.decider.check(IsPositive(perms), Verifier.config.checkTimeout(), analysisInfos)) { Some(ch.snap) } else { Some(Ite(IsPositive(perms), ch.snap.convert(sorts.Snap), Unit)) @@ -150,14 +150,14 @@ object chunkSupporter extends ChunkSupportRules { case _ => None } QS(s2.copy(h = s.h), h2, snap, v1) - case (_, s2, h2, _) if v1.decider.checkSmoke(analysisInfoes, isAssert = true) => + case (_, s2, h2, _) if v1.decider.checkSmoke(analysisInfos, isAssert = true) => if(Verifier.config.disableInfeasibilityChecks()) QS(s2.copy(h = s.h), h2, None, v1) else Success() // TODO: Mark branch as dead? case _ => val failure = createFailure(ve, v1, s1, "consuming chunk", true) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfoes, v1.reportFurtherErrors()) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfos, v1.reportFurtherErrors()) if(s1.retryLevel == 0 && v1.reportFurtherErrors() && Verifier.config.disableInfeasibilityChecks()){ failure combine QS(s1.copy(h = s.h), s1.h, None, v1) }else{ @@ -176,7 +176,7 @@ object chunkSupporter extends ChunkSupportRules { perms: Term, permsExp: Option[ast.Exp], v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) : (ConsumptionResult, State, Heap, Option[NonQuantifiedChunk]) = { val consumeExact = terms.utils.consumeExactRead(perms, s.constrainableARPs) @@ -185,13 +185,13 @@ object chunkSupporter extends ChunkSupportRules { val interpreter = new NonQuantifiedPropertyInterpreter(heap.values, v) val resource = Resources.resourceDescriptions(chunk.resourceID) val pathCond = interpreter.buildPathConditionsForChunk(chunk, resource.instanceProperties(s.mayAssumeUpperBounds)) - pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), analysisInfoes)) + pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), analysisInfos)) } - findChunk[NonQuantifiedChunk](h.values, id, args, v, analysisInfoes) match { + findChunk[NonQuantifiedChunk](h.values, id, args, v, analysisInfos) match { case Some(ch) => if (s.assertReadAccessOnly) { - if (v.decider.check(Implies(IsPositive(perms), IsPositive(ch.perm)), Verifier.config.assertTimeout.getOrElse(0), analysisInfoes)) { + if (v.decider.check(Implies(IsPositive(perms), IsPositive(ch.perm)), Verifier.config.assertTimeout.getOrElse(0), analysisInfos)) { (Complete(), s, h, Some(ch)) } else { (Incomplete(perms, permsExp), s, h, None) @@ -200,22 +200,22 @@ object chunkSupporter extends ChunkSupportRules { val toTake = PermMin(ch.perm, perms) val toTakeExp = permsExp.map(pe => buildMinExp(Seq(ch.permExp.get, pe), ast.Perm)) val newPermExp = permsExp.map(pe => ast.PermSub(ch.permExp.get, toTakeExp.get)(pe.pos, pe.info, pe.errT)) - val newChunk = NonQuantifiedChunk.withPerm(ch, PermMinus(ch.perm, toTake), newPermExp, v.decider.getAnalysisInfo(analysisInfoes)) - val takenChunk = Some(NonQuantifiedChunk.withPerm(ch, toTake, toTakeExp, v.decider.getAnalysisInfo(analysisInfoes), isExhale=true)) + val newChunk = NonQuantifiedChunk.withPerm(ch, PermMinus(ch.perm, toTake), newPermExp, v.decider.getAnalysisInfo(analysisInfos)) + val takenChunk = Some(NonQuantifiedChunk.withPerm(ch, toTake, toTakeExp, v.decider.getAnalysisInfo(analysisInfos), isExhale=true)) var newHeap = h - ch - if (!v.decider.check(newChunk.perm === NoPerm, Verifier.config.checkTimeout(), analysisInfoes.withDependencyType(DependencyType.Internal))) { + if (!v.decider.check(newChunk.perm === NoPerm, Verifier.config.checkTimeout(), analysisInfos.withDependencyType(DependencyType.Internal))) { newHeap = newHeap + newChunk assumeProperties(newChunk, newHeap) } val remainingExp = permsExp.map(pe => ast.PermSub(pe, toTakeExp.get)(pe.pos, pe.info, pe.errT)) - (ConsumptionResult(PermMinus(perms, toTake), remainingExp, Seq(), v, 0, analysisInfoes), s, newHeap, takenChunk) + (ConsumptionResult(PermMinus(perms, toTake), remainingExp, Seq(), v, 0, analysisInfos), s, newHeap, takenChunk) } else { - if (v.decider.check(ch.perm !== NoPerm, Verifier.config.checkTimeout(), analysisInfoes)) { + if (v.decider.check(ch.perm !== NoPerm, Verifier.config.checkTimeout(), analysisInfos)) { val constraintExp = permsExp.map(pe => ast.PermLtCmp(pe, ch.permExp.get)(pe.pos, pe.info, pe.errT)) - v.decider.assume(PermLess(perms, ch.perm), Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp)), analysisInfoes) + v.decider.assume(PermLess(perms, ch.perm), Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp)), analysisInfos) val newPermExp = permsExp.map(pe => ast.PermSub(ch.permExp.get, pe)(pe.pos, pe.info, pe.errT)) - val newChunk = NonQuantifiedChunk.withPerm(ch, PermMinus(ch.perm, perms), newPermExp, v.decider.getAnalysisInfo(analysisInfoes)) - val takenChunk = NonQuantifiedChunk.withPerm(ch, perms, permsExp, v.decider.getAnalysisInfo(analysisInfoes), isExhale=true) + val newChunk = NonQuantifiedChunk.withPerm(ch, PermMinus(ch.perm, perms), newPermExp, v.decider.getAnalysisInfo(analysisInfos)) + val takenChunk = NonQuantifiedChunk.withPerm(ch, perms, permsExp, v.decider.getAnalysisInfo(analysisInfos), isExhale=true) val newHeap = h - ch + newChunk assumeProperties(newChunk, newHeap) (Complete(), s, newHeap, Some(takenChunk)) @@ -224,7 +224,7 @@ object chunkSupporter extends ChunkSupportRules { } } case None => - if (consumeExact && s.retrying && v.decider.check(perms === NoPerm, Verifier.config.checkTimeout(), analysisInfoes)) { + if (consumeExact && s.retrying && v.decider.check(perms === NoPerm, Verifier.config.checkTimeout(), analysisInfos)) { (Complete(), s, h, None) } else { (Incomplete(perms, permsExp), s, h, None) @@ -237,10 +237,10 @@ object chunkSupporter extends ChunkSupportRules { : VerificationResult = { // TODO ake - val analysisInfoes = DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes.withSource(StringAnalysisSourceInfo("produce", ast.NoPosition)).withDependencyType(DependencyType.Internal) + val analysisInfos = DependencyAnalysisInfos.DefaultDependencyAnalysisInfos.withSource(StringAnalysisSourceInfo("produce", ast.NoPosition)).withDependencyType(DependencyType.Internal) // Try to merge the chunk into the heap by finding an alias. // In any case, property assumptions are added after the merge step. - val (fr1, h1) = v.stateConsolidator(s).merge(s.functionRecorder, s, h, ch, v, analysisInfoes) + val (fr1, h1) = v.stateConsolidator(s).merge(s.functionRecorder, s, h, ch, v, analysisInfos) Q(s.copy(functionRecorder = fr1), h1, v) } @@ -251,11 +251,11 @@ object chunkSupporter extends ChunkSupportRules { argsExp: Option[Seq[ast.Exp]], ve: VerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Heap, Term, Verifier) => VerificationResult) : VerificationResult = { if(v.decider.isPathInfeasible){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfoes) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfos) return Q(s, h, Unit, v) } @@ -263,7 +263,7 @@ object chunkSupporter extends ChunkSupportRules { val lookupFunction = if (s1.moreCompleteExhale) moreCompleteExhaleSupporter.lookupComplete _ else lookupGreedy _ - lookupFunction(s1, s1.h, resource, args, argsExp, ve, v1, analysisInfoes)((s2, tSnap, v2) => + lookupFunction(s1, s1.h, resource, args, argsExp, ve, v1, analysisInfos)((s2, tSnap, v2) => QS(s2.copy(h = s.h), s2.h, tSnap, v2)) })(Q) } @@ -275,16 +275,16 @@ object chunkSupporter extends ChunkSupportRules { argsExp: Option[Seq[ast.Exp]], ve: VerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Term, Verifier) => VerificationResult) : VerificationResult = { val id = ChunkIdentifier(resource, s.program) - val findRes = findChunk[NonQuantifiedChunk](h.values, id, args, v, analysisInfoes) + val findRes = findChunk[NonQuantifiedChunk](h.values, id, args, v, analysisInfos) findRes match { - case Some(ch) if v.decider.check(IsPositive(ch.perm), Verifier.config.assertTimeout.getOrElse(0), analysisInfoes) => + case Some(ch) if v.decider.check(IsPositive(ch.perm), Verifier.config.assertTimeout.getOrElse(0), analysisInfos) => Q(s, ch.snap, v) - case _ if v.decider.checkSmoke(analysisInfoes, isAssert = true) => + case _ if v.decider.checkSmoke(analysisInfos, isAssert = true) => if (s.isInPackage || Verifier.config.disableInfeasibilityChecks()) { val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) Q(s, snap, v) @@ -293,7 +293,7 @@ object chunkSupporter extends ChunkSupportRules { } case _ => val failure = createFailure(ve, v, s, "looking up chunk", true) - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfoes, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfos, v.reportFurtherErrors()) if(s.retryLevel == 0 && v.reportFurtherErrors() && Verifier.config.disableInfeasibilityChecks()){ val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) failure combine Q(s, snap, v) @@ -308,10 +308,10 @@ object chunkSupporter extends ChunkSupportRules { id: ChunkIdentifer, args: Iterable[Term], v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) : Option[CH] = { val relevantChunks = findChunksWithID[CH](chunks, id) - findChunkLiterally(relevantChunks, args) orElse findChunkWithProver(relevantChunks, args, v, analysisInfoes) + findChunkLiterally(relevantChunks, args) orElse findChunkWithProver(relevantChunks, args, v, analysisInfos) } def findChunksWithID[CH <: NonQuantifiedChunk: ClassTag](chunks: Iterable[Chunk], id: ChunkIdentifer): Iterable[CH] = { @@ -348,9 +348,9 @@ object chunkSupporter extends ChunkSupportRules { chunks find (ch => ch.args == args) } - private def findChunkWithProver[CH <: NonQuantifiedChunk](chunks: Iterable[CH], args: Iterable[Term], v: Verifier, analysisInfoes: DependencyAnalysisInfoes) = { + private def findChunkWithProver[CH <: NonQuantifiedChunk](chunks: Iterable[CH], args: Iterable[Term], v: Verifier, analysisInfos: DependencyAnalysisInfos) = { chunks find (ch => args.size == ch.args.size && - v.decider.check(And(ch.args zip args map (x => x._1 === x._2)), Verifier.config.checkTimeout(), analysisInfoes)) + v.decider.check(And(ch.args zip args map (x => x._1 === x._2)), Verifier.config.checkTimeout(), analysisInfos)) } } diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index fc71cd867..83d206bc5 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -38,7 +38,7 @@ trait ConsumptionRules extends SymbolicExecutionRules { * consumed partial heap. * @return The result of the continuation. */ - def consume(s: State, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, analysisInfoes: DependencyAnalysisInfoes) + def consume(s: State, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, analysisInfos: DependencyAnalysisInfos) (Q: (State, Option[Term], Verifier) => VerificationResult) : VerificationResult @@ -63,7 +63,7 @@ trait ConsumptionRules extends SymbolicExecutionRules { returnSnap: Boolean, pvef: ast.Exp => PartialVerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Option[Term], Verifier) => VerificationResult) : VerificationResult } @@ -77,11 +77,11 @@ object consumer extends ConsumptionRules { */ /** @inheritdoc */ - def consume(s: State, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, analysisInfoes: DependencyAnalysisInfoes) + def consume(s: State, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, analysisInfos: DependencyAnalysisInfos) (Q: (State, Option[Term], Verifier) => VerificationResult) : VerificationResult = { - consumeR(s, s.h, a.whenExhaling, returnSnap, pve, v, analysisInfoes)((s1, h1, snap, v1) => { + consumeR(s, s.h, a.whenExhaling, returnSnap, pve, v, analysisInfos)((s1, h1, snap, v1) => { val s2 = s1.copy(h = h1, partiallyConsumedHeap = s.partiallyConsumedHeap) Q(s2, snap, v1)}) @@ -93,7 +93,7 @@ object consumer extends ConsumptionRules { returnSnap: Boolean, pvef: ast.Exp => PartialVerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -108,7 +108,7 @@ object consumer extends ConsumptionRules { allPves ++= pves }) - consumeTlcs(s, s.h, allTlcs.result(), returnSnap, allPves.result(), v, analysisInfoes)((s1, h1, snap1, v1) => { + consumeTlcs(s, s.h, allTlcs.result(), returnSnap, allPves.result(), v, analysisInfos)((s1, h1, snap1, v1) => { val s2 = s1.copy(h = h1, partiallyConsumedHeap = s.partiallyConsumedHeap) Q(s2, snap1, v1) @@ -121,7 +121,7 @@ object consumer extends ConsumptionRules { returnSnap: Boolean, pves: Seq[PartialVerificationError], v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -132,10 +132,10 @@ object consumer extends ConsumptionRules { val pve = pves.head if (tlcs.tail.isEmpty) - wrappedConsumeTlc(s, h, a, returnSnap, pve, v, analysisInfoes)(Q) + wrappedConsumeTlc(s, h, a, returnSnap, pve, v, analysisInfos)(Q) else - wrappedConsumeTlc(s, h, a, returnSnap, pve, v, analysisInfoes)((s1, h1, snap1, v1) => { - consumeTlcs(s1, h1, tlcs.tail, returnSnap, pves.tail, v1, analysisInfoes)((s2, h2, snap2, v2) => + wrappedConsumeTlc(s, h, a, returnSnap, pve, v, analysisInfos)((s1, h1, snap1, v1) => { + consumeTlcs(s1, h1, tlcs.tail, returnSnap, pves.tail, v1, analysisInfos)((s2, h2, snap2, v2) => (snap1, snap2) match { case (Some(sn1), Some(sn2)) if returnSnap => Q(s2, h2, Some(Combine(sn1, sn2)), v2) @@ -146,14 +146,14 @@ object consumer extends ConsumptionRules { } } - private def consumeR(s: State, h: Heap, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, analysisInfoes: DependencyAnalysisInfoes) + private def consumeR(s: State, h: Heap, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, analysisInfos: DependencyAnalysisInfos) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { val tlcs = a.topLevelConjuncts val pves = Seq.fill(tlcs.length)(pve) - consumeTlcs(s, h, tlcs, returnSnap, pves, v, analysisInfoes)(Q) + consumeTlcs(s, h, tlcs, returnSnap, pves, v, analysisInfos)(Q) } /** Wrapper/decorator for consume that injects the following operations: @@ -166,7 +166,7 @@ object consumer extends ConsumptionRules { returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -180,15 +180,15 @@ object consumer extends ConsumptionRules { val s1 = s0.copy(h = s.h) /* s1 is s, but the retrying flag might be set */ val sepIdentifier = v1.symbExLog.openScope(new ConsumeRecord(a, s1, v.decider.pcs)) - val analysisInfoes1 = analysisInfoes.addInfo(a.info, a) + val analysisInfos1 = analysisInfos.addInfo(a.info, a) - consumeTlc(s1, h0, a, returnSnap, pve, v1, analysisInfoes1)((s2, h2, snap2, v2) => { + consumeTlc(s1, h0, a, returnSnap, pve, v1, analysisInfos1)((s2, h2, snap2, v2) => { v2.symbExLog.closeScope(sepIdentifier) QS(s2, h2, snap2, v2)}) })(Q) } - private def consumeTlc(s: State, h: Heap, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, analysisInfoes: DependencyAnalysisInfoes) + private def consumeTlc(s: State, h: Heap, a: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, analysisInfos: DependencyAnalysisInfos) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -200,7 +200,7 @@ object consumer extends ConsumptionRules { */ if(v.decider.isPathInfeasible){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfoes) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfos) return Q(s, h, Option.when(returnSnap)(Unit), v) } @@ -214,15 +214,15 @@ object consumer extends ConsumptionRules { case imp @ ast.Implies(e0, a0) if !a.isPure && s.moreJoins.id >= JoinMode.Impure.id => val impliesRecord = new ImpliesRecord(imp, s, v.decider.pcs, "consume") val uidImplies = v.symbExLog.openScope(impliesRecord) - consumeConditionalTlcMoreJoins(s, h, e0, a0, None, uidImplies, returnSnap, pve, v, analysisInfoes)(Q) + consumeConditionalTlcMoreJoins(s, h, e0, a0, None, uidImplies, returnSnap, pve, v, analysisInfos)(Q) case imp @ ast.Implies(e0, a0) if !a.isPure => val impliesRecord = new ImpliesRecord(imp, s, v.decider.pcs, "consume") val uidImplies = v.symbExLog.openScope(impliesRecord) - evaluator.eval(s, e0, pve, v, analysisInfoes)((s1, t0, e0New, v1) => - branch(s1, t0, (e0, e0New), v1, analysisInfoes)( - (s2, v2) => consumeR(s2, h, a0, returnSnap, pve, v2, analysisInfoes)((s3, h1, t1, v3) => { + evaluator.eval(s, e0, pve, v, analysisInfos)((s1, t0, e0New, v1) => + branch(s1, t0, (e0, e0New), v1, analysisInfos)( + (s2, v2) => consumeR(s2, h, a0, returnSnap, pve, v2, analysisInfos)((s3, h1, t1, v3) => { v3.symbExLog.closeScope(uidImplies) Q(s3, h1, t1, v3) }), @@ -234,19 +234,19 @@ object consumer extends ConsumptionRules { case ite @ ast.CondExp(e0, a1, a2) if !a.isPure && s.moreJoins.id >= JoinMode.Impure.id => val condExpRecord = new CondExpRecord(ite, s, v.decider.pcs, "consume") val uidCondExp = v.symbExLog.openScope(condExpRecord) - consumeConditionalTlcMoreJoins(s, h, e0, a1, Some(a2), uidCondExp, returnSnap, pve, v, analysisInfoes)(Q) + consumeConditionalTlcMoreJoins(s, h, e0, a1, Some(a2), uidCondExp, returnSnap, pve, v, analysisInfos)(Q) case ite @ ast.CondExp(e0, a1, a2) if !a.isPure => val condExpRecord = new CondExpRecord(ite, s, v.decider.pcs, "consume") val uidCondExp = v.symbExLog.openScope(condExpRecord) - eval(s, e0, pve, v, analysisInfoes)((s1, t0, e0New, v1) => - branch(s1, t0, (e0, e0New), v1, analysisInfoes)( - (s2, v2) => consumeR(s2, h, a1, returnSnap, pve, v2, analysisInfoes)((s3, h1, t1, v3) => { + eval(s, e0, pve, v, analysisInfos)((s1, t0, e0New, v1) => + branch(s1, t0, (e0, e0New), v1, analysisInfos)( + (s2, v2) => consumeR(s2, h, a1, returnSnap, pve, v2, analysisInfos)((s3, h1, t1, v3) => { v3.symbExLog.closeScope(uidCondExp) Q(s3, h1, t1, v3) }), - (s2, v2) => consumeR(s2, h, a2, returnSnap, pve, v2, analysisInfoes)((s3, h1, t1, v3) => { + (s2, v2) => consumeR(s2, h, a2, returnSnap, pve, v2, analysisInfos)((s3, h1, t1, v3) => { v3.symbExLog.closeScope(uidCondExp) Q(s3, h1, t1, v3) }))) @@ -256,17 +256,17 @@ object consumer extends ConsumptionRules { val resource = accPred.res(s.program) val ePerm = accPred.perm - evals(s, eArgs, _ => pve, v, analysisInfoes)((s1, tArgs, eArgsNew, v1) => - eval(s1, ePerm, pve, v1, analysisInfoes)((s1a, tPerm, ePermNew, v1a) => - permissionSupporter.assertNotNegative(s1a, tPerm, ePerm, ePermNew, pve, v1a, analysisInfoes)((s2, v2) => { + evals(s, eArgs, _ => pve, v, analysisInfos)((s1, tArgs, eArgsNew, v1) => + eval(s1, ePerm, pve, v1, analysisInfos)((s1a, tPerm, ePermNew, v1a) => + permissionSupporter.assertNotNegative(s1a, tPerm, ePerm, ePermNew, pve, v1a, analysisInfos)((s2, v2) => { val loss = if (!Verifier.config.unsafeWildcardOptimization() || (resource.isInstanceOf[ast.Location] && s2.permLocations.contains(resource.asInstanceOf[ast.Location]))) PermTimes(tPerm, s2.permissionScalingFactor) else WildcardSimplifyingPermTimes(tPerm, s2.permissionScalingFactor) val lossExp = ePermNew.map(p => ast.PermMul(p, s2.permissionScalingFactorExp.get)(p.pos, p.info, p.errT)) - val s3 = v2.heapSupporter.triggerResourceIfNeeded(s2, accPred.loc, tArgs, eArgsNew, v2, analysisInfoes) - v2.heapSupporter.consumeSingle(s3, h, accPred.loc, tArgs, eArgsNew, loss, lossExp, returnSnap, pve, v2, analysisInfoes)((s4, h4, snap, v4) => { + val s3 = v2.heapSupporter.triggerResourceIfNeeded(s2, accPred.loc, tArgs, eArgsNew, v2, analysisInfos) + v2.heapSupporter.consumeSingle(s3, h, accPred.loc, tArgs, eArgsNew, loss, lossExp, returnSnap, pve, v2, analysisInfos)((s4, h4, snap, v4) => { val s5 = s4.copy(constrainableARPs = s.constrainableARPs, partiallyConsumedHeap = Some(h4)) Q(s5, h4, snap, v4) @@ -296,7 +296,7 @@ object consumer extends ConsumptionRules { if (forall.triggers.isEmpty) None else Some(forall.triggers) val s0 = s.copy(functionRecorder = s.functionRecorder.enterQuantifiedExp(qpa)) - evalQuantified(s0, Forall, forall.variables, Seq(cond), ePerm +: eArgs, optTrigger, qid, pve, v, analysisInfoes) { + evalQuantified(s0, Forall, forall.variables, Seq(cond), ePerm +: eArgs, optTrigger, qid, pve, v, analysisInfos) { case (s1, qvars, qvarExps, Seq(tCond), condNew, Some((Seq(tPerm, tArgs@_*), permArgsNew, tTriggers, (auxGlobals, auxNonGlobals), auxExps)), v1) => v1.heapSupporter.consumeQuantified( s = s1, @@ -325,7 +325,7 @@ object consumer extends ConsumptionRules { notInjectiveReason = QPAssertionNotInjective(resAcc), insufficientPermissionReason = insuffReason, v1, - analysisInfoes)((s2, h2, snap, v2) => { + analysisInfos)((s2, h2, snap, v2) => { val s3 = s2.copy(constrainableARPs = s.constrainableARPs, functionRecorder = s2.functionRecorder.leaveQuantifiedExp(qpa)) Q(s3, h2, snap, v2) }) @@ -333,15 +333,15 @@ object consumer extends ConsumptionRules { } case let: ast.Let if !let.isPure => - letSupporter.handle[ast.Exp](s, let, pve, v, analysisInfoes)((s1, g1, body, v1) => { + letSupporter.handle[ast.Exp](s, let, pve, v, analysisInfos)((s1, g1, body, v1) => { val s2 = s1.copy(g = s1.g + g1) - consumeR(s2, h, body, returnSnap, pve, v1, analysisInfoes)(Q)}) + consumeR(s2, h, body, returnSnap, pve, v1, analysisInfos)(Q)}) case _: ast.InhaleExhaleExp => createFailure(viper.silicon.utils.consistency.createUnexpectedInhaleExhaleExpressionError(a), v, s, "valid AST") case _ => - evalAndAssert(s, a, returnSnap, pve, v, analysisInfoes)((s1, t, v1) => { + evalAndAssert(s, a, returnSnap, pve, v, analysisInfos)((s1, t, v1) => { Q(s1, h, t, v1) }) } @@ -351,20 +351,20 @@ object consumer extends ConsumptionRules { private def consumeConditionalTlcMoreJoins(s: State, h: Heap, e0: ast.Exp, a1: ast.Exp, a2: Option[ast.Exp], scopeUid: Int, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { - eval(s, e0, pve, v, analysisInfoes)((s1, t0, e0New, v1) => - joiner.join[(Heap, Option[Term]), (Heap, Option[Term])](s1, v1, analysisInfoes, resetState = false)((s1, v1, QB) => { - branch(s1.copy(parallelizeBranches = false), t0, (e0, e0New), v1, analysisInfoes)( + eval(s, e0, pve, v, analysisInfos)((s1, t0, e0New, v1) => + joiner.join[(Heap, Option[Term]), (Heap, Option[Term])](s1, v1, analysisInfos, resetState = false)((s1, v1, QB) => { + branch(s1.copy(parallelizeBranches = false), t0, (e0, e0New), v1, analysisInfos)( (s2, v2) => - consumeR(s2.copy(parallelizeBranches = s1.parallelizeBranches), h, a1, returnSnap, pve, v2, analysisInfoes)((s3, h1, t1, v3) => { + consumeR(s2.copy(parallelizeBranches = s1.parallelizeBranches), h, a1, returnSnap, pve, v2, analysisInfos)((s3, h1, t1, v3) => { v3.symbExLog.closeScope(scopeUid) QB(s3, (h1, t1), v3) }), (s2, v2) => a2 match { - case Some(a2) => consumeR(s2.copy(parallelizeBranches = s1.parallelizeBranches), h, a2, returnSnap, pve, v2, analysisInfoes)((s3, h1, t1, v3) => { + case Some(a2) => consumeR(s2.copy(parallelizeBranches = s1.parallelizeBranches), h, a2, returnSnap, pve, v2, analysisInfos)((s3, h1, t1, v3) => { v3.symbExLog.closeScope(scopeUid) QB(s3, (h1, t1), v3) }) @@ -381,7 +381,7 @@ object consumer extends ConsumptionRules { State.mergeHeap( entry1.data._1, And(entry1.pathConditions.branchConditions), Option.when(withExp)(BigAnd(entry1.pathConditions.branchConditionExps.map(_._2.get))), entry2.data._1, And(entry2.pathConditions.branchConditions), Option.when(withExp)(BigAnd(entry2.pathConditions.branchConditionExps.map(_._2.get))), - AnalysisInfo(v.decider, v.decider.dependencyAnalyzer, analysisInfoes.withSource(StringAnalysisSourceInfo("conditional join", e0.pos))) + AnalysisInfo(v.decider, v.decider.dependencyAnalyzer, analysisInfos.withSource(StringAnalysisSourceInfo("conditional join", e0.pos))) ), // Assume that entry1.pcs is inverse of entry2.pcs (entry1.data._2, entry2.data._2) match { @@ -402,7 +402,7 @@ object consumer extends ConsumptionRules { } - private def evalAndAssert(s: State, e: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, analysisInfoes: DependencyAnalysisInfoes) + private def evalAndAssert(s: State, e: ast.Exp, returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, analysisInfos: DependencyAnalysisInfos) (Q: (State, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -421,23 +421,23 @@ object consumer extends ConsumptionRules { exhaleExt = false) executionFlowController.tryOrFail0(s1, v)((s2, v1, QS) => { - eval(s2, e, pve, v1, analysisInfoes)((s3, t, eNew, v2) => { + eval(s2, e, pve, v1, analysisInfos)((s3, t, eNew, v2) => { val termToAssert = t match { case Quantification(q, vars, body, trgs, name, isGlob, weight) => val transformed = FunctionPreconditionTransformer.transform(body, s3.program) - v2.decider.assume(Quantification(q, vars, transformed, trgs, name+"_precondition", isGlob, weight), Option.when(withExp)(e), eNew, analysisInfoes) + v2.decider.assume(Quantification(q, vars, transformed, trgs, name+"_precondition", isGlob, weight), Option.when(withExp)(e), eNew, analysisInfos) Quantification(q, vars, Implies(transformed, body), trgs, name, isGlob, weight) case _ => t } - v2.decider.assert(termToAssert, analysisInfoes) { + v2.decider.assert(termToAssert, analysisInfos) { case true => - v2.decider.assume(t, Option.when(withExp)(e), eNew, analysisInfoes.withDependencyType(DependencyType.Internal)) + v2.decider.assume(t, Option.when(withExp)(e), eNew, analysisInfos.withDependencyType(DependencyType.Internal)) QS(s3, v2) case false => val failure = createFailure(pve dueTo AssertionFalse(e), v2, s3, termToAssert, eNew) - if(s3.retryLevel == 0) v2.decider.handleFailedAssertionForDependencyAnalysis(t, analysisInfoes, assumeFailedAssertion=false) + if(s3.retryLevel == 0) v2.decider.handleFailedAssertionForDependencyAnalysis(t, analysisInfos, assumeFailedAssertion=false) if (s3.retryLevel == 0 && v2.reportFurtherErrors()){ - v2.decider.assume(t, Option.when(withExp)(e), eNew, analysisInfoes.withDependencyType(DependencyType.Explicit)) + v2.decider.assume(t, Option.when(withExp)(e), eNew, analysisInfos.withDependencyType(DependencyType.Explicit)) failure combine QS(s3, v2) } else failure}}) })((s4, v4) => { diff --git a/src/main/scala/rules/ConsumptionResult.scala b/src/main/scala/rules/ConsumptionResult.scala index 3ce09a087..7e1536a06 100644 --- a/src/main/scala/rules/ConsumptionResult.scala +++ b/src/main/scala/rules/ConsumptionResult.scala @@ -6,7 +6,7 @@ package viper.silicon.rules -import viper.silicon.dependencyAnalysis.{AnalysisInfoes, DependencyAnalysisInfoes} +import viper.silicon.dependencyAnalysis.{AnalysisInfos, DependencyAnalysisInfos} import viper.silicon.state.terms.{Forall, Term, Var} import viper.silicon.state.terms.perms.IsNonPositive import viper.silver.ast @@ -28,13 +28,13 @@ private case class Incomplete(permsNeeded: Term, permsNeededExp: Option[ast.Exp] } object ConsumptionResult { - def apply(term: Term, exp: Option[ast.Exp], qvars: Seq[Var], v: Verifier, timeout: Int, analysisInfoes: DependencyAnalysisInfoes): ConsumptionResult = { + def apply(term: Term, exp: Option[ast.Exp], qvars: Seq[Var], v: Verifier, timeout: Int, analysisInfos: DependencyAnalysisInfos): ConsumptionResult = { val toCheck = if (qvars.isEmpty) { IsNonPositive(term) } else { Forall(qvars, IsNonPositive(term), Seq()) } - if (v.decider.check(toCheck, timeout, analysisInfoes)) + if (v.decider.check(toCheck, timeout, analysisInfos)) Complete() else Incomplete(term, exp) diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 681c8b069..d4015ec9f 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -10,7 +10,7 @@ import viper.silicon import viper.silicon.Config.JoinMode import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp -import viper.silicon.dependencyAnalysis.DependencyAnalysisInfoes +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfos import viper.silicon.interfaces._ import viper.silicon.interfaces.state.ChunkIdentifer import viper.silicon.logger.records.data.{CondExpRecord, EvaluateRecord, ImpliesRecord} @@ -39,11 +39,11 @@ import viper.silver.verifier.{CounterexampleTransformer, PartialVerificationErro */ trait EvaluationRules extends SymbolicExecutionRules { - def evals(s: State, es: Seq[ast.Exp], pvef: ast.Exp => PartialVerificationError, v: Verifier, analysisInfoes: DependencyAnalysisInfoes) + def evals(s: State, es: Seq[ast.Exp], pvef: ast.Exp => PartialVerificationError, v: Verifier, analysisInfos: DependencyAnalysisInfos) (Q: (State, List[Term], Option[List[ast.Exp]], Verifier) => VerificationResult) : VerificationResult - def eval(s: State, e: ast.Exp, pve: PartialVerificationError, v: Verifier, analysisInfoes: DependencyAnalysisInfoes) + def eval(s: State, e: ast.Exp, pve: PartialVerificationError, v: Verifier, analysisInfos: DependencyAnalysisInfos) (Q: (State, Term, Option[ast.Exp], Verifier) => VerificationResult) : VerificationResult @@ -56,7 +56,7 @@ trait EvaluationRules extends SymbolicExecutionRules { name: String, pve: PartialVerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Seq[Var], Option[Seq[ast.LocalVarDecl]], Seq[Term], Option[Seq[ast.Exp]], Option[(Seq[Term], Option[Seq[ast.Exp]], Seq[Trigger], (Seq[Term], Seq[Quantification]), Option[(InsertionOrderedSet[DebugExp], InsertionOrderedSet[DebugExp])])], Verifier) => VerificationResult) : VerificationResult } @@ -65,38 +65,38 @@ object evaluator extends EvaluationRules { import consumer._ import producer._ - def evals(s: State, es: Seq[ast.Exp], pvef: ast.Exp => PartialVerificationError, v: Verifier, analysisInfoes: DependencyAnalysisInfoes) + def evals(s: State, es: Seq[ast.Exp], pvef: ast.Exp => PartialVerificationError, v: Verifier, analysisInfos: DependencyAnalysisInfos) (Q: (State, List[Term], Option[List[ast.Exp]], Verifier) => VerificationResult) : VerificationResult = - evals2(s, es, Nil, pvef, v, analysisInfoes)(Q) + evals2(s, es, Nil, pvef, v, analysisInfos)(Q) - private def evals2(s: State, es: Seq[ast.Exp], ts: List[Term], pvef: ast.Exp => PartialVerificationError, v: Verifier, analysisInfoes: DependencyAnalysisInfoes) + private def evals2(s: State, es: Seq[ast.Exp], ts: List[Term], pvef: ast.Exp => PartialVerificationError, v: Verifier, analysisInfos: DependencyAnalysisInfos) (Q: (State, List[Term], Option[List[ast.Exp]], Verifier) => VerificationResult) : VerificationResult = { if (es.isEmpty) Q(s, ts.reverse, if (withExp) Some(List.empty) else None, v) else - eval(s, es.head, pvef(es.head), v, analysisInfoes)((s1, t, eNew, v1) => - evals2(s1, es.tail, t :: ts, pvef, v1, analysisInfoes)((s2, ts2, es2, v2) => Q(s2, ts2, eNew.map(eN => eN :: es2.get), v2))) + eval(s, es.head, pvef(es.head), v, analysisInfos)((s1, t, eNew, v1) => + evals2(s1, es.tail, t :: ts, pvef, v1, analysisInfos)((s2, ts2, es2, v2) => Q(s2, ts2, eNew.map(eN => eN :: es2.get), v2))) } /** Wrapper Method for eval, for logging. See Executor.scala for explanation of analogue. **/ @inline - def eval(s: State, e: ast.Exp, pve: PartialVerificationError, v: Verifier, analysisInfoes: DependencyAnalysisInfoes) + def eval(s: State, e: ast.Exp, pve: PartialVerificationError, v: Verifier, analysisInfos: DependencyAnalysisInfos) (Q: (State, Term, Option[ast.Exp], Verifier) => VerificationResult) : VerificationResult = { val sepIdentifier = v.symbExLog.openScope(new EvaluateRecord(e, s, v.decider.pcs)) - val analysisInfoes1 = analysisInfoes.addInfo(e.info, e) + val analysisInfos1 = analysisInfos.addInfo(e.info, e) - eval3(s, e, pve, v, analysisInfoes1)((s1, t, eNew, v1) => { + eval3(s, e, pve, v, analysisInfos1)((s1, t, eNew, v1) => { v1.symbExLog.closeScope(sepIdentifier) Q(s1, t, eNew, v1)}) } - def eval3(s: State, e: ast.Exp, pve: PartialVerificationError, v: Verifier, analysisInfoes: DependencyAnalysisInfoes) + def eval3(s: State, e: ast.Exp, pve: PartialVerificationError, v: Verifier, analysisInfos: DependencyAnalysisInfos) (Q: (State, Term, Option[ast.Exp], Verifier) => VerificationResult) : VerificationResult = { @@ -106,7 +106,7 @@ object evaluator extends EvaluationRules { // assertionNodesForJoin.foreach(n => v.decider.dependencyAnalyzer.addAssumption(True, CompositeAnalysisSourceInfo(v.decider.analysisSourceInfoStack.getFullSourceInfo, AnalysisSourceInfo.createAnalysisSourceInfo(n)), v.decider.analysisSourceInfoStack.getAssumptionType, isJoinNode=true)) if(!Expressions.isKnownWellDefined(e, Some(s.program))){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfoes) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfos) } val sort = v.symbolConverter.toSort(e.typ) val newVar = v.decider.fresh(sort, None) // just make sure the returned term typechecks @@ -143,7 +143,7 @@ object evaluator extends EvaluationRules { reserveHeaps = Nil, exhaleExt = false) - eval2(s1, e, pve, v, analysisInfoes)((s2, t, eNew, v1) => { + eval2(s1, e, pve, v, analysisInfos)((s2, t, eNew, v1) => { val s3 = if (s2.recordPossibleTriggers) e match { @@ -161,7 +161,7 @@ object evaluator extends EvaluationRules { Q(s4, t, eNew, v1)}) } - protected def eval2(s: State, e: ast.Exp, pve: PartialVerificationError, v: Verifier, analysisInfoes: DependencyAnalysisInfoes) + protected def eval2(s: State, e: ast.Exp, pve: PartialVerificationError, v: Verifier, analysisInfos: DependencyAnalysisInfos) (Q: (State, Term, Option[ast.Exp], Verifier) => VerificationResult) : VerificationResult = { val eOpt = Option.when(withExp)(e) @@ -172,9 +172,9 @@ object evaluator extends EvaluationRules { case _: ast.NullLit => Q(s, Null, eOpt, v) case ast.IntLit(bigval) => Q(s, IntLiteral(bigval), eOpt, v) - case ast.EqCmp(e0, e1) => evalBinOp(s, e0, e1, Equals, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => + case ast.EqCmp(e0, e1) => evalBinOp(s, e0, e1, Equals, pve, v, analysisInfos)((s1, t, e0New, e1New, v1) => Q(s1, t, Option.when(withExp)(ast.EqCmp(e0New.get, e1New.get)(e.pos, e.info, e.errT)), v1)) - case ast.NeCmp(e0, e1) => evalBinOp(s, e0, e1, (p0: Term, p1: Term) => Not(Equals(p0, p1)), pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => + case ast.NeCmp(e0, e1) => evalBinOp(s, e0, e1, (p0: Term, p1: Term) => Not(Equals(p0, p1)), pve, v, analysisInfos)((s1, t, e0New, e1New, v1) => Q(s1, t, Option.when(withExp)(ast.NeCmp(e0New.get, e1New.get)(e.pos, e.info, e.errT)), v1)) case x: ast.LocalVarWithVersion => @@ -189,8 +189,8 @@ object evaluator extends EvaluationRules { case ast.FractionalPerm(e0, e1) => var t1: Term = null - evalBinOp(s, e0, e1, (t0, _t1) => {t1 = _t1; FractionPerm(t0, t1)}, pve, v, analysisInfoes)((s1, tFP, e0New, e1New, v1) => - failIfDivByZero(s1, tFP, e1, e1New, t1, predef.Zero, pve, v1, analysisInfoes)((s2, t, v2) + evalBinOp(s, e0, e1, (t0, _t1) => {t1 = _t1; FractionPerm(t0, t1)}, pve, v, analysisInfos)((s1, tFP, e0New, e1New, v1) => + failIfDivByZero(s1, tFP, e1, e1New, t1, predef.Zero, pve, v1, analysisInfos)((s2, t, v2) => Q(s2, t, e0New.map(ast.FractionalPerm(_, e1New.get)(e.pos, e.info, e.errT)), v2))) case _: ast.WildcardPerm if s.assertReadAccessOnly => @@ -201,7 +201,7 @@ object evaluator extends EvaluationRules { case _: ast.WildcardPerm => val (tVar, tConstraints, eVar) = v.decider.freshARP() val constraintExp = Option.when(withExp)(DebugExp.createInstance(s"${eVar.get.toString} > none", true)) - v.decider.assumeDefinition(tConstraints, constraintExp, analysisInfoes) + v.decider.assumeDefinition(tConstraints, constraintExp, analysisInfos) /* TODO: Only record wildcards in State.constrainableARPs that are used in exhale * position. Currently, wildcards used in inhale position (only) may not be removed * from State.constrainableARPs (potentially inefficient, but should be sound). @@ -219,14 +219,14 @@ object evaluator extends EvaluationRules { Q(s1, tVar, eVar, v) case fa: ast.FieldAccess => - eval(s, fa.rcv, pve, v, analysisInfoes)((s1, tRcvr, eRcvr, v1) => { + eval(s, fa.rcv, pve, v, analysisInfos)((s1, tRcvr, eRcvr, v1) => { val (debugHeapName, debugLabel) = v1.getDebugOldLabel(s1, fa.pos) val newFa = Option.when(withExp)({ if (s1.isEvalInOld) ast.FieldAccess(eRcvr.get, fa.field)(fa.pos, fa.info, fa.errT) else ast.DebugLabelledOld(ast.FieldAccess(eRcvr.get, fa.field)(), debugLabel)(fa.pos, fa.info, fa.errT) }) val ve = pve dueTo InsufficientPermission(fa) - v.heapSupporter.evalFieldAccess(s1, fa, tRcvr, eRcvr, ve, v1, analysisInfoes)((s2, snap, v2) => { + v.heapSupporter.evalFieldAccess(s1, fa, tRcvr, eRcvr, ve, v1, analysisInfos)((s2, snap, v2) => { val s3 = if (Verifier.config.enableDebugging() && !s2.isEvalInOld) s2.copy(oldHeaps = s2.oldHeaps + (debugHeapName -> magicWandSupporter.getEvalHeap(s2))) else s2 @@ -235,15 +235,15 @@ object evaluator extends EvaluationRules { }) case ast.Not(e0) => - eval(s, e0, pve, v, analysisInfoes)((s1, t0, e0New, v1) => + eval(s, e0, pve, v, analysisInfos)((s1, t0, e0New, v1) => Q(s1, Not(t0), e0New.map(ast.Not(_)(e.pos, e.info, e.errT)), v1)) case ast.Minus(e0) => - eval(s, e0, pve, v, analysisInfoes)((s1, t0, e0New, v1) => + eval(s, e0, pve, v, analysisInfos)((s1, t0, e0New, v1) => Q(s1, Minus(0, t0), e0New.map(ast.Minus(_)(e.pos, e.info, e.errT)), v1)) case ast.Old(e0) => - evalInOldState(s, Verifier.PRE_STATE_LABEL, e0, pve, v, analysisInfoes)((s1, t0, e0New, v1) => + evalInOldState(s, Verifier.PRE_STATE_LABEL, e0, pve, v, analysisInfos)((s1, t0, e0New, v1) => Q(s1, t0, e0New.map(ast.Old(_)(e.pos, e.info, e.errT)), v1)) case old@ast.DebugLabelledOld(e0, lbl) => @@ -254,11 +254,11 @@ object evaluator extends EvaluationRules { s.oldHeaps.get(heapName) match { case None => val failure = createFailure(pve dueTo LabelledStateNotReached(ast.LabelledOld(e0, heapName)(old.pos, old.info, old.errT)), v, s, "labelled state reached") - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfoes, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfos, v.reportFurtherErrors()) val freshVar = v.decider.fresh(v.symbolConverter.toSort(old.typ), None) if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, freshVar, None, v) else failure case _ => - evalInOldState(s, heapName, e0, pve, v, analysisInfoes)((s1, t0, _, v1) => + evalInOldState(s, heapName, e0, pve, v, analysisInfos)((s1, t0, _, v1) => Q(s1, t0, Some(old), v1)) } @@ -266,21 +266,21 @@ object evaluator extends EvaluationRules { s.oldHeaps.get(lbl) match { case None => val failure = createFailure(pve dueTo LabelledStateNotReached(old), v, s, "labelled state reached") - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfoes, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfos, v.reportFurtherErrors()) val freshVar = v.decider.fresh(v.symbolConverter.toSort(old.typ), None) if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, freshVar, None, v) else failure case _ => - evalInOldState(s, lbl, e0, pve, v, analysisInfoes)((s1, t0, e0New, v1) => + evalInOldState(s, lbl, e0, pve, v, analysisInfos)((s1, t0, e0New, v1) => Q(s1, t0, e0New.map(ast.LabelledOld(_, lbl)(old.pos, old.info, old.errT)), v1))} case l@ast.Let(x, e0, e1) => - eval(s, e0, pve, v, analysisInfoes)((s1, t0, e0New, v1) => { + eval(s, e0, pve, v, analysisInfos)((s1, t0, e0New, v1) => { val t = v1.decider.appliedFresh("letvar", v1.symbolConverter.toSort(x.typ), s1.relevantQuantifiedVariables.map(_._1)) val debugExp = Option.when(withExp)(DebugExp.createInstance("letvar assignment", InsertionOrderedSet(DebugExp.createInstance(ast.EqCmp(x.localVar, e0)(), ast.EqCmp(x.localVar, e0New.get)())))) - v1.decider.assumeDefinition(BuiltinEquals(t, t0), debugExp, analysisInfoes) + v1.decider.assumeDefinition(BuiltinEquals(t, t0), debugExp, analysisInfos) val newFuncRec = s1.functionRecorder.recordFreshSnapshot(t.applicable.asInstanceOf[Function]).enterLet(l) val possibleTriggersBefore = if (s1.recordPossibleTriggers) s1.possibleTriggers else Map.empty - eval(s1.copy(g = s1.g + (x.localVar, (t0, e0New)), functionRecorder = newFuncRec), e1, pve, v1, analysisInfoes)((s2, t2, e1New, v2) => { + eval(s1.copy(g = s1.g + (x.localVar, (t0, e0New)), functionRecorder = newFuncRec), e1, pve, v1, analysisInfos)((s2, t2, e1New, v2) => { val newPossibleTriggers = if (s2.recordPossibleTriggers) { val addedTriggers = s2.possibleTriggers -- possibleTriggersBefore.keys val addedTriggersReplaced = addedTriggers.map(at => at._1.replace(x.localVar, e0) -> at._2) @@ -295,30 +295,30 @@ object evaluator extends EvaluationRules { /* Strict evaluation of AND */ case ast.And(e0, e1) if Verifier.config.disableShortCircuitingEvaluations() => - evalBinOp(s, e0, e1, (t1, t2) => And(t1, t2), pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => + evalBinOp(s, e0, e1, (t1, t2) => And(t1, t2), pve, v, analysisInfos)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(ast.And(_, e1New.get)(e.pos, e.info, e.errT)), v1)) /* Short-circuiting evaluation of AND */ case ae @ ast.And(_, _) => val flattened = flattenOperator(ae, {case ast.And(e0, e1) => Seq(e0, e1)}) - evalSeqShortCircuit(And, s, flattened, pve, v, analysisInfoes)(Q) + evalSeqShortCircuit(And, s, flattened, pve, v, analysisInfos)(Q) /* Strict evaluation of OR */ case ast.Or(e0, e1) if Verifier.config.disableShortCircuitingEvaluations() => - evalBinOp(s, e0, e1, (t1, t2) => Or(t1, t2), pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => + evalBinOp(s, e0, e1, (t1, t2) => Or(t1, t2), pve, v, analysisInfos)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(ast.Or(_, e1New.get)(e.pos, e.info, e.errT)), v1)) /* Short-circuiting evaluation of OR */ case oe @ ast.Or(_, _) => val flattened = flattenOperator(oe, {case ast.Or(e0, e1) => Seq(e0, e1)}) - evalSeqShortCircuit(Or, s, flattened, pve, v, analysisInfoes)(Q) + evalSeqShortCircuit(Or, s, flattened, pve, v, analysisInfos)(Q) case implies @ ast.Implies(e0, e1) => val impliesRecord = new ImpliesRecord(implies, s, v.decider.pcs, "Implies") val uidImplies = v.symbExLog.openScope(impliesRecord) - eval(s, e0, pve, v, analysisInfoes)((s1, t0, e0New, v1) => - evalImplies(s1, t0, (e0, e0New), e1, implies.info == FromShortCircuitingAnd, pve, v1, analysisInfoes)((s2, t1, e1New, v2) => { + eval(s, e0, pve, v, analysisInfos)((s1, t0, e0New, v1) => + evalImplies(s1, t0, (e0, e0New), e1, implies.info == FromShortCircuitingAnd, pve, v1, analysisInfos)((s2, t1, e1New, v2) => { v2.symbExLog.closeScope(uidImplies) val implExpP = e0New.map(ast.Implies(_, e1New.get)(e.pos, e.info, e.errT)) Q(s2, t1, implExpP, v2) @@ -327,11 +327,11 @@ object evaluator extends EvaluationRules { case condExp @ ast.CondExp(e0, e1, e2) => val condExpRecord = new CondExpRecord(condExp, s, v.decider.pcs, "CondExp") val uidCondExp = v.symbExLog.openScope(condExpRecord) - eval(s, e0, pve, v, analysisInfoes)((s1, t0, e0New, v1) => - joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s1, v1, analysisInfoes)((s2, v2, QB) => - brancher.branch(s2.copy(parallelizeBranches = false), t0, (e0, e0New), v2, analysisInfoes.withDependencyType(DependencyType.Internal))( - (s3, v3) => eval(s3.copy(parallelizeBranches = s2.parallelizeBranches), e1, pve, v3, analysisInfoes)((s4, t4, e4, v4) => QB(s4, (t4, e4), v4)), - (s3, v3) => eval(s3.copy(parallelizeBranches = s2.parallelizeBranches), e2, pve, v3, analysisInfoes)((s4, t4, e4, v4) => QB(s4, (t4, e4), v4))) + eval(s, e0, pve, v, analysisInfos)((s1, t0, e0New, v1) => + joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s1, v1, analysisInfos)((s2, v2, QB) => + brancher.branch(s2.copy(parallelizeBranches = false), t0, (e0, e0New), v2, analysisInfos.withDependencyType(DependencyType.Internal))( + (s3, v3) => eval(s3.copy(parallelizeBranches = s2.parallelizeBranches), e1, pve, v3, analysisInfos)((s4, t4, e4, v4) => QB(s4, (t4, e4), v4)), + (s3, v3) => eval(s3.copy(parallelizeBranches = s2.parallelizeBranches), e2, pve, v3, analysisInfos)((s4, t4, e4, v4) => QB(s4, (t4, e4), v4))) )(entries => { /* TODO: If branch(...) took orElse-continuations that are executed if a branch is dead, then then comparisons with t0/Not(t0) wouldn't be necessary. */ @@ -353,88 +353,88 @@ object evaluator extends EvaluationRules { /* Integers */ case ast.Add(e0, e1) => - evalBinOp(s, e0, e1, Plus, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.Add(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) + evalBinOp(s, e0, e1, Plus, pve, v, analysisInfos)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.Add(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case ast.Sub(e0, e1) => - evalBinOp(s, e0, e1, Minus, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.Sub(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) + evalBinOp(s, e0, e1, Minus, pve, v, analysisInfos)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.Sub(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case ast.Mul(e0, e1) => - evalBinOp(s, e0, e1, Times, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.Mul(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) + evalBinOp(s, e0, e1, Times, pve, v, analysisInfos)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.Mul(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case ast.Div(e0, e1) => - evalBinOp(s, e0, e1, Div, pve, v, analysisInfoes)((s1, tDiv, e0New, e1New, v1) => - failIfDivByZero(s1, tDiv, e1, e1New, tDiv.p1, 0, pve, v1, analysisInfoes)((s2, t, v2) + evalBinOp(s, e0, e1, Div, pve, v, analysisInfos)((s1, tDiv, e0New, e1New, v1) => + failIfDivByZero(s1, tDiv, e1, e1New, tDiv.p1, 0, pve, v1, analysisInfos)((s2, t, v2) => Q(s2, t, e0New.map(e0p => ast.Div(e0p, e1New.get)(e.pos, e.info, e.errT)), v2))) case ast.Mod(e0, e1) => - evalBinOp(s, e0, e1, Mod, pve, v, analysisInfoes)((s1, tMod, e0New, e1New, v1) => - failIfDivByZero(s1, tMod, e1, e1New, tMod.p1, 0, pve, v1, analysisInfoes)((s2, t, v2) + evalBinOp(s, e0, e1, Mod, pve, v, analysisInfos)((s1, tMod, e0New, e1New, v1) => + failIfDivByZero(s1, tMod, e1, e1New, tMod.p1, 0, pve, v1, analysisInfos)((s2, t, v2) => Q(s2, t, e0New.map(e0p => ast.Mod(e0p, e1New.get)(e.pos, e.info, e.errT)), v2))) case ast.LeCmp(e0, e1) => - evalBinOp(s, e0, e1, AtMost, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.LeCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) + evalBinOp(s, e0, e1, AtMost, pve, v, analysisInfos)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.LeCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case ast.LtCmp(e0, e1) => - evalBinOp(s, e0, e1, Less, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.LtCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) + evalBinOp(s, e0, e1, Less, pve, v, analysisInfos)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.LtCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case ast.GeCmp(e0, e1) => - evalBinOp(s, e0, e1, AtLeast, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.GeCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) + evalBinOp(s, e0, e1, AtLeast, pve, v, analysisInfos)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.GeCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case ast.GtCmp(e0, e1) => - evalBinOp(s, e0, e1, Greater, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.GtCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) + evalBinOp(s, e0, e1, Greater, pve, v, analysisInfos)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.GtCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) /* Permissions */ case ast.PermAdd(e0, e1) => - evalBinOp(s, e0, e1, PermPlus, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.PermAdd(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) + evalBinOp(s, e0, e1, PermPlus, pve, v, analysisInfos)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.PermAdd(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case ast.PermSub(e0, e1) => - evalBinOp(s, e0, e1, PermMinus, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.PermSub(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) + evalBinOp(s, e0, e1, PermMinus, pve, v, analysisInfos)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.PermSub(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case ast.PermMinus(e0) => - eval(s, e0, pve, v, analysisInfoes)((s1, t0, e0New, v1) => + eval(s, e0, pve, v, analysisInfos)((s1, t0, e0New, v1) => Q(s1, PermMinus(NoPerm, t0), e0New.map(e0p => ast.PermMinus(e0p)(e.pos, e.info, e.errT)), v1)) case ast.PermMul(e0, e1) => - evalBinOp(s, e0, e1, PermTimes, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.PermMul(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) + evalBinOp(s, e0, e1, PermTimes, pve, v, analysisInfos)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.PermMul(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case ast.DebugPermMin(e0, e1) => - evalBinOp(s, e0, e1, PermMin, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.DebugPermMin(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) + evalBinOp(s, e0, e1, PermMin, pve, v, analysisInfos)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.DebugPermMin(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case ast.IntPermMul(e0, e1) => - eval(s, e0, pve, v, analysisInfoes)((s1, t0, e0New, v1) => - eval(s1, e1, pve, v1, analysisInfoes)((s2, t1, e1New, v2) => + eval(s, e0, pve, v, analysisInfos)((s1, t0, e0New, v1) => + eval(s1, e1, pve, v1, analysisInfos)((s2, t1, e1New, v2) => Q(s2, IntPermTimes(t0, t1), e0New.map(e0p => ast.IntPermMul(e0p, e1New.get)(e.pos, e.info, e.errT)), v2))) case ast.PermDiv(e0, e1) => - eval(s, e0, pve, v, analysisInfoes)((s1, t0, e0New, v1) => - eval(s1, e1, pve, v1, analysisInfoes)((s2, t1, e1New, v2) => - failIfDivByZero(s2, PermIntDiv(t0, t1), e1, e1New, t1, 0, pve, v2, analysisInfoes)((s3, t, v3) + eval(s, e0, pve, v, analysisInfos)((s1, t0, e0New, v1) => + eval(s1, e1, pve, v1, analysisInfos)((s2, t1, e1New, v2) => + failIfDivByZero(s2, PermIntDiv(t0, t1), e1, e1New, t1, 0, pve, v2, analysisInfos)((s3, t, v3) => Q(s3, t, e0New.map(e0p => ast.PermDiv(e0p, e1New.get)(e.pos, e.info, e.errT)), v3)))) case ast.PermPermDiv(e0, e1) => - eval(s, e0, pve, v, analysisInfoes)((s1, t0, e0New, v1) => - eval(s1, e1, pve, v1, analysisInfoes)((s2, t1, e1New, v2) => - failIfDivByZero(s2, PermPermDiv(t0, t1), e1, e1New, t1, FractionPermLiteral(Rational(0, 1)), pve, v2, analysisInfoes)((s3, t, v3) => + eval(s, e0, pve, v, analysisInfos)((s1, t0, e0New, v1) => + eval(s1, e1, pve, v1, analysisInfos)((s2, t1, e1New, v2) => + failIfDivByZero(s2, PermPermDiv(t0, t1), e1, e1New, t1, FractionPermLiteral(Rational(0, 1)), pve, v2, analysisInfos)((s3, t, v3) => Q(s3, t, e0New.map(e0p => ast.PermPermDiv(e0p, e1New.get)(e.pos, e.info, e.errT)), v3)))) case ast.PermLeCmp(e0, e1) => - evalBinOp(s, e0, e1, AtMost, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.PermLeCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) + evalBinOp(s, e0, e1, AtMost, pve, v, analysisInfos)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.PermLeCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case ast.PermLtCmp(e0, e1) => - evalBinOp(s, e0, e1, Less, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.PermLtCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) + evalBinOp(s, e0, e1, Less, pve, v, analysisInfos)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.PermLtCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case ast.PermGeCmp(e0, e1) => - evalBinOp(s, e0, e1, AtLeast, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.PermGeCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) + evalBinOp(s, e0, e1, AtLeast, pve, v, analysisInfos)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.PermGeCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case ast.PermGtCmp(e0, e1) => - evalBinOp(s, e0, e1, Greater, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.PermGtCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) + evalBinOp(s, e0, e1, Greater, pve, v, analysisInfos)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.PermGtCmp(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) /* Others */ /* Domains not handled directly */ case dfa @ ast.DomainFuncApp(funcName, eArgs, m) => - evals(s, eArgs, _ => pve, v, analysisInfoes)((s1, tArgs, eArgsNew, v1) => { + evals(s, eArgs, _ => pve, v, analysisInfos)((s1, tArgs, eArgsNew, v1) => { val inSorts = tArgs map (_.sort) val outSort = v1.symbolConverter.toSort(dfa.typ) val fi = v1.symbolConverter.toFunction(s.program.findDomainFunction(funcName), inSorts :+ outSort, s.program) @@ -442,7 +442,7 @@ object evaluator extends EvaluationRules { Q(s1, App(fi, tArgs), dfaP, v1)}) case bf @ ast.BackendFuncApp(funcName, eArgs) => - evals(s, eArgs, _ => pve, v, analysisInfoes)((s1, tArgs, eArgsNew, v1) => { + evals(s, eArgs, _ => pve, v, analysisInfos)((s1, tArgs, eArgsNew, v1) => { val func = s.program.findDomainFunction(funcName) val fi = v1.symbolConverter.toFunction(func, s.program) val bfP = Option.when(withExp)(ast.BackendFuncApp(funcName, eArgsNew.get)(bf.pos, bf.info, bf.typ, bf.interpretation, bf.errT)) @@ -450,8 +450,8 @@ object evaluator extends EvaluationRules { case ast.CurrentPerm(resacc) => val h = s.partiallyConsumedHeap.getOrElse(s.h) - evalResourceAccess(s, resacc, pve, v, analysisInfoes)((s1, identifier, args, eArgsNew, v1) => { - v1.heapSupporter.evalCurrentPerm(s1, h, resacc, identifier, args, eArgsNew, v1, analysisInfoes)((s2, t, v2) => + evalResourceAccess(s, resacc, pve, v, analysisInfos)((s1, identifier, args, eArgsNew, v1) => { + v1.heapSupporter.evalCurrentPerm(s1, h, resacc, identifier, args, eArgsNew, v1, analysisInfos)((s2, t, v2) => Q(s2, t, Option.when(withExp)(e), v2)) }) @@ -472,7 +472,7 @@ object evaluator extends EvaluationRules { val s2 = s1.copy(s1.g + gVars, quantifiedVariables = varPairs.map(v => v._1 -> Option.when(withExp)(v._2)) ++ s1.quantifiedVariables) - evals(s2, args, _ => pve, v, analysisInfoes)((s3, ts, es, v3) => { + evals(s2, args, _ => pve, v, analysisInfos)((s3, ts, es, v3) => { val possibleConds = v3.heapSupporter.collectForPermConditions(s3, resource, varPairs, ts, es) def evalOptions(s: State, @@ -485,7 +485,7 @@ object evaluator extends EvaluationRules { val impliesRecord = new ImpliesRecord(null, s, v.decider.pcs, "ForPerm") val uidImplies = v.symbExLog.openScope(impliesRecord) val (t, (e0, e1), qVars, defs, triggers) = conds.head - evalImplies(s.copy(g = s.g + defs), t, (e0, e1), body, false, pve, v, analysisInfoes)((sNext, tImplies, bodyNew, vNext) => { + evalImplies(s.copy(g = s.g + defs), t, (e0, e1), body, false, pve, v, analysisInfos)((sNext, tImplies, bodyNew, vNext) => { val tQuant = SimplifyingForall(qVars, tImplies, triggers) val eQuantNew = Option.when(withExp)(ast.Forall(varsNew.get, Seq(), ast.Implies(e0, bodyNew.get)())()) v.symbExLog.closeScope(uidImplies) @@ -552,18 +552,18 @@ object evaluator extends EvaluationRules { } val name = s"prog.$posString" val s0 = s.copy(functionRecorder = s.functionRecorder.enterQuantifiedExp(sourceQuant)) - evalQuantified(s0, qantOp, eQuant.variables, Nil, Seq(body), Some(eTriggers), name, pve, v, analysisInfoes){ + evalQuantified(s0, qantOp, eQuant.variables, Nil, Seq(body), Some(eTriggers), name, pve, v, analysisInfos){ case (s1, tVars, eVars, _, _, Some((Seq(tBody), bodyNew, tTriggers, (tAuxGlobal, tAux), auxExps)), v1) => val tAuxHeapIndep = tAux.flatMap(v.quantifierSupporter.makeTriggersHeapIndependent(_, v1.decider.fresh)) val auxGlobalsExp = auxExps.map(_._1) val auxNonGlobalsExp = auxExps.map(_._2) val commentGlobal = "Nested auxiliary terms: globals (aux)" v1.decider.prover.comment(commentGlobal) - val auxAnalysisInfoes = analysisInfoes.withDependencyType(DependencyType.Internal).withMergeInfo(NoDependencyAnalysisMerge()) - v1.decider.assume(tAuxGlobal, Option.when(withExp)(DebugExp.createInstance(description=commentGlobal, children=auxGlobalsExp.get)), enforceAssumption = false, auxAnalysisInfoes) + val auxAnalysisInfos = analysisInfos.withDependencyType(DependencyType.Internal).withMergeInfo(NoDependencyAnalysisMerge()) + v1.decider.assume(tAuxGlobal, Option.when(withExp)(DebugExp.createInstance(description=commentGlobal, children=auxGlobalsExp.get)), enforceAssumption = false, auxAnalysisInfos) val commentNonGlobals = "Nested auxiliary terms: non-globals (aux)" v1.decider.prover.comment(commentNonGlobals) - v1.decider.assume(tAuxHeapIndep/*tAux*/, Option.when(withExp)(DebugExp.createInstance(description=commentNonGlobals, children=auxNonGlobalsExp.get)), enforceAssumption = false, auxAnalysisInfoes) + v1.decider.assume(tAuxHeapIndep/*tAux*/, Option.when(withExp)(DebugExp.createInstance(description=commentNonGlobals, children=auxNonGlobalsExp.get)), enforceAssumption = false, auxAnalysisInfos) if (qantOp == Exists) { // For universal quantification, the non-global auxiliary assumptions will contain the information that // forall vars :: all function preconditions are fulfilled. @@ -576,7 +576,7 @@ object evaluator extends EvaluationRules { val exp = ast.Forall(eQuant.variables, eTriggers, body)(sourceQuant.pos, sourceQuant.info, sourceQuant.errT) DebugExp.createInstance(exp, expNew) }) - v1.decider.assume(Quantification(Forall, tVars, FunctionPreconditionTransformer.transform(tBody, s1.program), tTriggers, name, quantWeight), debugExp, analysisInfoes) + v1.decider.assume(Quantification(Forall, tVars, FunctionPreconditionTransformer.transform(tBody, s1.program), tTriggers, name, quantWeight), debugExp, analysisInfos) } val tQuant = Quantification(qantOp, tVars, tBody, tTriggers, name, quantWeight) @@ -585,11 +585,11 @@ object evaluator extends EvaluationRules { Q(s2, tQuant, eQuantNew, v1) case (s1, _, _, _, _, None, v1) => // This should not happen unless the current path is dead. - if (v1.decider.checkSmoke(analysisInfoes, isAssert = true)) { + if (v1.decider.checkSmoke(analysisInfos, isAssert = true)) { Unreachable() } else { val failure = createFailure(pve.dueTo(InternalReason(sourceQuant, "Quantifier evaluation failed.")), v1, s1, "quantifier could be evaluated") - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfoes, v1.reportFurtherErrors()) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfos, v1.reportFurtherErrors()) val freshVar = v1.decider.fresh(v1.symbolConverter.toSort(sourceQuant.typ), None) if(s1.retryLevel == 0 && v1.reportFurtherErrors()) failure combine Q(s1, freshVar, None, v1) else failure } @@ -597,7 +597,7 @@ object evaluator extends EvaluationRules { case fapp @ ast.FuncApp(funcName, eArgs) => val func = s.program.findFunction(funcName) - evals2(s, eArgs, Nil, _ => pve, v, analysisInfoes)((s1, tArgs, eArgsNew, v1) => { + evals2(s, eArgs, Nil, _ => pve, v, analysisInfos)((s1, tArgs, eArgsNew, v1) => { // bookkeeper.functionApplications += 1 val joinFunctionArgs = tArgs //++ c2a.quantifiedVariables.filterNot(tArgs.contains) val (debugHeapName, debugLabel) = v1.getDebugOldLabel(s1, fapp.pos) @@ -612,7 +612,7 @@ object evaluator extends EvaluationRules { * Hence, the joinedFApp will take two arguments, namely, i*i and i, * although the latter is not necessary. */ - joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s1a, v1, analysisInfoes)((s2, v2, QB) => { + joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s1a, v1, analysisInfos)((s2, v2, QB) => { val pres = func.pres.map(_.transform { /* [Malte 2018-08-20] Two examples of the test suite, one of which is the regression * for Carbon issue #210, fail if the subsequent code that strips out triggers from @@ -670,14 +670,14 @@ object evaluator extends EvaluationRules { moreJoins = JoinMode.Off, assertReadAccessOnly = if (Verifier.config.respectFunctionPrePermAmounts()) s2.assertReadAccessOnly /* should currently always be false */ else true) - val precondAnalysisInfoes = analysisInfoes.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Source, EdgeType.Up)) - consumes(s3, pres, true, _ => pvePre, v2, precondAnalysisInfoes)((s4, snap, v3) => { + val precondAnalysisInfos = analysisInfos.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Source, EdgeType.Up)) + consumes(s3, pres, true, _ => pvePre, v2, precondAnalysisInfos)((s4, snap, v3) => { val snap1 = snap.get.convert(sorts.Snap) val preFApp = App(functionSupporter.preconditionVersion(v3.symbolConverter.toFunction(func)), snap1 :: tArgs) val preExp = Option.when(withExp)({ DebugExp.createInstance(Some(s"precondition of ${func.name}(${eArgsNew.get.mkString(", ")}) holds"), None, None, InsertionOrderedSet.empty) }) - v3.decider.assume(preFApp, preExp, precondAnalysisInfoes) + v3.decider.assume(preFApp, preExp, precondAnalysisInfos) val funcAnn = func.info.getUniqueInfo[AnnotationInfo] val tFApp = funcAnn match { case Some(a) if a.values.contains("opaque") => @@ -707,7 +707,7 @@ object evaluator extends EvaluationRules { /* TODO: The join-function is heap-independent, and it is not obvious how a * joined snapshot could be defined and represented */ - })(join(func.typ, s"joined_${func.name}", joinFunctionArgs, Option.when(withExp)(eArgs), v1, analysisInfoes))((s6, r, v4) + })(join(func.typ, s"joined_${func.name}", joinFunctionArgs, Option.when(withExp)(eArgs), v1, analysisInfos))((s6, r, v4) => Q(s6, r._1, r._2, v4))}) case ast.Unfolding( @@ -717,11 +717,11 @@ object evaluator extends EvaluationRules { val predicate = s.program.findPredicate(predicateName) if (s.cycles(predicate) < Verifier.config.recursivePredicateUnfoldings()) { v.decider.startDebugSubExp() - evals(s, eArgs, _ => pve, v, analysisInfoes)((s1, tArgs, eArgsNew, v1) => - eval(s1, ePerm.getOrElse(ast.FullPerm()()), pve, v1, analysisInfoes)((s2, tPerm, ePermNew, v2) => - v2.decider.assert(IsPositive(tPerm), analysisInfoes) { // TODO: Replace with permissionSupporter.assertNotNegative + evals(s, eArgs, _ => pve, v, analysisInfos)((s1, tArgs, eArgsNew, v1) => + eval(s1, ePerm.getOrElse(ast.FullPerm()()), pve, v1, analysisInfos)((s2, tPerm, ePermNew, v2) => + v2.decider.assert(IsPositive(tPerm), analysisInfos) { // TODO: Replace with permissionSupporter.assertNotNegative case true => - joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s2, v2, analysisInfoes)((s3, v3, QB) => { + joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s2, v2, analysisInfos)((s3, v3, QB) => { val s4 = s3.incCycleCounter(predicate) .copy(recordVisited = true) /* [2014-12-10 Malte] The commented code should replace the code following @@ -733,7 +733,7 @@ object evaluator extends EvaluationRules { // val c4 = c3.decCycleCounter(predicate) // eval(σ1, eIn, pve, c4)((tIn, c5) => // QB(tIn, c5))}) - consume(s4, acc, true, pve, v3, analysisInfoes)((s5, snap, v4) => { + consume(s4, acc, true, pve, v3, analysisInfos)((s5, snap, v4) => { val fr6 = s5.functionRecorder.recordSnapshot(pa, v4.decider.pcs.branchConditions, snap.get) .changeDepthBy(+1) @@ -748,7 +748,7 @@ object evaluator extends EvaluationRules { if (!Verifier.config.disableFunctionUnfoldTrigger()) { val eArgsString = eArgsNew.mkString(", ") val debugExp = Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}($eArgsString))", isInternal_ = true)) - v4.decider.assume(App(s.predicateData(predicate.name).triggerFunction, snap.get.convert(terms.sorts.Snap) +: tArgs), debugExp, analysisInfoes.withDependencyType(DependencyType.Trigger)) + v4.decider.assume(App(s.predicateData(predicate.name).triggerFunction, snap.get.convert(terms.sorts.Snap) +: tArgs), debugExp, analysisInfos.withDependencyType(DependencyType.Trigger)) } val body = predicate.body.get /* Only non-abstract predicates can be unfolded */ val s7 = s6.scalePermissionFactor(tPerm, ePermNew) @@ -758,7 +758,7 @@ object evaluator extends EvaluationRules { if (s7a.predicateData(predicate.name).predContents.isDefined) { val toReplace: silicon.Map[Term, Term] = silicon.Map.from(s7a.predicateData(predicate.name).params.get.zip(Seq(snap.get) ++ tArgs)) - predicateSupporter.producePredicateContents(s7a, s7a.predicateData(predicate.name).predContents.get, toReplace, v4, analysisInfoes.withDependencyType(DependencyType.Internal), true)((s8, v5) => { + predicateSupporter.producePredicateContents(s7a, s7a.predicateData(predicate.name).predContents.get, toReplace, v4, analysisInfos.withDependencyType(DependencyType.Internal), true)((s8, v5) => { val s9 = s8.copy(g = s7.g, functionRecorder = s8.functionRecorder.changeDepthBy(-1), recordVisited = s3.recordVisited, @@ -767,10 +767,10 @@ object evaluator extends EvaluationRules { constrainableARPs = s1.constrainableARPs) .decCycleCounter(predicate) val s10 = v5.stateConsolidator(s9).consolidateOptionally(s9, v5) - eval(s10, eIn, pve, v5, analysisInfoes)((s9, t9, e9, v9) => QB(s9, (t9, e9), v9)) + eval(s10, eIn, pve, v5, analysisInfos)((s9, t9, e9, v9) => QB(s9, (t9, e9), v9)) }) } else { - produce(s7a, toSf(snap.get), body, pve, v4, analysisInfoes)((s8, v5) => { + produce(s7a, toSf(snap.get), body, pve, v4, analysisInfos)((s8, v5) => { val s9 = s8.copy(g = s7.g, functionRecorder = s8.functionRecorder.changeDepthBy(-1), recordVisited = s3.recordVisited, @@ -779,18 +779,18 @@ object evaluator extends EvaluationRules { constrainableARPs = s1.constrainableARPs) .decCycleCounter(predicate) val s10 = v5.stateConsolidator(s9).consolidateOptionally(s9, v5) - eval(s10, eIn, pve, v5, analysisInfoes)((s9, t9, e9, v9) => QB(s9, (t9, e9), v9))}) + eval(s10, eIn, pve, v5, analysisInfos)((s9, t9, e9, v9) => QB(s9, (t9, e9), v9))}) } }) })(join(eIn.typ, "joined_unfolding", s2.relevantQuantifiedVariables.map(_._1), - Option.when(withExp)(s2.relevantQuantifiedVariables.map(_._2.get)), v2, analysisInfoes))((s12, r12, v7) + Option.when(withExp)(s2.relevantQuantifiedVariables.map(_._2.get)), v2, analysisInfos))((s12, r12, v7) => { v7.decider.finishDebugSubExp(s"unfolded(${predicate.name})") Q(s12, r12._1, r12._2, v7)}) case false => v2.decider.finishDebugSubExp(s"unfolded(${predicate.name})") val failure = createFailure(pve dueTo NonPositivePermission(ePerm.get), v2, s2, IsPositive(tPerm), ePermNew.map(p => ast.PermGtCmp(p, ast.NoPerm()())(p.pos, p.info, p.errT))) - if(s2.retryLevel == 0) v2.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfoes, v2.reportFurtherErrors()) + if(s2.retryLevel == 0) v2.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfos, v2.reportFurtherErrors()) val freshVar = v2.decider.fresh(v2.symbolConverter.toSort(e.typ), None) // TODO ake: function recorder if(s2.retryLevel == 0 && v2.reportFurtherErrors() && Verifier.config.disableInfeasibilityChecks()) failure combine Q(s2, freshVar, None, v2) else failure })) @@ -801,123 +801,123 @@ object evaluator extends EvaluationRules { } case ast.Applying(wand, eIn) => - joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s, v, analysisInfoes)((s1, v1, QB) => - magicWandSupporter.applyWand(s1, wand, pve, v1, analysisInfoes)((s2, v2) => { - eval(s2, eIn, pve, v2, analysisInfoes)((s3, t, eInNew, v3) => QB(s3, (t, eInNew), v3)) + joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s, v, analysisInfos)((s1, v1, QB) => + magicWandSupporter.applyWand(s1, wand, pve, v1, analysisInfos)((s2, v2) => { + eval(s2, eIn, pve, v2, analysisInfos)((s3, t, eInNew, v3) => QB(s3, (t, eInNew), v3)) }))(join(eIn.typ, "joined_applying", s.relevantQuantifiedVariables.map(_._1), - Option.when(withExp)(s.relevantQuantifiedVariables.map(_._2.get)), v, analysisInfoes))((s4, r4, v4) + Option.when(withExp)(s.relevantQuantifiedVariables.map(_._2.get)), v, analysisInfos))((s4, r4, v4) => Q(s4, r4._1, r4._2, v4)) case ast.Asserting(eAss, eIn) => - consume(s, eAss, false, pve, v, analysisInfoes /* TODO ake: explicit assertion? */)((s2, _, v2) => { + consume(s, eAss, false, pve, v, analysisInfos /* TODO ake: explicit assertion? */)((s2, _, v2) => { val s3 = s2.copy(g = s.g, h = s.h) - eval(s3, eIn, pve, v2, analysisInfoes)(Q) + eval(s3, eIn, pve, v2, analysisInfos)(Q) }) /* Sequences */ - case ast.SeqContains(e0, e1) => evalBinOp(s, e1, e0, SeqIn, pve, v, analysisInfoes)((s1, t, e1New, e0New, v1) => + case ast.SeqContains(e0, e1) => evalBinOp(s, e1, e0, SeqIn, pve, v, analysisInfos)((s1, t, e1New, e0New, v1) => Q(s1, t, e0New.map(e0p => ast.SeqContains(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) /* Note the reversed order of the arguments! */ case ast.SeqIndex(e0, e1) => - evals2(s, Seq(e0, e1), Nil, _ => pve, v, analysisInfoes)({case (s1, Seq(t0, t1), esNew, v1) => + evals2(s, Seq(e0, e1), Nil, _ => pve, v, analysisInfos)({case (s1, Seq(t0, t1), esNew, v1) => val eNew = esNew.map(es => ast.SeqIndex(es.head, es(1))(e.pos, e.info, e.errT)) if (s1.triggerExp) { Q(s1, SeqAt(t0, t1), eNew, v1) } else { - v1.decider.assert(AtLeast(t1, IntLiteral(0)), analysisInfoes) { + v1.decider.assert(AtLeast(t1, IntLiteral(0)), analysisInfos) { case true => - v1.decider.assert(Less(t1, SeqLength(t0)), analysisInfoes) { + v1.decider.assert(Less(t1, SeqLength(t0)), analysisInfos) { case true => Q(s1, SeqAt(t0, t1), eNew, v1) case false => val assertExp2 = Option.when(withExp)(ast.LtCmp(e1, ast.SeqLength(e0)())(e1.pos, e1.info, e1.errT)) val failure = createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, Less(t1, SeqLength(t0)), assertExp2) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(Less(t1, SeqLength(t0)), analysisInfoes, assumeFailedAssertion=false) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(Less(t1, SeqLength(t0)), analysisInfos, assumeFailedAssertion=false) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { val assertExp2 = Option.when(withExp)(ast.LeCmp(e1, ast.SeqLength(e0)())()) val assertExp2New = esNew.map(es => ast.LeCmp(es(1), ast.SeqLength(es.head)())()) - v1.decider.assume(Less(t1, SeqLength(t0)), assertExp2, assertExp2New, analysisInfoes.withDependencyType(DependencyType.Explicit)) + v1.decider.assume(Less(t1, SeqLength(t0)), assertExp2, assertExp2New, analysisInfos.withDependencyType(DependencyType.Explicit)) failure combine Q(s1, SeqAt(t0, t1), eNew, v1) } else failure} case false => val assertExp1 = Option.when(withExp)(ast.GeCmp(e1, ast.IntLit(0)())(e1.pos, e1.info, e1.errT)) val assertExp1New = Option.when(withExp)(ast.GeCmp(esNew.get(1), ast.IntLit(0)())(e1.pos, e1.info, e1.errT)) val failure1 = createFailure(pve dueTo SeqIndexNegative(e0, e1), v1, s1, AtLeast(t1, IntLiteral(0)), assertExp1New) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(AtLeast(t1, IntLiteral(0)), analysisInfoes, assumeFailedAssertion=false) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(AtLeast(t1, IntLiteral(0)), analysisInfos, assumeFailedAssertion=false) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { - v1.decider.assume(AtLeast(t1, IntLiteral(0)), assertExp1, assertExp1New, analysisInfoes.withDependencyType(DependencyType.Explicit)) + v1.decider.assume(AtLeast(t1, IntLiteral(0)), assertExp1, assertExp1New, analysisInfos.withDependencyType(DependencyType.Explicit)) val assertExp2 = Option.when(withExp)(ast.LtCmp(e1, ast.SeqLength(e0)())(e1.pos, e1.info, e1.errT)) val assertExp2New = Option.when(withExp)(ast.LtCmp(esNew.get(1), ast.SeqLength(esNew.get(0))())(e1.pos, e1.info, e1.errT)) - v1.decider.assert(Less(t1, SeqLength(t0)), analysisInfoes) { + v1.decider.assert(Less(t1, SeqLength(t0)), analysisInfos) { case true => failure1 combine Q(s1, SeqAt(t0, t1), eNew, v1) case false => val failure2 = failure1 combine createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, Less(t1, SeqLength(t0)), assertExp2New) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(Less(t1, SeqLength(t0)), analysisInfoes, assumeFailedAssertion=false) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(Less(t1, SeqLength(t0)), analysisInfos, assumeFailedAssertion=false) if (v1.reportFurtherErrors()) { - v1.decider.assume(Less(t1, SeqLength(t0)), assertExp2, assertExp2New, analysisInfoes.withDependencyType(DependencyType.Explicit)) + v1.decider.assume(Less(t1, SeqLength(t0)), assertExp2, assertExp2New, analysisInfos.withDependencyType(DependencyType.Explicit)) failure2 combine Q(s1, SeqAt(t0, t1), eNew, v1) } else failure2} } else failure1}}}) - case ast.SeqAppend(e0, e1) => evalBinOp(s, e0, e1, SeqAppend, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => + case ast.SeqAppend(e0, e1) => evalBinOp(s, e0, e1, SeqAppend, pve, v, analysisInfos)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.SeqAppend(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) - case ast.SeqDrop(e0, e1) => evalBinOp(s, e0, e1, SeqDrop, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => + case ast.SeqDrop(e0, e1) => evalBinOp(s, e0, e1, SeqDrop, pve, v, analysisInfos)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.SeqDrop(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) - case ast.SeqTake(e0, e1) => evalBinOp(s, e0, e1, SeqTake, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => + case ast.SeqTake(e0, e1) => evalBinOp(s, e0, e1, SeqTake, pve, v, analysisInfos)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.SeqTake(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) - case ast.SeqLength(e0) => eval(s, e0, pve, v, analysisInfoes)((s1, t0, e0New, v1) => + case ast.SeqLength(e0) => eval(s, e0, pve, v, analysisInfos)((s1, t0, e0New, v1) => Q(s1, SeqLength(t0), e0New.map(e0p => ast.SeqLength(e0p)(e.pos, e.info, e.errT)), v1)) case ast.EmptySeq(typ) => Q(s, SeqNil(v.symbolConverter.toSort(typ)), Option.when(withExp)(e), v) - case ast.RangeSeq(e0, e1) => evalBinOp(s, e0, e1, SeqRanged, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) => + case ast.RangeSeq(e0, e1) => evalBinOp(s, e0, e1, SeqRanged, pve, v, analysisInfos)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.RangeSeq(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case ast.SeqUpdate(e0, e1, e2) => - evals2(s, Seq(e0, e1, e2), Nil, _ => pve, v, analysisInfoes)({ case (s1, Seq(t0, t1, t2), esNew, v1) => + evals2(s, Seq(e0, e1, e2), Nil, _ => pve, v, analysisInfos)({ case (s1, Seq(t0, t1, t2), esNew, v1) => val eNew = esNew.map(es => ast.SeqUpdate(es.head, es(1), es(2))(e.pos, e.info, e.errT)) if (s1.triggerExp) { Q(s1, SeqUpdate(t0, t1, t2), eNew, v1) } else { val assertExp = Option.when(withExp)(ast.GeCmp(e1, ast.IntLit(0)())(e1.pos, e1.info, e1.errT)) val assertExpNew = Option.when(withExp)(ast.GeCmp(esNew.get(1), ast.IntLit(0)())(e1.pos, e1.info, e1.errT)) - v1.decider.assert(AtLeast(t1, IntLiteral(0)), analysisInfoes) { + v1.decider.assert(AtLeast(t1, IntLiteral(0)), analysisInfos) { case true => val assertExp2New = Option.when(withExp)(ast.LtCmp(esNew.get(1), ast.SeqLength(esNew.get(0))())(e1.pos, e1.info, e1.errT)) - v1.decider.assert(Less(t1, SeqLength(t0)), analysisInfoes) { + v1.decider.assert(Less(t1, SeqLength(t0)), analysisInfos) { case true => Q(s1, SeqUpdate(t0, t1, t2), eNew, v1) case false => val failure = createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, Less(t1, SeqLength(t0)), assertExp2New) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(Less(t1, SeqLength(t0)), analysisInfoes, assumeFailedAssertion=false) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(Less(t1, SeqLength(t0)), analysisInfos, assumeFailedAssertion=false) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { val assertExp3 = Option.when(withExp)(ast.LeCmp(e1, ast.SeqLength(e0)())()) val assertExp3New = Option.when(withExp)(ast.LeCmp(esNew.get(1), ast.SeqLength(esNew.get(0))())()) - v1.decider.assume(Less(t1, SeqLength(t0)), assertExp3, assertExp3New, analysisInfoes.withDependencyType(DependencyType.Explicit)) + v1.decider.assume(Less(t1, SeqLength(t0)), assertExp3, assertExp3New, analysisInfos.withDependencyType(DependencyType.Explicit)) failure combine Q(s1, SeqUpdate(t0, t1, t2), eNew, v1)} else failure} case false => val failure1 = createFailure(pve dueTo SeqIndexNegative(e0, e1), v1, s1, AtLeast(t1, IntLiteral(0)), assertExpNew) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(AtLeast(t1, IntLiteral(0)), analysisInfoes, assumeFailedAssertion=false) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(AtLeast(t1, IntLiteral(0)), analysisInfos, assumeFailedAssertion=false) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { - v1.decider.assume(AtLeast(t1, IntLiteral(0)), assertExp, assertExpNew, analysisInfoes.withDependencyType(DependencyType.Explicit)) + v1.decider.assume(AtLeast(t1, IntLiteral(0)), assertExp, assertExpNew, analysisInfos.withDependencyType(DependencyType.Explicit)) val assertExp2 = Option.when(withExp)(ast.LtCmp(e1, ast.SeqLength(e0)())(e1.pos, e1.info, e1.errT)) val assertExp2New = Option.when(withExp)(ast.LtCmp(esNew.get(1), ast.SeqLength(esNew.get(0))())(e1.pos, e1.info, e1.errT)) - v1.decider.assert(Less(t1, SeqLength(t0)), analysisInfoes) { + v1.decider.assert(Less(t1, SeqLength(t0)), analysisInfos) { case true => failure1 combine Q(s1, SeqUpdate(t0, t1, t2), eNew, v1) case false => val failure2 = failure1 combine createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, Less(t1, SeqLength(t0)), assertExp2New) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(Less(t1, SeqLength(t0)), analysisInfoes, assumeFailedAssertion=false) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(Less(t1, SeqLength(t0)), analysisInfos, assumeFailedAssertion=false) if (v1.reportFurtherErrors()) { - v1.decider.assume(Less(t1, SeqLength(t0)), assertExp2, assertExp2New, analysisInfoes.withDependencyType(DependencyType.Explicit)) + v1.decider.assume(Less(t1, SeqLength(t0)), assertExp2, assertExp2New, analysisInfos.withDependencyType(DependencyType.Explicit)) failure2 combine Q(s1, SeqUpdate(t0, t1, t2), eNew, v1) } else failure2} } else failure1}}}) case seq@ast.ExplicitSeq(es) => - evals2(s, es, Nil, _ => pve, v, analysisInfoes)((s1, tEs, esNew, v1) => { + evals2(s, es, Nil, _ => pve, v, analysisInfos)((s1, tEs, esNew, v1) => { val tSeq = tEs.tail.foldLeft[SeqTerm](SeqSingleton(tEs.head))((tSeq, te) => SeqAppend(tSeq, SeqSingleton(te))) @@ -926,7 +926,7 @@ object evaluator extends EvaluationRules { val exp = ast.EqCmp(ast.SeqLength(seq)(), ast.IntLit(es.size)())(seq.pos, seq.info, seq.errT) DebugExp.createInstance(exp, expNew) }) - v1.decider.assume(SeqLength(tSeq) === IntLiteral(es.size), debugExp, analysisInfoes) + v1.decider.assume(SeqLength(tSeq) === IntLiteral(es.size), debugExp, analysisInfos) Q(s1, tSeq, esNew.map(en => ast.ExplicitSeq(en)(e.pos, e.info, e.errT)), v1)}) /* Sets and multisets */ @@ -937,68 +937,68 @@ object evaluator extends EvaluationRules { Q(s, EmptyMultiset(v.symbolConverter.toSort(typ)), Option.when(withExp)(e), v) case ast.ExplicitSet(es) => - evals2(s, es, Nil, _ => pve, v, analysisInfoes)((s1, tEs, esNew, v1) => { + evals2(s, es, Nil, _ => pve, v, analysisInfos)((s1, tEs, esNew, v1) => { val tSet = tEs.tail.foldLeft[SetTerm](SingletonSet(tEs.head))((tSet, te) => SetAdd(tSet, te)) Q(s1, tSet, esNew.map(es => ast.ExplicitSet(es)(e.pos, e.info, e.errT)), v1)}) case ast.ExplicitMultiset(es) => - evals2(s, es, Nil, _ => pve, v, analysisInfoes)((s1, tEs, esNew, v1) => { + evals2(s, es, Nil, _ => pve, v, analysisInfos)((s1, tEs, esNew, v1) => { val tMultiset = tEs.tail.foldLeft[MultisetTerm](SingletonMultiset(tEs.head))((tMultiset, te) => MultisetAdd(tMultiset, te)) Q(s1, tMultiset, esNew.map(es => ast.ExplicitMultiset(es)(e.pos, e.info, e.errT)), v1)}) case ast.AnySetUnion(e0, e1) => e.typ match { - case _: ast.SetType => evalBinOp(s, e0, e1, SetUnion, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) + case _: ast.SetType => evalBinOp(s, e0, e1, SetUnion, pve, v, analysisInfos)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.AnySetUnion(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) - case _: ast.MultisetType => evalBinOp(s, e0, e1, MultisetUnion, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) + case _: ast.MultisetType => evalBinOp(s, e0, e1, MultisetUnion, pve, v, analysisInfos)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.AnySetUnion(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case _ => sys.error("Expected a (multi)set-typed expression but found %s (%s) of sort %s" .format(e, e.getClass.getName, e.typ)) } case ast.AnySetIntersection(e0, e1) => e.typ match { - case _: ast.SetType => evalBinOp(s, e0, e1, SetIntersection, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) + case _: ast.SetType => evalBinOp(s, e0, e1, SetIntersection, pve, v, analysisInfos)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.AnySetIntersection(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) - case _: ast.MultisetType => evalBinOp(s, e0, e1, MultisetIntersection, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) + case _: ast.MultisetType => evalBinOp(s, e0, e1, MultisetIntersection, pve, v, analysisInfos)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.AnySetIntersection(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case _ => sys.error("Expected a (multi)set-typed expression but found %s (%s) of sort %s" .format(e, e.getClass.getName, e.typ)) } case ast.AnySetSubset(e0, e1) => e0.typ match { - case _: ast.SetType => evalBinOp(s, e0, e1, SetSubset, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) + case _: ast.SetType => evalBinOp(s, e0, e1, SetSubset, pve, v, analysisInfos)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.AnySetSubset(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) - case _: ast.MultisetType => evalBinOp(s, e0, e1, MultisetSubset, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) + case _: ast.MultisetType => evalBinOp(s, e0, e1, MultisetSubset, pve, v, analysisInfos)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.AnySetSubset(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case _ => sys.error("Expected a (multi)set-typed expression but found %s (%s) of sort %s" .format(e, e.getClass.getName, e.typ)) } case ast.AnySetMinus(e0, e1) => e.typ match { - case _: ast.SetType => evalBinOp(s, e0, e1, SetDifference, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) + case _: ast.SetType => evalBinOp(s, e0, e1, SetDifference, pve, v, analysisInfos)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.AnySetMinus(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) - case _: ast.MultisetType => evalBinOp(s, e0, e1, MultisetDifference, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) + case _: ast.MultisetType => evalBinOp(s, e0, e1, MultisetDifference, pve, v, analysisInfos)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.AnySetMinus(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case _ => sys.error("Expected a (multi)set-typed expression but found %s (%s) of sort %s" .format(e, e.getClass.getName, e.typ)) } case ast.AnySetContains(e0, e1) => e1.typ match { - case _: ast.SetType => evalBinOp(s, e0, e1, SetIn, pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) + case _: ast.SetType => evalBinOp(s, e0, e1, SetIn, pve, v, analysisInfos)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.AnySetContains(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) - case _: ast.MultisetType => evalBinOp(s, e0, e1, (t0, t1) => MultisetCount(t1, t0), pve, v, analysisInfoes)((s1, t, e0New, e1New, v1) + case _: ast.MultisetType => evalBinOp(s, e0, e1, (t0, t1) => MultisetCount(t1, t0), pve, v, analysisInfos)((s1, t, e0New, e1New, v1) => Q(s1, t, e0New.map(e0p => ast.AnySetContains(e0p, e1New.get)(e.pos, e.info, e.errT)), v1)) case _ => sys.error("Expected a (multi)set-typed expression but found %s (%s) of sort %s" .format(e, e.getClass.getName, e.typ)) } case ast.AnySetCardinality(e0) => e0.typ match { - case _: ast.SetType => eval(s, e0, pve, v, analysisInfoes)((s1, t0, e0New, v1) + case _: ast.SetType => eval(s, e0, pve, v, analysisInfos)((s1, t0, e0New, v1) => Q(s1, SetCardinality(t0), e0New.map(e0p => ast.AnySetCardinality(e0p)(e.pos, e.info, e.errT)), v1)) - case _: ast.MultisetType => eval(s, e0, pve, v, analysisInfoes)((s1, t0, e0New, v1) + case _: ast.MultisetType => eval(s, e0, pve, v, analysisInfos)((s1, t0, e0New, v1) => Q(s1, MultisetCardinality(t0), e0New.map(e0p => ast.AnySetCardinality(e0p)(e.pos, e.info, e.errT)), v1)) case _ => sys.error("Expected a (multi)set-typed expression but found %s (%s) of type %s" .format(e0, e0.getClass.getName, e0.typ)) @@ -1009,29 +1009,29 @@ object evaluator extends EvaluationRules { case ast.EmptyMap(keyType, valueType) => Q(s, EmptyMap(v.symbolConverter.toSort(keyType), v.symbolConverter.toSort(valueType)), Option.when(withExp)(e), v) case em: ast.ExplicitMap => - eval(s, em.desugared, pve, v, analysisInfoes)((s1, t0, _, v1) => Q(s1, t0, Option.when(withExp)(em), v1)) + eval(s, em.desugared, pve, v, analysisInfos)((s1, t0, _, v1) => Q(s1, t0, Option.when(withExp)(em), v1)) case ast.MapCardinality(base) => - eval(s, base, pve, v, analysisInfoes)((s1, t0, baseNew, v1) => Q(s1, MapCardinality(t0), baseNew.map(ast.MapCardinality(_)(e.pos, e.info, e.errT)), v1)) + eval(s, base, pve, v, analysisInfos)((s1, t0, baseNew, v1) => Q(s1, MapCardinality(t0), baseNew.map(ast.MapCardinality(_)(e.pos, e.info, e.errT)), v1)) case ast.MapDomain(base) => - eval(s, base, pve, v, analysisInfoes)((s1, t0, baseNew, v1) => Q(s1, MapDomain(t0), baseNew.map(ast.MapDomain(_)(e.pos, e.info, e.errT)), v1)) + eval(s, base, pve, v, analysisInfos)((s1, t0, baseNew, v1) => Q(s1, MapDomain(t0), baseNew.map(ast.MapDomain(_)(e.pos, e.info, e.errT)), v1)) case ast.MapRange(base) => - eval(s, base, pve, v, analysisInfoes)((s1, t0, baseNew, v1) => Q(s1, MapRange(t0), baseNew.map(ast.MapRange(_)(e.pos, e.info, e.errT)), v1)) + eval(s, base, pve, v, analysisInfos)((s1, t0, baseNew, v1) => Q(s1, MapRange(t0), baseNew.map(ast.MapRange(_)(e.pos, e.info, e.errT)), v1)) case ml@ast.MapLookup(base, key) => - evals2(s, Seq(base, key), Nil, _ => pve, v, analysisInfoes)({ + evals2(s, Seq(base, key), Nil, _ => pve, v, analysisInfos)({ case (s1, Seq(baseT, keyT), esNew, v1) if s1.triggerExp => Q(s1, MapLookup(baseT, keyT), esNew.map(es => ast.MapLookup(es(0), es(1))(e.pos, e.info, e.errT)), v1) case (s1, Seq(baseT, keyT), esNew, v1) => val eNew = esNew.map(es => ast.MapLookup(es(0), es(1))(e.pos, e.info, e.errT)) - v1.decider.assert(SetIn(keyT, MapDomain(baseT)), analysisInfoes) { + v1.decider.assert(SetIn(keyT, MapDomain(baseT)), analysisInfos) { case true => Q(s1, MapLookup(baseT, keyT), eNew, v1) case false => val assertExp = Option.when(withExp)(ast.MapContains(key, base)(ml.pos, ml.info, ml.errT)) val assertExpNew = Option.when(withExp)(ast.MapContains(esNew.get(1), esNew.get(0))(ml.pos, ml.info, ml.errT)) val failure1 = createFailure(pve dueTo MapKeyNotContained(base, key), v1, s1, SetIn(keyT, MapDomain(baseT)), assertExpNew) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(SetIn(keyT, MapDomain(baseT)), analysisInfoes, assumeFailedAssertion=false) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(SetIn(keyT, MapDomain(baseT)), analysisInfos, assumeFailedAssertion=false) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { - v1.decider.assume(SetIn(keyT, MapDomain(baseT)), assertExp, assertExpNew, analysisInfoes.withDependencyType(DependencyType.Explicit)) + v1.decider.assume(SetIn(keyT, MapDomain(baseT)), assertExp, assertExpNew, analysisInfos.withDependencyType(DependencyType.Explicit)) failure1 combine Q(s1, MapLookup(baseT, keyT), eNew, v1) } else { failure1 @@ -1040,13 +1040,13 @@ object evaluator extends EvaluationRules { }) case ast.MapUpdate(base, key, value) => - evals2(s, Seq(base, key, value), Nil, _ => pve, v, analysisInfoes)({ + evals2(s, Seq(base, key, value), Nil, _ => pve, v, analysisInfos)({ case (s1, Seq(baseT, keyT, valueT), esNew, v1) => Q(s1, MapUpdate(baseT, keyT, valueT), esNew.map(es => ast.MapUpdate(es(0), es(1), es(2))(e.pos, e.info, e.errT)), v1) }) case ast.MapContains(key, base) => - evals2(s, Seq(key, base), Nil, _ => pve, v, analysisInfoes)({ + evals2(s, Seq(key, base), Nil, _ => pve, v, analysisInfos)({ case (s1, Seq(keyT, baseT), esNew, v1) => Q(s1, SetIn(keyT, MapDomain(baseT)), esNew.map(es => ast.MapContains(es(0), es(1))(e.pos, e.info, e.errT)), v1) }) @@ -1077,7 +1077,7 @@ object evaluator extends EvaluationRules { name: String, pve: PartialVerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Seq[Var], /* Variables from vars */ Option[Seq[ast.LocalVarDecl]], @@ -1105,18 +1105,18 @@ object evaluator extends EvaluationRules { type R = (State, Seq[Term], Option[Seq[ast.Exp]], Option[(Seq[Term], Option[Seq[ast.Exp]], Seq[Trigger], (Seq[Term], Seq[Quantification]), Option[(InsertionOrderedSet[DebugExp], InsertionOrderedSet[DebugExp])], Map[ast.Exp, Term])]) executionFlowController.locallyWithResult[R](s1, v)((s2, v1, QB) => { val preMark = v1.decider.setPathConditionMark() - evals(s2, es1, _ => pve, v1, analysisInfoes)((s3, ts1, es1New, v2) => { + evals(s2, es1, _ => pve, v1, analysisInfos)((s3, ts1, es1New, v2) => { val bc = And(ts1) // ME: If bc is unsatisfiable, we are assuming false here. In that case, evaluating es2 and the triggers // may not return any value (e.g. if es2 contains a field read for which we don't have permission, a smoke // check succeeds, then the continuation for evals(es2) is never invoked). This caused issue #842. // In this case, we return None. val expPair = (viper.silicon.utils.ast.BigAnd(es1), es1New.map(viper.silicon.utils.ast.BigAnd(_))) - v2.decider.setCurrentBranchCondition(bc, expPair, analysisInfoes) + v2.decider.setCurrentBranchCondition(bc, expPair, analysisInfos) var es2AndTriggerTerms: Option[(Seq[Term], Option[Seq[ast.Exp]], Seq[Trigger], (Seq[Term], Seq[Quantification]), Option[(InsertionOrderedSet[DebugExp], InsertionOrderedSet[DebugExp])], Map[ast.Exp, Term])] = None var finalState = s3 - val es2AndTriggerResult = evals(s3, es2, _ => pve, v2, analysisInfoes)((s4, ts2, es2New, v3) => { - evalTriggers(s4, optTriggers.getOrElse(Nil), pve, v3, analysisInfoes)((s5, tTriggers, _) => { // TODO: v4 isn't forward - problem? + val es2AndTriggerResult = evals(s3, es2, _ => pve, v2, analysisInfos)((s4, ts2, es2New, v3) => { + evalTriggers(s4, optTriggers.getOrElse(Nil), pve, v3, analysisInfos)((s5, tTriggers, _) => { // TODO: v4 isn't forward - problem? val (auxGlobals, auxNonGlobalQuants) = v3.decider.pcs.after(preMark).quantified(quant, tVars, tTriggers, s"$name-aux", isGlobal = false, bc) val auxExps = @@ -1146,13 +1146,13 @@ object evaluator extends EvaluationRules { fromShortCircuitingAnd: Boolean, pve: PartialVerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Term, Option[ast.Exp], Verifier) => VerificationResult) : VerificationResult = { - joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s, v, analysisInfoes)((s1, v1, QB) => - brancher.branch(s1.copy(parallelizeBranches = false), tLhs, eLhs, v1, analysisInfoes.withDependencyType(DependencyType.Internal), fromShortCircuitingAnd = fromShortCircuitingAnd)( - (s2, v2) => eval(s2.copy(parallelizeBranches = s1.parallelizeBranches), eRhs, pve, v2, analysisInfoes)((s2, tRhs, eRhsNew, v2) => QB(s2, (tRhs, eRhsNew), v2)), + joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s, v, analysisInfos)((s1, v1, QB) => + brancher.branch(s1.copy(parallelizeBranches = false), tLhs, eLhs, v1, analysisInfos.withDependencyType(DependencyType.Internal), fromShortCircuitingAnd = fromShortCircuitingAnd)( + (s2, v2) => eval(s2.copy(parallelizeBranches = s1.parallelizeBranches), eRhs, pve, v2, analysisInfos)((s2, tRhs, eRhsNew, v2) => QB(s2, (tRhs, eRhsNew), v2)), (s2, v2) => QB(s2.copy(parallelizeBranches = s1.parallelizeBranches), (True, Option.when(withExp)(ast.TrueLit()())), v2)) )(entries => { assert(entries.length <= 2) @@ -1171,7 +1171,7 @@ object evaluator extends EvaluationRules { e: ast.Exp, pve: PartialVerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Term, Option[ast.Exp], Verifier) => VerificationResult) : VerificationResult = { @@ -1180,7 +1180,7 @@ object evaluator extends EvaluationRules { val s2 = v.stateConsolidator(s1).consolidateOptionally(s1, v) val possibleTriggersBefore: Map[ast.Exp, Term] = if (s.recordPossibleTriggers) s.possibleTriggers else Map.empty - eval(s2, e, pve, v, analysisInfoes)((s3, t, eNew, v1) => { + eval(s2, e, pve, v, analysisInfos)((s3, t, eNew, v1) => { val newPossibleTriggers = if (s.recordPossibleTriggers) { // For all new possible trigger expressions e and translated term t, // make sure we remember t as the term for old[label](e) instead. @@ -1210,10 +1210,10 @@ object evaluator extends EvaluationRules { Q(s4, t, eNew, v1)}) } - def evalResourceAccess(s: State, resacc: ast.ResourceAccess, pve: PartialVerificationError, v: Verifier, analysisInfoes: DependencyAnalysisInfoes) + def evalResourceAccess(s: State, resacc: ast.ResourceAccess, pve: PartialVerificationError, v: Verifier, analysisInfos: DependencyAnalysisInfos) (Q: (State, ChunkIdentifer, Seq[Term], Option[Seq[ast.Exp]], Verifier) => VerificationResult) : VerificationResult = { - evals(s, resacc.args(s.program), _ => pve, v, analysisInfoes)((s1, tArgs, eArgsNew, v1) => + evals(s, resacc.args(s.program), _ => pve, v, analysisInfos)((s1, tArgs, eArgsNew, v1) => Q(s1, ChunkIdentifier(resacc.res(s1.program), s1.program), tArgs, eArgsNew, v1)) } @@ -1223,11 +1223,11 @@ object evaluator extends EvaluationRules { termOp: ((Term, Term)) => T, pve: PartialVerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, T, Option[ast.Exp], Option[ast.Exp], Verifier) => VerificationResult) : VerificationResult = { - evalBinOp(s, e0, e1, (t0, t1) => termOp((t0, t1)), pve, v, analysisInfoes)(Q) + evalBinOp(s, e0, e1, (t0, t1) => termOp((t0, t1)), pve, v, analysisInfos)(Q) } private def evalBinOp[T <: Term] @@ -1237,12 +1237,12 @@ object evaluator extends EvaluationRules { termOp: (Term, Term) => T, pve: PartialVerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, T, Option[ast.Exp], Option[ast.Exp], Verifier) => VerificationResult) : VerificationResult = { - eval(s, e0, pve, v, analysisInfoes)((s1, t0, e0New, v1) => - eval(s1, e1, pve, v1, analysisInfoes)((s2, t1, e1New, v2) => + eval(s, e0, pve, v, analysisInfos)((s1, t0, e0New, v1) => + eval(s1, e1, pve, v1, analysisInfos)((s2, t1, e1New, v2) => Q(s2, termOp(t0, t1), e0New, e1New, v2))) } @@ -1254,20 +1254,20 @@ object evaluator extends EvaluationRules { tZero: Term, pve: PartialVerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Term, Verifier) => VerificationResult) : VerificationResult = { - v.decider.assert(tDivisor !== tZero, analysisInfoes){ + v.decider.assert(tDivisor !== tZero, analysisInfos){ case true => Q(s, t, v) case false => val (notZeroExp, notZeroExpNew) = if (withExp) { (Some(ast.NeCmp(eDivisor, ast.IntLit(0)())(eDivisor.pos, eDivisor.info, eDivisor.errT)), Some(ast.NeCmp(eDivisorNew.get, ast.IntLit(0)())(eDivisor.pos, eDivisor.info, eDivisor.errT))) } else { (None, None) } val failure = createFailure(pve dueTo DivisionByZero(eDivisor), v, s, tDivisor !== tZero, notZeroExpNew) - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(tDivisor !== tZero, analysisInfoes, assumeFailedAssertion=false) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(tDivisor !== tZero, analysisInfos, assumeFailedAssertion=false) if (s.retryLevel == 0 && v.reportFurtherErrors()) { - v.decider.assume(tDivisor !== tZero, notZeroExp, notZeroExpNew, analysisInfoes.withDependencyType(DependencyType.Explicit)) + v.decider.assume(tDivisor !== tZero, notZeroExp, notZeroExpNew, analysisInfos.withDependencyType(DependencyType.Explicit)) failure combine Q(s, t, v) } else failure } @@ -1277,11 +1277,11 @@ object evaluator extends EvaluationRules { silverTriggers: Seq[ast.Trigger], pve: PartialVerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Seq[Trigger], Verifier) => VerificationResult) : VerificationResult = { - evalTriggers(s, silverTriggers map (_.exps), Nil, pve, v, analysisInfoes)((s1, tTriggersSets, v1) => { + evalTriggers(s, silverTriggers map (_.exps), Nil, pve, v, analysisInfos)((s1, tTriggersSets, v1) => { /* [2015-12-15 Malte] * Evaluating triggers that did not occur in the body (and whose corresponding term has * therefore not already been recorded in the context) might introduce new path conditions, @@ -1310,7 +1310,7 @@ object evaluator extends EvaluationRules { tTriggersSets: TriggerSets[Term], pve: PartialVerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, TriggerSets[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -1318,15 +1318,15 @@ object evaluator extends EvaluationRules { Q(s, tTriggersSets, v) else { if (eTriggerSets.head.collect{case fa: ast.FieldAccess => fa; case pa: ast.PredicateAccess => pa; case wand: ast.MagicWand => wand }.nonEmpty ) { - evalHeapTrigger(s, eTriggerSets.head, pve, v, analysisInfoes)((s1, ts, v1) => - evalTriggers(s1, eTriggerSets.tail, tTriggersSets :+ ts, pve, v1, analysisInfoes)(Q)) + evalHeapTrigger(s, eTriggerSets.head, pve, v, analysisInfos)((s1, ts, v1) => + evalTriggers(s1, eTriggerSets.tail, tTriggersSets :+ ts, pve, v1, analysisInfos)(Q)) } else { - evalTrigger(s, eTriggerSets.head, pve, v, analysisInfoes)((s1, ts, v1) => - evalTriggers(s1, eTriggerSets.tail, tTriggersSets :+ ts, pve, v1, analysisInfoes)(Q)) + evalTrigger(s, eTriggerSets.head, pve, v, analysisInfos)((s1, ts, v1) => + evalTriggers(s1, eTriggerSets.tail, tTriggersSets :+ ts, pve, v1, analysisInfos)(Q)) }} } - private def evalTrigger(s: State, exps: Seq[ast.Exp], pve: PartialVerificationError, v: Verifier, analysisInfoes: DependencyAnalysisInfoes) + private def evalTrigger(s: State, exps: Seq[ast.Exp], pve: PartialVerificationError, v: Verifier, analysisInfos: DependencyAnalysisInfos) (Q: (State, Seq[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -1398,7 +1398,7 @@ object evaluator extends EvaluationRules { */ val r = - evals(s.copy(triggerExp = true), remainingTriggerExpressions, _ => pve, v, analysisInfoes)((_, remainingTriggerTerms, _, v1) => { + evals(s.copy(triggerExp = true), remainingTriggerExpressions, _ => pve, v, analysisInfos)((_, remainingTriggerTerms, _, v1) => { optRemainingTriggerTerms = Some(remainingTriggerTerms) pcDelta = v1.decider.pcs.after(preMark).assumptions //decider.π -- πPre pcDeltaExp = v1.decider.pcs.after(preMark).assumptionExps @@ -1414,7 +1414,7 @@ object evaluator extends EvaluationRules { (r, optRemainingTriggerTerms) match { case (Success(), Some(remainingTriggerTerms)) => // TODO ake: wrap pcDelta with labels? - v.decider.assume(pcDelta, Option.when(withExp)(DebugExp.createInstance("pcDeltaExp", children = pcDeltaExp)), enforceAssumption = false, analysisInfoes.withDependencyType(DependencyType.Internal)) + v.decider.assume(pcDelta, Option.when(withExp)(DebugExp.createInstance("pcDeltaExp", children = pcDeltaExp)), enforceAssumption = false, analysisInfos.withDependencyType(DependencyType.Internal)) Q(s, cachedTriggerTerms ++ remainingTriggerTerms, v) case _ => for (e <- remainingTriggerExpressions) @@ -1429,7 +1429,7 @@ object evaluator extends EvaluationRules { joinFunctionArgs: Seq[Term], joinFunctionArgsExp: Option[Seq[ast.Exp]], v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (entries: Seq[JoinDataEntry[(Term, Option[ast.Exp])]]) : (State, (Term, Option[ast.Exp])) = { @@ -1464,13 +1464,13 @@ object evaluator extends EvaluationRules { var sJoined = entries.tail.foldLeft(entries.head.s)((sAcc, entry) => sAcc.merge(entry.s)) sJoined = sJoined.copy(functionRecorder = sJoined.functionRecorder.recordPathSymbol(joinSymbol)) - joinDefEqs foreach { case (t, exp, expNew) => v.decider.assume(t, exp, expNew, analysisInfoes.withDependencyType(DependencyType.Internal))} + joinDefEqs foreach { case (t, exp, expNew) => v.decider.assume(t, exp, expNew, analysisInfos.withDependencyType(DependencyType.Internal))} (sJoined, (joinTerm, joinExp)) } } - def evalHeapTrigger(s: State, exps: Seq[ast.Exp], pve: PartialVerificationError, v: Verifier, analysisInfoes: DependencyAnalysisInfoes) + def evalHeapTrigger(s: State, exps: Seq[ast.Exp], pve: PartialVerificationError, v: Verifier, analysisInfos: DependencyAnalysisInfos) (Q: (State, Seq[Term], Verifier) => VerificationResult) : VerificationResult = { var triggers: Seq[Term] = Seq() var triggerAxioms: Seq[Term] = Seq() @@ -1478,18 +1478,18 @@ object evaluator extends EvaluationRules { exps foreach { case ra: ast.ResourceAccess if s.isUsedAsTrigger(ra.res(s.program)) => - val (axioms, trigs, _, smDef) = generateResourceTrigger(ra, s, pve, v, analysisInfoes) + val (axioms, trigs, _, smDef) = generateResourceTrigger(ra, s, pve, v, analysisInfos) triggers = triggers ++ trigs triggerAxioms = triggerAxioms ++ axioms smDefs = smDefs ++ smDef - case e => evalTrigger(s.copy(triggerExp = true), Seq(e), pve, v, analysisInfoes)((_, t, _) => { + case e => evalTrigger(s.copy(triggerExp = true), Seq(e), pve, v, analysisInfos)((_, t, _) => { triggers = triggers ++ t Success() }) } val triggerString = exps.mkString(", ") - v.decider.assume(triggerAxioms, Option.when(withExp)(DebugExp.createInstance(s"Heap Triggers ($triggerString)")), enforceAssumption = false, analysisInfoes.withDependencyType(DependencyType.Trigger)) + v.decider.assume(triggerAxioms, Option.when(withExp)(DebugExp.createInstance(s"Heap Triggers ($triggerString)")), enforceAssumption = false, analysisInfos.withDependencyType(DependencyType.Trigger)) var fr = s.functionRecorder for (smDef <- smDefs){ fr = fr.recordFvfAndDomain(smDef) @@ -1501,7 +1501,7 @@ object evaluator extends EvaluationRules { s: State, pve: PartialVerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) : (Seq[Term], Seq[Term], Term, Seq[SnapshotMapDefinition]) = { var axioms = Seq.empty[Term] var triggers = Seq.empty[Term] @@ -1522,7 +1522,7 @@ object evaluator extends EvaluationRules { s, resource, codomainQVars, relevantChunks, v, optSmDomainDefinitionCondition) val s1 = s.copy(smCache = smCache1) - evals(s1.copy(triggerExp = true), eArgs, _ => pve, v, analysisInfoes)((_, tArgs, _, _) => { + evals(s1.copy(triggerExp = true), eArgs, _ => pve, v, analysisInfos)((_, tArgs, _, _) => { axioms = axioms ++ smDef1.valueDefinitions mostRecentTrig = ResourceTriggerFunction(resource, smDef1.sm, tArgs, s.program) triggers = triggers :+ mostRecentTrig @@ -1541,7 +1541,7 @@ object evaluator extends EvaluationRules { exps: Seq[ast.Exp], pve: PartialVerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Term, Option[ast.Exp], Verifier) => VerificationResult) : VerificationResult = { assert( @@ -1552,16 +1552,16 @@ object evaluator extends EvaluationRules { val stop = if (constructor == Or) True else False - eval(s, exps.head, pve, v, analysisInfoes)((s1, t0, e0New, v1) => { + eval(s, exps.head, pve, v, analysisInfos)((s1, t0, e0New, v1) => { t0 match { case _ if exps.tail.isEmpty => Q(s1, t0, e0New, v1) // Done, if no expressions left (necessary) case `stop` => Q(s1, t0, e0New, v1) // Done, if last expression was true/false for or/and (optimisation) case _ => val expPair = if (constructor == Or) (exps.head, e0New) else (ast.Not(exps.head)(), e0New.map(ast.Not(_)())) - joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s1, v1, analysisInfoes)((s2, v2, QB) => - brancher.branch(s2.copy(parallelizeBranches = false), if (constructor == Or) t0 else Not(t0), expPair, v2, analysisInfoes.withDependencyType(DependencyType.Internal), fromShortCircuitingAnd = true)( + joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s1, v1, analysisInfos)((s2, v2, QB) => + brancher.branch(s2.copy(parallelizeBranches = false), if (constructor == Or) t0 else Not(t0), expPair, v2, analysisInfos.withDependencyType(DependencyType.Internal), fromShortCircuitingAnd = true)( (s3, v3) => QB(s3.copy(parallelizeBranches = s2.parallelizeBranches), (t0, e0New), v3), - (s3, v3) => evalSeqShortCircuit(constructor, s3.copy(parallelizeBranches = s2.parallelizeBranches), exps.tail, pve, v3, analysisInfoes)((s2, t2, e2, v2) => QB(s2, (t2, e2), v2))) + (s3, v3) => evalSeqShortCircuit(constructor, s3.copy(parallelizeBranches = s2.parallelizeBranches), exps.tail, pve, v3, analysisInfos)((s2, t2, e2, v2) => QB(s2, (t2, e2), v2))) ){case Seq(ent) => (ent.s, ent.data) case Seq(ent1, ent2) => diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 2e2fa259d..8233d57ba 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -10,8 +10,8 @@ import viper.silicon.Config.JoinMode import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.decider.RecordedPathConditions -import viper.silicon.dependencyAnalysis.DependencyAnalysisInfoes -import viper.silicon.dependencyAnalysis.DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfos +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfos.DefaultDependencyAnalysisInfos import viper.silicon.interfaces._ import viper.silicon.interfaces.state.{NonQuantifiedChunk, QuantifiedChunk} import viper.silicon.logger.records.data.{CommentRecord, ConditionalEdgeRecord, ExecuteRecord, MethodCallRecord} @@ -70,12 +70,12 @@ object executor extends ExecutionRules { val condEdgeRecord = new ConditionalEdgeRecord(ce.condition, s, v.decider.pcs) val sepIdentifier = v.symbExLog.openScope(condEdgeRecord) val s1 = handleOutEdge(s, edge, v) - val analysisInfoes = DefaultDependencyAnalysisInfoes.addInfo(ce.condition.info, ce.condition) - eval(s1, ce.condition, IfFailed(ce.condition), v, analysisInfoes)((s2, tCond, condNew, v1) => + val analysisInfos = DefaultDependencyAnalysisInfos.addInfo(ce.condition.info, ce.condition) + eval(s1, ce.condition, IfFailed(ce.condition), v, analysisInfos)((s2, tCond, condNew, v1) => /* Using branch(...) here ensures that the edge condition is recorded * as a branch condition on the pathcondition stack. */ - brancher.branch(s2.copy(parallelizeBranches = false), tCond, (ce.condition, condNew), v1, analysisInfoes)( + brancher.branch(s2.copy(parallelizeBranches = false), tCond, (ce.condition, condNew), v1, analysisInfos)( (s3, v3) => exec(s3.copy(parallelizeBranches = s2.parallelizeBranches), ce.target, ce.kind, v3, joinPoint)((s4, v4) => { v4.symbExLog.closeScope(sepIdentifier) @@ -96,8 +96,8 @@ object executor extends ExecutionRules { def handleOutEdge(s: State, edge: SilverEdge, v: Verifier): State = { edge.kind match { case cfg.Kind.Out if !v.decider.isPathInfeasible => - val analysisInfoes = DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes // TODO ake - val (fr1, h1) = v.stateConsolidator(s).merge(s.functionRecorder, s, s.h, s.invariantContexts.head, v, analysisInfoes) + val analysisInfos = DependencyAnalysisInfos.DefaultDependencyAnalysisInfos // TODO ake + val (fr1, h1) = v.stateConsolidator(s).merge(s.functionRecorder, s, s.h, s.invariantContexts.head, v, analysisInfos) val s1 = s.copy(functionRecorder = fr1, h = h1, invariantContexts = s.invariantContexts.tail) s1 @@ -150,12 +150,12 @@ object executor extends ExecutionRules { case _ => false }) - val analysisInfoes = DefaultDependencyAnalysisInfoes.addInfo(cedge1.condition.info, cedge1.condition) + val analysisInfos = DefaultDependencyAnalysisInfos.addInfo(cedge1.condition.info, cedge1.condition) - eval(s, cedge1.condition, pvef(cedge1.condition), v, analysisInfoes)((s1, t0, condNew, v1) => + eval(s, cedge1.condition, pvef(cedge1.condition), v, analysisInfos)((s1, t0, condNew, v1) => // The type arguments here are Null because there is no need to pass any join data. - joiner.join[scala.Null, scala.Null](s1, v1, analysisInfoes, resetState = false)((s2, v2, QB) => { - brancher.branch(s2, t0, (cedge1.condition, condNew), v2, analysisInfoes)( + joiner.join[scala.Null, scala.Null](s1, v1, analysisInfos, resetState = false)((s2, v2, QB) => { + brancher.branch(s2, t0, (cedge1.condition, condNew), v2, analysisInfos)( // Follow only until join point. (s3, v3) => follow(s3, edge1, v3, Some(newJoinPoint))((s, v) => QB(s, null, v)), (s3, v3) => follow(s3, edge2, v3, Some(newJoinPoint))((s, v) => QB(s, null, v)) @@ -186,9 +186,9 @@ object executor extends ExecutionRules { if Verifier.config.parallelizeBranches() && cond2 == ast.Not(cond1)() => val condEdgeRecord = new ConditionalEdgeRecord(thenEdge.condition, s, v.decider.pcs) val sepIdentifier = v.symbExLog.openScope(condEdgeRecord) - val analysisInfoes = DefaultDependencyAnalysisInfoes.addInfo(thenEdge.condition.info, thenEdge.condition) - val res = eval(s, thenEdge.condition, IfFailed(thenEdge.condition), v, analysisInfoes)((s2, tCond, eCondNew, v1) => - brancher.branch(s2, tCond, (thenEdge.condition, eCondNew), v1, analysisInfoes)( + val analysisInfos = DefaultDependencyAnalysisInfos.addInfo(thenEdge.condition.info, thenEdge.condition) + val res = eval(s, thenEdge.condition, IfFailed(thenEdge.condition), v, analysisInfos)((s2, tCond, eCondNew, v1) => + brancher.branch(s2, tCond, (thenEdge.condition, eCondNew), v1, analysisInfos)( (s3, v3) => { follow(s3, thenEdge, v3, joinPoint)(Q) }, @@ -261,8 +261,8 @@ object executor extends ExecutionRules { map.updated(x, xNew)})) val sBody = s.copy(g = gBody, h = v.heapSupporter.getEmptyHeap(s.program)) - val analysisInfoesInv = DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes - val analysisInfoesLoopInternal = DependencyAnalysisInfoes.create(s"Loop ${block.id}\"", DependencyType.Internal) + val analysisInfosInv = DependencyAnalysisInfos.DefaultDependencyAnalysisInfos + val analysisInfosLoopInternal = DependencyAnalysisInfos.create(s"Loop ${block.id}\"", DependencyType.Internal) val edges = s.methodCfg.outEdges(block) val (outEdges, otherEdges) = edges partition(_.kind == cfg.Kind.Out) @@ -276,7 +276,7 @@ object executor extends ExecutionRules { (executionFlowController.locally(sBody, v)((s0, v0) => { v0.decider.prover.comment("Loop head block: Check well-definedness of invariant") val mark = v0.decider.setPathConditionMark() - produces(s0, freshSnap, invs, ContractNotWellformed, v0, analysisInfoesInv)((s1, v1) => { + produces(s0, freshSnap, invs, ContractNotWellformed, v0, analysisInfosInv)((s1, v1) => { phase1data = phase1data :+ (s1, v1.decider.pcs.after(mark), v1.decider.freshFunctions /* [BRANCH-PARALLELISATION] */, @@ -285,7 +285,7 @@ object executor extends ExecutionRules { })}) combine executionFlowController.locally(s, v)((s0, v0) => { v0.decider.prover.comment("Loop head block: Establish invariant") - consumes(s0, invs, false, LoopInvariantNotEstablished, v0, analysisInfoesInv)((sLeftover, _, v1) => { + consumes(s0, invs, false, LoopInvariantNotEstablished, v0, analysisInfosInv)((sLeftover, _, v1) => { v1.decider.prover.comment("Loop head block: Execute statements of loop head block (in invariant state)") phase1data.foldLeft(Success(): VerificationResult) { case (result, _) if !result.continueVerification => result @@ -295,9 +295,9 @@ object executor extends ExecutionRules { v2.decider.declareAndRecordAsFreshFunctions(ff1 -- v2.decider.freshFunctions) /* [BRANCH-PARALLELISATION] */ v2.decider.declareAndRecordAsFreshMacros(fm1.filter(!v2.decider.freshMacros.contains(_))) /* [BRANCH-PARALLELISATION] */ if(v2.decider.pcs.getCurrentInfeasibilityNode.isEmpty) v2.decider.pcs.setCurrentInfeasibilityNode(pcs.infeasibilityNodeId) - v2.decider.assume(pcs.assumptions map (t => v.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t))), Some(pcs.assumptionExps), "Loop invariant", enforceAssumption=false, analysisInfoesLoopInternal) + v2.decider.assume(pcs.assumptions map (t => v.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t))), Some(pcs.assumptionExps), "Loop invariant", enforceAssumption=false, analysisInfosLoopInternal) v2.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) - if (!Verifier.config.disableInfeasibilityChecks() && v2.decider.checkSmoke(analysisInfoesLoopInternal)) + if (!Verifier.config.disableInfeasibilityChecks() && v2.decider.checkSmoke(analysisInfosLoopInternal)) Success() else { execs(s3, stmts, v2)((s4, v3) => { @@ -307,7 +307,7 @@ object executor extends ExecutionRules { case (result, _) if !result.continueVerification => result case (intermediateResult, eCond) => intermediateResult combine executionFlowController.locally(s4, v3)((s5, v4) => { - eval(s5, eCond, WhileFailed(eCond), v4, DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes)((_, _, _, _) => + eval(s5, eCond, WhileFailed(eCond), v4, DependencyAnalysisInfos.DefaultDependencyAnalysisInfos)((_, _, _, _) => Success()) }) } @@ -321,8 +321,8 @@ object executor extends ExecutionRules { * attempting to re-establish the invariant. */ v.decider.prover.comment("Loop head block: Re-establish invariant") - val analysisInfoesInv = DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes - consumes(s, invs, false, e => LoopInvariantNotPreserved(e), v, analysisInfoesInv)((_, _, _) => + val analysisInfosInv = DependencyAnalysisInfos.DefaultDependencyAnalysisInfos + consumes(s, invs, false, e => LoopInvariantNotPreserved(e), v, analysisInfosInv)((_, _, _) => Success()) } } @@ -342,14 +342,14 @@ object executor extends ExecutionRules { (Q: (State, Verifier) => VerificationResult) : VerificationResult = { val sepIdentifier = v.symbExLog.openScope(new ExecuteRecord(stmt, s, v.decider.pcs)) - val analysisInfoes = DefaultDependencyAnalysisInfoes.addInfo(stmt.info, stmt) - exec2(s, stmt, v, analysisInfoes)((s1, v1) => { + val analysisInfos = DefaultDependencyAnalysisInfos.addInfo(stmt.info, stmt) + exec2(s, stmt, v, analysisInfos)((s1, v1) => { v1.symbExLog.closeScope(sepIdentifier) Q(s1, v1) }) } - def exec2(state: State, stmt: ast.Stmt, v: Verifier, analysisInfoes: DependencyAnalysisInfoes) + def exec2(state: State, stmt: ast.Stmt, v: Verifier, analysisInfos: DependencyAnalysisInfos) (continuation: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -359,10 +359,10 @@ object executor extends ExecutionRules { // assertionNodesForJoin.foreach(n => v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, CompositeAnalysisSourceInfo(v.decider.analysisSourceInfoStack.getFullSourceInfo, AnalysisSourceInfo.createAnalysisSourceInfo(n)), v.decider.analysisSourceInfoStack.getDependencyType, isJoinNode=true)) if(Statements.hasProofObligations(stmt, state.program)){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfoes) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfos) } if(Statements.introducesSmtAssumptions(stmt)){ - v.decider.dependencyAnalyzer.addAssumption(True, analysisInfoes) + v.decider.dependencyAnalyzer.addAssumption(True, analysisInfos) } return continuation(state, v) } @@ -397,8 +397,8 @@ object executor extends ExecutionRules { Q(s.copy(g = s.g + (x -> (t, newExp))), v) case ass @ ast.LocalVarAssign(x, rhs) => - eval(s, rhs, AssignmentFailed(ass), v, analysisInfoes)((s1, tRhs, rhsNew, v1) => { - val (t, e) = ssaifyRhs(tRhs, rhs, rhsNew, x.name, x.typ, v, s1, analysisInfoes) + eval(s, rhs, AssignmentFailed(ass), v, analysisInfos)((s1, tRhs, rhsNew, v1) => { + val (t, e) = ssaifyRhs(tRhs, rhs, rhsNew, x.name, x.typ, v, s1, analysisInfos) Q(s1.copy(g = s1.g + (x, (t, e))), v1)}) /* TODO: Encode assignments e1.f := e2 as @@ -410,10 +410,10 @@ object executor extends ExecutionRules { case ass @ ast.FieldAssign(ast.FieldAccess(eRcvr, field), rhs) => assert(!s.exhaleExt) val pve = AssignmentFailed(ass) - eval(s, eRcvr, pve, v, analysisInfoes)((s1, tRcvr, eRcvrNew, v1) => { - eval(s1, rhs, pve, v1, analysisInfoes)((s2, tRhs, eRhsNew, v2) => { - val (tSnap, _) = ssaifyRhs(tRhs, rhs, eRhsNew, field.name, field.typ, v2, s2, analysisInfoes) - v2.heapSupporter.execFieldAssign(s2, ass, tRcvr, eRcvrNew, tSnap, eRhsNew, pve, v2, analysisInfoes)(Q) + eval(s, eRcvr, pve, v, analysisInfos)((s1, tRcvr, eRcvrNew, v1) => { + eval(s1, rhs, pve, v1, analysisInfos)((s2, tRhs, eRhsNew, v2) => { + val (tSnap, _) = ssaifyRhs(tRhs, rhs, eRhsNew, field.name, field.typ, v2, s2, analysisInfos) + v2.heapSupporter.execFieldAssign(s2, ass, tRcvr, eRcvrNew, tSnap, eRhsNew, pve, v2, analysisInfos)(Q) }) }) @@ -422,7 +422,7 @@ object executor extends ExecutionRules { val debugExp = Option.when(withExp)(ast.NeCmp(x, ast.NullLit()())()) val debugExpSubst = Option.when(withExp)(ast.NeCmp(eRcvrNew.get, ast.NullLit()())()) val (debugHeapName, debugLabel) = v.getDebugOldLabel(s, stmt.pos) - v.decider.assume(tRcvr !== Null, debugExp, debugExpSubst, analysisInfoes) + v.decider.assume(tRcvr !== Null, debugExp, debugExpSubst, analysisInfos) val eRcvr = Option.when(withExp)(Seq(x)) val p = FullPerm @@ -436,7 +436,7 @@ object executor extends ExecutionRules { val fld = flds.head val snap = v.decider.fresh(fld.name, v.symbolConverter.toSort(fld.typ), Option.when(withExp)(extractPTypeFromExp(x))) val snapExp = Option.when(withExp)(ast.DebugLabelledOld(ast.FieldAccess(eRcvrNew.get, fld)(), debugLabel)(stmt.pos, stmt.info, stmt.errT)) - v.heapSupporter.produceSingle(s, fld, Seq(tRcvr), eRcvr, snap, snapExp, p, pExp, NullPartialVerificationError, false, v, analysisInfoes)((s1, v1) => { + v.heapSupporter.produceSingle(s, fld, Seq(tRcvr), eRcvr, snap, snapExp, p, pExp, NullPartialVerificationError, false, v, analysisInfos)((s1, v1) => { addFieldPerms(s1, flds.tail, v1)(QB) }) } @@ -446,7 +446,7 @@ object executor extends ExecutionRules { addFieldPerms(s, fields, v)((s0, v0) => { val s1 = s0.copy(g = s0.g + (x, (tRcvr, eRcvrNew))) val s2 = if (withExp) s1.copy(oldHeaps = s1.oldHeaps + (debugHeapName -> magicWandSupporter.getEvalHeap(s1))) else s1 - v0.decider.assume(ts, Option.when(withExp)(DebugExp.createInstance(Some("Reference Disjointness"), esNew, esNew, InsertionOrderedSet.empty)), enforceAssumption = false, analysisInfoes) + v0.decider.assume(ts, Option.when(withExp)(DebugExp.createInstance(Some("Reference Disjointness"), esNew, esNew, InsertionOrderedSet.empty)), enforceAssumption = false, analysisInfos) Q(s2, v0) }) @@ -457,25 +457,25 @@ object executor extends ExecutionRules { case _: ast.TrueLit => Q(s, v) case _ => - produce(s, freshSnap, a, InhaleFailed(inhale), v, analysisInfoes)((s1, v1) => { + produce(s, freshSnap, a, InhaleFailed(inhale), v, analysisInfos)((s1, v1) => { v1.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterInhale) - if(v1.decider.isDependencyAnalysisEnabled && a.isInstanceOf[ast.FalseLit]) v1.decider.checkSmokeAndSetInfeasibilityNode(analysisInfoes) + if(v1.decider.isDependencyAnalysisEnabled && a.isInstanceOf[ast.FalseLit]) v1.decider.checkSmokeAndSetInfeasibilityNode(analysisInfos) Q(s1, v1)}) } case exhale @ ast.Exhale(a) => val pve = ExhaleFailed(exhale) - consume(s, a, false, pve, v, analysisInfoes)((s1, _, v1) => + consume(s, a, false, pve, v, analysisInfos)((s1, _, v1) => Q(s1, v1)) case assert @ ast.Assert(a: ast.FalseLit) if !s.isInPackage => /* "assert false" triggers a smoke check. If successful, we backtrack. */ executionFlowController.tryOrFail0(s.copy(h = magicWandSupporter.getEvalHeap(s)), v)((s1, v1, QS) => { - if (v1.decider.checkSmoke(analysisInfoes, isAssert = true)) + if (v1.decider.checkSmoke(analysisInfos, isAssert = true)) QS(s1.copy(h = s.h), v1) else { val failure = createFailure(AssertFailed(assert) dueTo AssertionFalse(a), v1, s1, False, true, Option.when(withExp)(a)) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfoes, v1.reportFurtherErrors()) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfos, v1.reportFurtherErrors()) if(s1.retryLevel == 0 && v1.reportFurtherErrors()) failure combine QS(s1, v1) else failure } })((s2, v2) => @@ -487,7 +487,7 @@ object executor extends ExecutionRules { case assert @ ast.Assert(a) if Verifier.config.disableSubsumption() => val r = - consume(s, a, false, AssertFailed(assert), v, analysisInfoes)((_, _, _) => + consume(s, a, false, AssertFailed(assert), v, analysisInfos)((_, _, _) => Success()) r combine Q(s, v) @@ -503,11 +503,11 @@ object executor extends ExecutionRules { * hUsed (reserveHeaps.head) instead of consuming them. hUsed is later discarded and replaced * by s.h. By copying hUsed to s.h the contained permissions remain available inside the wand. */ - consume(s, a, false, pve, v, analysisInfoes)((s2, _, v1) => { + consume(s, a, false, pve, v, analysisInfos)((s2, _, v1) => { Q(s2.copy(h = s2.reserveHeaps.head), v1) }) } else - consume(s, a, false, pve, v, analysisInfoes)((s1, _, v1) => { + consume(s, a, false, pve, v, analysisInfos)((s1, _, v1) => { val s2 = s1.copy(h = s.h, reserveHeaps = s.reserveHeaps) Q(s2, v1)}) @@ -516,7 +516,7 @@ object executor extends ExecutionRules { case ast.MethodCall(methodName, _, _) if !Verifier.config.disableHavocHack407() && methodName.startsWith(hack407_method_name_prefix) => - val analysisInfo = v.decider.getAnalysisInfo(analysisInfoes) + val analysisInfo = v.decider.getAnalysisInfo(analysisInfos) val resourceName = methodName.stripPrefix(hack407_method_name_prefix) val member = s.program.collectFirst { case m: ast.Field if m.name == resourceName => m @@ -547,13 +547,13 @@ object executor extends ExecutionRules { val pveCall = CallFailed(call) val pveCallTransformed = pveCall.withReasonNodeTransformed(reasonTransformer) - v.decider.dependencyAnalyzer.addAssumption(True, analysisInfoes, None) // make sure method calls are represented as a node, even if there are no postconditions + v.decider.dependencyAnalyzer.addAssumption(True, analysisInfos, None) // make sure method calls are represented as a node, even if there are no postconditions val mcLog = new MethodCallRecord(call, s, v.decider.pcs) val sepIdentifier = v.symbExLog.openScope(mcLog) val paramLog = new CommentRecord("Parameters", s, v.decider.pcs) val paramId = v.symbExLog.openScope(paramLog) - evals(s, eArgs, _ => pveCall, v, analysisInfoes)((s1, tArgs, eArgsNew, v1) => { + evals(s, eArgs, _ => pveCall, v, analysisInfos)((s1, tArgs, eArgsNew, v1) => { v1.symbExLog.closeScope(paramId) val exampleTrafo = CounterexampleTransformer({ case ce: SiliconCounterexample => ce.withStore(s1.g) @@ -569,14 +569,14 @@ object executor extends ExecutionRules { val s2 = s1.copy(g = Store(fargs.zip(argsWithExp)), recordVisited = true) - consumes(s2, meth.pres, false, _ => pvePre, v1, analysisInfoes.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Source, EdgeType.Up)))((s3, _, v2) => { + consumes(s2, meth.pres, false, _ => pvePre, v1, analysisInfos.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Source, EdgeType.Up)))((s3, _, v2) => { v2.symbExLog.closeScope(preCondId) val postCondLog = new CommentRecord("Postcondition", s3, v2.decider.pcs) val postCondId = v2.symbExLog.openScope(postCondLog) val outs = meth.formalReturns.map(_.localVar) val gOuts = Store(outs.map(x => (x, v2.decider.fresh(x))).toMap) val s4 = s3.copy(g = s3.g + gOuts, oldHeaps = s3.oldHeaps + (Verifier.PRE_STATE_LABEL -> magicWandSupporter.getEvalHeap(s1))) - produces(s4, freshSnap, meth.posts, _ => pveCallTransformed, v2, analysisInfoes.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Sink, EdgeType.Down)))((s5, v3) => { + produces(s4, freshSnap, meth.posts, _ => pveCallTransformed, v2, analysisInfos.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Sink, EdgeType.Down)))((s5, v3) => { v3.symbExLog.closeScope(postCondId) v3.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) val gLhs = Store(lhs.zip(outs) @@ -593,11 +593,11 @@ object executor extends ExecutionRules { val ePerm = pap.perm val predicate = s.program.findPredicate(predicateName) val pve = FoldFailed(fold) - evals(s, eArgs, _ => pve, v, analysisInfoes)((s1, tArgs, eArgsNew, v1) => - eval(s1, ePerm, pve, v1, analysisInfoes)((s2, tPerm, ePermNew, v2) => - permissionSupporter.assertPositive(s2, tPerm, if (withExp) ePermNew.get else ePerm, pve, v2, analysisInfoes)((s3, v3) => { + evals(s, eArgs, _ => pve, v, analysisInfos)((s1, tArgs, eArgsNew, v1) => + eval(s1, ePerm, pve, v1, analysisInfos)((s2, tPerm, ePermNew, v2) => + permissionSupporter.assertPositive(s2, tPerm, if (withExp) ePermNew.get else ePerm, pve, v2, analysisInfos)((s3, v3) => { val wildcards = s3.constrainableARPs -- s1.constrainableARPs - predicateSupporter.fold(s3, predAcc, tArgs, eArgsNew, tPerm, ePermNew, wildcards, pve, v3, analysisInfoes)((s4, v4) => { + predicateSupporter.fold(s3, predAcc, tArgs, eArgsNew, tPerm, ePermNew, wildcards, pve, v3, analysisInfos)((s4, v4) => { v3.decider.finishDebugSubExp(s"folded ${predAcc.toString}") Q(s4, v4) } @@ -609,13 +609,13 @@ object executor extends ExecutionRules { val ePerm = pap.perm val predicate = s.program.findPredicate(predicateName) val pve = UnfoldFailed(unfold) - evals(s, eArgs, _ => pve, v, analysisInfoes)((s1, tArgs, eArgsNew, v1) => - eval(s1, ePerm, pve, v1, analysisInfoes)((s2, tPerm, ePermNew, v2) => { - val s2a = v2.heapSupporter.triggerResourceIfNeeded(s2, pa, tArgs, eArgsNew, v2, analysisInfoes) + evals(s, eArgs, _ => pve, v, analysisInfos)((s1, tArgs, eArgsNew, v1) => + eval(s1, ePerm, pve, v1, analysisInfos)((s2, tPerm, ePermNew, v2) => { + val s2a = v2.heapSupporter.triggerResourceIfNeeded(s2, pa, tArgs, eArgsNew, v2, analysisInfos) - permissionSupporter.assertPositive(s2a, tPerm, if (withExp) ePermNew.get else ePerm, pve, v2, analysisInfoes)((s3, v3) => { + permissionSupporter.assertPositive(s2a, tPerm, if (withExp) ePermNew.get else ePerm, pve, v2, analysisInfos)((s3, v3) => { val wildcards = s3.constrainableARPs -- s1.constrainableARPs - predicateSupporter.unfold(s3, predicate, tArgs, eArgsNew, tPerm, ePermNew, wildcards, pve, v3, pa, analysisInfoes)( + predicateSupporter.unfold(s3, predicate, tArgs, eArgsNew, tPerm, ePermNew, wildcards, pve, v3, pa, analysisInfos)( (s4, v4) => { v2.decider.finishDebugSubExp(s"unfolded ${pa.toString}") Q(s4, v4) @@ -625,7 +625,7 @@ object executor extends ExecutionRules { case pckg @ ast.Package(wand, proofScript) => val pve = PackageFailed(pckg) - magicWandSupporter.packageWand(s.copy(isInPackage = true), wand, proofScript, pve, v, analysisInfoes)((s1, chWand, v1) => { + magicWandSupporter.packageWand(s.copy(isInPackage = true), wand, proofScript, pve, v, analysisInfos)((s1, chWand, v1) => { val hOps = s1.reserveHeaps.head + chWand assert(s.exhaleExt || s1.reserveHeaps.length == 1) @@ -651,7 +651,7 @@ object executor extends ExecutionRules { val s3 = chWand match { case ch: QuantifiedMagicWandChunk => - v1.heapSupporter.triggerResourceIfNeeded(s2, wand, ch.singletonArgs.get, ch.singletonArgExps, v1, analysisInfoes) + v1.heapSupporter.triggerResourceIfNeeded(s2, wand, ch.singletonArgs.get, ch.singletonArgExps, v1, analysisInfos) case _ => s2 } @@ -660,13 +660,13 @@ object executor extends ExecutionRules { case apply @ ast.Apply(e) => val pve = ApplyFailed(apply) - magicWandSupporter.applyWand(s, e, pve, v, analysisInfoes)(Q) + magicWandSupporter.applyWand(s, e, pve, v, analysisInfos)(Q) case havoc: ast.Quasihavoc => - havocSupporter.execHavoc(havoc, v, s, analysisInfoes)(Q) + havocSupporter.execHavoc(havoc, v, s, analysisInfos)(Q) case havocall: ast.Quasihavocall => - havocSupporter.execHavocall(havocall, v, s, analysisInfoes)(Q) + havocSupporter.execHavocall(havocall, v, s, analysisInfos)(Q) case viper.silicon.extensions.TryBlock(body) => var bodySucceeded = false @@ -690,7 +690,7 @@ object executor extends ExecutionRules { executed } - private def ssaifyRhs(rhs: Term, rhsExp: ast.Exp, rhsExpNew: Option[ast.Exp], name: String, typ: ast.Type, v: Verifier, s : State, analysisInfoes: DependencyAnalysisInfoes): (Term, Option[ast.Exp]) = { + private def ssaifyRhs(rhs: Term, rhsExp: ast.Exp, rhsExpNew: Option[ast.Exp], name: String, typ: ast.Type, v: Verifier, s : State, analysisInfos: DependencyAnalysisInfos): (Term, Option[ast.Exp]) = { rhs match { case _: Var | _: Literal if !v.decider.isDependencyAnalysisEnabled => (rhs, rhsExpNew) @@ -717,7 +717,7 @@ object executor extends ExecutionRules { } else { (None, None) } - v.decider.assumeDefinition(BuiltinEquals(t, rhs), debugExp, analysisInfoes) + v.decider.assumeDefinition(BuiltinEquals(t, rhs), debugExp, analysisInfos) (t, eNew) } } diff --git a/src/main/scala/rules/HavocSupporter.scala b/src/main/scala/rules/HavocSupporter.scala index 808d54319..5da1e9ee4 100644 --- a/src/main/scala/rules/HavocSupporter.scala +++ b/src/main/scala/rules/HavocSupporter.scala @@ -7,7 +7,7 @@ package viper.silicon.rules import viper.silicon.debugger.DebugExp -import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfoes} +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfos import viper.silicon.interfaces.VerificationResult import viper.silicon.rules.evaluator.{eval, evalQuantified, evals} import viper.silicon.state._ @@ -35,7 +35,7 @@ object havocSupporter extends SymbolicExecutionRules { def execHavoc(havoc: ast.Quasihavoc, v: Verifier, s: State, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -44,15 +44,15 @@ object havocSupporter extends SymbolicExecutionRules { // If there is no havoc condition, use True as the condition val lhsExpr = havoc.lhs.getOrElse(ast.TrueLit()(havoc.pos)) - eval(s, lhsExpr, pve, v, analysisInfoes)((s0, lhsTerm, _, v0) => { - evals(s0, havoc.exp.args(s0.program), _ => pve, v0, analysisInfoes)((s1, tRcvrs, _, v1) => { + eval(s, lhsExpr, pve, v, analysisInfos)((s0, lhsTerm, _, v0) => { + evals(s0, havoc.exp.args(s0.program), _ => pve, v0, analysisInfos)((s1, tRcvrs, _, v1) => { val resource = havoc.exp.res(s1.program) // Call the havoc helper function, which returns a new heap, which is // partially havocked. Since we are executing a Havoc statement, we wrap // the HavocHelperData inside of a HavocOneData case (as opposed to HavocAllData). val condInfo = HavocOneData(tRcvrs) - val newHeap = v1.heapSupporter.havocResource(s1, lhsTerm, resource, condInfo, v1, analysisInfoes) + val newHeap = v1.heapSupporter.havocResource(s1, lhsTerm, resource, condInfo, v1, analysisInfos) Q(s1.copy(h = newHeap), v1) }) @@ -73,7 +73,7 @@ object havocSupporter extends SymbolicExecutionRules { def execHavocall(havocall: ast.Quasihavocall, v: Verifier, s: State, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -99,7 +99,7 @@ object havocSupporter extends SymbolicExecutionRules { name = qid, pve = pve, v = v, - analysisInfoes = analysisInfoes) + analysisInfos = analysisInfos) { case (s1, tVars, eVars, Seq(tCond), _, Some((tArgs, eArgs, Seq(), _, _)), v1) => // Seq() represents an empty list of Triggers @@ -123,11 +123,11 @@ object havocSupporter extends SymbolicExecutionRules { val notInjectiveReason = QuasihavocallNotInjective(havocall) val comment = "QP receiver injectivity check is well-defined" val injectivityDebugExp = Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)) - v.decider.assume(FunctionPreconditionTransformer.transform(receiverInjectivityCheck, s.program), injectivityDebugExp, analysisInfoes) - v.decider.assert(receiverInjectivityCheck, analysisInfoes) { + v.decider.assume(FunctionPreconditionTransformer.transform(receiverInjectivityCheck, s.program), injectivityDebugExp, analysisInfos) + v.decider.assert(receiverInjectivityCheck, analysisInfos) { case false => val failure = createFailure(pve dueTo notInjectiveReason, v, s1, receiverInjectivityCheck, "QP receiver injective") - if(s1.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(receiverInjectivityCheck, analysisInfoes, v.reportFurtherErrors()) + if(s1.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(receiverInjectivityCheck, analysisInfos, v.reportFurtherErrors()) if(s1.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s1, v1) else failure case true => // Generate the inverse axioms @@ -148,13 +148,13 @@ object havocSupporter extends SymbolicExecutionRules { ) val comment = "Definitional axioms for havocall inverse functions" v.decider.prover.comment(comment) - v.decider.assume(inverseFunctions.definitionalAxioms, Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, analysisInfoes=analysisInfoes) + v.decider.assume(inverseFunctions.definitionalAxioms, Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, analysisInfos=analysisInfos) // Call the havoc helper function, which returns a new heap, which is // partially havocked. Since we are executing a Havocall statement, we wrap // the HavocHelperData inside of a HavocAllData case. val condInfo = HavocallData(inverseFunctions, codomainQVars, imagesOfCodomain) - val newHeap = v1.heapSupporter.havocResource(s1, tCond, resource, condInfo, v1, analysisInfoes) + val newHeap = v1.heapSupporter.havocResource(s1, tCond, resource, condInfo, v1, analysisInfos) Q(s1.copy(h = newHeap), v1) } diff --git a/src/main/scala/rules/HeapSupporter.scala b/src/main/scala/rules/HeapSupporter.scala index 62254f897..131ab1f74 100644 --- a/src/main/scala/rules/HeapSupporter.scala +++ b/src/main/scala/rules/HeapSupporter.scala @@ -39,7 +39,7 @@ trait HeapSupportRules extends SymbolicExecutionRules { eRcvr: Option[ast.Exp], ve: VerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Term, Verifier) => VerificationResult) : VerificationResult @@ -50,7 +50,7 @@ trait HeapSupportRules extends SymbolicExecutionRules { tArgs: Seq[Term], eArgs: Option[Seq[ast.Exp]], v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Term, Verifier) => VerificationResult): VerificationResult def isPossibleTrigger(s: State, fa: ast.FieldAccess): Boolean @@ -63,7 +63,7 @@ trait HeapSupportRules extends SymbolicExecutionRules { eRhsNew: Option[ast.Exp], pve: PartialVerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Verifier) => VerificationResult) : VerificationResult @@ -72,7 +72,7 @@ trait HeapSupportRules extends SymbolicExecutionRules { tArgs: Seq[Term], eArgs: Option[Seq[ast.Exp]], v: Verifier, - analysisInfoes: DependencyAnalysisInfoes): State + analysisInfos: DependencyAnalysisInfos): State def consumeSingle(s: State, h: Heap, @@ -84,7 +84,7 @@ trait HeapSupportRules extends SymbolicExecutionRules { returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult): VerificationResult def consumeQuantified(s: State, @@ -113,7 +113,7 @@ trait HeapSupportRules extends SymbolicExecutionRules { notInjectiveReason: => ErrorReason, insufficientPermissionReason: => ErrorReason, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult): VerificationResult def produceSingle(s: State, @@ -127,7 +127,7 @@ trait HeapSupportRules extends SymbolicExecutionRules { pve: PartialVerificationError, mergeAndTrigger: Boolean, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Verifier) => VerificationResult): VerificationResult def produceQuantified(s: State, @@ -155,7 +155,7 @@ trait HeapSupportRules extends SymbolicExecutionRules { negativePermissionReason: => ErrorReason, notInjectiveReason: => ErrorReason, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Verifier) => VerificationResult): VerificationResult def havocResource(s: State, @@ -163,7 +163,7 @@ trait HeapSupportRules extends SymbolicExecutionRules { resource: ast.Resource, condInfo: HavocHelperData, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes): Heap + analysisInfos: DependencyAnalysisInfos): Heap def collectForPermConditions(s: State, resource: ast.Resource, @@ -188,12 +188,12 @@ class DefaultHeapSupportRules extends HeapSupportRules { eRhsNew: Option[ast.Exp], pve: PartialVerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { if(v.decider.isPathInfeasible){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfoes) - v.decider.dependencyAnalyzer.addAssumption(False, analysisInfoes) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfos) + v.decider.dependencyAnalyzer.addAssumption(False, analysisInfos) return Q(s, v) } @@ -203,10 +203,10 @@ class DefaultHeapSupportRules extends HeapSupportRules { val (relevantChunks, otherChunks) = quantifiedChunkSupporter.splitHeap[QuantifiedFieldChunk](s.h, BasicChunkIdentifier(field.name)) val hints = quantifiedChunkSupporter.extractHints(None, Seq(tRcvr)) - val chunkOrderHeuristics = quantifiedChunkSupporter.singleReceiverChunkOrderHeuristic(Seq(tRcvr), hints, v, analysisInfoes) - val s2 = triggerResourceIfNeeded(s, ass.lhs, Seq(tRcvr), eRcvrNew.map(Seq(_)), v, analysisInfoes) + val chunkOrderHeuristics = quantifiedChunkSupporter.singleReceiverChunkOrderHeuristic(Seq(tRcvr), hints, v, analysisInfos) + val s2 = triggerResourceIfNeeded(s, ass.lhs, Seq(tRcvr), eRcvrNew.map(Seq(_)), v, analysisInfos) v.decider.clearModel() - val lhsSourceInfoes = analysisInfoes.withMergeInfo(SimpleDependencyAnalysisMerge(AnalysisSourceInfo.createAnalysisSourceInfo(ass.lhs))) // splitting lhs and rhs to make permission flow analysis more precise + val lhsSourceInfos = analysisInfos.withMergeInfo(SimpleDependencyAnalysisMerge(AnalysisSourceInfo.createAnalysisSourceInfo(ass.lhs))) // splitting lhs and rhs to make permission flow analysis more precise val result = quantifiedChunkSupporter.removePermissions( s2, relevantChunks, @@ -220,7 +220,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { Option.when(withExp)(ast.FullPerm()()), chunkOrderHeuristics, v, - lhsSourceInfoes + lhsSourceInfos ) result match { case (Complete(), s3, remainingChunks) => @@ -228,12 +228,12 @@ class DefaultHeapSupportRules extends HeapSupportRules { val (sm, smValueDef) = quantifiedChunkSupporter.singletonSnapshotMap(s3, field, Seq(tRcvr), tRhs, v) v.decider.prover.comment("Definitional axioms for singleton-FVF's value") val debugExp = Option.when(withExp)(DebugExp.createInstance("Definitional axioms for singleton-FVF's value", isInternal_ = true)) - v.decider.assumeDefinition(smValueDef, debugExp, analysisInfoes) + v.decider.assumeDefinition(smValueDef, debugExp, analysisInfos) val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(Seq(`?r`), Option.when(withExp)(Seq(ast.LocalVarDecl("r", ast.Ref)(ass.pos, ass.info, ass.errT))), - field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), sm, s.program, v, lhsSourceInfoes.withDependencyType(DependencyType.Internal), isExhale=false) + field, Seq(tRcvr), Option.when(withExp)(Seq(eRcvrNew.get)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), sm, s.program, v, lhsSourceInfos.withDependencyType(DependencyType.Internal), isExhale=false) if (s3.heapDependentTriggers.contains(field)) { val debugExp2 = Option.when(withExp)(DebugExp.createInstance(s"FieldTrigger(${eRcvrNew.toString}.${field.name})")) - v.decider.assume(FieldTrigger(field.name, sm, tRcvr), debugExp2, lhsSourceInfoes.withDependencyType(DependencyType.Trigger)) + v.decider.assume(FieldTrigger(field.name, sm, tRcvr), debugExp2, lhsSourceInfos.withDependencyType(DependencyType.Trigger)) } val s4 = s3.copy(h = h3 + ch) val (debugHeapName, _) = v.getDebugOldLabel(s4, ass.lhs.pos) @@ -241,15 +241,15 @@ class DefaultHeapSupportRules extends HeapSupportRules { Q(s5, v) case (Incomplete(_, _), s3, _) => val failure = createFailure(ve, v, s3, "sufficient permission") - if(s3.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfoes, v.reportFurtherErrors()) + if(s3.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfos, v.reportFurtherErrors()) if(s3.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s3, v) else failure } } else { val description = s"consume ${ass.pos}: $ass" - val lhsSourceInfoes = analysisInfoes.withMergeInfo(SimpleDependencyAnalysisMerge(AnalysisSourceInfo.createAnalysisSourceInfo(ass.lhs))) // splitting lhs and rhs to make permission flow analysis more precise - chunkSupporter.consume(s, s.h, field, Seq(tRcvr), eRcvrNew.map(Seq(_)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), false, ve, v, description, lhsSourceInfoes)((s3, h3, _, v3) => { + val lhsSourceInfos = analysisInfos.withMergeInfo(SimpleDependencyAnalysisMerge(AnalysisSourceInfo.createAnalysisSourceInfo(ass.lhs))) // splitting lhs and rhs to make permission flow analysis more precise + chunkSupporter.consume(s, s.h, field, Seq(tRcvr), eRcvrNew.map(Seq(_)), FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), false, ve, v, description, lhsSourceInfos)((s3, h3, _, v3) => { val id = BasicChunkIdentifier(field.name) - val newChunk = BasicChunk.apply(FieldID, id, Seq(tRcvr), eRcvrNew.map(Seq(_)), tRhs, eRhsNew, FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), v3.decider.getAnalysisInfo(lhsSourceInfoes.withDependencyType(DependencyType.Internal))) + val newChunk = BasicChunk.apply(FieldID, id, Seq(tRcvr), eRcvrNew.map(Seq(_)), tRhs, eRhsNew, FullPerm, Option.when(withExp)(ast.FullPerm()(ass.pos, ass.info, ass.errT)), v3.decider.getAnalysisInfo(lhsSourceInfos.withDependencyType(DependencyType.Internal))) chunkSupporter.produce(s3, h3, newChunk, v3)((s4, h4, v4) => { val s5 = s4.copy(h = h4) val (debugHeapName, _) = v4.getDebugOldLabel(s5, ass.lhs.pos) @@ -267,11 +267,11 @@ class DefaultHeapSupportRules extends HeapSupportRules { tArgs: Seq[Term], eArgs: Option[Seq[ast.Exp]], v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Term, Verifier) => VerificationResult): VerificationResult = { if(v.decider.isPathInfeasible){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfoes) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfos) return Q(s, NoPerm, v) } @@ -300,7 +300,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { case w: ast.MagicWand => MagicWandIdentifier(w, s2.program).toString } DebugExp.createInstance(s"Resource trigger(${name}($argsString))", isInternal_ = true) - }), analysisInfoes.withDependencyType(DependencyType.Trigger)) + }), analysisInfos.withDependencyType(DependencyType.Trigger)) } val currentPermAmount = ResourcePermissionLookup(res, pmDef.pm, tArgs, s2.program) @@ -310,7 +310,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { v.decider.prover.comment(s"perm($resAcc) ~~> assume upper permission bound") val (debugHeapName, debugLabel) = v.getDebugOldLabel(s2, resAcc.pos, Some(h)) val exp = Option.when(withExp)(ast.PermLeCmp(ast.DebugLabelledOld(ast.CurrentPerm(resAcc)(), debugLabel)(), ast.FullPerm()())()) - v.decider.assume(PermAtMost(currentPermAmount, FullPerm), exp, exp.map(s2.substituteVarsInExp(_)), analysisInfoes.withDependencyType(DependencyType.Internal)) + v.decider.assume(PermAtMost(currentPermAmount, FullPerm), exp, exp.map(s2.substituteVarsInExp(_)), analysisInfos.withDependencyType(DependencyType.Internal)) val s3 = if (Verifier.config.enableDebugging()) s2.copy(oldHeaps = s2.oldHeaps + (debugHeapName -> h)) else s2 s3 case _ => s2 @@ -335,11 +335,11 @@ class DefaultHeapSupportRules extends HeapSupportRules { eRcvr: Option[ast.Exp], ve: VerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Term, Verifier) => VerificationResult) : VerificationResult = { if(v.decider.isPathInfeasible){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfoes) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfos) val sort = v.symbolConverter.toSort(fa.field.typ) val newVar = v.decider.fresh(sort, None) // just make sure the returned term typechecks @@ -359,11 +359,11 @@ class DefaultHeapSupportRules extends HeapSupportRules { * quantifier in whose body field 'fa.field' was accessed) * which is protected by a trigger term that we currently don't have. */ - v.decider.assume(And(fvfDef.valueDefinitions), Option.when(withExp)(DebugExp.createInstance("Value definitions", isInternal_ = true)), analysisInfoes.withDependencyType(DependencyType.Internal)) + v.decider.assume(And(fvfDef.valueDefinitions), Option.when(withExp)(DebugExp.createInstance("Value definitions", isInternal_ = true)), analysisInfos.withDependencyType(DependencyType.Internal)) if (s.heapDependentTriggers.contains(fa.field)) { val trigger = FieldTrigger(fa.field.name, fvfDef.sm, tRcvr) val triggerExp = Option.when(withExp)(DebugExp.createInstance(s"FieldTrigger(${eRcvr.toString()}.${fa.field.name})")) - v.decider.assume(trigger, triggerExp, analysisInfoes.withDependencyType(DependencyType.Trigger)) + v.decider.assume(trigger, triggerExp, analysisInfos.withDependencyType(DependencyType.Trigger)) } if (s.triggerExp) { val fvfLookup = Lookup(fa.field.name, fvfDef.sm, tRcvr) @@ -372,10 +372,10 @@ class DefaultHeapSupportRules extends HeapSupportRules { Q(s2, fvfLookup, v) } else { val toAssert = IsPositive(totalPermissions.replace(`?r`, tRcvr)) - v.decider.assert(toAssert, analysisInfoes) { + v.decider.assert(toAssert, analysisInfos) { case false => val failure = createFailure(ve, v, s, toAssert, Option.when(withExp)(perms.IsPositive(ast.CurrentPerm(fa)())())) - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(toAssert, analysisInfoes, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(toAssert, analysisInfos, v.reportFurtherErrors()) val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(fa.field, s, v), Option.when(withExp)(PUnknown())) if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, snap, v) else failure case true => @@ -410,7 +410,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { if (s2.heapDependentTriggers.contains(fa.field)) { val trigger = FieldTrigger(fa.field.name, sm, tRcvr) val triggerExp = Option.when(withExp)(DebugExp.createInstance(s"FieldTrigger(${eRcvr.toString()}.${fa.field.name})")) - v.decider.assume(trigger, triggerExp, analysisInfoes.withDependencyType(DependencyType.Trigger)) + v.decider.assume(trigger, triggerExp, analysisInfos.withDependencyType(DependencyType.Trigger)) } val (permCheck, permCheckExp, s3) = if (s2.triggerExp) { @@ -426,10 +426,10 @@ class DefaultHeapSupportRules extends HeapSupportRules { } (Implies(lhs, IsPositive(totalPerms)), Option.when(withExp)(perms.IsPositive(ast.CurrentPerm(fa)(fa.pos, fa.info, fa.errT))(fa.pos, fa.info, fa.errT)), s3) } - v.decider.assert(permCheck,analysisInfoes) { + v.decider.assert(permCheck,analysisInfos) { case false => val failure = createFailure(ve, v, s3, permCheck, permCheckExp) - if(s3.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(permCheck, analysisInfoes, v.reportFurtherErrors()) + if(s3.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(permCheck, analysisInfos, v.reportFurtherErrors()) val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(fa.field, s3, v), Option.when(withExp)(PUnknown())) if(s3.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s3, snap, v) else failure case true => @@ -442,7 +442,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { } } else { val resource = fa.res(s.program) - chunkSupporter.lookup(s, s.h, resource, Seq(tRcvr), Option.when(withExp)(Seq(eRcvr.get)), ve, v, analysisInfoes)((s2, h2, tSnap, v2) => { + chunkSupporter.lookup(s, s.h, resource, Seq(tRcvr), Option.when(withExp)(Seq(eRcvr.get)), ve, v, analysisInfos)((s2, h2, tSnap, v2) => { val fr = s2.functionRecorder.recordSnapshot(fa, v2.decider.pcs.branchConditions, tSnap) val s3 = s2.copy(h = h2, functionRecorder = fr) Q(s3, tSnap, v) @@ -455,7 +455,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { tArgs: Seq[Term], eArgs: Option[Seq[ast.Exp]], v: Verifier, - analysisInfoes: DependencyAnalysisInfoes): State = { + analysisInfos: DependencyAnalysisInfos): State = { if (s.isUsedAsTrigger(resAcc.res(s.program))) { val resource = resAcc.res(s.program) val chunkId = ChunkIdentifier(resource, s.program) @@ -477,7 +477,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { val eArgsStr = eArgs.mkString(", ") val debugExp = Option.when(withExp)(DebugExp.createInstance(Some(s"Resource trigger(${name}($eArgsStr))"), Some(resAcc), Some(resAcc), None, isInternal_ = true, InsertionOrderedSet.empty)) - v.decider.assume(trigger(smDef1.sm), debugExp, analysisInfoes.withDependencyType(DependencyType.Trigger)) + v.decider.assume(trigger(smDef1.sm), debugExp, analysisInfos.withDependencyType(DependencyType.Trigger)) s.copy(smCache = smCache1, functionRecorder = s.functionRecorder.recordFvfAndDomain(smDef1)) } else { s @@ -495,7 +495,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { pve: PartialVerificationError, mergeAndTrigger: Boolean, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { val useQPs = s.isQuantifiedResource(resource) if (useQPs) { @@ -503,17 +503,17 @@ class DefaultHeapSupportRules extends HeapSupportRules { val tFormalArgs = s.getFormalArgVars(resource, v) val eFormalArgs = Option.when(withExp)(s.getFormalArgDecls(resource)) quantifiedChunkSupporter.produceSingleLocation( - s, resource, tFormalArgs, eFormalArgs, tArgs, eArgs, tSnap, tPerm, ePerm, trigger, mergeAndTrigger, v, analysisInfoes)(Q) + s, resource, tFormalArgs, eFormalArgs, tArgs, eArgs, tSnap, tPerm, ePerm, trigger, mergeAndTrigger, v, analysisInfos)(Q) } else { resource match { case w: ast.MagicWand => - magicWandSupporter.createChunk(s, w, MagicWandSnapshot(tSnap), pve, v, analysisInfoes)((s2, chWand, v2) => + magicWandSupporter.createChunk(s, w, MagicWandSnapshot(tSnap), pve, v, analysisInfos)((s2, chWand, v2) => chunkSupporter.produce(s2, s2.h, chWand, v2)((s3, h3, v3) => Q(s3.copy(h = h3), v3))) case _ => val chunkId = ChunkIdentifier(resource, s.program) val (resId, snap1) = if (resource.isInstanceOf[ast.Field]) (FieldID, tSnap) else (PredicateID, tSnap.convert(sorts.Snap)) - val ch = BasicChunk.apply(resId, chunkId.asInstanceOf[BasicChunkIdentifier], tArgs, eArgs, snap1, eSnap, tPerm, ePerm, v.decider.getAnalysisInfo(analysisInfoes)) + val ch = BasicChunk.apply(resId, chunkId.asInstanceOf[BasicChunkIdentifier], tArgs, eArgs, snap1, eSnap, tPerm, ePerm, v.decider.getAnalysisInfo(analysisInfos)) if (mergeAndTrigger) { chunkSupporter.produce(s, s.h, ch, v)((s2, h2, v2) => { if (resource.isInstanceOf[ast.Predicate] && Verifier.config.enablePredicateTriggersOnInhale() && s2.functionRecorder == NoopFunctionRecorder @@ -521,7 +521,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { val predicate = resource.asInstanceOf[ast.Predicate] val argsString = eArgs.mkString(", ") val debugExp = Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}($argsString))", isInternal_ = true)) - v2.decider.assume(App(s2.predicateData(predicate.name).triggerFunction, snap1 +: tArgs), debugExp, analysisInfoes.withDependencyType(DependencyType.Trigger)) + v2.decider.assume(App(s2.predicateData(predicate.name).triggerFunction, snap1 +: tArgs), debugExp, analysisInfos.withDependencyType(DependencyType.Trigger)) } Q(s2.copy(h = h2), v2) }) @@ -542,10 +542,10 @@ class DefaultHeapSupportRules extends HeapSupportRules { returnSnap: Boolean, pve: PartialVerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult): VerificationResult = { if(v.decider.isPathInfeasible){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfoes) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfos) return Q(s, h, Some(Unit), v) } @@ -555,14 +555,14 @@ class DefaultHeapSupportRules extends HeapSupportRules { val tFormalArgs = s.getFormalArgVars(resource, v) val eFormalArgs = Option.when(withExp)(s.getFormalArgDecls(resource)) quantifiedChunkSupporter.consumeSingleLocation( - s, h, tFormalArgs, eFormalArgs, tArgs, eArgs, resAcc, tPerm, ePerm, returnSnap, None, pve, v, analysisInfoes)(Q) + s, h, tFormalArgs, eFormalArgs, tArgs, eArgs, resAcc, tPerm, ePerm, returnSnap, None, pve, v, analysisInfos)(Q) } else { val ve = resAcc match { case l: ast.LocationAccess => pve dueTo InsufficientPermission(l) case w: ast.MagicWand => pve dueTo MagicWandChunkNotFound(w) } val description = s"consume ${resAcc.pos}: $resAcc" - chunkSupporter.consume(s, h, resource, tArgs, eArgs, tPerm, ePerm, returnSnap, ve, v, description, analysisInfoes)(Q) + chunkSupporter.consume(s, h, resource, tArgs, eArgs, tPerm, ePerm, returnSnap, ve, v, description, analysisInfos)(Q) } } @@ -591,7 +591,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { negativePermissionReason: => ErrorReason, notInjectiveReason: => ErrorReason, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Verifier) => VerificationResult): VerificationResult = { val tSnap = resource match { case f: ast.Field => @@ -628,7 +628,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { negativePermissionReason, notInjectiveReason, v, - analysisInfoes + analysisInfos )(Q) } @@ -658,10 +658,10 @@ class DefaultHeapSupportRules extends HeapSupportRules { notInjectiveReason: => ErrorReason, insufficientPermissionReason: => ErrorReason, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult): VerificationResult = { if(v.decider.isPathInfeasible){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfoes) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfos) return Q(s, h, Some(Unit), v) } @@ -692,7 +692,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { notInjectiveReason, insufficientPermissionReason, v, - analysisInfoes + analysisInfos )(Q) } @@ -701,11 +701,11 @@ class DefaultHeapSupportRules extends HeapSupportRules { resource: ast.Resource, condInfo: HavocHelperData, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes): Heap = { + analysisInfos: DependencyAnalysisInfos): Heap = { if (s.isQuantifiedResource(resource)) { - havocQuantifiedResource(s, lhs, resource, condInfo, v, analysisInfoes) + havocQuantifiedResource(s, lhs, resource, condInfo, v, analysisInfos) } else { - havocNonQuantifiedResource(s, lhs, resource, condInfo, v, analysisInfoes) + havocNonQuantifiedResource(s, lhs, resource, condInfo, v, analysisInfos) } } @@ -727,7 +727,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { resource: ast.Resource, condInfo: HavocHelperData, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) : Heap = { val id = ChunkIdentifier(resource, s.program) @@ -738,12 +738,12 @@ class DefaultHeapSupportRules extends HeapSupportRules { val havockedSnap = v.decider.fresh("mwsf", sorts.MagicWandSnapFunction, Option.when(withExp)(PUnknown())) val cond = replacementCond(lhs, ch.args, condInfo) val magicWandSnapshot = MagicWandSnapshot(Ite(cond, havockedSnap, ch.snap.mwsf)) - NonQuantifiedChunk.withSnap(ch, magicWandSnapshot, None, v.decider.getAnalysisInfo(analysisInfoes)) + NonQuantifiedChunk.withSnap(ch, magicWandSnapshot, None, v.decider.getAnalysisInfo(analysisInfos)) case ch => val havockedSnap = freshSnap(ch.snap.sort, v) val cond = replacementCond(lhs, ch.args, condInfo) - NonQuantifiedChunk.withSnap(ch, Ite(cond, havockedSnap, ch.snap), None, v.decider.getAnalysisInfo(analysisInfoes)) + NonQuantifiedChunk.withSnap(ch, Ite(cond, havockedSnap, ch.snap), None, v.decider.getAnalysisInfo(analysisInfos)) } Heap(otherChunks ++ newChunks) } @@ -770,7 +770,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { resource: ast.Resource, condInfo: HavocHelperData, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) : Heap = { + analysisInfos: DependencyAnalysisInfos) : Heap = { // Quantified field chunks are of the form R(r; sm, pm). // Conceptually, quantified predicate/wand chunks look like R(r1, ..., rn; sm, pm). @@ -823,9 +823,9 @@ class DefaultHeapSupportRules extends HeapSupportRules { v.decider.prover.comment("axiomatized snapshot map after havoc") val debugExp = Option.when(withExp)(DebugExp.createInstance("havoc new axiom", isInternal_ = true)) - v.decider.assume(newAxiom, debugExp, analysisInfoes) + v.decider.assume(newAxiom, debugExp, analysisInfos) - QuantifiedChunk.withSnapshotMap(ch, newSm, v.decider.getAnalysisInfo(analysisInfoes)) + QuantifiedChunk.withSnapshotMap(ch, newSm, v.decider.getAnalysisInfo(analysisInfos)) } Heap(newChunks ++ otherChunks) } diff --git a/src/main/scala/rules/Joiner.scala b/src/main/scala/rules/Joiner.scala index 9bc023266..8f87c83b0 100644 --- a/src/main/scala/rules/Joiner.scala +++ b/src/main/scala/rules/Joiner.scala @@ -9,7 +9,7 @@ package viper.silicon.rules import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.decider.RecordedPathConditions -import viper.silicon.dependencyAnalysis.{AnalysisInfo, DependencyAnalysisInfoes} +import viper.silicon.dependencyAnalysis.{AnalysisInfo, DependencyAnalysisInfos} import viper.silicon.interfaces.{Success, VerificationResult} import viper.silicon.logger.records.structural.JoiningRecord import viper.silicon.state.State @@ -22,24 +22,24 @@ import viper.silver.dependencyAnalysis.{DependencyType, StringAnalysisSourceInfo case class JoinDataEntry[D](s: State, data: D, pathConditions: RecordedPathConditions) { - private val analysisInfoes = DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes.withSource(StringAnalysisSourceInfo("merge", NoPosition)).withDependencyType(DependencyType.Internal) // TODO ake + private val analysisInfos = DependencyAnalysisInfos.DefaultDependencyAnalysisInfos.withSource(StringAnalysisSourceInfo("merge", NoPosition)).withDependencyType(DependencyType.Internal) // TODO ake // Instead of merging states by calling State.merge, // we can directly merge JoinDataEntries to obtain new States, // and the join data entries themselves provide information about the path conditions to State.merge. def pathConditionAwareMerge(other: JoinDataEntry[D], v: Verifier): State = { - val res = State.merge(this.s, this.pathConditions, other.s, other.pathConditions, AnalysisInfo(v.decider, v.decider.dependencyAnalyzer, analysisInfoes)) + val res = State.merge(this.s, this.pathConditions, other.s, other.pathConditions, AnalysisInfo(v.decider, v.decider.dependencyAnalyzer, analysisInfos)) v.stateConsolidator(s).consolidate(res, v) } def pathConditionAwareMergeWithoutConsolidation(other: JoinDataEntry[D], v: Verifier): State = { - State.merge(this.s, this.pathConditions, other.s, other.pathConditions, AnalysisInfo(v.decider, v.decider.dependencyAnalyzer, analysisInfoes)) + State.merge(this.s, this.pathConditions, other.s, other.pathConditions, AnalysisInfo(v.decider, v.decider.dependencyAnalyzer, analysisInfos)) } } trait JoiningRules extends SymbolicExecutionRules { - def join[D, JD](s: State, v: Verifier, analysisInfoes: DependencyAnalysisInfoes, resetState: Boolean = true) + def join[D, JD](s: State, v: Verifier, analysisInfos: DependencyAnalysisInfos, resetState: Boolean = true) (block: (State, Verifier, (State, D, Verifier) => VerificationResult) => VerificationResult) (merge: Seq[JoinDataEntry[D]] => (State, JD)) (Q: (State, JD, Verifier) => VerificationResult) @@ -47,7 +47,7 @@ trait JoiningRules extends SymbolicExecutionRules { } object joiner extends JoiningRules { - def join[D, JD](s: State, v: Verifier, analysisInfoes: DependencyAnalysisInfoes, resetState: Boolean = true) + def join[D, JD](s: State, v: Verifier, analysisInfos: DependencyAnalysisInfos, resetState: Boolean = true) (block: (State, Verifier, (State, D, Verifier) => VerificationResult) => VerificationResult) (merge: Seq[JoinDataEntry[D]] => (State, JD)) (Q: (State, JD, Verifier) => VerificationResult) @@ -108,13 +108,13 @@ object joiner extends JoiningRules { val pcsExp = Option.when(withExp)(entry.pathConditions.conditionalizedExp) val comment = "Joined path conditions" v.decider.prover.comment(comment) - v.decider.assume(pcs, Option.when(withExp)(DebugExp.createInstance(comment, InsertionOrderedSet(pcsExp.get))), enforceAssumption = false, analysisInfoes) + v.decider.assume(pcs, Option.when(withExp)(DebugExp.createInstance(comment, InsertionOrderedSet(pcsExp.get))), enforceAssumption = false, analysisInfos) feasibleBranches = And(entry.pathConditions.branchConditions) :: feasibleBranches feasibleBranchesExp = feasibleBranchesExp.map(fbe => BigAnd(entry.pathConditions.branchConditionExps.map(_._1)) :: fbe) feasibleBranchesExpNew = feasibleBranchesExpNew.map(fbe => BigAnd(entry.pathConditions.branchConditionExps.map(_._2.get)) :: fbe) }) // Assume we are in a feasible branch - v.decider.assume(Or(feasibleBranches), Option.when(withExp)(DebugExp.createInstance(Some("Feasible Branches"), feasibleBranchesExp.map(BigOr(_)), feasibleBranchesExpNew.map(BigOr(_)), InsertionOrderedSet.empty)), analysisInfoes) + v.decider.assume(Or(feasibleBranches), Option.when(withExp)(DebugExp.createInstance(Some("Feasible Branches"), feasibleBranchesExp.map(BigOr(_)), feasibleBranchesExpNew.map(BigOr(_)), InsertionOrderedSet.empty)), analysisInfos) Q(sJoined, dataJoined, v) } } diff --git a/src/main/scala/rules/LetSupporter.scala b/src/main/scala/rules/LetSupporter.scala index 5c29016e4..e04af3c73 100644 --- a/src/main/scala/rules/LetSupporter.scala +++ b/src/main/scala/rules/LetSupporter.scala @@ -6,7 +6,7 @@ package viper.silicon.rules -import viper.silicon.dependencyAnalysis.DependencyAnalysisInfoes +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfos import viper.silicon.interfaces.VerificationResult import viper.silicon.state.terms.Term import viper.silicon.state.{State, Store} @@ -17,13 +17,13 @@ import viper.silver.verifier.PartialVerificationError trait LetSupportRules extends SymbolicExecutionRules { def handle[E <: ast.Exp] (s: State, e: ast.Exp, pve: PartialVerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Store, E, Verifier) => VerificationResult) : VerificationResult def handle[E <: ast.Exp] (s: State, let: ast.Let, pve: PartialVerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Store, E, Verifier) => VerificationResult) : VerificationResult } @@ -33,23 +33,23 @@ object letSupporter extends LetSupportRules { def handle[E <: ast.Exp] (s: State, e: ast.Exp, pve: PartialVerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Store, E, Verifier) => VerificationResult) : VerificationResult = { e match { - case let: ast.Let => handle(s, Nil, let, pve, v, analysisInfoes)(Q) + case let: ast.Let => handle(s, Nil, let, pve, v, analysisInfos)(Q) case _ => Q(s, Store(), e.asInstanceOf[E], v) } } def handle[E <: ast.Exp] (s: State, let: ast.Let, pve: PartialVerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Store, E, Verifier) => VerificationResult) : VerificationResult = { - handle(s, Nil, let, pve, v, analysisInfoes)(Q) + handle(s, Nil, let, pve, v, analysisInfos)(Q) } private def handle[E <: ast.Exp] @@ -58,17 +58,17 @@ object letSupporter extends LetSupportRules { let: ast.Let, pve: PartialVerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Store, E, Verifier) => VerificationResult) : VerificationResult = { val ast.Let(x, exp, body) = let - eval(s, exp, pve, v, analysisInfoes)((s1, t, expNew, v1) => { + eval(s, exp, pve, v, analysisInfos)((s1, t, expNew, v1) => { val bindings1 = bindings :+ (x.localVar, (t, expNew)) val s2 = s1.copy(s1.g + (x.localVar, (t, expNew))) body match { - case nestedLet: ast.Let => handle(s2, bindings1, nestedLet, pve, v1, analysisInfoes)(Q) + case nestedLet: ast.Let => handle(s2, bindings1, nestedLet, pve, v1, analysisInfos)(Q) case _ => Q(s2, Store(bindings1), body.asInstanceOf[E], v1)}}) } } diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index f21ece46c..754cfaaf7 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -10,7 +10,7 @@ import viper.silicon._ import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.decider.RecordedPathConditions -import viper.silicon.dependencyAnalysis.DependencyAnalysisInfoes +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfos import viper.silicon.interfaces._ import viper.silicon.interfaces.state._ import viper.silicon.state._ @@ -92,12 +92,12 @@ object magicWandSupporter extends SymbolicExecutionRules { snap: MagicWandSnapshot, pve: PartialVerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, MagicWandChunk, Verifier) => VerificationResult) : VerificationResult = { - evaluateWandArguments(s, wand, pve, v, analysisInfoes)((s1, ts, esNew, v1) => { + evaluateWandArguments(s, wand, pve, v, analysisInfos)((s1, ts, esNew, v1) => { val newChunk = MagicWandChunk(MagicWandIdentifier(wand, s.program), s1.g.values, ts, esNew, snap, FullPerm, - Option.when(withExp)(ast.FullPerm()(wand.pos, wand.info, wand.errT)), v.decider.getAnalysisInfo(analysisInfoes)) + Option.when(withExp)(ast.FullPerm()(wand.pos, wand.info, wand.errT)), v.decider.getAnalysisInfo(analysisInfos)) Q(s1, newChunk, v1) }) } @@ -115,14 +115,14 @@ object magicWandSupporter extends SymbolicExecutionRules { * @param v Verifier instance * @return Fresh instance of [[viper.silicon.state.terms.MagicWandSnapshot]] */ - def createMagicWandSnapshot(abstractLhs: Var, rhsSnapshot: Term, v: Verifier, analysisInfoes: DependencyAnalysisInfoes): MagicWandSnapshot = { + def createMagicWandSnapshot(abstractLhs: Var, rhsSnapshot: Term, v: Verifier, analysisInfos: DependencyAnalysisInfos): MagicWandSnapshot = { val mwsf = v.decider.fresh("mwsf", sorts.MagicWandSnapFunction, Option.when(withExp)(PUnknown())) val magicWandSnapshot = MagicWandSnapshot(mwsf) v.decider.assumeDefinition(Forall( abstractLhs, MWSFLookup(mwsf, abstractLhs) === rhsSnapshot, Trigger(MWSFLookup(mwsf, abstractLhs)) - ), Option.when(withExp)(DebugExp.createInstance("Magic wand snapshot definition", isInternal_ = true)), analysisInfoes) + ), Option.when(withExp)(DebugExp.createInstance("Magic wand snapshot definition", isInternal_ = true)), analysisInfos) magicWandSnapshot } @@ -137,13 +137,13 @@ object magicWandSupporter extends SymbolicExecutionRules { wand: ast.MagicWand, pve: PartialVerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Seq[Term], Option[Seq[ast.Exp]], Verifier) => VerificationResult) : VerificationResult = { val s1 = s.copy(exhaleExt = false) val es = wand.subexpressionsToEvaluate(s.program) - evals(s1, es, _ => pve, v, analysisInfoes)((s2, ts, esNew, v1) => { + evals(s1, es, _ => pve, v, analysisInfos)((s2, ts, esNew, v1) => { Q(s2.copy(exhaleExt = s.exhaleExt), ts, esNew, v1) }) } @@ -156,12 +156,12 @@ object magicWandSupporter extends SymbolicExecutionRules { failure: Failure, qvars: Seq[Var], v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (consumeFunction: (State, Heap, Term, Option[ast.Exp], Verifier) => (ConsumptionResult, State, Heap, Option[CH])) (Q: (State, Stack[Heap], Stack[Option[CH]], Verifier) => VerificationResult) : VerificationResult = { - val initialConsumptionResult = ConsumptionResult(pLoss, pLossExp, qvars, v, Verifier.config.checkTimeout(), analysisInfoes) + val initialConsumptionResult = ConsumptionResult(pLoss, pLossExp, qvars, v, Verifier.config.checkTimeout(), analysisInfos) /* TODO: Introduce a dedicated timeout for the permission check performed by ConsumptionResult, * instead of using checkTimeout. Reason: checkTimeout is intended for checks that are * optimisations, e.g. detecting if a chunk provided no permissions or if a branch is @@ -187,7 +187,7 @@ object magicWandSupporter extends SymbolicExecutionRules { case (Some(ch1: QuantifiedBasicChunk), Some(ch2: QuantifiedBasicChunk)) => ch1.snapshotMap === ch2.snapshotMap case _ => True } - v.decider.assume(tEq, Option.when(withExp)(DebugExp.createInstance("Snapshots", isInternal_ = true)), analysisInfoes) + v.decider.assume(tEq, Option.when(withExp)(DebugExp.createInstance("Snapshots", isInternal_ = true)), analysisInfos) /* In the future it might be worth to recheck whether the permissions needed, in the case of * success being an instance of Incomplete, are zero. @@ -202,7 +202,7 @@ object magicWandSupporter extends SymbolicExecutionRules { * from heap, i.e. that tEq does not result in already having the required permissions before * consuming from heap. */ - if (v.decider.checkSmoke(analysisInfoes)) { + if (v.decider.checkSmoke(analysisInfos)) { (Complete(), sOut, h +: hps, cch +: cchs) } else { (success, sOut, h +: hps, cch +: cchs) @@ -214,7 +214,7 @@ object magicWandSupporter extends SymbolicExecutionRules { assert(consumedChunks.length == hs.length) Q(s1, heaps.reverse, consumedChunks.reverse, v) case Incomplete(_, _) => - if(s1.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfoes, v.reportFurtherErrors()) + if(s1.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfos, v.reportFurtherErrors()) if(s1.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s1, heaps.reverse, consumedChunks.reverse, v) else failure } } @@ -243,7 +243,7 @@ object magicWandSupporter extends SymbolicExecutionRules { proofScript: ast.Seqn, pve: PartialVerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Chunk, Verifier) => VerificationResult) : VerificationResult = { @@ -315,16 +315,16 @@ object magicWandSupporter extends SymbolicExecutionRules { freshSnapRoot: Var, snapRhs: Term, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) : VerificationResult = { val preMark = v.decider.setPathConditionMark() v.decider.prover.comment(s"Create MagicWandSnapFunction for wand $wand") - val wandSnapshot = this.createMagicWandSnapshot(freshSnapRoot, snapRhs, v, analysisInfoes) + val wandSnapshot = this.createMagicWandSnapshot(freshSnapRoot, snapRhs, v, analysisInfos) val bodyVars = wand.subexpressionsToEvaluate(s.program) - evals(s, bodyVars, _ => pve, v, analysisInfoes)((s2, tArgs, eArgsNew, v2) => { + evals(s, bodyVars, _ => pve, v, analysisInfos)((s2, tArgs, eArgsNew, v2) => { // Currently, the snapshot of a wand differs depending on whether it is a quantified magic wand or not. // Therefore, we have to keep the case distinction here and cannot leave everything but the chunk creation // to the HeapSupporter. @@ -335,14 +335,14 @@ object magicWandSupporter extends SymbolicExecutionRules { val (sm, smValueDef) = quantifiedChunkSupporter.singletonSnapshotMap(s2, wand, tArgs, snapshotTerm, v2) v2.decider.prover.comment("Definitional axioms for singleton-SM's value") val debugExp = Option.when(withExp)(DebugExp.createInstance("Definitional axioms for singleton-SM's value", true)) - v2.decider.assumeDefinition(smValueDef, debugExp, analysisInfoes) + v2.decider.assumeDefinition(smValueDef, debugExp, analysisInfos) val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(formalVars, formalVarExps, wand, tArgs, - eArgsNew, FullPerm, Option.when(withExp)(ast.FullPerm()()), sm, s.program, v, analysisInfoes, isExhale=false) + eArgsNew, FullPerm, Option.when(withExp)(ast.FullPerm()()), sm, s.program, v, analysisInfos, isExhale=false) val conservedPcs = s2.conservedPcs.head :+ v2.decider.pcs.after(preMark).definitionsOnly (s2, ch, conservedPcs.flatMap(_.conditionalized), Option.when(withExp)(conservedPcs.flatMap(_.conditionalizedExp)), v2) } else { val ch = MagicWandChunk.apply(MagicWandIdentifier(wand, s.program), s2.g.values, tArgs, eArgsNew, wandSnapshot, FullPerm, - Option.when(withExp)(ast.FullPerm()(wand.pos, wand.info, wand.errT)), v.decider.getAnalysisInfo(analysisInfoes)) + Option.when(withExp)(ast.FullPerm()(wand.pos, wand.info, wand.errT)), v.decider.getAnalysisInfo(analysisInfos)) val conservedPcs = s2.conservedPcs.head :+ v2.decider.pcs.after(preMark).definitionsOnly // Partition path conditions into a set which include the freshSnapRoot and those which do not val (pcsWithFreshSnapRoot, pcsWithoutFreshSnapRoot) = conservedPcs.flatMap(pcs => pcs.conditionalized).partition(_.contains(freshSnapRoot)) @@ -376,8 +376,8 @@ object magicWandSupporter extends SymbolicExecutionRules { val freshSnapRoot = freshSnap(sorts.Snap, v1) // Produce the wand's LHS. - val analysisInfoesLeft = analysisInfoes.withSource(ExpAnalysisSourceInfo(wand.left, wand.left.pos)) - produce(s1.copy(conservingSnapshotGeneration = true), toSf(freshSnapRoot), wand.left, pve, v1, analysisInfoesLeft)((sLhs, v2) => { + val analysisInfosLeft = analysisInfos.withSource(ExpAnalysisSourceInfo(wand.left, wand.left.pos)) + produce(s1.copy(conservingSnapshotGeneration = true), toSf(freshSnapRoot), wand.left, pve, v1, analysisInfosLeft)((sLhs, v2) => { val proofScriptCfg = proofScript.toCfg() val emptyHeap = v2.heapSupporter.getEmptyHeap(sLhs.program) @@ -413,10 +413,10 @@ object magicWandSupporter extends SymbolicExecutionRules { // This part indirectly calls the methods `this.transfer` and `this.consumeFromMultipleHeaps`. consume( proofScriptState.copy(oldHeaps = s2.oldHeaps, reserveCfgs = proofScriptState.reserveCfgs.tail), - wand.right, true, pve, proofScriptVerifier, analysisInfoes + wand.right, true, pve, proofScriptVerifier, analysisInfos )((s3, snapRhs, v3) => { analysisLabels = v.decider.pcs.after(prePackageMark).analysisLabels - createWandChunkAndRecordResults(s3.copy(exhaleExt = false, oldHeaps = s.oldHeaps), freshSnapRoot, snapRhs.get, v3, analysisInfoes) + createWandChunkAndRecordResults(s3.copy(exhaleExt = false, oldHeaps = s.oldHeaps), freshSnapRoot, snapRhs.get, v3, analysisInfos) }) }) }) @@ -428,11 +428,11 @@ object magicWandSupporter extends SymbolicExecutionRules { // Moreover, we need to set reserveHeaps to structurally match [State RHS] below. val emptyHeap = v.heapSupporter.getEmptyHeap(sEmp.program) val s1 = sEmp.copy(reserveHeaps = emptyHeap +: emptyHeap +: emptyHeap +: s.reserveHeaps.tail) - createWandChunkAndRecordResults(s1, freshSnap(sorts.Snap, v), freshSnap(sorts.Snap, v), v, analysisInfoes) + createWandChunkAndRecordResults(s1, freshSnap(sorts.Snap, v), freshSnap(sorts.Snap, v), v, analysisInfos) } // some of the analysis labels, introduced while verifying the package statement, might be needed later on -> reassume them - analysisLabels foreach (l => v.decider.assume(v.decider.wrapWithDependencyAnalysisLabel(l, Set.empty, Set(l)), None, analysisInfoes.withDependencyType(DependencyType.Internal))) + analysisLabels foreach (l => v.decider.assume(v.decider.wrapWithDependencyAnalysisLabel(l, Set.empty, Set(l)), None, analysisInfos.withDependencyType(DependencyType.Internal))) recordedBranches.foldLeft(tempResult)((prevRes, recordedState) => { prevRes && { @@ -447,10 +447,10 @@ object magicWandSupporter extends SymbolicExecutionRules { val exp = viper.silicon.utils.ast.BigAnd(branchConditionsExp.map(_._1)) val expNew = Option.when(withExp)(viper.silicon.utils.ast.BigAnd(branchConditionsExp.map(_._2.get))) // Set the branch conditions - v1.decider.setCurrentBranchCondition(And(branchConditions map (t => v1.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t)))), (exp, expNew), analysisInfoes) + v1.decider.setCurrentBranchCondition(And(branchConditions map (t => v1.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t)))), (exp, expNew), analysisInfos) // Recreate all path conditions in the Z3 proof script that we recorded for that branch - v1.decider.assume(conservedPcs._1 map (t => v1.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t))), conservedPcs._2, analysisInfoes.withDependencyType(DependencyType.Internal)) + v1.decider.assume(conservedPcs._1 map (t => v1.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t))), conservedPcs._2, analysisInfos.withDependencyType(DependencyType.Internal)) // Execute the continuation Q Q(s2, magicWandChunk, v1) @@ -473,13 +473,13 @@ object magicWandSupporter extends SymbolicExecutionRules { wand: ast.MagicWand, pve: PartialVerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { // Consume the magic wand instance "A --* B". - consume(s, wand, true, pve, v, analysisInfoes)((s1, snapWand, v1) => { + consume(s, wand, true, pve, v, analysisInfos)((s1, snapWand, v1) => { // Consume the wand's LHS "A". - consume(s1, wand.left, true, pve, v1, analysisInfoes)((s2, snapLhs, v2) => { + consume(s1, wand.left, true, pve, v1, analysisInfos)((s2, snapLhs, v2) => { /* It is assumed that snap and MagicWandSnapshot.abstractLhs are structurally the same. * Equating the two snapshots is sound iff a wand is applied only once. * The old solution in this case did use this assumption: @@ -496,13 +496,13 @@ object magicWandSupporter extends SymbolicExecutionRules { case SortWrapper(snapshot: MagicWandSnapshot, _) => snapshot.applyToMWSF(snapLhs.get) // Fallback solution for quantified magic wands case predicateLookup: PredicateLookup => - v2.decider.assume(snapLhs.get === First(snapWand.get), Option.when(withExp)(DebugExp.createInstance("Magic wand snapshot", isInternal_ = true)), analysisInfoes) + v2.decider.assume(snapLhs.get === First(snapWand.get), Option.when(withExp)(DebugExp.createInstance("Magic wand snapshot", isInternal_ = true)), analysisInfos) Second(predicateLookup) case _ => snapWand.get } // Produce the wand's RHS. - produce(s3.copy(conservingSnapshotGeneration = true), toSf(magicWandSnapshotLookup), wand.right, pve, v2, analysisInfoes)((s4, v3) => { + produce(s3.copy(conservingSnapshotGeneration = true), toSf(magicWandSnapshotLookup), wand.right, pve, v2, analysisInfos)((s4, v3) => { // Recreate old state without the magic wand, and the state with the oldHeap called lhs. val s5 = s4.copy(g = s1.g, conservingSnapshotGeneration = s3.conservingSnapshotGeneration) @@ -522,7 +522,7 @@ object magicWandSupporter extends SymbolicExecutionRules { failure: Failure, qvars: Seq[Var], v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (consumeFunction: (State, Heap, Term, Option[ast.Exp], Verifier) => (ConsumptionResult, State, Heap, Option[CH])) (Q: (State, Option[CH], Verifier) => VerificationResult) : VerificationResult = { @@ -539,13 +539,13 @@ object magicWandSupporter extends SymbolicExecutionRules { */ val preMark = v.decider.setPathConditionMark() executionFlowController.tryOrFail2[Stack[Heap], Stack[Option[CH]]](s, v)((s1, v1, QS) => - this.consumeFromMultipleHeaps(s1, s1.reserveHeaps.tail, perms, permsExp, failure, qvars, v1, analysisInfoes)(consumeFunction)(QS) + this.consumeFromMultipleHeaps(s1, s1.reserveHeaps.tail, perms, permsExp, failure, qvars, v1, analysisInfos)(consumeFunction)(QS) )((s2, hs2, chs2, v2) => { val conservedPcs = s2.conservedPcs.head :+ v2.decider.pcs.after(preMark) val s3 = s2.copy(conservedPcs = conservedPcs +: s2.conservedPcs.tail, reserveHeaps = s.reserveHeaps.head +: hs2) val usedChunks = chs2.flatten - val (fr4, hUsed) = v2.stateConsolidator(s2).merge(s3.functionRecorder, s2, s2.reserveHeaps.head, Heap(usedChunks), v2, analysisInfoes) + val (fr4, hUsed) = v2.stateConsolidator(s2).merge(s3.functionRecorder, s2, s2.reserveHeaps.head, Heap(usedChunks), v2, analysisInfos) val s4 = s3.copy(functionRecorder = fr4, reserveHeaps = hUsed +: s3.reserveHeaps.tail) @@ -591,9 +591,9 @@ object magicWandSupporter extends SymbolicExecutionRules { * heap. After a statement is executed those permissions are transferred to hOps. */ - val analysisInfoes = DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes.withSource(StringAnalysisSourceInfo("merge", NoPosition)).withDependencyType(DependencyType.Internal) // TODO ake + val analysisInfos = DependencyAnalysisInfos.DefaultDependencyAnalysisInfos.withSource(StringAnalysisSourceInfo("merge", NoPosition)).withDependencyType(DependencyType.Internal) // TODO ake val emptyHeap = v.heapSupporter.getEmptyHeap(newState.program) - val (fr, hOpsJoinUsed) = v.stateConsolidator(newState).merge(newState.functionRecorder, newState, newState.reserveHeaps(1), newState.h, v, analysisInfoes) + val (fr, hOpsJoinUsed) = v.stateConsolidator(newState).merge(newState.functionRecorder, newState, newState.reserveHeaps(1), newState.h, v, analysisInfos) newState.copy(functionRecorder = fr, h = emptyHeap, reserveHeaps = emptyHeap +: hOpsJoinUsed +: newState.reserveHeaps.drop(2)) } else newState diff --git a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala index 55719cd98..1cc577167 100644 --- a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala +++ b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala @@ -7,7 +7,7 @@ package viper.silicon.rules import viper.silicon.debugger.DebugExp -import viper.silicon.dependencyAnalysis.DependencyAnalysisInfoes +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfos import viper.silicon.interfaces.state._ import viper.silicon.interfaces.{Success, VerificationResult} import viper.silicon.resources.{FieldID, NonQuantifiedPropertyInterpreter, Resources} @@ -109,7 +109,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { Implies(And(argumentEqualities, IsPositive(ch.perm)), `?s` === ch.snap) }) - val analysisInfoes = DependencyAnalysisInfoes.create("summarize", DependencyType.Internal) // TODO ake + val analysisInfos = DependencyAnalysisInfos.create("summarize", DependencyType.Internal) // TODO ake val taggedSummarisingSnapshot = summarisingSnapshotDefinitions @@ -125,8 +125,8 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { case None => // We have not yet checked for a definite alias val id = ChunkIdentifier(resource, s.program) - val potentialAlias = chunkSupporter.findChunk[NonQuantifiedChunk](relevantChunks, id, args, v, analysisInfoes) - potentialAlias.filter(c => v.decider.check(IsPositive(c.perm), Verifier.config.checkTimeout(), analysisInfoes)).map(_.snap) + val potentialAlias = chunkSupporter.findChunk[NonQuantifiedChunk](relevantChunks, id, args, v, analysisInfos) + potentialAlias.filter(c => v.decider.check(IsPositive(c.perm), Verifier.config.checkTimeout(), analysisInfos)).map(_.snap) case Some(v) => // We have checked for a definite alias and may or may not have found one. v @@ -168,18 +168,18 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { // query to check if the permission amount we have is sufficient to get the correct counterexample. If we perform // the query in two parts (one part here, one part in our caller to see if the permission amount is sufficient), // the counterexample might be wrong. - val analysisInfoes = DependencyAnalysisInfoes.create("summarise", DependencyType.Internal) // TODO ake + val analysisInfos = DependencyAnalysisInfos.create("summarise", DependencyType.Internal) // TODO ake if (relevantChunks.size == 1 && !Verifier.config.counterexample.isDefined) { val chunk = relevantChunks.head val argsEqual = And(chunk.args.zip(args).map { case (t1, t2) => t1 === t2 }) - if (v.decider.check(argsEqual, Verifier.config.checkTimeout(), analysisInfoes)) { + if (v.decider.check(argsEqual, Verifier.config.checkTimeout(), analysisInfos)) { return Q(s, chunk.snap, chunk.perm, chunk.permExp, v) } } val (s1, taggedSnap, snapDefs, permSum, permSumExp) = summariseOnly(s, relevantChunks, resource, args, argsExp, knownValue, v) - v.decider.assumeDefinition(And(snapDefs), Option.when(withExp)(DebugExp.createInstance("Snapshot", isInternal_ = true)), analysisInfoes.withDependencyType(DependencyType.Internal)) + v.decider.assumeDefinition(And(snapDefs), Option.when(withExp)(DebugExp.createInstance("Snapshot", isInternal_ = true)), analysisInfos.withDependencyType(DependencyType.Internal)) // v.decider.assume(PermAtMost(permSum, FullPerm())) /* Done in StateConsolidator instead */ val s2 = @@ -203,7 +203,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { argsExp: Option[Seq[ast.Exp]], ve: VerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Term, Verifier) => VerificationResult) : VerificationResult = { @@ -211,7 +211,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { val relevantChunks = findChunksWithID[NonQuantifiedChunk](h.values, id).toSeq if (relevantChunks.isEmpty) { - if (v.decider.checkSmoke(analysisInfoes, isAssert = true)) { + if (v.decider.checkSmoke(analysisInfos, isAssert = true)) { if (s.isInPackage || Verifier.config.disableInfeasibilityChecks()) { val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) Q(s, snap, v) @@ -220,7 +220,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { } } else { val failure = createFailure(ve, v, s, False, "branch is dead") - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfoes, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfos, v.reportFurtherErrors()) if(s.retryLevel == 0 && v.reportFurtherErrors() && Verifier.config.disableInfeasibilityChecks()){ val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) failure combine Q(s, snap, v) @@ -230,12 +230,12 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { } } else { summarise(s, relevantChunks, resource, args, argsExp, None, v)((s1, snap, permSum, permSumExp, v1) => - v.decider.assert(IsPositive(permSum), analysisInfoes) { + v.decider.assert(IsPositive(permSum), analysisInfos) { case true => Q(s1, snap, v1) case false => val failure = createFailure(ve, v, s1, IsPositive(permSum), permSumExp.map(IsPositive(_)())) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(IsPositive(permSum), analysisInfoes, v1.reportFurtherErrors()) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(IsPositive(permSum), analysisInfos, v1.reportFurtherErrors()) if(s1.retryLevel == 0 && v1.reportFurtherErrors()) failure combine Q(s1, snap, v1) else failure }) } @@ -251,14 +251,14 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { returnSnap: Boolean, ve: VerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { if (!s.assertReadAccessOnly) - actualConsumeComplete(s, h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, analysisInfoes)(Q) + actualConsumeComplete(s, h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, analysisInfos)(Q) else - summariseHeapAndAssertReadAccess(s, h, resource, perms, args, argsExp, returnSnap, ve, v, analysisInfoes)(Q) + summariseHeapAndAssertReadAccess(s, h, resource, perms, args, argsExp, returnSnap, ve, v, analysisInfos)(Q) } private def summariseHeapAndAssertReadAccess(s: State, @@ -270,7 +270,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { returnSnap: Boolean, ve: VerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -279,22 +279,22 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { if (returnSnap) { summarise(s, relevantChunks, resource, args, argsExp, None, v)((s1, snap, permSum, permSumExp, v1) => - v.decider.assert(Implies(IsPositive(perm), IsPositive(permSum)), analysisInfoes) { + v.decider.assert(Implies(IsPositive(perm), IsPositive(permSum)), analysisInfos) { case true => Q(s1, h, Some(snap), v1) case false => val failure = createFailure(ve, v, s1, IsPositive(permSum), permSumExp.map(IsPositive(_)())) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(Implies(IsPositive(perm), IsPositive(permSum)), analysisInfoes, v1.reportFurtherErrors()) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(Implies(IsPositive(perm), IsPositive(permSum)), analysisInfos, v1.reportFurtherErrors()) if(s1.retryLevel == 0 && v1.reportFurtherErrors()) failure combine Q(s1, h, Some(snap), v1) else failure }) } else { val (s1, permSum, permSumExp) = permSummariseOnly(s, relevantChunks, resource, args, argsExp) - v.decider.assert(Implies(IsPositive(perm), IsPositive(permSum)), analysisInfoes) { + v.decider.assert(Implies(IsPositive(perm), IsPositive(permSum)), analysisInfos) { case true => Q(s1, h, None, v) case false => val failure = createFailure(ve, v, s1, IsPositive(permSum), permSumExp.map(IsPositive(_)())) - if(s1.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(Implies(IsPositive(perm), IsPositive(permSum)), analysisInfoes, v.reportFurtherErrors()) + if(s1.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(Implies(IsPositive(perm), IsPositive(permSum)), analysisInfos, v.reportFurtherErrors()) if(s1.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s1, h, None, v) else failure } } @@ -310,7 +310,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { returnSnap: Boolean, ve: VerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -324,16 +324,16 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { if (relevantChunks.isEmpty) { // if no permission is exhaled, return none - v.decider.assert(perms === NoPerm, analysisInfoes) { + v.decider.assert(perms === NoPerm, analysisInfos) { case true => Q(s, h, None, v) case false => val failure = createFailure(ve, v, s, perms === NoPerm, permsExp.map(pe => ast.EqCmp(pe, ast.NoPerm()())(pe.pos, pe.info, pe.errT))) - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(perms === NoPerm, analysisInfoes, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(perms === NoPerm, analysisInfos, v.reportFurtherErrors()) if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, h, None, v) else failure } } else { if (!terms.utils.consumeExactRead(perms, s.constrainableARPs)) { - actualConsumeCompleteConstrainable(s, relevantChunks, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, analysisInfoes)((s1, updatedChunks, optSnap, v2) => { + actualConsumeCompleteConstrainable(s, relevantChunks, resource, args, argsExp, perms, permsExp, returnSnap, ve, v, analysisInfos)((s1, updatedChunks, optSnap, v2) => { Q(s1, Heap(updatedChunks ++ otherChunks), optSnap, v2) }) } else { @@ -344,8 +344,8 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { val newChunks = ListBuffer[NonQuantifiedChunk]() var moreNeeded = true - val definiteAlias = chunkSupporter.findChunk[NonQuantifiedChunk](relevantChunks, id, args, v, analysisInfoes).filter(c => - v.decider.check(IsPositive(c.perm), Verifier.config.checkTimeout(), analysisInfoes) + val definiteAlias = chunkSupporter.findChunk[NonQuantifiedChunk](relevantChunks, id, args, v, analysisInfos).filter(c => + v.decider.check(IsPositive(c.perm), Verifier.config.checkTimeout(), analysisInfos) ) val sortFunction: (NonQuantifiedChunk, NonQuantifiedChunk) => Boolean = (ch1, ch2) => { @@ -386,16 +386,16 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { pSum = PermPlus(pSum, Ite(eq, ch.perm, NoPerm)) pSumExp = eqExp.map(eq => ast.PermAdd(pSumExp.get, ast.CondExp(eq, ch.permExp.get, ast.NoPerm()())(eq.pos, eq.info, eq.errT))()) - val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, pTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, pTakenExp.get)(pe.pos, pe.info, pe.errT)), v.decider.getAnalysisInfo(analysisInfoes)).asInstanceOf[NonQuantifiedChunk] - val _ = GeneralChunk.withPerm(ch, pTaken, None, v.decider.getAnalysisInfo(analysisInfoes), isExhale=true) + val newChunk = GeneralChunk.withPerm(ch, PermMinus(ch.perm, pTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, pTakenExp.get)(pe.pos, pe.info, pe.errT)), v.decider.getAnalysisInfo(analysisInfos)).asInstanceOf[NonQuantifiedChunk] + val _ = GeneralChunk.withPerm(ch, pTaken, None, v.decider.getAnalysisInfo(analysisInfos), isExhale=true) pNeeded = PermMinus(pNeeded, pTaken) pNeededExp = permsExp.map(pe => ast.PermSub(pNeededExp.get, pTakenExp.get)(pe.pos, pe.info, pe.errT)) - if (!v.decider.check(IsNonPositive(newChunk.perm), Verifier.config.splitTimeout(), analysisInfoes.withDependencyType(DependencyType.Internal))) { + if (!v.decider.check(IsNonPositive(newChunk.perm), Verifier.config.splitTimeout(), analysisInfos.withDependencyType(DependencyType.Internal))) { newChunks.append(newChunk) } - moreNeeded = !v.decider.check(pNeeded === NoPerm, Verifier.config.splitTimeout(), analysisInfoes) + moreNeeded = !v.decider.check(pNeeded === NoPerm, Verifier.config.splitTimeout(), analysisInfos) } else { newChunks.append(ch) } @@ -408,7 +408,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { newChunks foreach { ch => val resource = Resources.resourceDescriptions(ch.resourceID) val pathCond = interpreter.buildPathConditionsForChunk(ch, resource.instanceProperties(s.mayAssumeUpperBounds)) - pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), analysisInfoes)) + pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), analysisInfos)) } val newHeap = Heap(allChunks) @@ -418,7 +418,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { if (returnSnap) { summarise(s0, relevantChunks.toSeq, resource, args, argsExp, Some(definiteAlias.map(_.snap)), v)((s1, snap, _, _, v1) => { - val condSnap = Some(if (v1.decider.check(IsPositive(perms), Verifier.config.checkTimeout(), analysisInfoes.withDependencyType(DependencyType.Internal))) { + val condSnap = Some(if (v1.decider.check(IsPositive(perms), Verifier.config.checkTimeout(), analysisInfos.withDependencyType(DependencyType.Internal))) { snap } else { Ite(IsPositive(perms), snap.convert(sorts.Snap), Unit) @@ -426,12 +426,12 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { if (!moreNeeded) { Q(s1, newHeap, condSnap, v1) } else { - v1.decider.assert(pNeeded === NoPerm, analysisInfoes) { + v1.decider.assert(pNeeded === NoPerm, analysisInfos) { case true => Q(s1, newHeap, condSnap, v1) case false => val failure = createFailure(ve, v1, s1, pNeeded === NoPerm, pNeededExp.map(pn => ast.EqCmp(pn, ast.NoPerm()())(pn.pos, pn.info, pn.errT))) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(pNeeded === NoPerm, analysisInfoes, v1.reportFurtherErrors()) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(pNeeded === NoPerm, analysisInfos, v1.reportFurtherErrors()) if(s1.retryLevel == 0 && v1.reportFurtherErrors()) failure combine Q(s1, newHeap, condSnap, v1) else failure } } @@ -440,12 +440,12 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { if (!moreNeeded) { Q(s0, newHeap, None, v) } else { - v.decider.assert(pNeeded === NoPerm, analysisInfoes) { + v.decider.assert(pNeeded === NoPerm, analysisInfos) { case true => Q(s0, newHeap, None, v) case false => val failure = createFailure(ve, v, s0, pNeeded === NoPerm, pNeededExp.map(pn => ast.EqCmp(pn, ast.NoPerm()())(pn.pos, pn.info, pn.errT))) - if(s0.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(pNeeded === NoPerm, analysisInfoes, v.reportFurtherErrors()) + if(s0.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(pNeeded === NoPerm, analysisInfos, v.reportFurtherErrors()) if(s0.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s0, newHeap, None, v) else failure } } @@ -464,7 +464,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { returnSnap: Boolean, ve: VerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, ListBuffer[NonQuantifiedChunk], Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -501,13 +501,13 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { ast.Implies(ast.Not(eqExp.get)(), ast.EqCmp(permTakenExp.get, ast.NoPerm()())())(pe.pos, pe.info, pe.errT)))) - v.decider.assume(constraint, Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp)), analysisInfoes) + v.decider.assume(constraint, Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp)), analysisInfos) newFr = newFr.recordPathSymbol(permTaken.applicable.asInstanceOf[Function]).recordConstraint(constraint) @unused // required in order to ensure a sound dependency analysis - val _ = GeneralChunk.withPerm(ch, permTaken, None, v.decider.getAnalysisInfo(analysisInfoes), isExhale=true) - NonQuantifiedChunk.withPerm(ch, PermMinus(ch.perm, permTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, permTakenExp.get)(pe.pos, pe.info, pe.errT)), v.decider.getAnalysisInfo(analysisInfoes)) + val _ = GeneralChunk.withPerm(ch, permTaken, None, v.decider.getAnalysisInfo(analysisInfos), isExhale=true) + NonQuantifiedChunk.withPerm(ch, PermMinus(ch.perm, permTaken), permsExp.map(pe => ast.PermSub(ch.permExp.get, permTakenExp.get)(pe.pos, pe.info, pe.errT)), v.decider.getAnalysisInfo(analysisInfos)) }) val totalTakenBounds = @@ -519,16 +519,16 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { val constraintExp = permsExp.map(pe => ast.Implies(ast.NeCmp(totalPermSumExp.get, ast.NoPerm()())(), ast.And(ast.PermLeCmp(ast.NoPerm()(), totalPermTakenExp.get)(), ast.PermLeCmp(totalPermTakenExp.get, totalPermSumExp.get)())(pe.pos, pe.info, pe.errT))()) - v.decider.assume(totalTakenBounds, constraintExp, constraintExp, analysisInfoes) + v.decider.assume(totalTakenBounds, constraintExp, constraintExp, analysisInfos) newFr = newFr.recordConstraint(totalTakenBounds) val s1 = s.copy(functionRecorder = newFr) - v.decider.assert(Implies(PermLess(NoPerm, perms), totalPermTaken !== NoPerm), analysisInfoes) { + v.decider.assert(Implies(PermLess(NoPerm, perms), totalPermTaken !== NoPerm), analysisInfos) { case true => val constraintExp = permsExp.map(pe => ast.EqCmp(pe, totalPermTakenExp.get)()) - v.decider.assume(perms === totalPermTaken, Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp)), analysisInfoes) + v.decider.assume(perms === totalPermTaken, Option.when(withExp)(DebugExp.createInstance(constraintExp, constraintExp)), analysisInfos) if (returnSnap) { summarise(s1, relevantChunks.toSeq, resource, args, argsExp, None, v)((s2, snap, _, _, v1) => Q(s2, updatedChunks, Some(snap), v1)) @@ -538,7 +538,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { case false => v.decider.finishDebugSubExp(s"consume permissions for ${resource.toString()}") val failure = createFailure(ve, v, s, totalPermTaken !== NoPerm, totalPermTakenExp.map(tpt => ast.NeCmp(tpt, ast.NoPerm()())())) - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(Implies(PermLess(NoPerm, perms), totalPermTaken !== NoPerm), analysisInfoes, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(Implies(PermLess(NoPerm, perms), totalPermTaken !== NoPerm), analysisInfos, v.reportFurtherErrors()) if(s.retryLevel == 0 && v.reportFurtherErrors()){ val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) // TODO ake: function recorder failure combine Q(s1, updatedChunks, if(returnSnap) Some(snap) else None, v) @@ -551,7 +551,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { private val freeReceiver = Var(Identifier("?rcvr"), sorts.Ref, false) private val freeReceiverExp = ast.LocalVar("?rcvr", ast.Ref)() - def assumeFieldPermissionUpperBounds(h: Heap, v: Verifier, analysisInfoes: DependencyAnalysisInfoes): Unit = { + def assumeFieldPermissionUpperBounds(h: Heap, v: Verifier, analysisInfos: DependencyAnalysisInfos): Unit = { // TODO: Instead of "manually" assuming such upper bounds, appropriate PropertyInterpreters // should be used, see StateConsolidator val relevantChunksPerField = MMap.empty[String, MList[BasicChunk]] @@ -579,7 +579,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { relevantChunks foreach (chunk => { val instantiatedPermSum = permissionSum.replace(freeReceiver, chunk.args.head) val exp = permissionSumExp.map(pse => ast.PermLeCmp(replaceVarsInExp(pse, Seq(freeReceiverExp.name), Seq(chunk.argsExp.get.head)), ast.FullPerm()())()) - v.decider.assume(PermAtMost(instantiatedPermSum, FullPerm), exp, exp, analysisInfoes.withDependencyType(DependencyType.Internal)) + v.decider.assume(PermAtMost(instantiatedPermSum, FullPerm), exp, exp, analysisInfos.withDependencyType(DependencyType.Internal)) }) } } diff --git a/src/main/scala/rules/PermissionSupporter.scala b/src/main/scala/rules/PermissionSupporter.scala index 17f6fda83..339c665f3 100644 --- a/src/main/scala/rules/PermissionSupporter.scala +++ b/src/main/scala/rules/PermissionSupporter.scala @@ -6,7 +6,7 @@ package viper.silicon.rules -import viper.silicon.dependencyAnalysis.DependencyAnalysisInfoes +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfos import viper.silicon.interfaces.VerificationResult import viper.silicon.state.State import viper.silicon.state.terms.{Term, Var, perms} @@ -16,7 +16,7 @@ import viper.silver.verifier.PartialVerificationError import viper.silver.verifier.reasons.{NegativePermission, NonPositivePermission} object permissionSupporter extends SymbolicExecutionRules { - def assertNotNegative(s: State, tPerm: Term, ePerm: ast.Exp, ePermNew: Option[ast.Exp], pve: PartialVerificationError, v: Verifier, analysisInfoes: DependencyAnalysisInfoes) + def assertNotNegative(s: State, tPerm: Term, ePerm: ast.Exp, ePermNew: Option[ast.Exp], pve: PartialVerificationError, v: Verifier, analysisInfos: DependencyAnalysisInfos) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -24,18 +24,18 @@ object permissionSupporter extends SymbolicExecutionRules { case k: Var if s.constrainableARPs.contains(k) => Q(s, v) case _ => - v.decider.assert(perms.IsNonNegative(tPerm), analysisInfoes) { + v.decider.assert(perms.IsNonNegative(tPerm), analysisInfos) { case true => Q(s, v) case false => val assertExp = ePermNew.map(ep => perms.IsNonNegative(ep)(ep.pos, ep.info, ep.errT)) val failure = createFailure(pve dueTo NegativePermission(ePerm), v, s, perms.IsNonNegative(tPerm), assertExp) - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(perms.IsNonNegative(tPerm), analysisInfoes, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(perms.IsNonNegative(tPerm), analysisInfos, v.reportFurtherErrors()) if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, v) else failure } } } - def assertPositive(s: State, tPerm: Term, ePerm: ast.Exp, pve: PartialVerificationError, v: Verifier, analysisInfoes: DependencyAnalysisInfoes) + def assertPositive(s: State, tPerm: Term, ePerm: ast.Exp, pve: PartialVerificationError, v: Verifier, analysisInfos: DependencyAnalysisInfos) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -43,11 +43,11 @@ object permissionSupporter extends SymbolicExecutionRules { case k: Var if s.constrainableARPs.contains(k) => Q(s, v) case _ => - v.decider.assert(perms.IsPositive(tPerm), analysisInfoes) { + v.decider.assert(perms.IsPositive(tPerm), analysisInfos) { case true => Q(s, v) case false => val failure = createFailure(pve dueTo NonPositivePermission(ePerm), v, s, perms.IsPositive(tPerm), Option.when(withExp)(perms.IsPositive(ePerm)())) - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(perms.IsPositive(tPerm), analysisInfoes, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(perms.IsPositive(tPerm), analysisInfos, v.reportFurtherErrors()) if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, v) else failure } } diff --git a/src/main/scala/rules/PredicateSupporter.scala b/src/main/scala/rules/PredicateSupporter.scala index 123c5ae88..7a823e0a9 100644 --- a/src/main/scala/rules/PredicateSupporter.scala +++ b/src/main/scala/rules/PredicateSupporter.scala @@ -10,7 +10,7 @@ import viper.silicon import viper.silicon.Config.JoinMode import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp -import viper.silicon.dependencyAnalysis.DependencyAnalysisInfoes +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfos import viper.silicon.interfaces.VerificationResult import viper.silicon.interfaces.state.{ChunkIdentifer, GeneralChunk, NonQuantifiedChunk} import viper.silicon.resources.FieldID @@ -34,7 +34,7 @@ trait PredicateSupportRules extends SymbolicExecutionRules { constrainableWildcards: InsertionOrderedSet[Var], pve: PartialVerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Verifier) => VerificationResult) : VerificationResult @@ -48,7 +48,7 @@ trait PredicateSupportRules extends SymbolicExecutionRules { pve: PartialVerificationError, v: Verifier, pa: ast.PredicateAccess, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Verifier) => VerificationResult) : VerificationResult } @@ -66,7 +66,7 @@ object predicateSupporter extends PredicateSupportRules { constrainableWildcards: InsertionOrderedSet[Var], pve: PartialVerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -80,33 +80,33 @@ object predicateSupporter extends PredicateSupportRules { val s1 = s.copy(g = gIns, smDomainNeeded = true) .scalePermissionFactor(tPerm, ePerm) - consume(s1, body, true, pve, v, analysisInfoes)((s1a, snap, v1) => { + consume(s1, body, true, pve, v, analysisInfos)((s1a, snap, v1) => { if (!Verifier.config.disableFunctionUnfoldTrigger()) { val predTrigger = App(s1a.predicateData(predicate.name).triggerFunction, snap.get.convert(terms.sorts.Snap) +: tArgs) val eArgsString = eArgs.mkString(", ") - v1.decider.assume(predTrigger, Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}($eArgsString))")), analysisInfoes) + v1.decider.assume(predTrigger, Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}($eArgsString))")), analysisInfos) } val s2 = s1a.copy(g = s.g, smDomainNeeded = s.smDomainNeeded, permissionScalingFactor = s.permissionScalingFactor, permissionScalingFactorExp = s.permissionScalingFactorExp).setConstrainable(constrainableWildcards, false) - v1.heapSupporter.produceSingle(s2, predicate, tArgs, eArgs, snap.get.convert(s2.predicateSnapMap(predicate.name)), None, tPerm, ePerm, pve, true, v1, analysisInfoes)((s3, v3) => { - val s4 = v3.heapSupporter.triggerResourceIfNeeded(s3, pa, tArgs, eArgs, v3, analysisInfoes) + v1.heapSupporter.produceSingle(s2, predicate, tArgs, eArgs, snap.get.convert(s2.predicateSnapMap(predicate.name)), None, tPerm, ePerm, pve, true, v1, analysisInfos)((s3, v3) => { + val s4 = v3.heapSupporter.triggerResourceIfNeeded(s3, pa, tArgs, eArgs, v3, analysisInfos) Q(s4, v3) }) }) } - def producePredicateContents(s: State, tree: PredicateContentsTree, toReplace: silicon.Map[Term, Term], v: Verifier, analysisInfoes: DependencyAnalysisInfoes, isUnfolding: Boolean = false) + def producePredicateContents(s: State, tree: PredicateContentsTree, toReplace: silicon.Map[Term, Term], v: Verifier, analysisInfos: DependencyAnalysisInfos, isUnfolding: Boolean = false) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { tree match { case PredicateLeafNode(h, assumptions) => val debugExp = Option.when(withExp)(DebugExp.createInstance("Assumption from unfolded predicate body")) - assumptions.foreach(a => v.decider.assume(a.replace(toReplace), debugExp, analysisInfoes)) - val substChunks = h.values.map(chunk => GeneralChunk.permScale(GeneralChunk.substitute(chunk.asInstanceOf[GeneralChunk], toReplace, v.decider.getAnalysisInfo(analysisInfoes)), s.permissionScalingFactor, s.permissionScalingFactorExp, v.decider.getAnalysisInfo(analysisInfoes))) + assumptions.foreach(a => v.decider.assume(a.replace(toReplace), debugExp, analysisInfos)) + val substChunks = h.values.map(chunk => GeneralChunk.permScale(GeneralChunk.substitute(chunk.asInstanceOf[GeneralChunk], toReplace, v.decider.getAnalysisInfo(analysisInfos)), s.permissionScalingFactor, s.permissionScalingFactorExp, v.decider.getAnalysisInfo(analysisInfos))) val quantifiedResourceIdentifiers: Set[ChunkIdentifer] = s.qpPredicates.map(p => BasicChunkIdentifier(p.name)) ++ s.qpFields.map(f => BasicChunkIdentifier(f.name)) ++ s.qpMagicWands @@ -120,28 +120,28 @@ object predicateSupporter extends PredicateSupportRules { case _ => s.program.findPredicate(bc.id.name) } val (sm, smValueDef) = quantifiedChunkSupporter.singletonSnapshotMap(s, resource, bc.args, bc.snap, v) - v.decider.assumeDefinition(smValueDef, None, analysisInfoes) + v.decider.assumeDefinition(smValueDef, None, analysisInfos) val codQvars = bc.resourceID match { case FieldID => Seq(`?r`) case _ => s.predicateFormalVarMap(resource.asInstanceOf[ast.Predicate].name) } newFr = newFr.recordFvfAndDomain(SnapshotMapDefinition(resource, sm, Seq(smValueDef), Seq())) - quantifiedChunkSupporter.createSingletonQuantifiedChunk(codQvars, None, resource, bc.args, None, bc.perm, None, sm, s.program, v, analysisInfoes, isExhale=false) + quantifiedChunkSupporter.createSingletonQuantifiedChunk(codQvars, None, resource, bc.args, None, bc.perm, None, sm, s.program, v, analysisInfos, isExhale=false) case mwc: MagicWandChunk => val wand = mwc.id.ghostFreeWand val bodyVars = wand.subexpressionsToEvaluate(s.program) val codQvars = bodyVars.indices.toList.map(i => Var(Identifier(s"x$i"), v.symbolConverter.toSort(bodyVars(i).typ), false)) val (sm, smValueDef) = quantifiedChunkSupporter.singletonSnapshotMap(s, wand, mwc.args, mwc.snap, v) - v.decider.assumeDefinition(smValueDef, None, analysisInfoes) + v.decider.assumeDefinition(smValueDef, None, analysisInfos) newFr = newFr.recordFvfAndDomain(SnapshotMapDefinition(wand, sm, Seq(smValueDef), Seq())) - quantifiedChunkSupporter.createSingletonQuantifiedChunk(codQvars, None, wand, mwc.args, None, mwc.perm, None, sm, s.program, v, analysisInfoes, isExhale=false) + quantifiedChunkSupporter.createSingletonQuantifiedChunk(codQvars, None, wand, mwc.args, None, mwc.perm, None, sm, s.program, v, analysisInfos, isExhale=false) } } else { c } }) val substHeap = Heap(substChunksOptQps) - val (fr1, h1) = v.stateConsolidator(s).merge(newFr, s, s.h, substHeap, v, analysisInfoes) + val (fr1, h1) = v.stateConsolidator(s).merge(newFr, s, s.h, substHeap, v, analysisInfos) val s1 = s.copy(h = h1, functionRecorder = fr1) Q(s1, v) @@ -149,13 +149,13 @@ object predicateSupporter extends PredicateSupportRules { val substCond = cond.replace(toReplace) if (!isUnfolding && s.moreJoins.id >= JoinMode.Impure.id) { - joiner.join[scala.Null, scala.Null](s, v, analysisInfoes, resetState = false)((s1, v1, QB) => { - brancher.branch(s1, substCond, condExp, v1, analysisInfoes)( + joiner.join[scala.Null, scala.Null](s, v, analysisInfos, resetState = false)((s1, v1, QB) => { + brancher.branch(s1, substCond, condExp, v1, analysisInfos)( (s2, v2) => { - producePredicateContents(s2, left, toReplace, v2, analysisInfoes, isUnfolding)((s3, v3) => QB(s3, null, v3)) + producePredicateContents(s2, left, toReplace, v2, analysisInfos, isUnfolding)((s3, v3) => QB(s3, null, v3)) }, (s2, v2) => { - producePredicateContents(s2, right, toReplace, v2, analysisInfoes, isUnfolding)((s3, v3) => QB(s3, null, v3)) + producePredicateContents(s2, right, toReplace, v2, analysisInfos, isUnfolding)((s3, v3) => QB(s3, null, v3)) } ) }) (entries => { @@ -170,12 +170,12 @@ object predicateSupporter extends PredicateSupportRules { (s2, null) }) ((sp, _, vp) => Q(sp, vp)) } else { - brancher.branch(s, substCond, condExp, v, analysisInfoes)( + brancher.branch(s, substCond, condExp, v, analysisInfos)( (s1, v1) => { - producePredicateContents(s1, left, toReplace, v1, analysisInfoes, isUnfolding)(Q) + producePredicateContents(s1, left, toReplace, v1, analysisInfos, isUnfolding)(Q) }, (s2, v2) => { - producePredicateContents(s2, right, toReplace, v2, analysisInfoes, isUnfolding)(Q) + producePredicateContents(s2, right, toReplace, v2, analysisInfos, isUnfolding)(Q) } ) } @@ -192,7 +192,7 @@ object predicateSupporter extends PredicateSupportRules { pve: PartialVerificationError, v: Verifier, pa: ast.PredicateAccess, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -204,19 +204,19 @@ object predicateSupporter extends PredicateSupportRules { val body = predicate.body.get /* Only non-abstract predicates can be unfolded */ val s1 = s.scalePermissionFactor(tPerm, ePerm) - v.heapSupporter.consumeSingle(s1, s1.h, pa, tArgs, eArgs, tPerm, ePerm, true, pve, v, analysisInfoes)((s2, h2, snap, v1) => { + v.heapSupporter.consumeSingle(s1, s1.h, pa, tArgs, eArgs, tPerm, ePerm, true, pve, v, analysisInfos)((s2, h2, snap, v1) => { val s3 = s2.copy(g = gIns, h = h2) .setConstrainable(constrainableWildcards, false) if (s3.predicateData(predicate.name).predContents.isDefined) { val toReplace: silicon.Map[Term, Term] = silicon.Map.from(s3.predicateData(predicate.name).params.get.zip(Seq(snap.get) ++ tArgs)) - producePredicateContents(s3, s3.predicateData(predicate.name).predContents.get, toReplace, v1, analysisInfoes, false)((s4, v4) => { + producePredicateContents(s3, s3.predicateData(predicate.name).predContents.get, toReplace, v1, analysisInfos, false)((s4, v4) => { v4.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterUnfold) if (!Verifier.config.disableFunctionUnfoldTrigger()) { val predicateTrigger = App(s4.predicateData(predicate.name).triggerFunction, snap.get.convert(terms.sorts.Snap) +: tArgs) val eargs = eArgs.mkString(", ") - v4.decider.assume(predicateTrigger, Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}($eargs))")), analysisInfoes.withDependencyType(DependencyType.Trigger)) + v4.decider.assume(predicateTrigger, Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}($eargs))")), analysisInfos.withDependencyType(DependencyType.Trigger)) } Q(s4.copy(g = s.g, permissionScalingFactor = s.permissionScalingFactor, @@ -224,14 +224,14 @@ object predicateSupporter extends PredicateSupportRules { v4) }) } else { - produce(s3, toSf(snap.get), body, pve, v1, analysisInfoes)((s4, v2) => { + produce(s3, toSf(snap.get), body, pve, v1, analysisInfos)((s4, v2) => { v2.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterUnfold) if (!Verifier.config.disableFunctionUnfoldTrigger()) { val predicateTrigger = App(s4.predicateData(predicate.name).triggerFunction, snap.get.convert(terms.sorts.Snap) +: tArgs) val eargs = eArgs.mkString(", ") - v2.decider.assume(predicateTrigger, Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}($eargs))")), analysisInfoes.withDependencyType(DependencyType.Trigger)) + v2.decider.assume(predicateTrigger, Option.when(withExp)(DebugExp.createInstance(s"PredicateTrigger(${predicate.name}($eargs))")), analysisInfos.withDependencyType(DependencyType.Trigger)) } Q(s4.copy(g = s.g, permissionScalingFactor = s.permissionScalingFactor, diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index 72336f879..a9a7c3f99 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -8,7 +8,7 @@ package viper.silicon.rules import viper.silicon.Config.JoinMode import viper.silicon.debugger.DebugExp -import viper.silicon.dependencyAnalysis.DependencyAnalysisInfoes +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfos import viper.silicon.interfaces.{Unreachable, VerificationResult} import viper.silicon.logger.records.data.{CondExpRecord, ImpliesRecord, ProduceRecord} import viper.silicon.state._ @@ -41,7 +41,7 @@ trait ProductionRules extends SymbolicExecutionRules { a: ast.Exp, pve: PartialVerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Verifier) => VerificationResult) : VerificationResult @@ -66,7 +66,7 @@ trait ProductionRules extends SymbolicExecutionRules { as: Seq[ast.Exp], pvef: ast.Exp => PartialVerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Verifier) => VerificationResult) : VerificationResult } @@ -106,11 +106,11 @@ object producer extends ProductionRules { a: ast.Exp, pve: PartialVerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Verifier) => VerificationResult) : VerificationResult = - produceR(s, sf, a.whenInhaling, pve, v, analysisInfoes)(Q) + produceR(s, sf, a.whenInhaling, pve, v, analysisInfos)(Q) /** @inheritdoc */ def produces(s: State, @@ -118,7 +118,7 @@ object producer extends ProductionRules { as: Seq[ast.Exp], pvef: ast.Exp => PartialVerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -133,7 +133,7 @@ object producer extends ProductionRules { allPves ++= pves }) - produceTlcs(s, sf, allTlcs.result(), allPves.result(), v, analysisInfoes)(Q) + produceTlcs(s, sf, allTlcs.result(), allPves.result(), v, analysisInfos)(Q) } private def produceTlcs(s: State, @@ -141,7 +141,7 @@ object producer extends ProductionRules { as: Seq[ast.Exp], pves: Seq[PartialVerificationError], v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -151,29 +151,29 @@ object producer extends ProductionRules { val a = as.head.whenInhaling val pve = pves.head - val analysisInfoes1 = analysisInfoes.addInfo(a.info, a) + val analysisInfos1 = analysisInfos.addInfo(a.info, a) if (as.tail.isEmpty) - wrappedProduceTlc(s, sf, a, pve, v, analysisInfoes1)((s1, v1) => { + wrappedProduceTlc(s, sf, a, pve, v, analysisInfos1)((s1, v1) => { Q(s1, v1) }) else { try { val (sf0, sf1) = - v.snapshotSupporter.createSnapshotPair(s, sf, a, viper.silicon.utils.ast.BigAnd(as.tail), v, analysisInfoes1) + v.snapshotSupporter.createSnapshotPair(s, sf, a, viper.silicon.utils.ast.BigAnd(as.tail), v, analysisInfos1) /* TODO: Refactor createSnapshotPair s.t. it can be used with Seq[Exp], * then remove use of BigAnd; for one it is not efficient since * the tail of the (decreasing list parameter as) is BigAnd-ed * over and over again. */ - wrappedProduceTlc(s, sf0, a, pve, v, analysisInfoes1)((s1, v1) => { - produceTlcs(s1, sf1, as.tail, pves.tail, v1, analysisInfoes)(Q) + wrappedProduceTlc(s, sf0, a, pve, v, analysisInfos1)((s1, v1) => { + produceTlcs(s1, sf1, as.tail, pves.tail, v1, analysisInfos)(Q) }) } catch { // We will get an IllegalArgumentException from createSnapshotPair if sf(...) returns Unit. // This should never happen if we're in a reachable state, so here we check for that // (without timeout, since there is no fallback) and stop verifying the current branch. - case _: IllegalArgumentException if v.decider.check(False, Verifier.config.assertTimeout.getOrElse(0), analysisInfoes) => + case _: IllegalArgumentException if v.decider.check(False, Verifier.config.assertTimeout.getOrElse(0), analysisInfos) => Unreachable() } @@ -186,14 +186,14 @@ object producer extends ProductionRules { a: ast.Exp, pve: PartialVerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { val tlcs = a.topLevelConjuncts val pves = Seq.fill(tlcs.length)(pve) - produceTlcs(s, sf, tlcs, pves, v, analysisInfoes)(Q) + produceTlcs(s, sf, tlcs, pves, v, analysisInfos)(Q) } /** Wrapper/decorator for consume that injects the following operations: @@ -204,21 +204,21 @@ object producer extends ProductionRules { a: ast.Exp, pve: PartialVerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { if(v.decider.isPathInfeasible){ if(!Expressions.isKnownWellDefined(a, Some(s.program))){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfoes) + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfos) } - v.decider.dependencyAnalyzer.addAssumption(True, analysisInfoes) + v.decider.dependencyAnalyzer.addAssumption(True, analysisInfos) return Q(s, v) } val sepIdentifier = v.symbExLog.openScope(new ProduceRecord(a, s, v.decider.pcs)) - produceTlc(s, sf, a, pve, v, analysisInfoes)((s1, v1) => { + produceTlc(s, sf, a, pve, v, analysisInfos)((s1, v1) => { v1.symbExLog.closeScope(sepIdentifier) Q(s1, v1)}) } @@ -228,7 +228,7 @@ object producer extends ProductionRules { a: ast.Exp, pve: PartialVerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (continuation: (State, Verifier) => VerificationResult) : VerificationResult = { v.logger.debug(s"\nPRODUCE ${viper.silicon.utils.ast.sourceLineColumn(a)}: $a") @@ -242,16 +242,16 @@ object producer extends ProductionRules { val impliesRecord = new ImpliesRecord(imp, s, v.decider.pcs, "produce") val uidImplies = v.symbExLog.openScope(impliesRecord) - eval(s, e0, pve, v, analysisInfoes)((s1, t0, e0New, v1) => + eval(s, e0, pve, v, analysisInfos)((s1, t0, e0New, v1) => // The type arguments here are Null because there is no need to pass any join data. - joiner.join[scala.Null, scala.Null](s1, v1, analysisInfoes, resetState = false)((s1, v1, QB) => - branch(s1.copy(parallelizeBranches = false), t0, (e0, e0New), v1, analysisInfoes)( - (s2, v2) => produceR(s2.copy(parallelizeBranches = s1.parallelizeBranches), sf, a0, pve, v2, analysisInfoes)((s3, v3) => { + joiner.join[scala.Null, scala.Null](s1, v1, analysisInfos, resetState = false)((s1, v1, QB) => + branch(s1.copy(parallelizeBranches = false), t0, (e0, e0New), v1, analysisInfos)( + (s2, v2) => produceR(s2.copy(parallelizeBranches = s1.parallelizeBranches), sf, a0, pve, v2, analysisInfos)((s3, v3) => { v3.symbExLog.closeScope(uidImplies) QB(s3, null, v3) }), (s2, v2) => { - v2.decider.assume(sf(sorts.Snap, v2) === Unit, Option.when(withExp)(DebugExp.createInstance("Empty snapshot", true)), analysisInfoes) + v2.decider.assume(sf(sorts.Snap, v2) === Unit, Option.when(withExp)(DebugExp.createInstance("Empty snapshot", true)), analysisInfos) /* TODO: Avoid creating a fresh var (by invoking) `sf` that is not used * otherwise. In order words, only make this assumption if `sf` has * already been used, e.g. in a snapshot equality such as `s0 == (s1, s2)`. @@ -275,14 +275,14 @@ object producer extends ProductionRules { val impliesRecord = new ImpliesRecord(imp, s, v.decider.pcs, "produce") val uidImplies = v.symbExLog.openScope(impliesRecord) - eval(s, e0, pve, v, analysisInfoes)((s1, t0, e0New, v1) => - branch(s1, t0, (e0, e0New), v1, analysisInfoes)( - (s2, v2) => produceR(s2, sf, a0, pve, v2, analysisInfoes)((s3, v3) => { + eval(s, e0, pve, v, analysisInfos)((s1, t0, e0New, v1) => + branch(s1, t0, (e0, e0New), v1, analysisInfos)( + (s2, v2) => produceR(s2, sf, a0, pve, v2, analysisInfos)((s3, v3) => { v3.symbExLog.closeScope(uidImplies) Q(s3, v3) }), (s2, v2) => { - v2.decider.assume(sf(sorts.Snap, v2) === Unit, Option.when(withExp)(DebugExp.createInstance("Empty snapshot", isInternal_ = true)), analysisInfoes) + v2.decider.assume(sf(sorts.Snap, v2) === Unit, Option.when(withExp)(DebugExp.createInstance("Empty snapshot", isInternal_ = true)), analysisInfos) /* TODO: Avoid creating a fresh var (by invoking) `sf` that is not used * otherwise. In order words, only make this assumption if `sf` has * already been used, e.g. in a snapshot equality such as `s0 == (s1, s2)`. @@ -295,15 +295,15 @@ object producer extends ProductionRules { val condExpRecord = new CondExpRecord(ite, s, v.decider.pcs, "produce") val uidCondExp = v.symbExLog.openScope(condExpRecord) - eval(s, e0, pve, v, analysisInfoes)((s1, t0, e0New, v1) => + eval(s, e0, pve, v, analysisInfos)((s1, t0, e0New, v1) => // The type arguments here are Null because there is no need to pass any join data. - joiner.join[scala.Null, scala.Null](s1, v1, analysisInfoes, resetState = false)((s1, v1, QB) => - branch(s1.copy(parallelizeBranches = false), t0, (e0, e0New), v1, analysisInfoes)( - (s2, v2) => produceR(s2.copy(parallelizeBranches = s1.parallelizeBranches), sf, a1, pve, v2, analysisInfoes)((s3, v3) => { + joiner.join[scala.Null, scala.Null](s1, v1, analysisInfos, resetState = false)((s1, v1, QB) => + branch(s1.copy(parallelizeBranches = false), t0, (e0, e0New), v1, analysisInfos)( + (s2, v2) => produceR(s2.copy(parallelizeBranches = s1.parallelizeBranches), sf, a1, pve, v2, analysisInfos)((s3, v3) => { v3.symbExLog.closeScope(uidCondExp) QB(s3, null, v3) }), - (s2, v2) => produceR(s2.copy(parallelizeBranches = s1.parallelizeBranches), sf, a2, pve, v2, analysisInfoes)((s3, v3) => { + (s2, v2) => produceR(s2.copy(parallelizeBranches = s1.parallelizeBranches), sf, a2, pve, v2, analysisInfos)((s3, v3) => { v3.symbExLog.closeScope(uidCondExp) QB(s3, null, v3) })) @@ -323,29 +323,29 @@ object producer extends ProductionRules { val condExpRecord = new CondExpRecord(ite, s, v.decider.pcs, "produce") val uidCondExp = v.symbExLog.openScope(condExpRecord) - eval(s, e0, pve, v, analysisInfoes)((s1, t0, e0New, v1) => - branch(s1, t0, (e0, e0New), v1, analysisInfoes)( - (s2, v2) => produceR(s2, sf, a1, pve, v2, analysisInfoes)((s3, v3) => { + eval(s, e0, pve, v, analysisInfos)((s1, t0, e0New, v1) => + branch(s1, t0, (e0, e0New), v1, analysisInfos)( + (s2, v2) => produceR(s2, sf, a1, pve, v2, analysisInfos)((s3, v3) => { v3.symbExLog.closeScope(uidCondExp) Q(s3, v3) }), - (s2, v2) => produceR(s2, sf, a2, pve, v2, analysisInfoes)((s3, v3) => { + (s2, v2) => produceR(s2, sf, a2, pve, v2, analysisInfos)((s3, v3) => { v3.symbExLog.closeScope(uidCondExp) Q(s3, v3) }))) case let: ast.Let if !let.isPure => - letSupporter.handle[ast.Exp](s, let, pve, v, analysisInfoes)((s1, g1, body, v1) => - produceR(s1.copy(g = s1.g + g1), sf, body, pve, v1, analysisInfoes)(Q)) + letSupporter.handle[ast.Exp](s, let, pve, v, analysisInfos)((s1, g1, body, v1) => + produceR(s1.copy(g = s1.g + g1), sf, body, pve, v1, analysisInfos)(Q)) case accPred: ast.AccessPredicate => val eArgs = accPred.loc.args(s.program) val ePerm = accPred.perm val resource = accPred.res(s.program) - evals(s, eArgs, _ => pve, v, analysisInfoes)((s1, tArgs, eArgsNew, v1) => - eval(s1, ePerm, pve, v1, analysisInfoes)((s1a, tPerm, ePermNew, v1a) => - permissionSupporter.assertNotNegative(s1a, tPerm, ePerm, ePermNew, pve, v1a, analysisInfoes)((s1b, v2) => { + evals(s, eArgs, _ => pve, v, analysisInfos)((s1, tArgs, eArgsNew, v1) => + eval(s1, ePerm, pve, v1, analysisInfos)((s1a, tPerm, ePermNew, v1a) => + permissionSupporter.assertNotNegative(s1a, tPerm, ePerm, ePermNew, pve, v1a, analysisInfos)((s1b, v2) => { val s2 = s1b.copy(constrainableARPs = s.constrainableARPs) val snap = sf(v2.snapshotSupporter.optimalSnapshotSort(resource, s2, v2), v2) val gain = if (!Verifier.config.unsafeWildcardOptimization() || @@ -354,7 +354,7 @@ object producer extends ProductionRules { else WildcardSimplifyingPermTimes(tPerm, s2.permissionScalingFactor) val gainExp = ePermNew.map(p => ast.PermMul(p, s2.permissionScalingFactorExp.get)(p.pos, p.info, p.errT)) - v2.heapSupporter.produceSingle(s2, resource, tArgs, eArgsNew, snap, None, gain, gainExp, pve, true, v2, analysisInfoes)(Q) + v2.heapSupporter.produceSingle(s2, resource, tArgs, eArgsNew, snap, None, gain, gainExp, pve, true, v2, analysisInfos)(Q) }))) @@ -378,12 +378,12 @@ object producer extends ProductionRules { if (forall.triggers.isEmpty) None else Some(forall.triggers) val s0 = s.copy(functionRecorder = s.functionRecorder.enterQuantifiedExp(qpa)) - evalQuantified(s0, Forall, forall.variables, Seq(cond), ePerm +: eArgs, optTrigger, qid, pve, v, analysisInfoes) { + evalQuantified(s0, Forall, forall.variables, Seq(cond), ePerm +: eArgs, optTrigger, qid, pve, v, analysisInfos) { case (s1, qvars, qvarExps, Seq(tCond), eCondNew, Some((Seq(tPerm, tArgs@_*), permArgs, tTriggers, (auxGlobals, auxNonGlobals), auxExps)), v1) => val s1a = s1.copy(constrainableARPs = s.constrainableARPs) v1.heapSupporter.produceQuantified(s1a, sf, forall, resource, qvars, qvarExps, tFormalArgs, eFormalArgs, qid, optTrigger, tTriggers, auxGlobals, auxNonGlobals, auxExps.map(_._1), auxExps.map(_._2), tCond, eCondNew.map(_.head), tArgs, permArgs.map(_.tail), tPerm, permArgs.map(_.head), pve, NegativePermission(ePerm), - QPAssertionNotInjective(resAcc), v1, analysisInfoes)((s2, v2) => { + QPAssertionNotInjective(resAcc), v1, analysisInfos)((s2, v2) => { Q(s2.copy(functionRecorder = s2.functionRecorder.leaveQuantifiedExp(qpa)), v2) }) case (s1, _, _, _, _, None, v1) => Q(s1.copy(constrainableARPs = s.constrainableARPs), v1) @@ -395,9 +395,9 @@ object producer extends ProductionRules { /* Any regular expressions, i.e. boolean and arithmetic. */ case _ => v.decider.assume(sf(sorts.Snap, v) === Unit, - Option.when(withExp)(DebugExp.createInstance("Empty snapshot", true)), analysisInfoes) /* TODO: See comment for case ast.Implies above */ - eval(s, a, pve, v, analysisInfoes)((s1, t, aNew, v1) => { - v1.decider.assume(t, Option.when(withExp)(a), aNew, analysisInfoes) + Option.when(withExp)(DebugExp.createInstance("Empty snapshot", true)), analysisInfos) /* TODO: See comment for case ast.Implies above */ + eval(s, a, pve, v, analysisInfos)((s1, t, aNew, v1) => { + v1.decider.assume(t, Option.when(withExp)(a), aNew, analysisInfos) Q(s1, v1)}) } diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index d5e2cf0ff..276f86f42 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -10,7 +10,7 @@ import viper.silicon import viper.silicon.Map import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp -import viper.silicon.dependencyAnalysis.DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfos.DefaultDependencyAnalysisInfos import viper.silicon.dependencyAnalysis._ import viper.silicon.interfaces.VerificationResult import viper.silicon.interfaces.state._ @@ -193,7 +193,7 @@ trait QuantifiedChunkSupport extends SymbolicExecutionRules { sm: Term, program: ast.Program, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes, + analysisInfos: DependencyAnalysisInfos, isExhale: Boolean) : QuantifiedBasicChunk @@ -235,7 +235,7 @@ trait QuantifiedChunkSupport extends SymbolicExecutionRules { qidPrefix: String, v: Verifier, program: ast.Program, - analysisInfoes: DependencyAnalysisInfoes, + analysisInfos: DependencyAnalysisInfos, isExhale: Boolean) : (QuantifiedBasicChunk, InverseFunctions) @@ -248,7 +248,7 @@ trait QuantifiedChunkSupport extends SymbolicExecutionRules { def hintBasedChunkOrderHeuristic(hints: Seq[Term]) : Seq[QuantifiedBasicChunk] => Seq[QuantifiedBasicChunk] - def findChunk(chunks: Iterable[Chunk], chunk: QuantifiedChunk, v: Verifier, analysisInfoes: DependencyAnalysisInfoes): Option[QuantifiedChunk] + def findChunk(chunks: Iterable[Chunk], chunk: QuantifiedChunk, v: Verifier, analysisInfos: DependencyAnalysisInfos): Option[QuantifiedChunk] /** Merge the snapshots of two quantified heap chunks that denote the same field locations * @@ -295,7 +295,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { sm: Term, program: ast.Program, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes, + analysisInfos: DependencyAnalysisInfos, isExhale: Boolean) : QuantifiedBasicChunk = { @@ -326,7 +326,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { hints, program, v, - analysisInfoes, + analysisInfos, isExhale) } @@ -350,7 +350,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { qidPrefix: String, v: Verifier, program: ast.Program, - analysisInfoes: DependencyAnalysisInfoes, + analysisInfos: DependencyAnalysisInfos, isExhale: Boolean) : (QuantifiedBasicChunk, InverseFunctions) = { @@ -394,7 +394,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { hints, program, v, - analysisInfoes, + analysisInfos, isExhale) (ch, inverseFunctions) @@ -439,7 +439,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { hints: Seq[Term], program: ast.Program, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes, + analysisInfos: DependencyAnalysisInfos, isExhale: Boolean) : QuantifiedBasicChunk = { @@ -460,7 +460,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optSingletonArguments.map(_.head), optSingletonArgumentsExp.map(_.head), hints, - v.decider.getAnalysisInfo(analysisInfoes), + v.decider.getAnalysisInfo(analysisInfos), isExhale) case predicate: ast.Predicate => @@ -477,7 +477,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optSingletonArguments, optSingletonArgumentsExp, hints, - v.decider.getAnalysisInfo(analysisInfoes), + v.decider.getAnalysisInfo(analysisInfos), isExhale) case wand: ast.MagicWand => @@ -494,7 +494,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optSingletonArguments, optSingletonArgumentsExp, hints, - v.decider.getAnalysisInfo(analysisInfoes), + v.decider.getAnalysisInfo(analysisInfos), isExhale) case other => @@ -674,16 +674,16 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { smDef: SnapshotMapDefinition, v: Verifier) : (PermMapDefinition, PmCache) = { - val analysisInfoes = DependencyAnalysisInfoes.create("summarizing heap", DependencyType.Internal, NoDependencyAnalysisMerge()) + val analysisInfos = DependencyAnalysisInfos.create("summarizing heap", DependencyType.Internal, NoDependencyAnalysisMerge()) val res = Verifier.config.mapCache(s.pmCache.get(resource, relevantChunks)) match { case Some(pmDef) => - v.decider.assume(pmDef.valueDefinitions, Option.when(withExp)(DebugExp.createInstance("value definitions", isInternal_ = true)), enforceAssumption = false, analysisInfoes=analysisInfoes) + v.decider.assume(pmDef.valueDefinitions, Option.when(withExp)(DebugExp.createInstance("value definitions", isInternal_ = true)), enforceAssumption = false, analysisInfos=analysisInfos) (pmDef, s.pmCache) case _ => val (pm, valueDef) = quantifiedChunkSupporter.summarisePerm(s, relevantChunks, formalQVars, resource, smDef, v) val pmDef = PermMapDefinition(resource, pm, valueDef) - v.decider.assume(valueDef, Option.when(withExp)(DebugExp.createInstance("value definitions", isInternal_ = true)), enforceAssumption = false, analysisInfoes=analysisInfoes) + v.decider.assume(valueDef, Option.when(withExp)(DebugExp.createInstance("value definitions", isInternal_ = true)), enforceAssumption = false, analysisInfos=analysisInfos) (pmDef, s.pmCache + ((resource, relevantChunks) -> pmDef)) } res @@ -713,7 +713,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optSmDomainDefinitionCondition: Option[Term] = None, optQVarsInstantiations: Option[Seq[Term]] = None) : (SnapshotMapDefinition, SnapshotMapCache) = { - val analysisInfoes = DependencyAnalysisInfoes.create("summarizing heap", DependencyType.Internal, NoDependencyAnalysisMerge()) + val analysisInfos = DependencyAnalysisInfos.create("summarizing heap", DependencyType.Internal, NoDependencyAnalysisMerge()) def emitSnapshotMapDefinition(s: State, smDef: SnapshotMapDefinition, @@ -726,7 +726,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { case None => val comment = "Definitional axioms for snapshot map domain" v.decider.prover.comment(comment) - v.decider.assume(smDef.domainDefinitions, Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, analysisInfoes=analysisInfoes) + v.decider.assume(smDef.domainDefinitions, Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, analysisInfos=analysisInfos) case Some(_instantiations) => // TODO: Avoid pattern matching on resource val instantiations = resource match { @@ -738,7 +738,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.prover.comment(comment) // TODO: Avoid cast to Quantification v.decider.assume(smDef.domainDefinitions.map(_.asInstanceOf[Quantification].instantiate(instantiations)), - Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, analysisInfoes=analysisInfoes) + Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, analysisInfos=analysisInfos) } } @@ -746,7 +746,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { case None => val comment = "Definitional axioms for snapshot map values" v.decider.prover.comment(comment) - v.decider.assume(smDef.valueDefinitions, Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, analysisInfoes=analysisInfoes) + v.decider.assume(smDef.valueDefinitions, Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, analysisInfos=analysisInfos) case Some(_instantiations) => // TODO: Avoid pattern matching on resource val instantiations = resource match { @@ -758,7 +758,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.prover.comment(comment) // TODO: Avoid cast to Quantification v.decider.assume(smDef.valueDefinitions.map(_.asInstanceOf[Quantification].instantiate(instantiations)), - Option.when(withExp)(DebugExp.createInstance(comment, true)), enforceAssumption = false, analysisInfoes=analysisInfoes) + Option.when(withExp)(DebugExp.createInstance(comment, true)), enforceAssumption = false, analysisInfos=analysisInfos) } } @@ -856,7 +856,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { negativePermissionReason: => ErrorReason, notInjectiveReason: => ErrorReason, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -887,7 +887,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { qidPrefix = qid, v = v, program = s.program, - analysisInfoes = analysisInfoes, + analysisInfos = analysisInfos, isExhale = false) val (effectiveTriggers, effectiveTriggersQVars, effectiveTriggersQVarExps) = optTrigger match { @@ -933,9 +933,9 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val commentGlobals = "Nested auxiliary terms: globals" v.decider.prover.comment(commentGlobals) - val analysisInfoesGlobals = DependencyAnalysisInfoes.create(commentGlobals, DependencyType.Internal, NoDependencyAnalysisMerge()) + val analysisInfosGlobals = DependencyAnalysisInfos.create(commentGlobals, DependencyType.Internal, NoDependencyAnalysisMerge()) v.decider.assume(auxGlobals, Option.when(withExp)(DebugExp.createInstance(description=commentGlobals, children=auxGlobalsExp.get)), - enforceAssumption = false, analysisInfoes=analysisInfoesGlobals) + enforceAssumption = false, analysisInfos=analysisInfosGlobals) val commentNonGlobals = "Nested auxiliary terms: non-globals" v.decider.prover.comment(commentNonGlobals) @@ -943,13 +943,13 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { auxNonGlobals.map(_.copy( vars = effectiveTriggersQVars, triggers = effectiveTriggers)), - Option.when(withExp)(DebugExp.createInstance(description=commentNonGlobals, children=auxNonGlobalsExp.get)), enforceAssumption = false, analysisInfoes=analysisInfoes.withDependencyType(DependencyType.Internal)) + Option.when(withExp)(DebugExp.createInstance(description=commentNonGlobals, children=auxNonGlobalsExp.get)), enforceAssumption = false, analysisInfos=analysisInfos.withDependencyType(DependencyType.Internal)) val nonNegImplication = Implies(tCond, perms.IsNonNegative(tPerm)) val nonNegImplicationExp = eCond.map(c => ast.Implies(c, ast.PermGeCmp(ePerm.get, ast.NoPerm()())())(c.pos, c.info, c.errT)) val nonNegTerm = Forall(qvars, Implies(FunctionPreconditionTransformer.transform(nonNegImplication, s.program), nonNegImplication), Nil) // TODO: Replace by QP-analogue of permissionSupporter.assertNotNegative - v.decider.assert(nonNegTerm, analysisInfoes) { + v.decider.assert(nonNegTerm, analysisInfos) { case true => /* TODO: Can we omit/simplify the injectivity check in certain situations? */ @@ -971,7 +971,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.prover.comment(comment) val completeReceiverInjectivityCheck = Implies(FunctionPreconditionTransformer.transform(receiverInjectivityCheck, s.program), receiverInjectivityCheck) - v.decider.assert(completeReceiverInjectivityCheck, analysisInfoes) { + v.decider.assert(completeReceiverInjectivityCheck, analysisInfos) { case true => val ax = inverseFunctions.axiomInversesOfInvertibles val inv = inverseFunctions.copy(axiomInversesOfInvertibles = Forall(ax.vars, ax.body, effectiveTriggers)) @@ -980,8 +980,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.prover.comment(comment) val definitionalAxiomMark = v.decider.setPathConditionMark() v.decider.assume(inv.definitionalAxioms.map(a => FunctionPreconditionTransformer.transform(a, s.program)), - Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, analysisInfoes=analysisInfoes) - v.decider.assume(inv.definitionalAxioms, Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, analysisInfoes=analysisInfoes) + Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, analysisInfos=analysisInfos) + v.decider.assume(inv.definitionalAxioms, Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), enforceAssumption = false, analysisInfos=analysisInfos) val conservedPcs = if (s.recordPcs) (s.conservedPcs.head :+ v.decider.pcs.after(definitionalAxiomMark)) +: s.conservedPcs.tail else s.conservedPcs @@ -1004,9 +1004,9 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { triggers = effectiveTriggers, qidPrefix = qid ) - v.decider.assume(pcsForChunk, pcsForChunkExp, pcsForChunkExp, analysisInfoes.withDependencyType(DependencyType.Internal)) + v.decider.assume(pcsForChunk, pcsForChunkExp, pcsForChunkExp, analysisInfos.withDependencyType(DependencyType.Internal)) }) - val (fr1, h1) = v.stateConsolidator(s).merge(s.functionRecorder, s, s.h, Heap(Seq(ch)), v, DefaultDependencyAnalysisInfoes) + val (fr1, h1) = v.stateConsolidator(s).merge(s.functionRecorder, s, s.h, Heap(Seq(ch)), v, DefaultDependencyAnalysisInfos) val (smCache1, fr2) = if (s.isUsedAsTrigger(resource)){ // TODO: Why not formalQVars? Used as codomainVars, see above. @@ -1028,7 +1028,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val qvarsToInv = inv.qvarsToInversesOf(codomainVars) val condOfInv = tCond.replace(qvarsToInv) v.decider.assume(Forall(codomainVars, Implies(condOfInv, trigger), Trigger(inv.inversesOf(codomainVars))), - Option.when(withExp)(DebugExp.createInstance("Inverse Trigger", true)), analysisInfoes.withDependencyType(DependencyType.Trigger)) + Option.when(withExp)(DebugExp.createInstance("Inverse Trigger", true)), analysisInfos.withDependencyType(DependencyType.Trigger)) val newFuncRec = fr1.recordFvfAndDomain(smDef1) (smCache1, newFuncRec) } else { @@ -1042,12 +1042,12 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { Q(s1, v) case false => val failure = createFailure(pve dueTo notInjectiveReason, v, s, receiverInjectivityCheck, "QP receiver is injective") - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(completeReceiverInjectivityCheck, analysisInfoes, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(completeReceiverInjectivityCheck, analysisInfos, v.reportFurtherErrors()) if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, v) else failure } case false => val failure = createFailure(pve dueTo negativePermissionReason, v, s, nonNegImplication, nonNegImplicationExp) - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(nonNegTerm, analysisInfoes, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(nonNegTerm, analysisInfos, v.reportFurtherErrors()) if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, v) else failure } @@ -1065,7 +1065,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { resourceTriggerFactory: Term => Term, /* Trigger with some snapshot */ mergeAndTrigger: Boolean, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { @@ -1074,19 +1074,19 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val comment = "Definitional axioms for singleton-SM's value" v.decider.prover.comment(comment) val definitionalAxiomMark = v.decider.setPathConditionMark() - v.decider.assumeDefinition(smValueDef, Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), analysisInfoes) + v.decider.assumeDefinition(smValueDef, Option.when(withExp)(DebugExp.createInstance(comment, isInternal_ = true)), analysisInfos) val conservedPcs = if (s.recordPcs) (s.conservedPcs.head :+ v.decider.pcs.after(definitionalAxiomMark)) +: s.conservedPcs.tail else s.conservedPcs - val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(formalQVars, formalQVarsExp, resource, tArgs, eArgs, tPerm, ePerm, sm, s.program, v, analysisInfoes, isExhale=false) + val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(formalQVars, formalQVarsExp, resource, tArgs, eArgs, tPerm, ePerm, sm, s.program, v, analysisInfos, isExhale=false) val s1 = if (mergeAndTrigger) { - val (fr1, h1) = v.stateConsolidator(s).merge(s.functionRecorder, s, s.h, Heap(Seq(ch)), v, DefaultDependencyAnalysisInfoes) + val (fr1, h1) = v.stateConsolidator(s).merge(s.functionRecorder, s, s.h, Heap(Seq(ch)), v, DefaultDependencyAnalysisInfos) val interpreter = new NonQuantifiedPropertyInterpreter(h1.values, v) val resourceDescription = Resources.resourceDescriptions(ch.resourceID) val pcs = interpreter.buildPathConditionsForChunk(ch, resourceDescription.instanceProperties(s.mayAssumeUpperBounds)) - pcs.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), analysisInfoes)) + pcs.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), analysisInfos)) val smCache1 = if (s.isUsedAsTrigger(resource)) { val (relevantChunks, _) = @@ -1094,7 +1094,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val (smDef1, smCache1) = quantifiedChunkSupporter.summarisingSnapshotMap( s, resource, formalQVars, relevantChunks, v) - v.decider.assume(resourceTriggerFactory(smDef1.sm), Option.when(withExp)(DebugExp.createInstance("Resource Trigger", true)), analysisInfoes.withDependencyType(DependencyType.Trigger)) + v.decider.assume(resourceTriggerFactory(smDef1.sm), Option.when(withExp)(DebugExp.createInstance("Resource Trigger", true)), analysisInfos.withDependencyType(DependencyType.Trigger)) smCache1 } else { s.smCache @@ -1138,7 +1138,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { notInjectiveReason: => ErrorReason, insufficientPermissionReason: => ErrorReason, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -1174,7 +1174,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val comment = "Nested auxiliary terms: globals" v.decider.prover.comment(comment) - v.decider.assume(auxGlobals, Option.when(withExp)(DebugExp.createInstance(description=comment, children=auxGlobalsExp.get)), enforceAssumption = false, analysisInfoes=DependencyAnalysisInfoes.create(comment, DependencyType.Internal)) + v.decider.assume(auxGlobals, Option.when(withExp)(DebugExp.createInstance(description=comment, children=auxGlobalsExp.get)), enforceAssumption = false, analysisInfos=DependencyAnalysisInfos.create(comment, DependencyType.Internal)) val comment2 = "Nested auxiliary terms: non-globals" v.decider.prover.comment(comment2) @@ -1184,10 +1184,10 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.assume( auxNonGlobals.map(_.copy( vars = effectiveTriggersQVars, - triggers = effectiveTriggers)), Option.when(withExp)(DebugExp.createInstance(description=comment2, children=auxNonGlobalsExp.get)), enforceAssumption = false, analysisInfoes=analysisInfoes.withDependencyType(DependencyType.Internal)) + triggers = effectiveTriggers)), Option.when(withExp)(DebugExp.createInstance(description=comment2, children=auxNonGlobalsExp.get)), enforceAssumption = false, analysisInfos=analysisInfos.withDependencyType(DependencyType.Internal)) case Some(_) => /* Explicit triggers were provided. */ - v.decider.assume(auxNonGlobals, Option.when(withExp)(DebugExp.createInstance(description=comment2, children=auxNonGlobalsExp.get)), enforceAssumption = false, analysisInfoes=analysisInfoes.withDependencyType(DependencyType.Internal)) + v.decider.assume(auxNonGlobals, Option.when(withExp)(DebugExp.createInstance(description=comment2, children=auxNonGlobalsExp.get)), enforceAssumption = false, analysisInfos=analysisInfos.withDependencyType(DependencyType.Internal)) } val nonNegImplication = Implies(tCond, perms.IsNonNegative(tPerm)) @@ -1195,11 +1195,11 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val nonNegTerm = Forall(qvars, Implies(FunctionPreconditionTransformer.transform(nonNegImplication, s.program), nonNegImplication), Nil) val nonNegExp = qvarExps.map(qv => ast.Forall(qv, Nil, nonNegImplicationExp.get)()) // TODO: Replace by QP-analogue of permissionSupporter.assertNotNegative - v.decider.assert(nonNegTerm, analysisInfoes) { + v.decider.assert(nonNegTerm, analysisInfos) { case true => val hints = quantifiedChunkSupporter.extractHints(Some(tCond), tArgs) val chunkOrderHeuristics = - qpAppChunkOrderHeuristics(inverseFunctions.invertibles, qvars, hints, v, analysisInfoes) + qpAppChunkOrderHeuristics(inverseFunctions.invertibles, qvars, hints, v, analysisInfos) val loss = if (!Verifier.config.unsafeWildcardOptimization() || (resource.isInstanceOf[ast.Location] && s.permLocations.contains(resource.asInstanceOf[ast.Location]))) PermTimes(tPerm, s.permissionScalingFactor) @@ -1231,7 +1231,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { program = s.program) v.decider.prover.comment("Check receiver injectivity") val completeReceiverInjectivityCheck = Implies(FunctionPreconditionTransformer.transform(receiverInjectivityCheck, s.program), receiverInjectivityCheck) - v.decider.assert(completeReceiverInjectivityCheck, analysisInfoes) { + v.decider.assert(completeReceiverInjectivityCheck, analysisInfos) { case true => val qvarsToInvOfLoc = inverseFunctions.qvarsToInversesOf(formalQVars) val condOfInvOfLoc = tCond.replace(qvarsToInvOfLoc) @@ -1245,8 +1245,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.prover.comment("Definitional axioms for inverse functions") v.decider.assume(inverseFunctions.definitionalAxioms.map(a => FunctionPreconditionTransformer.transform(a, s.program)), - Option.when(withExp)(DebugExp.createInstance("Inverse Function Axioms", isInternal_ = true)), enforceAssumption = false, analysisInfoes = analysisInfoes) - v.decider.assume(inverseFunctions.definitionalAxioms, Option.when(withExp)(DebugExp.createInstance("Inverse function axiom", isInternal_ = true)), enforceAssumption = false, analysisInfoes=analysisInfoes) + Option.when(withExp)(DebugExp.createInstance("Inverse Function Axioms", isInternal_ = true)), enforceAssumption = false, analysisInfos = analysisInfos) + v.decider.assume(inverseFunctions.definitionalAxioms, Option.when(withExp)(DebugExp.createInstance("Inverse function axiom", isInternal_ = true)), enforceAssumption = false, analysisInfos=analysisInfos) if (s.isUsedAsTrigger(resource)){ v.decider.assume( @@ -1254,7 +1254,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { formalQVars, Implies(condOfInvOfLoc, ResourceTriggerFunction(resource, smDef1.get.sm, formalQVars, s.program)), Trigger(inverseFunctions.inversesOf(formalQVars)))), - Option.when(withExp)(DebugExp.createInstance("Inverse Function", isInternal_ = true)), enforceAssumption = false, analysisInfoes=analysisInfoes) + Option.when(withExp)(DebugExp.createInstance("Inverse Function", isInternal_ = true)), enforceAssumption = false, analysisInfos=analysisInfos) } @@ -1268,7 +1268,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { createFailure(pve dueTo insufficientPermissionReason/*InsufficientPermission(acc.loc)*/, v, s, "consuming QP"), formalQVars, v, - analysisInfoes)((s2, heap, rPerm, rPermExp, v2) => { + analysisInfos)((s2, heap, rPerm, rPermExp, v2) => { val (relevantChunks, otherChunks) = quantifiedChunkSupporter.splitHeap[QuantifiedBasicChunk]( heap, ChunkIdentifier(resource, s.program)) @@ -1286,7 +1286,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { rPermExp, chunkOrderHeuristics, v2, - analysisInfoes) + analysisInfos) val optSmDomainDefinitionCondition2 = if (s3.smDomainNeeded) Some(And(condOfInvOfLoc, IsPositive(lossOfInvOfLoc), And(imagesOfFormalQVars))) else None @@ -1318,15 +1318,15 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { qid, v2, s.program, - analysisInfoes, + analysisInfos, isExhale = true ) val debugExp = Option.when(withExp)(DebugExp.createInstance("Inverse functions for quantified permission", isInternal_ = true)) - v.decider.assume(FunctionPreconditionTransformer.transform(inverseFunctions.axiomInvertiblesOfInverses, s3.program), debugExp, analysisInfoes) - v.decider.assume(inverseFunctions.axiomInvertiblesOfInverses, debugExp, analysisInfoes) + v.decider.assume(FunctionPreconditionTransformer.transform(inverseFunctions.axiomInvertiblesOfInverses, s3.program), debugExp, analysisInfos) + v.decider.assume(inverseFunctions.axiomInvertiblesOfInverses, debugExp, analysisInfos) val substitutedAxiomInversesOfInvertibles = inverseFunctions.axiomInversesOfInvertibles.replace(formalQVars, tArgs) - v.decider.assume(FunctionPreconditionTransformer.transform(substitutedAxiomInversesOfInvertibles, s3.program), debugExp, analysisInfoes) - v.decider.assume(substitutedAxiomInversesOfInvertibles, debugExp, analysisInfoes) + v.decider.assume(FunctionPreconditionTransformer.transform(substitutedAxiomInversesOfInvertibles, s3.program), debugExp, analysisInfos) + v.decider.assume(substitutedAxiomInversesOfInvertibles, debugExp, analysisInfos) val h2 = Heap(remainingChunks ++ otherChunks) val s4 = s3.copy(smCache = smCache2, constrainableARPs = s.constrainableARPs) @@ -1355,7 +1355,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { lossExp, chunkOrderHeuristics, v, - analysisInfoes + analysisInfos ) permissionRemovalResult match { case (Complete(), s2, remainingChunks) => @@ -1382,13 +1382,13 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { } case (Incomplete(_, _), s2, _) => val failure = createFailure(pve dueTo insufficientPermissionReason, v, s2, "QP consume") - if(s2.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfoes, v.reportFurtherErrors()) + if(s2.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfos, v.reportFurtherErrors()) if(s2.retryLevel == 0 && v.reportFurtherErrors() && Verifier.config.disableInfeasibilityChecks()) failure combine Q(s2, s2.h, None, v) else failure } } case false => val failure = createFailure(pve dueTo notInjectiveReason, v, s, receiverInjectivityCheck, "QP receiver injective") - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(receiverInjectivityCheck, analysisInfoes, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(receiverInjectivityCheck, analysisInfos, v.reportFurtherErrors()) if(s.retryLevel == 0 && v.reportFurtherErrors()){ val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) // TODO ake: function recorder failure combine Q(s, s.h, if(returnSnap) Some(snap) else None, v) @@ -1398,7 +1398,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { } case false => val failure = createFailure(pve dueTo negativePermissionReason, v, s, nonNegTerm, nonNegExp) - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(nonNegTerm, analysisInfoes, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(nonNegTerm, analysisInfos, v.reportFurtherErrors()) if(s.retryLevel == 0 && v.reportFurtherErrors()){ val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) failure combine Q(s, s.h, if(returnSnap) Some(snap) else None, v) @@ -1421,7 +1421,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optChunkOrderHeuristic: Option[Seq[QuantifiedBasicChunk] => Seq[QuantifiedBasicChunk]], pve: PartialVerificationError, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { @@ -1433,7 +1433,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { heuristics case None => quantifiedChunkSupporter.singleReceiverChunkOrderHeuristic(arguments, - quantifiedChunkSupporter.extractHints(None, arguments), v, analysisInfoes) + quantifiedChunkSupporter.extractHints(None, arguments), v, analysisInfos) } if (s.exhaleExt) { @@ -1442,7 +1442,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { case wand: ast.MagicWand => createFailure(pve dueTo MagicWandChunkNotFound(wand), v, s, "single QP consume inside package") case _ => sys.error(s"Found resource $resourceAccess, which is not yet supported as a quantified resource.") } - magicWandSupporter.transfer(s, permissions, permissionsExp, failure, Seq(), v, analysisInfoes)((s1, h1, rPerm, rPermExp, v1) => { + magicWandSupporter.transfer(s, permissions, permissionsExp, failure, Seq(), v, analysisInfos)((s1, h1, rPerm, rPermExp, v1) => { val (relevantChunks, otherChunks) = quantifiedChunkSupporter.splitHeap[QuantifiedBasicChunk](h1, chunkIdentifier) val (result, s2, remainingChunks) = quantifiedChunkSupporter.removePermissions( @@ -1458,7 +1458,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { rPermExp, chunkOrderHeuristics, v, - analysisInfoes + analysisInfos ) val h2 = Heap(remainingChunks ++ otherChunks) val (smDef1, smCache1) = @@ -1477,7 +1477,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { } val consumedChunk = quantifiedChunkSupporter.createSingletonQuantifiedChunk( - codomainQVars, codomainQVarsExp, resource, arguments, argumentsExp, permsTaken, permsTakenExp, smDef1.sm, s.program, v1, analysisInfoes, isExhale=true) + codomainQVars, codomainQVarsExp, resource, arguments, argumentsExp, permsTaken, permsTakenExp, smDef1.sm, s.program, v1, analysisInfos, isExhale=true) val s3 = s2.copy(functionRecorder = s2.functionRecorder.recordFvfAndDomain(smDef1), smCache = smCache1) (result, s3, h2, Some(consumedChunk)) @@ -1508,7 +1508,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { permissionsExp, chunkOrderHeuristics, v, - analysisInfoes + analysisInfos ) result match { case (Complete(), s1, remainingChunks) => @@ -1536,7 +1536,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { case wand: ast.MagicWand => createFailure(pve dueTo MagicWandChunkNotFound(wand), v, s, "single QP consume") case _ => sys.error(s"Found resource $resourceAccess, which is not yet supported as a quantified resource.") } - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfoes, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfos, v.reportFurtherErrors()) if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, s.h, None, v) else failure } } @@ -1549,7 +1549,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { perms: Term, permsExp: Option[ast.Exp], v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) : ConsumptionResult = { var permsAvailable: Term = NoPerm @@ -1566,7 +1566,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { // final check val result = - if (v.decider.check(tookEnoughCheck, Verifier.config.assertTimeout.getOrElse(0), analysisInfoes) /* This check is a must-check, i.e. an assert */ ) + if (v.decider.check(tookEnoughCheck, Verifier.config.assertTimeout.getOrElse(0), analysisInfos) /* This check is a must-check, i.e. an assert */ ) Complete() else Incomplete(PermMinus(permsAvailable, perms), permsAvailableExp.map(pa => ast.PermSub(pa, permsExp.get)())) @@ -1590,7 +1590,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { permsExp: Option[ast.Exp], // p(rs) chunkOrderHeuristic: Seq[QuantifiedBasicChunk] => Seq[QuantifiedBasicChunk], v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) : (ConsumptionResult, State, Seq[QuantifiedBasicChunk]) = { val rmPermRecord = new CommentRecord("removePermissions", s, v.decider.pcs) @@ -1607,7 +1607,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val constrainPermissions = !consumeExactRead(perms, s.constrainableARPs) if (s.assertReadAccessOnly) { - val result = assertReadPermission(s, candidates, codomainQVars, condition, perms, permsExp, v, analysisInfoes) + val result = assertReadPermission(s, candidates, codomainQVars, condition, perms, permsExp, v, analysisInfos) return (result, s, relevantChunks) } @@ -1671,23 +1671,23 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { if (constrainPermissions) { v.decider.prover.comment(s"Constrain original permissions $perms") - v.decider.assume(permissionConstraint, permissionConstraintExp, permissionConstraintExp, analysisInfoes) + v.decider.assume(permissionConstraint, permissionConstraintExp, permissionConstraintExp, analysisInfos) remainingChunks = - remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.getAnalysisInfo(analysisInfoes)) + remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.getAnalysisInfo(analysisInfos)) } else { v.decider.prover.comment(s"Chunk depleted?") - val chunkDepleted = v.decider.check(depletedCheck, Verifier.config.splitTimeout(), analysisInfoes.withDependencyType(DependencyType.Internal)) + val chunkDepleted = v.decider.check(depletedCheck, Verifier.config.splitTimeout(), analysisInfos.withDependencyType(DependencyType.Internal)) if (!chunkDepleted) { val unusedCheck = Forall(codomainQVars, ithPTaken === NoPerm, Nil) - val chunkUnused = v.decider.check(unusedCheck, Verifier.config.checkTimeout(), analysisInfoes.withDependencyType(DependencyType.Internal)) + val chunkUnused = v.decider.check(unusedCheck, Verifier.config.checkTimeout(), analysisInfos.withDependencyType(DependencyType.Internal)) if (chunkUnused) { remainingChunks = remainingChunks :+ ithChunk } else { remainingChunks = - remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.getAnalysisInfo(analysisInfoes)) + remainingChunks :+ QuantifiedBasicChunk.permMinus(ithChunk, ithPTaken, ithPTakenExp, v.decider.getAnalysisInfo(analysisInfos)) } }else{ - val _ = GeneralChunk.withPerm(ithChunk, ithPTaken, None, v.decider.getAnalysisInfo(analysisInfoes), isExhale=true) + val _ = GeneralChunk.withPerm(ithChunk, ithPTaken, None, v.decider.getAnalysisInfo(analysisInfos), isExhale=true) } } @@ -1700,7 +1700,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { Forall(codomainQVars, Implies(condition, ithPNeeded === NoPerm), Nil) v.decider.prover.comment(s"Intermediate check if already taken enough permissions") - success = if (v.decider.check(tookEnoughCheck, Verifier.config.splitTimeout(), analysisInfoes)) { + success = if (v.decider.check(tookEnoughCheck, Verifier.config.splitTimeout(), analysisInfos)) { Complete() } else { Incomplete(ithPNeeded, ithPNeededExp) @@ -1710,7 +1710,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { v.decider.prover.comment("Final check if taken enough permissions") success = - if (success.isComplete || v.decider.check(tookEnoughCheck, Verifier.config.assertTimeout.getOrElse(0), analysisInfoes) /* This check is a must-check, i.e. an assert */) + if (success.isComplete || v.decider.check(tookEnoughCheck, Verifier.config.assertTimeout.getOrElse(0), analysisInfos) /* This check is a must-check, i.e. an assert */) Complete() else success @@ -2029,7 +2029,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { matchingChunks ++ otherChunks } - override def findChunk(chunks: Iterable[Chunk], chunk: QuantifiedChunk, v: Verifier, analysisInfoes: DependencyAnalysisInfoes): Option[QuantifiedChunk] = { + override def findChunk(chunks: Iterable[Chunk], chunk: QuantifiedChunk, v: Verifier, analysisInfos: DependencyAnalysisInfos): Option[QuantifiedChunk] = { val lr = chunk match { case qfc: QuantifiedFieldChunk if qfc.invs.isDefined => val qvarsAndInverses = qfc.invs.get.qvarsToInverses.map(qvi => (qvi._1, App(qvi._2, qfc.invs.get.additionalArguments.toSeq ++ qfc.quantifiedVars))) @@ -2070,11 +2070,11 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { // Hence, we need to compare the conditions for equality in addition to verifying that the receivers match. val equalityCond = And(cond.replace(chunk.quantifiedVars, singletonArguments), cCond.replace(ch.quantifiedVars, cSingletonArguments)) - val result = v.decider.check(And(equalityCond, equalityTerm), Verifier.config.checkTimeout(), analysisInfoes) + val result = v.decider.check(And(equalityCond, equalityTerm), Verifier.config.checkTimeout(), analysisInfos) if (result) { // Learn the equality val debugExp = Option.when(withExp)(DebugExp.createInstance("Chunks alias", isInternal_ = true)) - v.decider.assume(equalityTerm, debugExp, analysisInfoes.withDependencyType(DependencyType.Internal)) + v.decider.assume(equalityTerm, debugExp, analysisInfos.withDependencyType(DependencyType.Internal)) } result case _ => false @@ -2100,11 +2100,11 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val condReplaced = cCond.replace(cQvars, quantVars) val secondReplaced = p._2.replace(cQvars, quantVars) val equalityTerm = SimplifyingForall(quantVars, And(Seq(p._1 === secondReplaced, cond === condReplaced)), Seq()) - val result = v.decider.check(equalityTerm, Verifier.config.checkTimeout(), analysisInfoes) + val result = v.decider.check(equalityTerm, Verifier.config.checkTimeout(), analysisInfos) if (result) { // Learn the equality val debugExp = Option.when(withExp)(DebugExp.createInstance("Chunks alias", isInternal_ = true)) - v.decider.assume(equalityTerm, debugExp, analysisInfoes.withDependencyType(DependencyType.Internal)) + v.decider.assume(equalityTerm, debugExp, analysisInfos.withDependencyType(DependencyType.Internal)) } result } else { @@ -2166,7 +2166,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { (fr2, sm, Forall(qVars, smDef, triggers)) } - def qpAppChunkOrderHeuristics(receiverTerms: Seq[Term], quantVars: Seq[Var], hints: Seq[Term], v: Verifier, analysisInfoes: DependencyAnalysisInfoes) + def qpAppChunkOrderHeuristics(receiverTerms: Seq[Term], quantVars: Seq[Var], hints: Seq[Term], v: Verifier, analysisInfos: DependencyAnalysisInfos) : Seq[QuantifiedBasicChunk] => Seq[QuantifiedBasicChunk] = { // Heuristics that looks for quantified chunks that have the same shape (as in, the same number and types of // quantified variables) and identical receiver terms. @@ -2192,7 +2192,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { receiverTerms.zip(cInvertibles).forall(p => { if (cQvars.length == quantVars.length && cQvars.zip(quantVars).forall(vars => vars._1.sort == vars._2.sort)) { val secondReplaced = p._2.replace(cQvars, quantVars) - v.decider.check(SimplifyingForall(quantVars, p._1 === secondReplaced, Seq()), Verifier.config.checkTimeout(), analysisInfoes) + v.decider.check(SimplifyingForall(quantVars, p._1 === secondReplaced, Seq()), Verifier.config.checkTimeout(), analysisInfos) } else { false } @@ -2208,7 +2208,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { } } - def singleReceiverChunkOrderHeuristic(receiver: Seq[Term], hints: Seq[Term], v: Verifier, analysisInfoes: DependencyAnalysisInfoes) + def singleReceiverChunkOrderHeuristic(receiver: Seq[Term], hints: Seq[Term], v: Verifier, analysisInfos: DependencyAnalysisInfos) : Seq[QuantifiedBasicChunk] => Seq[QuantifiedBasicChunk] = { // Heuristic that emulates greedy Silicon behavior for consuming single-receiver permissions. // First: Find singleton chunks that have the same receiver syntactically. @@ -2225,7 +2225,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { } else { val greedyMatch = chunks.find(c => c.singletonArguments match { case Some(args) if args.length == receiver.length => - args.zip(receiver).forall(ts => v.decider.check(ts._1 === ts._2, Verifier.config.checkTimeout(), analysisInfoes)) + args.zip(receiver).forall(ts => v.decider.check(ts._1 === ts._2, Verifier.config.checkTimeout(), analysisInfos)) case _ => false }).toSeq diff --git a/src/main/scala/rules/StateConsolidator.scala b/src/main/scala/rules/StateConsolidator.scala index 243080b9a..20670460c 100644 --- a/src/main/scala/rules/StateConsolidator.scala +++ b/src/main/scala/rules/StateConsolidator.scala @@ -28,11 +28,11 @@ import scala.annotation.unused trait StateConsolidationRules extends SymbolicExecutionRules { def consolidate(s: State, v: Verifier): State def consolidateOptionally(s: State, v: Verifier): State - def merge(fr: FunctionRecorder, s: State, h: Heap, newH: Heap, v: Verifier, analysisInfoes: DependencyAnalysisInfoes): (FunctionRecorder, Heap) - def merge(fr: FunctionRecorder, s: State, h: Heap, ch: NonQuantifiedChunk, v: Verifier, analysisInfoes: DependencyAnalysisInfoes): (FunctionRecorder, Heap) + def merge(fr: FunctionRecorder, s: State, h: Heap, newH: Heap, v: Verifier, analysisInfos: DependencyAnalysisInfos): (FunctionRecorder, Heap) + def merge(fr: FunctionRecorder, s: State, h: Heap, ch: NonQuantifiedChunk, v: Verifier, analysisInfos: DependencyAnalysisInfos): (FunctionRecorder, Heap) - protected def assumeUpperPermissionBoundForQPFields(s: State, v: Verifier, analysisInfoes: DependencyAnalysisInfoes): State - protected def assumeUpperPermissionBoundForQPFields(s: State, heaps: Seq[Heap], v: Verifier, analysisInfoes: DependencyAnalysisInfoes): State + protected def assumeUpperPermissionBoundForQPFields(s: State, v: Verifier, analysisInfos: DependencyAnalysisInfos): State + protected def assumeUpperPermissionBoundForQPFields(s: State, heaps: Seq[Heap], v: Verifier, analysisInfos: DependencyAnalysisInfos): State } /** Performs the minimal work necessary for any consolidator: merging two heaps combines the chunk @@ -44,15 +44,15 @@ class MinimalStateConsolidator extends StateConsolidationRules { def consolidate(s: State, @unused v: Verifier): State = s def consolidateOptionally(s: State, @unused v: Verifier): State = s - def merge(fr: FunctionRecorder, s: State, h: Heap, newH: Heap, v: Verifier, analysisInfoes: DependencyAnalysisInfoes): (FunctionRecorder, Heap) = + def merge(fr: FunctionRecorder, s: State, h: Heap, newH: Heap, v: Verifier, analysisInfos: DependencyAnalysisInfos): (FunctionRecorder, Heap) = (fr, Heap(h.values ++ newH.values)) - def merge(fr: FunctionRecorder, s: State, h: Heap, ch: NonQuantifiedChunk, v: Verifier, analysisInfoes: DependencyAnalysisInfoes): (FunctionRecorder, Heap) = + def merge(fr: FunctionRecorder, s: State, h: Heap, ch: NonQuantifiedChunk, v: Verifier, analysisInfos: DependencyAnalysisInfos): (FunctionRecorder, Heap) = (fr, h + ch) - protected def assumeUpperPermissionBoundForQPFields(s: State, @unused v: Verifier, @unused analysisInfoes: DependencyAnalysisInfoes): State = s + protected def assumeUpperPermissionBoundForQPFields(s: State, @unused v: Verifier, @unused analysisInfos: DependencyAnalysisInfos): State = s - protected def assumeUpperPermissionBoundForQPFields(s: State, @unused heaps: Seq[Heap], @unused v: Verifier, @unused analysisInfoes: DependencyAnalysisInfoes): State = s + protected def assumeUpperPermissionBoundForQPFields(s: State, @unused heaps: Seq[Heap], @unused v: Verifier, @unused analysisInfos: DependencyAnalysisInfos): State = s } /** Default implementation that merges as many known-alias chunks as possible, and deduces various @@ -64,7 +64,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol val comLog = new CommentRecord("state consolidation", s, v.decider.pcs) val sepIdentifier = v.symbExLog.openScope(comLog) - val analysisInfoes = DependencyAnalysisInfoes.create("state consolidation", DependencyType.Internal, NoDependencyAnalysisMerge()) // TODO ake: review + val analysisInfos = DependencyAnalysisInfos.create("state consolidation", DependencyType.Internal, NoDependencyAnalysisMerge()) // TODO ake: review v.decider.prover.comment("[state consolidation]") v.decider.prover.saturate(config.proverSaturationTimeouts.beforeIteration) @@ -84,9 +84,9 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol val roundLog = new CommentRecord("Round " + fixedPointRound, s, v.decider.pcs) val roundSepIdentifier = v.symbExLog.openScope(roundLog) - val (_functionRecorder, _mergedChunks, _newChunks, snapEqs) = singleMerge(functionRecorder, destChunks, newChunks, s.functionRecorderQuantifiedVariables().map(_._1), v, analysisInfoes) + val (_functionRecorder, _mergedChunks, _newChunks, snapEqs) = singleMerge(functionRecorder, destChunks, newChunks, s.functionRecorderQuantifiedVariables().map(_._1), v, analysisInfos) - snapEqs foreach (t => v.decider.assume(t, Option.when(withExp)(DebugExp.createInstance("Snapshot Equations", true)), analysisInfoes)) + snapEqs foreach (t => v.decider.assume(t, Option.when(withExp)(DebugExp.createInstance("Snapshot Equations", true)), analysisInfos)) functionRecorder = _functionRecorder mergedChunks = _mergedChunks @@ -104,12 +104,12 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol mergedChunks.filter(_.isInstanceOf[BasicChunk]) foreach { case ch: BasicChunk => val resource = Resources.resourceDescriptions(ch.resourceID) val pathCond = interpreter.buildPathConditionsForChunk(ch, resource.instanceProperties(s.mayAssumeUpperBounds)) - pathCond.foreach(p => v.decider.assume(v.decider.wrapWithDependencyAnalysisLabel(p._1, Set(ch)), Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), analysisInfoes)) + pathCond.foreach(p => v.decider.assume(v.decider.wrapWithDependencyAnalysisLabel(p._1, Set(ch)), Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), analysisInfos)) } Resources.resourceDescriptions foreach { case (id, desc) => val pathCond = interpreter.buildPathConditionsForResource(id, desc.delayedProperties(s.mayAssumeUpperBounds)) - pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), analysisInfoes)) + pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), analysisInfos)) } v.symbExLog.closeScope(sepIdentifier) @@ -120,7 +120,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol h = mergedHeaps.head, reserveHeaps = mergedHeaps.tail) - val s2 = assumeUpperPermissionBoundForQPFields(s1, v, analysisInfoes) + val s2 = assumeUpperPermissionBoundForQPFields(s1, v, analysisInfos) s2 } @@ -128,26 +128,26 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol if (s.retrying) consolidate(s, v) else s - def merge(fr: FunctionRecorder, s: State, h: Heap, ch: NonQuantifiedChunk, v: Verifier, analysisInfoes: DependencyAnalysisInfoes): (FunctionRecorder, Heap) = { - merge(fr, s, h, Heap(Seq(ch)), v, analysisInfoes) + def merge(fr: FunctionRecorder, s: State, h: Heap, ch: NonQuantifiedChunk, v: Verifier, analysisInfos: DependencyAnalysisInfos): (FunctionRecorder, Heap) = { + merge(fr, s, h, Heap(Seq(ch)), v, analysisInfos) } - def merge(fr1: FunctionRecorder, s: State, h: Heap, newH: Heap, v: Verifier, analysisInfoes: DependencyAnalysisInfoes): (FunctionRecorder, Heap) = { + def merge(fr1: FunctionRecorder, s: State, h: Heap, newH: Heap, v: Verifier, analysisInfos: DependencyAnalysisInfos): (FunctionRecorder, Heap) = { if(v.decider.isPathInfeasible) return (fr1, h) - val analysisInfoes1 = analysisInfoes.addInfo("merge", ast.NoPosition, DependencyType.Internal) + val analysisInfos1 = analysisInfos.addInfo("merge", ast.NoPosition, DependencyType.Internal) val mergeLog = new CommentRecord("Merge", null, v.decider.pcs) val sepIdentifier = v.symbExLog.openScope(mergeLog) - val (fr2, mergedChunks, newlyAddedChunks, snapEqs) = singleMerge(fr1, h.values.toSeq, newH.values.toSeq, s.functionRecorderQuantifiedVariables().map(_._1), v, analysisInfoes1) + val (fr2, mergedChunks, newlyAddedChunks, snapEqs) = singleMerge(fr1, h.values.toSeq, newH.values.toSeq, s.functionRecorderQuantifiedVariables().map(_._1), v, analysisInfos1) - v.decider.assume(snapEqs, Option.when(withExp)(DebugExp.createInstance("Snapshot", isInternal_ = true)), enforceAssumption = false, analysisInfoes1.withDependencyType(DependencyType.Internal)) + v.decider.assume(snapEqs, Option.when(withExp)(DebugExp.createInstance("Snapshot", isInternal_ = true)), enforceAssumption = false, analysisInfos1.withDependencyType(DependencyType.Internal)) val interpreter = new NonQuantifiedPropertyInterpreter(mergedChunks, v) newlyAddedChunks.filter(_.isInstanceOf[BasicChunk]) foreach { case ch: BasicChunk => val resource = Resources.resourceDescriptions(ch.resourceID) val pathCond = interpreter.buildPathConditionsForChunk(ch, resource.instanceProperties(s.mayAssumeUpperBounds)) - pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), analysisInfoes1.withDependencyType(DependencyType.Internal))) + pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2)), analysisInfos1.withDependencyType(DependencyType.Internal))) } v.symbExLog.closeScope(sepIdentifier) @@ -159,7 +159,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol newChunks: Seq[Chunk], qvars: Seq[Var], v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) : (FunctionRecorder, Seq[Chunk], Seq[Chunk], @@ -178,10 +178,10 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol * nextChunk: current chunk from the sequence of new chunks/of chunks to merge into the * sequence of destination chunks */ - val analysisInfoes = DependencyAnalysisInfoes.create("stat_consolidation", DependencyType.Internal, NoDependencyAnalysisMerge()) // TODO ake: review - val res = findMatchingChunk(accMergedChunks, nextChunk, v, analysisInfoes) match { + val analysisInfos = DependencyAnalysisInfos.create("stat_consolidation", DependencyType.Internal, NoDependencyAnalysisMerge()) // TODO ake: review + val res = findMatchingChunk(accMergedChunks, nextChunk, v, analysisInfos) match { case Some(ch) => - val resMerge = mergeChunks(fr1, ch, nextChunk, qvars, v, analysisInfoes) + val resMerge = mergeChunks(fr1, ch, nextChunk, qvars, v, analysisInfos) resMerge match { case Some((fr2, newChunk, snapEq)) => @@ -198,28 +198,28 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol result } - private def findMatchingChunk(chunks: Iterable[Chunk], chunk: Chunk, v: Verifier, analysisInfoes: DependencyAnalysisInfoes): Option[Chunk] = { + private def findMatchingChunk(chunks: Iterable[Chunk], chunk: Chunk, v: Verifier, analysisInfos: DependencyAnalysisInfos): Option[Chunk] = { chunk match { case chunk: BasicChunk => - chunkSupporter.findChunk[BasicChunk](chunks, chunk.id, chunk.args, v, analysisInfoes) - case chunk: QuantifiedChunk => quantifiedChunkSupporter.findChunk(chunks, chunk, v, analysisInfoes) + chunkSupporter.findChunk[BasicChunk](chunks, chunk.id, chunk.args, v, analysisInfos) + case chunk: QuantifiedChunk => quantifiedChunkSupporter.findChunk(chunks, chunk, v, analysisInfos) case _ => None } } // Merges two chunks that are aliases (i.e. that have the same id and the args are proven to be equal) // and returns the merged chunk or None, if the chunks could not be merged - private def mergeChunks(fr1: FunctionRecorder, chunk1: Chunk, chunk2: Chunk, qvars: Seq[Var], v: Verifier, analysisInfoes: DependencyAnalysisInfoes): Option[(FunctionRecorder, Chunk, Term)] = { - val result = mergeChunks1(fr1, chunk1, chunk2, qvars, v, analysisInfoes) + private def mergeChunks(fr1: FunctionRecorder, chunk1: Chunk, chunk2: Chunk, qvars: Seq[Var], v: Verifier, analysisInfos: DependencyAnalysisInfos): Option[(FunctionRecorder, Chunk, Term)] = { + val result = mergeChunks1(fr1, chunk1, chunk2, qvars, v, analysisInfos) result.map({case (fRec, ch, snapEq) => // v.decider.dependencyAnalyzer.addPermissionDependencies(Set(chunk1, chunk2), Set(), ch) (fRec, ch, v.decider.wrapWithDependencyAnalysisLabel(snapEq, Set(chunk1, chunk2)))}) } - private def mergeChunks1(fr1: FunctionRecorder, chunk1: Chunk, chunk2: Chunk, qvars: Seq[Var], v: Verifier, analysisInfoes: DependencyAnalysisInfoes): Option[(FunctionRecorder, Chunk, Term)] = (chunk1, chunk2) match { + private def mergeChunks1(fr1: FunctionRecorder, chunk1: Chunk, chunk2: Chunk, qvars: Seq[Var], v: Verifier, analysisInfos: DependencyAnalysisInfos): Option[(FunctionRecorder, Chunk, Term)] = (chunk1, chunk2) match { case (BasicChunk(rid1, id1, args1, args1Exp, snap1, snap1Exp, perm1, perm1Exp), BasicChunk(_, _, _, _, snap2, _, perm2, perm2Exp)) => val (fr2, combinedSnap, snapEq) = combineSnapshots(fr1, snap1, snap2, perm1, perm2, qvars, v) - Some(fr2, BasicChunk(rid1, id1, args1, args1Exp, combinedSnap, snap1Exp, PermPlus(perm1, perm2), perm1Exp.map(p1 => ast.PermAdd(p1, perm2Exp.get)()), v.decider.getAnalysisInfo(analysisInfoes.withDependencyType(DependencyType.Internal))), snapEq) + Some(fr2, BasicChunk(rid1, id1, args1, args1Exp, combinedSnap, snap1Exp, PermPlus(perm1, perm2), perm1Exp.map(p1 => ast.PermAdd(p1, perm2Exp.get)()), v.decider.getAnalysisInfo(analysisInfos.withDependencyType(DependencyType.Internal))), snapEq) case (l@QuantifiedFieldChunk(id1, fvf1, condition1, condition1Exp, perm1, perm1Exp, invs1, singletonRcvr1, singletonRcvr1Exp, hints1), r@QuantifiedFieldChunk(_, fvf2, _, _, perm2, perm2Exp, _, _, _, hints2)) => assert(l.quantifiedVars == Seq(`?r`)) @@ -229,14 +229,14 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol val permSum = PermPlus(perm1, perm2) val permSumExp = perm1Exp.map(p1 => ast.PermAdd(p1, perm2Exp.get)()) val bestHints = if (hints1.nonEmpty) hints1 else hints2 - Some(fr2, QuantifiedFieldChunk(id1, combinedSnap, condition1, condition1Exp, permSum, permSumExp, invs1, singletonRcvr1, singletonRcvr1Exp, bestHints, v.decider.getAnalysisInfo(analysisInfoes.withDependencyType(DependencyType.Internal))), snapEq) + Some(fr2, QuantifiedFieldChunk(id1, combinedSnap, condition1, condition1Exp, permSum, permSumExp, invs1, singletonRcvr1, singletonRcvr1Exp, bestHints, v.decider.getAnalysisInfo(analysisInfos.withDependencyType(DependencyType.Internal))), snapEq) case (l@QuantifiedPredicateChunk(id1, qVars1, qVars1Exp, psf1, _, _, perm1, perm1Exp, _, _, _, _), r@QuantifiedPredicateChunk(_, qVars2, qVars2Exp, psf2, condition2, condition2Exp, perm2, perm2Exp, invs2, singletonArgs2, singletonArgs2Exp, hints2)) => val (fr2, combinedSnap, snapEq) = quantifiedChunkSupporter.combinePredicateSnapshotMaps(fr1, id1.name, qVars2, qvars, psf1, psf2, l.perm.replace(qVars1, qVars2), r.perm, v) val permSum = PermPlus(perm1.replace(qVars1, qVars2), perm2) val permSumExp = perm1Exp.map(p1 => ast.PermAdd(p1.replace(qVars1Exp.get.zip(qVars2Exp.get).toMap), perm2Exp.get)()) - Some(fr2, QuantifiedPredicateChunk(id1, qVars2, qVars2Exp, combinedSnap, condition2, condition2Exp, permSum, permSumExp, invs2, singletonArgs2, singletonArgs2Exp, hints2, v.decider.getAnalysisInfo(analysisInfoes.withDependencyType(DependencyType.Internal))), snapEq) + Some(fr2, QuantifiedPredicateChunk(id1, qVars2, qVars2Exp, combinedSnap, condition2, condition2Exp, permSum, permSumExp, invs2, singletonArgs2, singletonArgs2Exp, hints2, v.decider.getAnalysisInfo(analysisInfos.withDependencyType(DependencyType.Internal))), snapEq) case _ => None } @@ -269,10 +269,10 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol } } - protected def assumeUpperPermissionBoundForQPFields(s: State, v: Verifier, analysisInfoes: DependencyAnalysisInfoes): State = - assumeUpperPermissionBoundForQPFields(s, s.h +: s.reserveHeaps, v, analysisInfoes) + protected def assumeUpperPermissionBoundForQPFields(s: State, v: Verifier, analysisInfos: DependencyAnalysisInfos): State = + assumeUpperPermissionBoundForQPFields(s, s.h +: s.reserveHeaps, v, analysisInfos) - protected def assumeUpperPermissionBoundForQPFields(s: State, heaps: Seq[Heap], v: Verifier, analysisInfoes: DependencyAnalysisInfoes): State = { + protected def assumeUpperPermissionBoundForQPFields(s: State, heaps: Seq[Heap], v: Verifier, analysisInfos: DependencyAnalysisInfos): State = { heaps.foldLeft(s) { case (si, heap) => val chunks: Seq[QuantifiedFieldChunk] = heap.values.collect({ case ch: QuantifiedFieldChunk => ch }).to(Seq) @@ -304,7 +304,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol Some(DebugExp.createInstance(exp, exp)) } else { None } v.decider.assume( - Forall(receiver, PermAtMost(currentPermAmount, FullPerm), Trigger(trigger), "qp-fld-prm-bnd"), debugExp, analysisInfoes.withDependencyType(DependencyType.Internal)) + Forall(receiver, PermAtMost(currentPermAmount, FullPerm), Trigger(trigger), "qp-fld-prm-bnd"), debugExp, analysisInfos.withDependencyType(DependencyType.Internal)) } else { /* If we don't use heap-dependent triggers, the trigger x.f does not work. Instead, we assume the permission @@ -321,7 +321,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol val exp = ast.PermLeCmp(permExp, ast.FullPerm()())() Some(DebugExp.createInstance(exp, exp)) } else { None } - v.decider.assume(PermAtMost(PermLookup(field.name, pmDef.pm, chunk.singletonRcvr.get), FullPerm), debugExp, analysisInfoes.withDependencyType(DependencyType.Internal)) + v.decider.assume(PermAtMost(PermLookup(field.name, pmDef.pm, chunk.singletonRcvr.get), FullPerm), debugExp, analysisInfos.withDependencyType(DependencyType.Internal)) } else { val chunkReceivers = chunk.invs.get.inverses.map(i => App(i, chunk.invs.get.additionalArguments ++ chunk.quantifiedVars)) val triggers = chunkReceivers.map(r => Trigger(r)).toSeq @@ -337,7 +337,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol Some(DebugExp.createInstance(exp, exp)) } else { None } v.decider.assume( - Forall(chunk.quantifiedVars, PermAtMost(currentPermAmount, FullPerm), triggers, "qp-fld-prm-bnd"), debugExp, analysisInfoes.withDependencyType(DependencyType.Internal)) + Forall(chunk.quantifiedVars, PermAtMost(currentPermAmount, FullPerm), triggers, "qp-fld-prm-bnd"), debugExp, analysisInfos.withDependencyType(DependencyType.Internal)) } } @@ -409,15 +409,15 @@ class LastRetryFailOnlyStateConsolidator(config: Config) extends LastRetryStateC * - Merging heaps and assuming QP permission bounds is equivalent to [[MinimalStateConsolidator]] */ class MinimalRetryingStateConsolidator(config: Config) extends RetryingStateConsolidator(config) { - override def merge(fr: FunctionRecorder, s: State, h: Heap, newH: Heap, v: Verifier, analysisInfoes: DependencyAnalysisInfoes): (FunctionRecorder, Heap) = + override def merge(fr: FunctionRecorder, s: State, h: Heap, newH: Heap, v: Verifier, analysisInfos: DependencyAnalysisInfos): (FunctionRecorder, Heap) = (fr, Heap(h.values ++ newH.values)) - override def merge(fr: FunctionRecorder, s: State, h: Heap, ch: NonQuantifiedChunk, v: Verifier, analysisInfoes: DependencyAnalysisInfoes): (FunctionRecorder, Heap) = + override def merge(fr: FunctionRecorder, s: State, h: Heap, ch: NonQuantifiedChunk, v: Verifier, analysisInfos: DependencyAnalysisInfos): (FunctionRecorder, Heap) = (fr, h + ch) - override protected def assumeUpperPermissionBoundForQPFields(s: State, @unused v: Verifier, analysisInfoes: DependencyAnalysisInfoes): State = s + override protected def assumeUpperPermissionBoundForQPFields(s: State, @unused v: Verifier, analysisInfos: DependencyAnalysisInfos): State = s - override protected def assumeUpperPermissionBoundForQPFields(s: State, @unused heaps: Seq[Heap], @unused v: Verifier, analysisInfoes: DependencyAnalysisInfoes): State = s + override protected def assumeUpperPermissionBoundForQPFields(s: State, @unused heaps: Seq[Heap], @unused v: Verifier, analysisInfos: DependencyAnalysisInfos): State = s } /** A variant of [[DefaultStateConsolidator]] that aims to work best when Silicon is run in @@ -437,12 +437,12 @@ class MoreComplexExhaleStateConsolidator(config: Config) extends DefaultStateCon // silver\src\test\resources\quantifiedpermissions\sets\generalised_shape.sil // to fail. - val analysisInfoes = DependencyAnalysisInfoes.create("state consolidation", DependencyType.Internal, NoDependencyAnalysisMerge()) // TODO ake: review + val analysisInfos = DependencyAnalysisInfos.create("state consolidation", DependencyType.Internal, NoDependencyAnalysisMerge()) // TODO ake: review if (s.retrying) { // TODO: apply to all heaps (s.h +: s.reserveHeaps, as done below) // NOTE: Doing this regardless of s.retrying might improve completeness in certain (rare) cases - moreCompleteExhaleSupporter.assumeFieldPermissionUpperBounds(s.h, v, analysisInfoes) + moreCompleteExhaleSupporter.assumeFieldPermissionUpperBounds(s.h, v, analysisInfos) } s diff --git a/src/main/scala/supporters/Domains.scala b/src/main/scala/supporters/Domains.scala index ce9b6d031..b0e199781 100644 --- a/src/main/scala/supporters/Domains.scala +++ b/src/main/scala/supporters/Domains.scala @@ -8,7 +8,7 @@ package viper.silicon.supporters import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.common.collections.immutable.MultiMap._ -import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfoes, DependencyAnalyzer} +import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfos, DependencyAnalyzer} import viper.silicon.interfaces.PreambleContributor import viper.silicon.interfaces.decider.ProverLike import viper.silicon.state.terms.{Distinct, DomainFun, Sort, Term} @@ -30,7 +30,7 @@ class DefaultDomainsContributor(symbolConverter: SymbolConverter, private var collectedSorts = InsertionOrderedSet[Sort]() private var collectedFunctions = InsertionOrderedSet[terms.DomainFun]() - private var collectedAxioms = InsertionOrderedSet[(Term, DependencyAnalysisInfoes)]() + private var collectedAxioms = InsertionOrderedSet[(Term, DependencyAnalysisInfos)]() private var uniqueSymbols = MultiMap.empty[Sort, DomainFun] /* Lifetime */ @@ -107,7 +107,7 @@ class DefaultDomainsContributor(symbolConverter: SymbolConverter, val tAx = domainTranslator.translateAxiom(axiom, symbolConverter.toSort) val tAxPres = FunctionPreconditionTransformer.transform(tAx, program) val enableAnalysis = DependencyAnalyzer.extractEnableAnalysisFromInfo(axiom.info).getOrElse(isAnalysisForDomainEnabled) - collectedAxioms = collectedAxioms.incl((terms.And(tAxPres, tAx), DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes.addInfo(axiom.exp.info, axiom.exp))) + collectedAxioms = collectedAxioms.incl((terms.And(tAxPres, tAx), DependencyAnalysisInfos.DefaultDependencyAnalysisInfos.addInfo(axiom.exp.info, axiom.exp))) }) }) } diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index e4849173e..5e3de394b 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -83,8 +83,8 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif val presAssertionNodeForJoin = pres.flatMap(_.topLevelConjuncts).map(pc => SimpleAssertionNode(True, AnalysisSourceInfo.createAnalysisSourceInfo(pc), AssumptionType.Precondition, SimpleDependencyAnalysisMerge(AnalysisSourceInfo.createAnalysisSourceInfo(pc)), List(SimpleDependencyAnalysisJoin(AnalysisSourceInfo.createAnalysisSourceInfo(pc), JoinType.Sink, EdgeType.Up)))) presAssertionNodeForJoin foreach v.decider.dependencyAnalyzer.addAssertionNode - val analysisInfoesPrecondition = DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Sink, EdgeType.Up)) - val analysisInfoesPostcondition = DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Source, EdgeType.Down)) + val analysisInfosPrecondition = DependencyAnalysisInfos.DefaultDependencyAnalysisInfos.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Sink, EdgeType.Up)) + val analysisInfosPostcondition = DependencyAnalysisInfos.DefaultDependencyAnalysisInfos.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Source, EdgeType.Down)) val daJoinNodeInfoOpt = method.info.getUniqueInfo[DependencyAnalysisJoinNodeInfo] @@ -112,14 +112,14 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif * rules in Smans' paper. */ executionFlowController.locally(s, v)((s1, v1) => { - produces(s1, freshSnap, pres, ContractNotWellformed, v1, analysisInfoesPrecondition)((s2, v2) => { + produces(s1, freshSnap, pres, ContractNotWellformed, v1, analysisInfosPrecondition)((s2, v2) => { v2.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) val s2a = s2.copy(oldHeaps = s2.oldHeaps + (Verifier.PRE_STATE_LABEL -> s2.h)) ( executionFlowController.locally(s2a, v2)((s3, v3) => { val s4 = s3.copy(h = v3.heapSupporter.getEmptyHeap(s3.program)) val impLog = new WellformednessCheckRecord(posts, s, v.decider.pcs) val sepIdentifier = symbExLog.openScope(impLog) - produces(s4, freshSnap, posts, ContractNotWellformed, v3, analysisInfoesPostcondition)((_, _) => { + produces(s4, freshSnap, posts, ContractNotWellformed, v3, analysisInfosPostcondition)((_, _) => { symbExLog.closeScope(sepIdentifier) Success()})}) && { @@ -128,11 +128,11 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif if(method.body.isEmpty) v3.decider.removeDependencyAnalyzer() exec(s3, body, v3)((s4, v4) => { if(method.body.isEmpty) v3.decider.dependencyAnalyzer = da - consumes(s4, posts, false, postViolated, v4, analysisInfoesPostcondition)((_, _, _) => + consumes(s4, posts, false, postViolated, v4, analysisInfosPostcondition)((_, _, _) => Success())})}) } )})}) if(method.body.isEmpty){ - v.decider.dependencyAnalyzer.addDependenciesForAbstractMembers(method.pres.flatMap(_.topLevelConjuncts), method.posts.flatMap(_.topLevelConjuncts), DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes) + v.decider.dependencyAnalyzer.addDependenciesForAbstractMembers(method.pres.flatMap(_.topLevelConjuncts), method.posts.flatMap(_.topLevelConjuncts), DependencyAnalysisInfos.DefaultDependencyAnalysisInfos) } val allErrors = (result :: result.previous.toList).filter(_.isInstanceOf[Failure]).map(_.asInstanceOf[Failure]) diff --git a/src/main/scala/supporters/PredicateVerificationUnit.scala b/src/main/scala/supporters/PredicateVerificationUnit.scala index e616809a1..65bf372c4 100644 --- a/src/main/scala/supporters/PredicateVerificationUnit.scala +++ b/src/main/scala/supporters/PredicateVerificationUnit.scala @@ -9,7 +9,7 @@ package viper.silicon.supporters import com.typesafe.scalalogging.Logger import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.decider.Decider -import viper.silicon.dependencyAnalysis.DependencyAnalysisInfoes +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfos import viper.silicon.interfaces._ import viper.silicon.interfaces.decider.ProverLike import viper.silicon.rules.executionFlowController @@ -123,7 +123,7 @@ trait DefaultPredicateVerificationUnitProvider extends VerifierComponent { v: Ve /* locallyXXX { magicWandSupporter.checkWandsAreSelfFraming(σ.γ, σ.h, predicate, c)} &&*/ executionFlowController.locally(s, v)((s1, _) => { - produce(s1, toSf(snap), body, err, v, DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes)((s2, v2) => { + produce(s1, toSf(snap), body, err, v, DependencyAnalysisInfos.DefaultDependencyAnalysisInfos)((s2, v2) => { val branchConds = v2.decider.pcs.branchConditions.reverse val branchCondExps = v2.decider.pcs.branchConditionExps.reverse assert(branchConds.length == branchCondExps.length) diff --git a/src/main/scala/supporters/SnapshotSupporter.scala b/src/main/scala/supporters/SnapshotSupporter.scala index dbc672a93..e515c07fa 100644 --- a/src/main/scala/supporters/SnapshotSupporter.scala +++ b/src/main/scala/supporters/SnapshotSupporter.scala @@ -7,7 +7,7 @@ package viper.silicon.supporters import viper.silicon.debugger.DebugExp -import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfoes} +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfos import viper.silicon.state.terms.{Combine, First, Second, Sort, Term, Unit, sorts} import viper.silicon.state.{MagicWandIdentifier, State, SymbolConverter} import viper.silicon.utils.toSf @@ -28,7 +28,7 @@ trait SnapshotSupporter { a0: ast.Exp, a1: ast.Exp, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) : ((Sort, Verifier) => Term, (Sort, Verifier) => Term) } @@ -120,10 +120,10 @@ class DefaultSnapshotSupporter(symbolConverter: SymbolConverter) extends Snapsho a0: ast.Exp, a1: ast.Exp, v: Verifier, - analysisInfoes: DependencyAnalysisInfoes) + analysisInfos: DependencyAnalysisInfos) : ((Sort, Verifier) => Term, (Sort, Verifier) => Term) = { - val (snap0, snap1) = createSnapshotPair(s, sf(sorts.Snap, v), a0, a1, v, analysisInfoes) + val (snap0, snap1) = createSnapshotPair(s, sf(sorts.Snap, v), a0, a1, v, analysisInfos) val sf0 = toSf(snap0) val sf1 = toSf(snap1) @@ -131,7 +131,7 @@ class DefaultSnapshotSupporter(symbolConverter: SymbolConverter) extends Snapsho (sf0, sf1) } - private def createSnapshotPair(@unused s: State, snap: Term, @unused a0: ast.Exp, @unused a1: ast.Exp, v: Verifier, analysisInfoes: DependencyAnalysisInfoes): (Term, Term) = { + private def createSnapshotPair(@unused s: State, snap: Term, @unused a0: ast.Exp, @unused a1: ast.Exp, v: Verifier, analysisInfos: DependencyAnalysisInfos): (Term, Term) = { /* [2015-11-17 Malte] If both fresh snapshot terms and first/second datatypes * are used, then the overall test suite verifies in 2min 10sec, whereas * it takes 2min 20sec when only first/second datatypes are used. Might be @@ -163,7 +163,7 @@ class DefaultSnapshotSupporter(symbolConverter: SymbolConverter) extends Snapsho (snap0, snap1, snap === Combine(snap0, snap1)) } - v.decider.assume(snapshotEq, Option.when(Verifier.config.enableDebugging())(DebugExp.createInstance("Snapshot", true)), analysisInfoes) + v.decider.assume(snapshotEq, Option.when(Verifier.config.enableDebugging())(DebugExp.createInstance("Snapshot", true)), analysisInfos) (snap0, snap1) } diff --git a/src/main/scala/supporters/functions/FunctionData.scala b/src/main/scala/supporters/functions/FunctionData.scala index 464b62373..6f7ac947d 100644 --- a/src/main/scala/supporters/functions/FunctionData.scala +++ b/src/main/scala/supporters/functions/FunctionData.scala @@ -8,7 +8,7 @@ package viper.silicon.supporters.functions import com.typesafe.scalalogging.LazyLogging import viper.silicon.common.collections.immutable.InsertionOrderedSet -import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfoes, DependencyAnalyzer} +import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfos, DependencyAnalyzer} import viper.silicon.interfaces.FatalResult import viper.silicon.rules.{InverseFunctions, PermMapDefinition, SnapshotMapDefinition, functionSupporter} import viper.silicon.state.terms._ @@ -140,21 +140,21 @@ class FunctionData(val programFunction: ast.Function, val preconditionFunctionApplication = App(preconditionFunction, `?s` +: formalArgs.values.toSeq) - private val bodyAnalysisInfoes: DependencyAnalysisInfoes = if(programFunction.body.isDefined) - DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes.addInfo(programFunction.body.get.info, programFunction.body.get) + private val bodyAnalysisInfos: DependencyAnalysisInfos = if(programFunction.body.isDefined) + DependencyAnalysisInfos.DefaultDependencyAnalysisInfos.addInfo(programFunction.body.get.info, programFunction.body.get) .withJoinInfo(SimpleDependencyAnalysisJoin(AnalysisSourceInfo.createAnalysisSourceInfo(programFunction.body.get), JoinType.Sink, EdgeType.Down)) - else DependencyAnalysisInfoes.create("unverified function body", DependencyType.Internal) + else DependencyAnalysisInfos.create("unverified function body", DependencyType.Internal) - val limitedAxiom: (Quantification, DependencyAnalysisInfoes) = + val limitedAxiom: (Quantification, DependencyAnalysisInfos) = (Forall(arguments, BuiltinEquals(limitedFunctionApplication, functionApplication), Trigger(functionApplication)), - DependencyAnalysisInfoes.create("Limited Axiom", DependencyType.Internal)) + DependencyAnalysisInfos.create("Limited Axiom", DependencyType.Internal)) - val triggerAxiom: (Quantification, DependencyAnalysisInfoes) = + val triggerAxiom: (Quantification, DependencyAnalysisInfos) = (Forall(arguments, triggerFunctionApplication, Trigger(limitedFunctionApplication)), - DependencyAnalysisInfoes.create("Trigger Axiom", DependencyType.Trigger)) + DependencyAnalysisInfos.create("Trigger Axiom", DependencyType.Trigger)) /* @@ -235,7 +235,7 @@ class FunctionData(val programFunction: ast.Function, } } - lazy val postAxiom: Seq[(Term, DependencyAnalysisInfoes)] = { + lazy val postAxiom: Seq[(Term, DependencyAnalysisInfos)] = { assert(phase == 1, s"Postcondition axiom must be generated in phase 1, current phase is $phase") if (programFunction.posts.nonEmpty) { @@ -243,17 +243,17 @@ class FunctionData(val programFunction: ast.Function, val bodyBindings: Map[Var, Term] = Map(formalResult -> limitedFunctionApplication) def wrapBody(body: Term): Term = Let(toMap(bodyBindings), body) - val analysisInfoes = DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes + val analysisInfos = DependencyAnalysisInfos.DefaultDependencyAnalysisInfos if(isAnalysisEnabled){ - (Forall(arguments, wrapBody(And(generateNestedDefinitionalAxioms)), Trigger(limitedFunctionApplication)), bodyAnalysisInfoes) +: + (Forall(arguments, wrapBody(And(generateNestedDefinitionalAxioms)), Trigger(limitedFunctionApplication)), bodyAnalysisInfos) +: programFunction.posts.flatMap(_.topLevelConjuncts).map({p => val terms = expressionTranslator.translatePostcondition(program, Seq(p), this) - (And(Forall(arguments, wrapBody(Implies(pre, And(terms))), Trigger(limitedFunctionApplication)), True), analysisInfoes.addInfo(p.info, p).withJoinInfo(SimpleDependencyAnalysisJoin(AnalysisSourceInfo.createAnalysisSourceInfo(p), JoinType.Sink, EdgeType.Down))) + (And(Forall(arguments, wrapBody(Implies(pre, And(terms))), Trigger(limitedFunctionApplication)), True), analysisInfos.addInfo(p.info, p).withJoinInfo(SimpleDependencyAnalysisJoin(AnalysisSourceInfo.createAnalysisSourceInfo(p), JoinType.Sink, EdgeType.Down))) }) }else{ val innermostBody = And(generateNestedDefinitionalAxioms ++ List(Implies(pre, And(translatedPosts)))) - Seq((Forall(arguments, wrapBody(innermostBody), Trigger(limitedFunctionApplication)), analysisInfoes)) + Seq((Forall(arguments, wrapBody(innermostBody), Trigger(limitedFunctionApplication)), analysisInfos)) } } else Seq.empty @@ -310,7 +310,7 @@ class FunctionData(val programFunction: ast.Function, expressionTranslator.translate(program, programFunction, this) } - lazy val definitionalAxiom: Option[(Term, DependencyAnalysisInfoes)] = { + lazy val definitionalAxiom: Option[(Term, DependencyAnalysisInfos)] = { assert(phase == 2, s"Definitional axiom must be generated in phase 2, current phase is $phase") optBody.map(translatedBody => { @@ -326,26 +326,26 @@ class FunctionData(val programFunction: ast.Function, Seq(Trigger(functionApplication)) ++ actualPredicateTriggers) (Forall(arguments, body, allTriggers), - bodyAnalysisInfoes) + bodyAnalysisInfos) }) } - lazy val bodyPreconditionPropagationAxiom: Seq[(Term, DependencyAnalysisInfoes)] = { + lazy val bodyPreconditionPropagationAxiom: Seq[(Term, DependencyAnalysisInfos)] = { val pre = preconditionFunctionApplication val bodyPreconditions = if (programFunction.body.isDefined) optBody.map(translatedBody => { val body = Implies(pre, FunctionPreconditionTransformer.transform(translatedBody, program)) - (Forall(arguments, body, Seq(Trigger(functionApplication))), bodyAnalysisInfoes) + (Forall(arguments, body, Seq(Trigger(functionApplication))), bodyAnalysisInfos) }) else None bodyPreconditions.toSeq } - lazy val postPreconditionPropagationAxiom: Seq[(Term, DependencyAnalysisInfoes)] = { + lazy val postPreconditionPropagationAxiom: Seq[(Term, DependencyAnalysisInfos)] = { val pre = preconditionFunctionApplication val postPreconditions = if (programFunction.posts.nonEmpty) { val bodyBindings: Map[Var, Term] = Map(formalResult -> limitedFunctionApplication) val bodies = translatedPosts.map(tPost => Let(bodyBindings, Implies(pre, FunctionPreconditionTransformer.transform(tPost, program)))) bodies.map(b => (Forall(arguments, b, Seq(Trigger(limitedFunctionApplication))), - DependencyAnalysisInfoes.create("postPreconditionPropagationAxiom", DependencyType.Internal))) // TODO ake + DependencyAnalysisInfos.create("postPreconditionPropagationAxiom", DependencyType.Internal))) // TODO ake } else Seq() postPreconditions } diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index 557146401..55e385a79 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -53,9 +53,9 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver @unused private var program: ast.Program = _ /*private*/ var functionData: Map[String, FunctionData] = Map.empty - private var emittedFunctionAxioms: Vector[(Term, DependencyAnalysisInfoes)] = Vector.empty + private var emittedFunctionAxioms: Vector[(Term, DependencyAnalysisInfos)] = Vector.empty private var freshVars: Vector[Var] = Vector.empty - private var postConditionAxioms: Vector[(Term, DependencyAnalysisInfoes)] = Vector.empty + private var postConditionAxioms: Vector[(Term, DependencyAnalysisInfos)] = Vector.empty private val expressionTranslator = { def resolutionFailureMessage(exp: ast.Positioned, data: FunctionData): String = ( @@ -206,7 +206,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver if (function.body.isEmpty) { decider.dependencyAnalyzer.addNodes(v.decider.prover.getPreambleAnalysisNodes) - decider.dependencyAnalyzer.addDependenciesForAbstractMembers(function.pres.flatMap(_.topLevelConjuncts), function.posts.flatMap(_.topLevelConjuncts), DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes /* TODO ake */) + decider.dependencyAnalyzer.addDependenciesForAbstractMembers(function.pres.flatMap(_.topLevelConjuncts), function.posts.flatMap(_.topLevelConjuncts), DependencyAnalysisInfos.DefaultDependencyAnalysisInfos /* TODO ake */) result1 } else { /* Phase 2: Verify the function's postcondition */ @@ -241,22 +241,22 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver val g = Store(argsStore + (function.result -> (data.formalResult, data.valFormalResultExp))) val s = sInit.copy(g = g, h = v.heapSupporter.getEmptyHeap(sInit.program), oldHeaps = OldHeaps()) - val analysisInfoesPrecondition = DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Sink, EdgeType.Up)) - val analysisInfoesPostcondition = DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Source, EdgeType.Down)) + val analysisInfosPrecondition = DependencyAnalysisInfos.DefaultDependencyAnalysisInfos.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Sink, EdgeType.Up)) + val analysisInfosPostcondition = DependencyAnalysisInfos.DefaultDependencyAnalysisInfos.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Source, EdgeType.Down)) var phase1Data: Seq[Phase1Data] = Vector.empty var recorders: Seq[FunctionRecorder] = Vector.empty val result = executionFlowController.locally(s, v)((s0, _) => { val preMark = decider.setPathConditionMark() - produces(s0, toSf(`?s`), pres, ContractNotWellformed, v, analysisInfoesPrecondition)((s1, _) => { + produces(s0, toSf(`?s`), pres, ContractNotWellformed, v, analysisInfosPrecondition)((s1, _) => { val relevantPathConditionStack = decider.pcs.after(preMark) phase1Data :+= Phase1Data(s1, relevantPathConditionStack.branchConditions, relevantPathConditionStack.branchConditionExps, relevantPathConditionStack.assumptions, Option.when(evaluator.withExp)(relevantPathConditionStack.assumptionExps)) // The postcondition must be produced with a fresh snapshot (different from `?s`) because // the postcondition's snapshot structure is most likely different than that of the // precondition - produces(s1, freshSnap, posts, ContractNotWellformed, v, analysisInfoesPostcondition)((s2, _) => { + produces(s1, freshSnap, posts, ContractNotWellformed, v, analysisInfosPostcondition)((s2, _) => { recorders :+= s2.functionRecorder Success()})})}) @@ -281,9 +281,9 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver val wExp = evaluator.withExp decider.dependencyAnalyzer.addNodes(v.decider.prover.getPreambleAnalysisNodes) - val precondAnalysisSourceInfoes = DependencyAnalysisInfoes.create("preconditions", DependencyType.Internal) - val analysisInfoesPostcondition = DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Source, EdgeType.Down)) - val analysisInfoesBody = DependencyAnalysisInfoes.DefaultDependencyAnalysisInfoes.addInfo(body.info, body) + val precondAnalysisSourceInfos = DependencyAnalysisInfos.create("preconditions", DependencyType.Internal) + val analysisInfosPostcondition = DependencyAnalysisInfos.DefaultDependencyAnalysisInfos.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Source, EdgeType.Down)) + val analysisInfosBody = DependencyAnalysisInfos.DefaultDependencyAnalysisInfos.addInfo(body.info, body) .withJoinInfo(SimpleDependencyAnalysisJoin(AnalysisSourceInfo.createAnalysisSourceInfo(body), JoinType.Source, EdgeType.Down)) val daJoinNodeInfoOpt = function.info.getUniqueInfo[DependencyAnalysisJoinNodeInfo] @@ -311,18 +311,18 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver case (intermediateResult, Phase1Data(sPre, bcsPre, bcsPreExp, pcsPre, pcsPreExp)) => intermediateResult && executionFlowController.locally(sPre, v)((s1, _) => { val labelledBcsPre = terms.And(bcsPre map (t => v.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t)))) - decider.setCurrentBranchCondition(labelledBcsPre, (BigAnd(bcsPreExp.map(_._1)), Option.when(wExp)(BigAnd(bcsPreExp.map(_._2.get)))), precondAnalysisSourceInfoes) + decider.setCurrentBranchCondition(labelledBcsPre, (BigAnd(bcsPreExp.map(_._1)), Option.when(wExp)(BigAnd(bcsPreExp.map(_._2.get)))), precondAnalysisSourceInfos) val labelledPcsPre = pcsPre map (t => v.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t))) - decider.assume(labelledPcsPre, pcsPreExp, s"precondition of ${function.name}", enforceAssumption=false, precondAnalysisSourceInfoes) + decider.assume(labelledPcsPre, pcsPreExp, s"precondition of ${function.name}", enforceAssumption=false, precondAnalysisSourceInfos) v.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) - eval(s1, body, FunctionNotWellformed(function), v, analysisInfoesBody)((s2, tBody, bodyNew, _) => { + eval(s1, body, FunctionNotWellformed(function), v, analysisInfosBody)((s2, tBody, bodyNew, _) => { val debugExp = if (wExp) { val e = ast.EqCmp(ast.Result(function.typ)(), body)(function.pos, function.info, function.errT) val eNew = ast.EqCmp(ast.Result(function.typ)(), bodyNew.get)(function.pos, function.info, function.errT) Some(DebugExp.createInstance(e, eNew)) } else { None } - decider.assume(BuiltinEquals(data.formalResult, tBody), debugExp, analysisInfoesBody) - consumes(s2, posts, false, postconditionViolated, v, analysisInfoesPostcondition)((s3, _, _) => { + decider.assume(BuiltinEquals(data.formalResult, tBody), debugExp, analysisInfosBody) + consumes(s2, posts, false, postconditionViolated, v, analysisInfosPostcondition)((s3, _, _) => { recorders :+= s3.functionRecorder Success()})})})} @@ -331,7 +331,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver result } - private def emitAndRecordFunctionAxioms(axiom: (Term, DependencyAnalysisInfoes)*): Unit = { + private def emitAndRecordFunctionAxioms(axiom: (Term, DependencyAnalysisInfos)*): Unit = { val cleanAxiom = if(!Verifier.config.enableDependencyAnalysis()) axiom else axiom.map(a => (a._1.transform{ diff --git a/src/main/scala/verifier/VerificationPoolManager.scala b/src/main/scala/verifier/VerificationPoolManager.scala index d3db0d76a..1e5749f9e 100644 --- a/src/main/scala/verifier/VerificationPoolManager.scala +++ b/src/main/scala/verifier/VerificationPoolManager.scala @@ -10,7 +10,7 @@ import org.apache.commons.pool2.impl.{DefaultPooledObject, GenericObjectPool, Ge import org.apache.commons.pool2.{BasePooledObjectFactory, ObjectPool, PoolUtils, PooledObject} import viper.silicon.Config import viper.silicon.common.collections.immutable.InsertionOrderedSet -import viper.silicon.dependencyAnalysis.DependencyAnalysisInfoes +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfos import viper.silicon.interfaces.VerificationResult import viper.silicon.interfaces.decider.ProverLike import viper.silicon.state.terms.{Decl, Term} @@ -33,7 +33,7 @@ class VerificationPoolManager(mainVerifier: MainVerifier) extends StatefulCompon def assume(term: Term): Unit = workerVerifiers foreach (_.decider.prover.assume(term)) def assume(term: Term, label: String): Unit = workerVerifiers foreach (_.decider.prover.assume(term, label)) override def assumeAxioms(terms: InsertionOrderedSet[Term], description: String): Unit = workerVerifiers foreach (_.decider.prover.assumeAxioms(terms, description)) - override def assumeAxiomsWithAnalysisInfo(axioms: InsertionOrderedSet[(Term, DependencyAnalysisInfoes)], description: String): Unit = workerVerifiers foreach (_.decider.prover.assumeAxiomsWithAnalysisInfo(axioms, description)) + override def assumeAxiomsWithAnalysisInfo(axioms: InsertionOrderedSet[(Term, DependencyAnalysisInfos)], description: String): Unit = workerVerifiers foreach (_.decider.prover.assumeAxiomsWithAnalysisInfo(axioms, description)) def declare(decl: Decl): Unit = workerVerifiers foreach (_.decider.prover.declare(decl)) def comment(content: String): Unit = workerVerifiers foreach (_.decider.prover.comment(content)) diff --git a/src/test/scala/DependencyAnalysisTestFramework.scala b/src/test/scala/DependencyAnalysisTestFramework.scala index fd59af739..bdea578d6 100644 --- a/src/test/scala/DependencyAnalysisTestFramework.scala +++ b/src/test/scala/DependencyAnalysisTestFramework.scala @@ -130,8 +130,8 @@ trait DependencyAnalysisTestFramework { protected def evaluatePrecision(relevantLines: Set[Int]): Unit = { val relevantNodes = relevantLines.flatMap(line => fullGraphInterpreter.getNodesByLine(line)) - val sourceInfoes = relevantNodes.groupBy(_.sourceInfo).keySet - println(s"Evaluating precision of\n\t${sourceInfoes.mkString("\n\t")}") + val sourceInfos = relevantNodes.groupBy(_.sourceInfo).keySet + println(s"Evaluating precision of\n\t${sourceInfos.mkString("\n\t")}") val reportedDependencies = fullGraphInterpreter.getAllNonInternalDependencies(relevantNodes.map(_.id)).diff(relevantNodes) val verifies = pruneAndVerify(reportedDependencies ++ relevantNodes) From 4cdbeeaa6d0f3d25dcd46a945544d0c75619eb41 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 31 Mar 2026 15:31:24 +0200 Subject: [PATCH 411/474] cleanup warnings --- .../dependencyAnalysis/AnalysisInfo.scala | 2 +- .../DependencyAnalysisInfo.scala | 9 +-------- .../DependencyAnalysisNode.scala | 8 ++++---- .../DependencyAnalysisUserTool.scala | 6 +++--- .../DependencyAnalyzer.scala | 3 +-- .../dependencyAnalysis/DependencyGraph.scala | 2 +- .../DependencyGraphImporter.scala | 18 +++++++++--------- .../DependencyGraphInterpreter.scala | 6 +++--- src/main/scala/rules/ConsumptionResult.scala | 6 +++--- 9 files changed, 26 insertions(+), 34 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala index 2d5e353c9..60954ec5c 100644 --- a/src/main/scala/dependencyAnalysis/AnalysisInfo.scala +++ b/src/main/scala/dependencyAnalysis/AnalysisInfo.scala @@ -5,6 +5,6 @@ import viper.silicon.decider.Decider import viper.silver.dependencyAnalysis.DependencyType case class AnalysisInfo(decider: Decider, dependencyAnalyzer: DependencyAnalyzer, analysisInfos: DependencyAnalysisInfos) { - def withDependencyType(dependencyType: DependencyType) = this.copy(analysisInfos=analysisInfos.withDependencyType(dependencyType)) + def withDependencyType(dependencyType: DependencyType): AnalysisInfo = this.copy(analysisInfos=analysisInfos.withDependencyType(dependencyType)) } diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala index 15afc86f0..de49e494e 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala @@ -5,10 +5,7 @@ import viper.silver.ast.{DependencyAnalysisJoinInfo, DependencyAnalysisMergeInfo import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, DependencyType, StringAnalysisSourceInfo} -trait AnalysisInfos { -} - -case class DependencyAnalysisInfos(sourceInfos: List[AnalysisSourceInfo], dependencyTypes: List[DependencyTypeInfo], mergeInfos: List[DependencyAnalysisMergeInfo], joinInfos: List[DependencyAnalysisJoinInfo], nodes: List[ast.Node]) extends AnalysisInfos { +case class DependencyAnalysisInfos(sourceInfos: List[AnalysisSourceInfo], dependencyTypes: List[DependencyTypeInfo], mergeInfos: List[DependencyAnalysisMergeInfo], joinInfos: List[DependencyAnalysisJoinInfo], nodes: List[ast.Node]) { def addInfo(info: ast.Info, node: ast.Node): DependencyAnalysisInfos = { val newSourceInfos = sourceInfos ++ info.getUniqueInfo[AnalysisSourceInfo].toList @@ -78,10 +75,6 @@ object DependencyAnalysisInfos { - - - - class CustomDependencyAnalysisNode(description: String, sourceInfoOpt: Option[AnalysisSourceInfo], dependencyTypeOpt: Option[DependencyType], createAssertionNode: Boolean, createAssumptionNode: Boolean, mergeInfoOpt: Option[DependencyAnalysisMergeInfo], joinInfoOpt: Option[DependencyAnalysisJoinInfo]) \ No newline at end of file diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala index 276f5ceb3..dc4c6db9c 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala @@ -72,7 +72,7 @@ trait GeneralAssertionNode extends DependencyAnalysisNode { val hasFailed: Boolean - def getAssertFailedNode(): GeneralAssertionNode + def getAssertFailedNode: GeneralAssertionNode } // this is not strictly needed anymore but storing the chunk and label node is useful for debugging purposes @@ -93,14 +93,14 @@ case class AxiomAssumptionNode(term: Term, description: Option[String], sourceIn case class SimpleAssertionNode(term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, mergeInfo: DependencyAnalysisMergeInfo, joinInfos: List[SimpleDependencyAnalysisJoin], hasFailed: Boolean = false, _id: Option[Int]=None) extends GeneralAssertionNode { override def getNodeString: String = "assert " + term.toString - override def getAssertFailedNode(): GeneralAssertionNode = SimpleAssertionNode(term, sourceInfo, assumptionType, mergeInfo, hasFailed=true, joinInfos=joinInfos) + override def getAssertFailedNode: GeneralAssertionNode = SimpleAssertionNode(term, sourceInfo, assumptionType, mergeInfo, hasFailed=true, joinInfos=joinInfos) } case class SimpleCheckNode(term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, mergeInfo: DependencyAnalysisMergeInfo, joinInfos: List[SimpleDependencyAnalysisJoin], hasFailed: Boolean = false, _id: Option[Int]=None) extends GeneralAssertionNode { override def getNodeString: String = "check " + term override def getNodeType: String = "Check" - override def getAssertFailedNode(): GeneralAssertionNode = SimpleCheckNode(term, sourceInfo, assumptionType, mergeInfo, joinInfos, hasFailed=true) + override def getAssertFailedNode: GeneralAssertionNode = SimpleCheckNode(term, sourceInfo, assumptionType, mergeInfo, joinInfos, hasFailed=true) } case class PermissionInhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSourceInfo, assumptionType: AssumptionType, mergeInfo: DependencyAnalysisMergeInfo, labelNode: LabelNode, joinInfos: List[SimpleDependencyAnalysisJoin], _id: Option[Int]=None) extends GeneralAssumptionNode with ChunkAnalysisInfo { @@ -112,7 +112,7 @@ case class PermissionExhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSo override def getNodeType: String = "Exhale" override def getNodeString: String = "exhale " + chunk.toString - override def getAssertFailedNode(): GeneralAssertionNode = PermissionExhaleNode(chunk, term, sourceInfo, assumptionType, mergeInfo, labelNode, joinInfos, hasFailed=true, _id=_id) + override def getAssertFailedNode: GeneralAssertionNode = PermissionExhaleNode(chunk, term, sourceInfo, assumptionType, mergeInfo, labelNode, joinInfos, hasFailed=true, _id=_id) } /** diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala index 20079cdc0..6dd0ef447 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala @@ -188,7 +188,7 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete println(s"Finished in ${naiveTime}ms") } - protected def getSourceInfoString(nodes: Set[DependencyAnalysisNode]): String = { + private def getSourceInfoString(nodes: Set[DependencyAnalysisNode]): String = { UserLevelDependencyAnalysisNode.mkUserLevelString(nodes, "\n\t") } @@ -452,7 +452,7 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete writer.close() } - def handleVerificationGuidanceQuery(): Unit = { + private def handleVerificationGuidanceQuery(): Unit = { val assumptionRanking = fullGraphInterpreter.computeAssumptionRanking().filter(_._2 > 0.0) println(s"Assumptions/unverified assertions and the number of dependents:\n\t${assumptionRanking.mkString("\n\t")}\n") @@ -464,7 +464,7 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete println(s"\nMethods and the number of uncovered statements:\n\t${memberCoverageRanking.mkString("\n\t")}\n") } - def handleVerificationGuidanceOldQuery(): Unit = { + private def handleVerificationGuidanceOldQuery(): Unit = { val assumptionRanking = fullGraphInterpreter.computeAssumptionRankingOld().filter(_._2 > 0) println(s"Assumptions and the number of dependents:\n\t${assumptionRanking.mkString("\n\t")}\n") diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index 5a1b9fb4e..1928c0d94 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -6,7 +6,6 @@ import viper.silicon.verifier.Verifier import viper.silver.ast import viper.silver.ast.JoinType.JoinType import viper.silver.ast._ -import viper.silver.dependencyAnalysis.{AssumptionType, DependencyType, FrontendDependencyAnalysisInfo} import scala.collection.mutable @@ -208,7 +207,7 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { Some(SimpleAssertionNode(term, analysisInfos.getSourceInfo, analysisInfos.getDependencyType.assumptionType, analysisInfos.getMergeInfo, analysisInfos.getJoinInfo)) } - def addAssertNode(term: Term, analysisInfos: DependencyAnalysisInfos): Option[Int] = { + private def addAssertNode(term: Term, analysisInfos: DependencyAnalysisInfos): Option[Int] = { val node = createAssertOrCheckNode(term, analysisInfos, isCheck=false) node foreach addAssertionNode node map (_.id) diff --git a/src/main/scala/dependencyAnalysis/DependencyGraph.scala b/src/main/scala/dependencyAnalysis/DependencyGraph.scala index f363f85a2..abbc2e502 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraph.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraph.scala @@ -152,7 +152,7 @@ class DependencyGraph extends ReadOnlyDependencyGraph { } - def addVacuousProof(assertionId: Int) = { + def addVacuousProof(assertionId: Int): Unit = { vacuousProofs = assertionId +: vacuousProofs } diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala index 08c3203c7..21dbf887b 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala @@ -16,7 +16,7 @@ import scala.io.Source object DependencyGraphImporter { - lazy val dummyLabelNode: LabelNode = LabelNode(dummyVar) + private lazy val dummyLabelNode: LabelNode = LabelNode(dummyVar) lazy val dummyVar: Var = Var.actualCreate((SimpleIdentifier("a"), Bool, false)) lazy val frontend: SiliconFrontend = createFrontend(Seq.empty) @@ -53,7 +53,7 @@ object DependencyGraphImporter { throw new IllegalArgumentException("Error: --graphFolder argument is required but not found.") } - def runUserTool(args: Array[String], userTool: DependencyAnalysisUserTool): Unit = { + private def runUserTool(args: Array[String], userTool: DependencyAnalysisUserTool): Unit = { val cmdsIndex = args.indexOf("--cmds") val cmds = if (0 <= cmdsIndex && cmdsIndex < args.length - 1) Some(args(cmdsIndex + 1).split(";").map(_.trim)) else None @@ -68,14 +68,14 @@ object DependencyGraphImporter { } - def importGraphFromCsv(csvFilePath: String): ReadOnlyDependencyGraph = { + private def importGraphFromCsv(csvFilePath: String): ReadOnlyDependencyGraph = { val graph = new DependencyGraph() createNodesFromCsv(graph, csvFilePath) createEdgesFromCsv(graph, csvFilePath) graph } - def createNodesFromCsv(graph: DependencyGraph, csvFilePath: String): Unit = { + private def createNodesFromCsv(graph: DependencyGraph, csvFilePath: String): Unit = { val bufferedSource = Source.fromFile(csvFilePath + "/nodes.csv") for (line <- bufferedSource.getLines().drop(1)) { @@ -99,10 +99,10 @@ object DependencyGraphImporter { val node = nodeType match { case "Assumption" => SimpleAssumptionNode(term, description, sourceInfo, assumptionType, mergeInfo, joinNodeInfos, _id=nodeId) case "Axiom" => AxiomAssumptionNode(term, description, sourceInfo, assumptionType, mergeInfo, joinNodeInfos, _id=nodeId) - case "Assertion" => SimpleAssertionNode(term, sourceInfo, assumptionType, mergeInfo, joinNodeInfos, hasFailed = false, _id=nodeId) - case "Check" => SimpleCheckNode(term, sourceInfo, assumptionType, mergeInfo, joinNodeInfos, hasFailed = false, _id=nodeId) + case "Assertion" => SimpleAssertionNode(term, sourceInfo, assumptionType, mergeInfo, joinNodeInfos, _id=nodeId) + case "Check" => SimpleCheckNode(term, sourceInfo, assumptionType, mergeInfo, joinNodeInfos, _id=nodeId) case "Inhale" => PermissionInhaleNode(chunk, term, sourceInfo, assumptionType, mergeInfo, labelNode, joinNodeInfos, _id=nodeId) - case "Exhale" => PermissionExhaleNode(chunk, term, sourceInfo, assumptionType, mergeInfo, labelNode, joinNodeInfos, hasFailed = false, _id=nodeId) + case "Exhale" => PermissionExhaleNode(chunk, term, sourceInfo, assumptionType, mergeInfo, labelNode, joinNodeInfos, _id=nodeId) case "Label" => LabelNode(dummyVar, _id=nodeId) case "Infeasible" => InfeasibilityNode(sourceInfo, assumptionType, _id=nodeId) case _ => throw new IllegalArgumentException(s"Unknown node type: $nodeType") @@ -113,7 +113,7 @@ object DependencyGraphImporter { bufferedSource.close() } - def createEdgesFromCsv(graph: DependencyGraph, csvFilePath: String): Unit = { + private def createEdgesFromCsv(graph: DependencyGraph, csvFilePath: String): Unit = { val bufferedSource = Source.fromFile(csvFilePath + "/edges.csv") for (line <- bufferedSource.getLines().drop(1)) { @@ -153,7 +153,7 @@ object DependencyGraphImporter { frontend.translationResult } - def parsePositionString(positionString: String): Position = positionString match { + private def parsePositionString(positionString: String): Position = positionString match { case "???" => NoPosition case str if str.startsWith("label ") => val identifier = str.stripPrefix("label ") diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 78dfbff6b..fab84fa8b 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -40,7 +40,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen val joinSinkNodes: Set[DependencyAnalysisNode] = getJoinCandidateNodes(getNodes).filter(_.joinInfos.exists(_.joinType.equals(JoinType.Sink))) val joinSourceNodes: Set[DependencyAnalysisNode] = getJoinCandidateNodes(getNodes).filter(_.joinInfos.exists(_.joinType.equals(JoinType.Source))) - def getJoinCandidateNodes(nodes: Set[DependencyAnalysisNode]): Set[DependencyAnalysisNode] = nodes.filter(node => node.joinInfos.nonEmpty) + private def getJoinCandidateNodes(nodes: Set[DependencyAnalysisNode]): Set[DependencyAnalysisNode] = nodes.filter(node => node.joinInfos.nonEmpty) private def toUserLevelNodes(nodes: Iterable[DependencyAnalysisNode]): Set[UserLevelDependencyAnalysisNode] = UserLevelDependencyAnalysisNode.from(nodes) @@ -344,7 +344,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen (numDepsTotal - explicitDeps.size).toDouble / numDepsTotal.toDouble } - def getAssertionsRelevantForProgress: Map[AnalysisSourceInfo, Set[DependencyAnalysisNode]] = sourceToAssertionNodes.filter(ass => ass._2.map(_.assumptionType).intersect(AssumptionType.importedTypes).isEmpty) + private def getAssertionsRelevantForProgress: Map[AnalysisSourceInfo, Set[DependencyAnalysisNode]] = sourceToAssertionNodes.filter(ass => ass._2.map(_.assumptionType).intersect(AssumptionType.importedTypes).isEmpty) def computeVerificationProgressOptimized(): (Double, Double, String) = { @@ -517,7 +517,7 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen .toList.sortBy(_._2).reverse } - def getAssertionsWithZeroQuality: Set[AnalysisSourceInfo] = { + private def getAssertionsWithZeroQuality: Set[AnalysisSourceInfo] = { val allAssertions = toUserLevelNodes(getNonInternalAssertionNodes) allAssertions.filter(assertion => assertion.hasFailures || assertion.assertionTypes.contains(AssumptionType.ExplicitPostcondition)).getSourceSet() } diff --git a/src/main/scala/rules/ConsumptionResult.scala b/src/main/scala/rules/ConsumptionResult.scala index 7e1536a06..3c2a74d08 100644 --- a/src/main/scala/rules/ConsumptionResult.scala +++ b/src/main/scala/rules/ConsumptionResult.scala @@ -6,11 +6,11 @@ package viper.silicon.rules -import viper.silicon.dependencyAnalysis.{AnalysisInfos, DependencyAnalysisInfos} -import viper.silicon.state.terms.{Forall, Term, Var} +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfos import viper.silicon.state.terms.perms.IsNonPositive -import viper.silver.ast +import viper.silicon.state.terms.{Forall, Term, Var} import viper.silicon.verifier.Verifier +import viper.silver.ast sealed trait ConsumptionResult { def isComplete: Boolean From 647501246191b0a16c62f921ed2ff0220d005fed Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 31 Mar 2026 15:44:45 +0200 Subject: [PATCH 412/474] refactoring --- silver | 2 +- src/main/scala/decider/Decider.scala | 33 +++++-------------- .../dependencyAnalysis/DependencyGraph.scala | 4 +-- .../DependencyGraphInterpreter.scala | 12 +------ src/main/scala/rules/Brancher.scala | 4 +-- src/main/scala/rules/ChunkSupporter.scala | 6 ++-- src/main/scala/rules/Consumer.scala | 2 +- src/main/scala/rules/Evaluator.scala | 26 +++++++-------- src/main/scala/rules/Executor.scala | 10 +++--- src/main/scala/rules/HavocSupporter.scala | 2 +- src/main/scala/rules/HeapSupporter.scala | 6 ++-- src/main/scala/rules/MagicWandSupporter.scala | 2 +- .../rules/MoreCompleteExhaleSupporter.scala | 22 ++++++------- .../scala/rules/PermissionSupporter.scala | 4 +-- .../scala/rules/QuantifiedChunkSupport.scala | 14 ++++---- .../functions/FunctionVerificationUnit.scala | 4 +-- 16 files changed, 63 insertions(+), 90 deletions(-) diff --git a/silver b/silver index 5bca3ee1a..6fee57cb9 160000 --- a/silver +++ b/silver @@ -1 +1 @@ -Subproject commit 5bca3ee1a0fb2ae52869390a7b7e445351ef48f3 +Subproject commit 6fee57cb9fa06cc510cc87272b87618db30fe6f4 diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 6f664ba6a..521516662 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -51,7 +51,6 @@ trait Decider { def pushScope(): Unit def popScope(): Unit - def checkSmoke(analysisInfos: DependencyAnalysisInfos): Boolean def checkSmoke(analysisInfos: DependencyAnalysisInfos, isAssert: Boolean = false): Boolean def setCurrentBranchCondition(t: Term, te: (ast.Exp, Option[ast.Exp]), analysisInfos: DependencyAnalysisInfos): Unit @@ -75,7 +74,6 @@ trait Decider { def assumeLabel(term: Term, assumptionLabel: String): Unit def check(t: Term, timeout: Int, analysisInfos: DependencyAnalysisInfos): Boolean - def checkSmokeAndSetInfeasibilityNode(analysisInfos: DependencyAnalysisInfos): Unit /* TODO: Consider changing assert such that * 1. It passes State and Operations to the continuation @@ -117,7 +115,7 @@ trait Decider { def removeDependencyAnalyzer(): Unit def getAnalysisInfo(daInfos: DependencyAnalysisInfos): AnalysisInfo def isDependencyAnalysisEnabled: Boolean - def handleFailedAssertionForDependencyAnalysis(failedAssertion: Term, analysisInfos: DependencyAnalysisInfos, assumeFailedAssertion: Boolean): Unit + def handleFailedAssertion(failedAssertion: Term, analysisInfos: DependencyAnalysisInfos, assumeFailedAssertion: Boolean): Unit } /* @@ -494,19 +492,16 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => /* Asserting facts */ - def checkSmoke(analysisInfos: DependencyAnalysisInfos): Boolean = checkSmoke(analysisInfos, isAssert = false) - def checkSmoke(analysisInfos: DependencyAnalysisInfos, isAssert: Boolean=false): Boolean = { + if(isPathInfeasible) return true + val checkNode = dependencyAnalyzer.createAssertOrCheckNode(False, analysisInfos, !isAssert) val label = DependencyAnalyzer.createAssertionLabel(checkNode.map(_.id)) val timeout = if (isAssert) Verifier.config.assertTimeout.toOption else Verifier.config.checkTimeout.toOption - val result = isPathInfeasible || prover.check(timeout, label) == Unsat + val result = prover.check(timeout, label) == Unsat - if(isPathInfeasible){ - checkNode foreach dependencyAnalyzer.addAssertionNode - dependencyAnalyzer.addDependency(pcs.getCurrentInfeasibilityNode, checkNode.map(_.id)) - }else if(result){ + if(result){ checkNode foreach dependencyAnalyzer.addAssertionNode dependencyAnalyzer.processUnsatCoreAndAddDependencies(prover.getLastUnsatCore, label) val infeasibleNodeId = dependencyAnalyzer.addInfeasibilityNode(!isAssert, analysisInfos) @@ -514,28 +509,18 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => // assumeWithoutSmokeChecks(InsertionOrderedSet((False, DependencyAnalyzer.createAssumptionLabel(infeasibleNodeId)))) dependencyAnalyzer.addDependency(checkNode.map(_.id), infeasibleNodeId) pcs.setCurrentInfeasibilityNode(checkNode.map(_.id)) + }else if(isAssert){ + checkNode foreach (node => dependencyAnalyzer.addAssertionNode(node.getAssertFailedNode)) } result } - def checkSmokeAndSetInfeasibilityNode(analysisInfos: DependencyAnalysisInfos): Unit = { - var infeasibilityNodeId: Option[Int] = pcs.getCurrentInfeasibilityNode - if(infeasibilityNodeId.isDefined) return - - val (success, checkNode) = deciderAssert(False, analysisInfos, Some(Verifier.config.checkTimeout()), isCheck=true) - if(success){ - infeasibilityNodeId = dependencyAnalyzer.addInfeasibilityNode(isCheck = true, analysisInfos) - dependencyAnalyzer.addDependency(checkNode.map(_.id), infeasibilityNodeId) - pcs.setCurrentInfeasibilityNode(infeasibilityNodeId) - } - } - - override def handleFailedAssertionForDependencyAnalysis(failedAssertion: Term, analysisInfos: DependencyAnalysisInfos, assumeFailedAssertion: Boolean): Unit = { + override def handleFailedAssertion(failedAssertion: Term, analysisInfos: DependencyAnalysisInfos, assumeFailedAssertion: Boolean): Unit = { dependencyAnalyzer.addAssertionFailedNode(failedAssertion, analysisInfos) if(assumeFailedAssertion){ assume(failedAssertion, None, None, analysisInfos) failedAssertion match { - case False => checkSmokeAndSetInfeasibilityNode(analysisInfos) + case False => checkSmoke(analysisInfos) case _ => } } diff --git a/src/main/scala/dependencyAnalysis/DependencyGraph.scala b/src/main/scala/dependencyAnalysis/DependencyGraph.scala index abbc2e502..d3ec0c309 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraph.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraph.scala @@ -208,7 +208,7 @@ class DependencyGraph extends ReadOnlyDependencyGraph { predecessors foreach (pid => edges.update(pid, edges.getOrElse(pid, Set.empty).filter(_ != id) ++ successors)) } - // TODO ake: maybe move to DependencyAnalyzer? + def removeLabelNodes(): Unit = { def filterCriteria(n: DependencyAnalysisNode) = n.isInstanceOf[LabelNode] @@ -245,7 +245,7 @@ class DependencyGraph extends ReadOnlyDependencyGraph { private def exportNodes(fileName: String): Unit = { val sep = "#" def getNodeExportString(node: DependencyAnalysisNode): String = { - val parts = mutable.Seq(node.id.toString, node.getNodeType, node.assumptionType.toString, node.getNodeString, node.sourceInfo.toString, node.sourceInfo.getPositionString, node.sourceInfo.toString /* TODO ake: merge info */, node.sourceInfo.getDescription) + val parts = mutable.Seq(node.id.toString, node.getNodeType, node.assumptionType.toString, node.getNodeString, node.sourceInfo.toString, node.sourceInfo.getPositionString, node.mergeInfo.toString, node.sourceInfo.getDescription) parts.map(_.replace("#", "@")).mkString(sep) } val headerParts = mutable.Seq("id", "node type", "assumption type", "node info", "source info", "position", "fine grained source", "description") diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index fab84fa8b..84cfddb6a 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -254,12 +254,6 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen computeVerificationProgressOptimized() } -// TODO ake: remove profiling artifacts -// var perMethodDependencyRuntime: Long = 0L -// var depsToPostcondRuntime: Long = 0L -// var aggregationOfSummaryNodesRuntime: Long = 0L -// var filteringNodesRuntime: Long = 0L - private lazy val sourceToAssertionNodes: Map[AnalysisSourceInfo, Set[DependencyAnalysisNode]] = getNonInternalAssertionNodes.groupBy(_.sourceInfo) @@ -407,15 +401,11 @@ class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependen // println(s"#assertions: ${allAssertions.size}") -// val startTime = System.nanoTime() - // TODO ake: this is suuuper slow. Can we reuse previously computed results? Caching? + // This is super slow. See optimized progress computation. val relevantDependenciesPerAssertion = allAssertions .map(ass => (ass, toUserLevelNodes(getAllNonInternalDependencies(ass.lowerLevelNodes.map(_.id))).diffBySource(Set(ass)))).toMap .filter{case (ass, assumptions) => assumptions.nonEmpty || ass.hasFailures || ass.assertionTypes.contains(AssumptionType.ExplicitPostcondition)} // filter out trivial assertions like `assert true` -// val endTime = System.nanoTime() -// println(s"Runtime of computing dependencies per assertion: ${(endTime-startTime)/1e6}ms") - val relevantDependencies = relevantDependenciesPerAssertion.flatMap(_._2).filter(_.assumptionTypes.nonEmpty).toSet val explicitAssertions = toUserLevelNodes(getExplicitAssertionNodes).getSourceSet() diff --git a/src/main/scala/rules/Brancher.scala b/src/main/scala/rules/Brancher.scala index 8c0290e33..5d4f495be 100644 --- a/src/main/scala/rules/Brancher.scala +++ b/src/main/scala/rules/Brancher.scala @@ -162,7 +162,7 @@ object brancher extends BranchingRules { executionFlowController.locally(s, v0)((s1, v1) => { v1.decider.prover.comment(s"[else-branch: $cnt | $negatedCondition]") v1.decider.setCurrentBranchCondition(negatedCondition, (negatedConditionExp, negatedConditionExpNew), analysisInfos1) - if(v.decider.isDependencyAnalysisEnabled && !executeElseBranch) v.decider.checkSmokeAndSetInfeasibilityNode(analysisInfos1) + if(v.decider.isDependencyAnalysisEnabled && !executeElseBranch) v.decider.checkSmoke(analysisInfos1) var functionsOfElseBranchdDeciderBefore: Set[FunctionDecl] = null var nMacrosOfElseBranchDeciderBefore: Int = 0 @@ -213,7 +213,7 @@ object brancher extends BranchingRules { executionFlowController.locally(s, v)((s1, v1) => { v1.decider.prover.comment(s"[then-branch: $cnt | $condition]") v1.decider.setCurrentBranchCondition(condition, conditionExp, analysisInfos1) - if(v.decider.isDependencyAnalysisEnabled && !executeThenBranch) v.decider.checkSmokeAndSetInfeasibilityNode(analysisInfos1) + if(v.decider.isDependencyAnalysisEnabled && !executeThenBranch) v.decider.checkSmoke(analysisInfos1) fThen(v1.stateConsolidator(s1).consolidateOptionally(s1, v1), v1) }) diff --git a/src/main/scala/rules/ChunkSupporter.scala b/src/main/scala/rules/ChunkSupporter.scala index 938b00968..2307774c6 100644 --- a/src/main/scala/rules/ChunkSupporter.scala +++ b/src/main/scala/rules/ChunkSupporter.scala @@ -157,7 +157,7 @@ object chunkSupporter extends ChunkSupportRules { Success() // TODO: Mark branch as dead? case _ => val failure = createFailure(ve, v1, s1, "consuming chunk", true) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfos, v1.reportFurtherErrors()) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertion(False, analysisInfos, v1.reportFurtherErrors()) if(s1.retryLevel == 0 && v1.reportFurtherErrors() && Verifier.config.disableInfeasibilityChecks()){ failure combine QS(s1.copy(h = s.h), s1.h, None, v1) }else{ @@ -235,8 +235,6 @@ object chunkSupporter extends ChunkSupportRules { def produce(s: State, h: Heap, ch: NonQuantifiedChunk, v: Verifier) (Q: (State, Heap, Verifier) => VerificationResult) : VerificationResult = { - - // TODO ake val analysisInfos = DependencyAnalysisInfos.DefaultDependencyAnalysisInfos.withSource(StringAnalysisSourceInfo("produce", ast.NoPosition)).withDependencyType(DependencyType.Internal) // Try to merge the chunk into the heap by finding an alias. // In any case, property assumptions are added after the merge step. @@ -293,7 +291,7 @@ object chunkSupporter extends ChunkSupportRules { } case _ => val failure = createFailure(ve, v, s, "looking up chunk", true) - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfos, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertion(False, analysisInfos, v.reportFurtherErrors()) if(s.retryLevel == 0 && v.reportFurtherErrors() && Verifier.config.disableInfeasibilityChecks()){ val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) failure combine Q(s, snap, v) diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index 83d206bc5..294b5746f 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -435,7 +435,7 @@ object consumer extends ConsumptionRules { QS(s3, v2) case false => val failure = createFailure(pve dueTo AssertionFalse(e), v2, s3, termToAssert, eNew) - if(s3.retryLevel == 0) v2.decider.handleFailedAssertionForDependencyAnalysis(t, analysisInfos, assumeFailedAssertion=false) + if(s3.retryLevel == 0) v2.decider.handleFailedAssertion(t, analysisInfos, assumeFailedAssertion=false) if (s3.retryLevel == 0 && v2.reportFurtherErrors()){ v2.decider.assume(t, Option.when(withExp)(e), eNew, analysisInfos.withDependencyType(DependencyType.Explicit)) failure combine QS(s3, v2) diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index d4015ec9f..f66488c06 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -254,7 +254,7 @@ object evaluator extends EvaluationRules { s.oldHeaps.get(heapName) match { case None => val failure = createFailure(pve dueTo LabelledStateNotReached(ast.LabelledOld(e0, heapName)(old.pos, old.info, old.errT)), v, s, "labelled state reached") - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfos, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertion(False, analysisInfos, v.reportFurtherErrors()) val freshVar = v.decider.fresh(v.symbolConverter.toSort(old.typ), None) if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, freshVar, None, v) else failure case _ => @@ -266,7 +266,7 @@ object evaluator extends EvaluationRules { s.oldHeaps.get(lbl) match { case None => val failure = createFailure(pve dueTo LabelledStateNotReached(old), v, s, "labelled state reached") - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfos, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertion(False, analysisInfos, v.reportFurtherErrors()) val freshVar = v.decider.fresh(v.symbolConverter.toSort(old.typ), None) if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, freshVar, None, v) else failure case _ => @@ -589,7 +589,7 @@ object evaluator extends EvaluationRules { Unreachable() } else { val failure = createFailure(pve.dueTo(InternalReason(sourceQuant, "Quantifier evaluation failed.")), v1, s1, "quantifier could be evaluated") - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfos, v1.reportFurtherErrors()) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertion(False, analysisInfos, v1.reportFurtherErrors()) val freshVar = v1.decider.fresh(v1.symbolConverter.toSort(sourceQuant.typ), None) if(s1.retryLevel == 0 && v1.reportFurtherErrors()) failure combine Q(s1, freshVar, None, v1) else failure } @@ -790,8 +790,8 @@ object evaluator extends EvaluationRules { case false => v2.decider.finishDebugSubExp(s"unfolded(${predicate.name})") val failure = createFailure(pve dueTo NonPositivePermission(ePerm.get), v2, s2, IsPositive(tPerm), ePermNew.map(p => ast.PermGtCmp(p, ast.NoPerm()())(p.pos, p.info, p.errT))) - if(s2.retryLevel == 0) v2.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfos, v2.reportFurtherErrors()) - val freshVar = v2.decider.fresh(v2.symbolConverter.toSort(e.typ), None) // TODO ake: function recorder + if(s2.retryLevel == 0) v2.decider.handleFailedAssertion(False, analysisInfos, v2.reportFurtherErrors()) + val freshVar = v2.decider.fresh(v2.symbolConverter.toSort(e.typ), None) if(s2.retryLevel == 0 && v2.reportFurtherErrors() && Verifier.config.disableInfeasibilityChecks()) failure combine Q(s2, freshVar, None, v2) else failure })) } else { @@ -834,7 +834,7 @@ object evaluator extends EvaluationRules { case false => val assertExp2 = Option.when(withExp)(ast.LtCmp(e1, ast.SeqLength(e0)())(e1.pos, e1.info, e1.errT)) val failure = createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, Less(t1, SeqLength(t0)), assertExp2) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(Less(t1, SeqLength(t0)), analysisInfos, assumeFailedAssertion=false) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertion(Less(t1, SeqLength(t0)), analysisInfos, assumeFailedAssertion=false) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { val assertExp2 = Option.when(withExp)(ast.LeCmp(e1, ast.SeqLength(e0)())()) val assertExp2New = esNew.map(es => ast.LeCmp(es(1), ast.SeqLength(es.head)())()) @@ -845,7 +845,7 @@ object evaluator extends EvaluationRules { val assertExp1 = Option.when(withExp)(ast.GeCmp(e1, ast.IntLit(0)())(e1.pos, e1.info, e1.errT)) val assertExp1New = Option.when(withExp)(ast.GeCmp(esNew.get(1), ast.IntLit(0)())(e1.pos, e1.info, e1.errT)) val failure1 = createFailure(pve dueTo SeqIndexNegative(e0, e1), v1, s1, AtLeast(t1, IntLiteral(0)), assertExp1New) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(AtLeast(t1, IntLiteral(0)), analysisInfos, assumeFailedAssertion=false) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertion(AtLeast(t1, IntLiteral(0)), analysisInfos, assumeFailedAssertion=false) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { v1.decider.assume(AtLeast(t1, IntLiteral(0)), assertExp1, assertExp1New, analysisInfos.withDependencyType(DependencyType.Explicit)) val assertExp2 = Option.when(withExp)(ast.LtCmp(e1, ast.SeqLength(e0)())(e1.pos, e1.info, e1.errT)) @@ -855,7 +855,7 @@ object evaluator extends EvaluationRules { failure1 combine Q(s1, SeqAt(t0, t1), eNew, v1) case false => val failure2 = failure1 combine createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, Less(t1, SeqLength(t0)), assertExp2New) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(Less(t1, SeqLength(t0)), analysisInfos, assumeFailedAssertion=false) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertion(Less(t1, SeqLength(t0)), analysisInfos, assumeFailedAssertion=false) if (v1.reportFurtherErrors()) { v1.decider.assume(Less(t1, SeqLength(t0)), assertExp2, assertExp2New, analysisInfos.withDependencyType(DependencyType.Explicit)) failure2 combine Q(s1, SeqAt(t0, t1), eNew, v1) @@ -890,7 +890,7 @@ object evaluator extends EvaluationRules { Q(s1, SeqUpdate(t0, t1, t2), eNew, v1) case false => val failure = createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, Less(t1, SeqLength(t0)), assertExp2New) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(Less(t1, SeqLength(t0)), analysisInfos, assumeFailedAssertion=false) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertion(Less(t1, SeqLength(t0)), analysisInfos, assumeFailedAssertion=false) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { val assertExp3 = Option.when(withExp)(ast.LeCmp(e1, ast.SeqLength(e0)())()) val assertExp3New = Option.when(withExp)(ast.LeCmp(esNew.get(1), ast.SeqLength(esNew.get(0))())()) @@ -899,7 +899,7 @@ object evaluator extends EvaluationRules { else failure} case false => val failure1 = createFailure(pve dueTo SeqIndexNegative(e0, e1), v1, s1, AtLeast(t1, IntLiteral(0)), assertExpNew) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(AtLeast(t1, IntLiteral(0)), analysisInfos, assumeFailedAssertion=false) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertion(AtLeast(t1, IntLiteral(0)), analysisInfos, assumeFailedAssertion=false) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { v1.decider.assume(AtLeast(t1, IntLiteral(0)), assertExp, assertExpNew, analysisInfos.withDependencyType(DependencyType.Explicit)) val assertExp2 = Option.when(withExp)(ast.LtCmp(e1, ast.SeqLength(e0)())(e1.pos, e1.info, e1.errT)) @@ -909,7 +909,7 @@ object evaluator extends EvaluationRules { failure1 combine Q(s1, SeqUpdate(t0, t1, t2), eNew, v1) case false => val failure2 = failure1 combine createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, Less(t1, SeqLength(t0)), assertExp2New) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(Less(t1, SeqLength(t0)), analysisInfos, assumeFailedAssertion=false) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertion(Less(t1, SeqLength(t0)), analysisInfos, assumeFailedAssertion=false) if (v1.reportFurtherErrors()) { v1.decider.assume(Less(t1, SeqLength(t0)), assertExp2, assertExp2New, analysisInfos.withDependencyType(DependencyType.Explicit)) failure2 combine Q(s1, SeqUpdate(t0, t1, t2), eNew, v1) @@ -1029,7 +1029,7 @@ object evaluator extends EvaluationRules { val assertExp = Option.when(withExp)(ast.MapContains(key, base)(ml.pos, ml.info, ml.errT)) val assertExpNew = Option.when(withExp)(ast.MapContains(esNew.get(1), esNew.get(0))(ml.pos, ml.info, ml.errT)) val failure1 = createFailure(pve dueTo MapKeyNotContained(base, key), v1, s1, SetIn(keyT, MapDomain(baseT)), assertExpNew) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(SetIn(keyT, MapDomain(baseT)), analysisInfos, assumeFailedAssertion=false) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertion(SetIn(keyT, MapDomain(baseT)), analysisInfos, assumeFailedAssertion=false) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { v1.decider.assume(SetIn(keyT, MapDomain(baseT)), assertExp, assertExpNew, analysisInfos.withDependencyType(DependencyType.Explicit)) failure1 combine Q(s1, MapLookup(baseT, keyT), eNew, v1) @@ -1265,7 +1265,7 @@ object evaluator extends EvaluationRules { (Some(ast.NeCmp(eDivisor, ast.IntLit(0)())(eDivisor.pos, eDivisor.info, eDivisor.errT)), Some(ast.NeCmp(eDivisorNew.get, ast.IntLit(0)())(eDivisor.pos, eDivisor.info, eDivisor.errT))) } else { (None, None) } val failure = createFailure(pve dueTo DivisionByZero(eDivisor), v, s, tDivisor !== tZero, notZeroExpNew) - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(tDivisor !== tZero, analysisInfos, assumeFailedAssertion=false) + if(s.retryLevel == 0) v.decider.handleFailedAssertion(tDivisor !== tZero, analysisInfos, assumeFailedAssertion=false) if (s.retryLevel == 0 && v.reportFurtherErrors()) { v.decider.assume(tDivisor !== tZero, notZeroExp, notZeroExpNew, analysisInfos.withDependencyType(DependencyType.Explicit)) failure combine Q(s, t, v) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 8233d57ba..a845e7da6 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -20,8 +20,8 @@ import viper.silicon.state.terms._ import viper.silicon.utils.ast.{BigAnd, extractPTypeFromExp, simplifyVariableName} import viper.silicon.utils.freshSnap import viper.silicon.verifier.Verifier -import viper.silver.ast.{EdgeType, EvalStackDependencyAnalysisJoin, JoinType} import viper.silver.ast.utility.Statements +import viper.silver.ast.{EdgeType, EvalStackDependencyAnalysisJoin, JoinType} import viper.silver.cfg.silver.SilverCfg import viper.silver.cfg.silver.SilverCfg.{SilverBlock, SilverEdge} import viper.silver.cfg.{ConditionalEdge, StatementBlock} @@ -96,7 +96,7 @@ object executor extends ExecutionRules { def handleOutEdge(s: State, edge: SilverEdge, v: Verifier): State = { edge.kind match { case cfg.Kind.Out if !v.decider.isPathInfeasible => - val analysisInfos = DependencyAnalysisInfos.DefaultDependencyAnalysisInfos // TODO ake + val analysisInfos = DependencyAnalysisInfos.DefaultDependencyAnalysisInfos val (fr1, h1) = v.stateConsolidator(s).merge(s.functionRecorder, s, s.h, s.invariantContexts.head, v, analysisInfos) val s1 = s.copy(functionRecorder = fr1, h = h1, invariantContexts = s.invariantContexts.tail) @@ -459,7 +459,7 @@ object executor extends ExecutionRules { case _ => produce(s, freshSnap, a, InhaleFailed(inhale), v, analysisInfos)((s1, v1) => { v1.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterInhale) - if(v1.decider.isDependencyAnalysisEnabled && a.isInstanceOf[ast.FalseLit]) v1.decider.checkSmokeAndSetInfeasibilityNode(analysisInfos) + if(v1.decider.isDependencyAnalysisEnabled && a.isInstanceOf[ast.FalseLit]) v1.decider.checkSmoke(analysisInfos) Q(s1, v1)}) } @@ -475,7 +475,7 @@ object executor extends ExecutionRules { QS(s1.copy(h = s.h), v1) else { val failure = createFailure(AssertFailed(assert) dueTo AssertionFalse(a), v1, s1, False, true, Option.when(withExp)(a)) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfos, v1.reportFurtherErrors()) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertion(False, analysisInfos, v1.reportFurtherErrors()) if(s1.retryLevel == 0 && v1.reportFurtherErrors()) failure combine QS(s1, v1) else failure } })((s2, v2) => @@ -536,7 +536,7 @@ object executor extends ExecutionRules { // Calling hack510() triggers a state consolidation. // See also Silicon issue #510. case ast.MethodCall(`hack510_method_name`, _, _) => - val s1 = v.stateConsolidator(s).consolidate(s, v) // TODO ake: pass assumption Type? + val s1 = v.stateConsolidator(s).consolidate(s, v) Q(s1, v) case call @ ast.MethodCall(methodName, eArgs, lhs) => diff --git a/src/main/scala/rules/HavocSupporter.scala b/src/main/scala/rules/HavocSupporter.scala index 5da1e9ee4..0cbcc164e 100644 --- a/src/main/scala/rules/HavocSupporter.scala +++ b/src/main/scala/rules/HavocSupporter.scala @@ -127,7 +127,7 @@ object havocSupporter extends SymbolicExecutionRules { v.decider.assert(receiverInjectivityCheck, analysisInfos) { case false => val failure = createFailure(pve dueTo notInjectiveReason, v, s1, receiverInjectivityCheck, "QP receiver injective") - if(s1.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(receiverInjectivityCheck, analysisInfos, v.reportFurtherErrors()) + if(s1.retryLevel == 0) v.decider.handleFailedAssertion(receiverInjectivityCheck, analysisInfos, v.reportFurtherErrors()) if(s1.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s1, v1) else failure case true => // Generate the inverse axioms diff --git a/src/main/scala/rules/HeapSupporter.scala b/src/main/scala/rules/HeapSupporter.scala index 131ab1f74..0d5b47d84 100644 --- a/src/main/scala/rules/HeapSupporter.scala +++ b/src/main/scala/rules/HeapSupporter.scala @@ -241,7 +241,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { Q(s5, v) case (Incomplete(_, _), s3, _) => val failure = createFailure(ve, v, s3, "sufficient permission") - if(s3.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfos, v.reportFurtherErrors()) + if(s3.retryLevel == 0) v.decider.handleFailedAssertion(False, analysisInfos, v.reportFurtherErrors()) if(s3.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s3, v) else failure } } else { @@ -375,7 +375,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { v.decider.assert(toAssert, analysisInfos) { case false => val failure = createFailure(ve, v, s, toAssert, Option.when(withExp)(perms.IsPositive(ast.CurrentPerm(fa)())())) - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(toAssert, analysisInfos, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertion(toAssert, analysisInfos, v.reportFurtherErrors()) val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(fa.field, s, v), Option.when(withExp)(PUnknown())) if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, snap, v) else failure case true => @@ -429,7 +429,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { v.decider.assert(permCheck,analysisInfos) { case false => val failure = createFailure(ve, v, s3, permCheck, permCheckExp) - if(s3.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(permCheck, analysisInfos, v.reportFurtherErrors()) + if(s3.retryLevel == 0) v.decider.handleFailedAssertion(permCheck, analysisInfos, v.reportFurtherErrors()) val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(fa.field, s3, v), Option.when(withExp)(PUnknown())) if(s3.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s3, snap, v) else failure case true => diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 754cfaaf7..51297a127 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -214,7 +214,7 @@ object magicWandSupporter extends SymbolicExecutionRules { assert(consumedChunks.length == hs.length) Q(s1, heaps.reverse, consumedChunks.reverse, v) case Incomplete(_, _) => - if(s1.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfos, v.reportFurtherErrors()) + if(s1.retryLevel == 0) v.decider.handleFailedAssertion(False, analysisInfos, v.reportFurtherErrors()) if(s1.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s1, heaps.reverse, consumedChunks.reverse, v) else failure } } diff --git a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala index 1cc577167..1bf422ad1 100644 --- a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala +++ b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala @@ -109,7 +109,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { Implies(And(argumentEqualities, IsPositive(ch.perm)), `?s` === ch.snap) }) - val analysisInfos = DependencyAnalysisInfos.create("summarize", DependencyType.Internal) // TODO ake + val analysisInfos = DependencyAnalysisInfos.create("summarize", DependencyType.Internal) val taggedSummarisingSnapshot = summarisingSnapshotDefinitions @@ -168,7 +168,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { // query to check if the permission amount we have is sufficient to get the correct counterexample. If we perform // the query in two parts (one part here, one part in our caller to see if the permission amount is sufficient), // the counterexample might be wrong. - val analysisInfos = DependencyAnalysisInfos.create("summarise", DependencyType.Internal) // TODO ake + val analysisInfos = DependencyAnalysisInfos.create("summarise", DependencyType.Internal) if (relevantChunks.size == 1 && !Verifier.config.counterexample.isDefined) { val chunk = relevantChunks.head @@ -220,7 +220,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { } } else { val failure = createFailure(ve, v, s, False, "branch is dead") - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfos, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertion(False, analysisInfos, v.reportFurtherErrors()) if(s.retryLevel == 0 && v.reportFurtherErrors() && Verifier.config.disableInfeasibilityChecks()){ val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) failure combine Q(s, snap, v) @@ -235,7 +235,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { Q(s1, snap, v1) case false => val failure = createFailure(ve, v, s1, IsPositive(permSum), permSumExp.map(IsPositive(_)())) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(IsPositive(permSum), analysisInfos, v1.reportFurtherErrors()) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertion(IsPositive(permSum), analysisInfos, v1.reportFurtherErrors()) if(s1.retryLevel == 0 && v1.reportFurtherErrors()) failure combine Q(s1, snap, v1) else failure }) } @@ -284,7 +284,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { Q(s1, h, Some(snap), v1) case false => val failure = createFailure(ve, v, s1, IsPositive(permSum), permSumExp.map(IsPositive(_)())) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(Implies(IsPositive(perm), IsPositive(permSum)), analysisInfos, v1.reportFurtherErrors()) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertion(Implies(IsPositive(perm), IsPositive(permSum)), analysisInfos, v1.reportFurtherErrors()) if(s1.retryLevel == 0 && v1.reportFurtherErrors()) failure combine Q(s1, h, Some(snap), v1) else failure }) } else { @@ -294,7 +294,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { Q(s1, h, None, v) case false => val failure = createFailure(ve, v, s1, IsPositive(permSum), permSumExp.map(IsPositive(_)())) - if(s1.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(Implies(IsPositive(perm), IsPositive(permSum)), analysisInfos, v.reportFurtherErrors()) + if(s1.retryLevel == 0) v.decider.handleFailedAssertion(Implies(IsPositive(perm), IsPositive(permSum)), analysisInfos, v.reportFurtherErrors()) if(s1.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s1, h, None, v) else failure } } @@ -328,7 +328,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { case true => Q(s, h, None, v) case false => val failure = createFailure(ve, v, s, perms === NoPerm, permsExp.map(pe => ast.EqCmp(pe, ast.NoPerm()())(pe.pos, pe.info, pe.errT))) - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(perms === NoPerm, analysisInfos, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertion(perms === NoPerm, analysisInfos, v.reportFurtherErrors()) if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, h, None, v) else failure } } else { @@ -431,7 +431,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { Q(s1, newHeap, condSnap, v1) case false => val failure = createFailure(ve, v1, s1, pNeeded === NoPerm, pNeededExp.map(pn => ast.EqCmp(pn, ast.NoPerm()())(pn.pos, pn.info, pn.errT))) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertionForDependencyAnalysis(pNeeded === NoPerm, analysisInfos, v1.reportFurtherErrors()) + if(s1.retryLevel == 0) v1.decider.handleFailedAssertion(pNeeded === NoPerm, analysisInfos, v1.reportFurtherErrors()) if(s1.retryLevel == 0 && v1.reportFurtherErrors()) failure combine Q(s1, newHeap, condSnap, v1) else failure } } @@ -445,7 +445,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { Q(s0, newHeap, None, v) case false => val failure = createFailure(ve, v, s0, pNeeded === NoPerm, pNeededExp.map(pn => ast.EqCmp(pn, ast.NoPerm()())(pn.pos, pn.info, pn.errT))) - if(s0.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(pNeeded === NoPerm, analysisInfos, v.reportFurtherErrors()) + if(s0.retryLevel == 0) v.decider.handleFailedAssertion(pNeeded === NoPerm, analysisInfos, v.reportFurtherErrors()) if(s0.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s0, newHeap, None, v) else failure } } @@ -538,9 +538,9 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { case false => v.decider.finishDebugSubExp(s"consume permissions for ${resource.toString()}") val failure = createFailure(ve, v, s, totalPermTaken !== NoPerm, totalPermTakenExp.map(tpt => ast.NeCmp(tpt, ast.NoPerm()())())) - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(Implies(PermLess(NoPerm, perms), totalPermTaken !== NoPerm), analysisInfos, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertion(Implies(PermLess(NoPerm, perms), totalPermTaken !== NoPerm), analysisInfos, v.reportFurtherErrors()) if(s.retryLevel == 0 && v.reportFurtherErrors()){ - val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) // TODO ake: function recorder + val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) failure combine Q(s1, updatedChunks, if(returnSnap) Some(snap) else None, v) }else{ failure diff --git a/src/main/scala/rules/PermissionSupporter.scala b/src/main/scala/rules/PermissionSupporter.scala index 339c665f3..62e367b3a 100644 --- a/src/main/scala/rules/PermissionSupporter.scala +++ b/src/main/scala/rules/PermissionSupporter.scala @@ -29,7 +29,7 @@ object permissionSupporter extends SymbolicExecutionRules { case false => val assertExp = ePermNew.map(ep => perms.IsNonNegative(ep)(ep.pos, ep.info, ep.errT)) val failure = createFailure(pve dueTo NegativePermission(ePerm), v, s, perms.IsNonNegative(tPerm), assertExp) - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(perms.IsNonNegative(tPerm), analysisInfos, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertion(perms.IsNonNegative(tPerm), analysisInfos, v.reportFurtherErrors()) if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, v) else failure } } @@ -47,7 +47,7 @@ object permissionSupporter extends SymbolicExecutionRules { case true => Q(s, v) case false => val failure = createFailure(pve dueTo NonPositivePermission(ePerm), v, s, perms.IsPositive(tPerm), Option.when(withExp)(perms.IsPositive(ePerm)())) - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(perms.IsPositive(tPerm), analysisInfos, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertion(perms.IsPositive(tPerm), analysisInfos, v.reportFurtherErrors()) if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, v) else failure } } diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 276f86f42..2215f347b 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -1042,12 +1042,12 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { Q(s1, v) case false => val failure = createFailure(pve dueTo notInjectiveReason, v, s, receiverInjectivityCheck, "QP receiver is injective") - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(completeReceiverInjectivityCheck, analysisInfos, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertion(completeReceiverInjectivityCheck, analysisInfos, v.reportFurtherErrors()) if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, v) else failure } case false => val failure = createFailure(pve dueTo negativePermissionReason, v, s, nonNegImplication, nonNegImplicationExp) - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(nonNegTerm, analysisInfos, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertion(nonNegTerm, analysisInfos, v.reportFurtherErrors()) if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, v) else failure } @@ -1382,15 +1382,15 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { } case (Incomplete(_, _), s2, _) => val failure = createFailure(pve dueTo insufficientPermissionReason, v, s2, "QP consume") - if(s2.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfos, v.reportFurtherErrors()) + if(s2.retryLevel == 0) v.decider.handleFailedAssertion(False, analysisInfos, v.reportFurtherErrors()) if(s2.retryLevel == 0 && v.reportFurtherErrors() && Verifier.config.disableInfeasibilityChecks()) failure combine Q(s2, s2.h, None, v) else failure } } case false => val failure = createFailure(pve dueTo notInjectiveReason, v, s, receiverInjectivityCheck, "QP receiver injective") - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(receiverInjectivityCheck, analysisInfos, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertion(receiverInjectivityCheck, analysisInfos, v.reportFurtherErrors()) if(s.retryLevel == 0 && v.reportFurtherErrors()){ - val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) // TODO ake: function recorder + val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) failure combine Q(s, s.h, if(returnSnap) Some(snap) else None, v) }else{ failure @@ -1398,7 +1398,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { } case false => val failure = createFailure(pve dueTo negativePermissionReason, v, s, nonNegTerm, nonNegExp) - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(nonNegTerm, analysisInfos, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertion(nonNegTerm, analysisInfos, v.reportFurtherErrors()) if(s.retryLevel == 0 && v.reportFurtherErrors()){ val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) failure combine Q(s, s.h, if(returnSnap) Some(snap) else None, v) @@ -1536,7 +1536,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { case wand: ast.MagicWand => createFailure(pve dueTo MagicWandChunkNotFound(wand), v, s, "single QP consume") case _ => sys.error(s"Found resource $resourceAccess, which is not yet supported as a quantified resource.") } - if(s.retryLevel == 0) v.decider.handleFailedAssertionForDependencyAnalysis(False, analysisInfos, v.reportFurtherErrors()) + if(s.retryLevel == 0) v.decider.handleFailedAssertion(False, analysisInfos, v.reportFurtherErrors()) if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, s.h, None, v) else failure } } diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index 55e385a79..06cd5a27d 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -24,8 +24,8 @@ import viper.silicon.utils.{freshSnap, toSf} import viper.silicon.verifier.{Verifier, VerifierComponent} import viper.silicon.{Map, Stack, toMap} import viper.silver.ast +import viper.silver.ast._ import viper.silver.ast.utility.Functions -import viper.silver.ast.{And, _} import viper.silver.components.StatefulComponent import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyAnalysisJoinNodeInfo, DependencyType} import viper.silver.parser.PType @@ -206,7 +206,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver if (function.body.isEmpty) { decider.dependencyAnalyzer.addNodes(v.decider.prover.getPreambleAnalysisNodes) - decider.dependencyAnalyzer.addDependenciesForAbstractMembers(function.pres.flatMap(_.topLevelConjuncts), function.posts.flatMap(_.topLevelConjuncts), DependencyAnalysisInfos.DefaultDependencyAnalysisInfos /* TODO ake */) + decider.dependencyAnalyzer.addDependenciesForAbstractMembers(function.pres.flatMap(_.topLevelConjuncts), function.posts.flatMap(_.topLevelConjuncts), DependencyAnalysisInfos.DefaultDependencyAnalysisInfos) result1 } else { /* Phase 2: Verify the function's postcondition */ From 89bfa0351513970c6e567cb986552edfe6740c38 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 31 Mar 2026 16:04:32 +0200 Subject: [PATCH 413/474] collect DependencyAnalysisInfos only if DA is enabled --- ...fo.scala => DependencyAnalysisInfos.scala} | 48 ++++++++++++------- 1 file changed, 31 insertions(+), 17 deletions(-) rename src/main/scala/dependencyAnalysis/{DependencyAnalysisInfo.scala => DependencyAnalysisInfos.scala} (71%) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisInfos.scala similarity index 71% rename from src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala rename to src/main/scala/dependencyAnalysis/DependencyAnalysisInfos.scala index de49e494e..dc8ee96e9 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisInfo.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisInfos.scala @@ -1,13 +1,15 @@ package viper.silicon.dependencyAnalysis +import viper.silicon.verifier.Verifier import viper.silver.ast -import viper.silver.ast.{DependencyAnalysisJoinInfo, DependencyAnalysisMergeInfo, DependencyTypeInfo, EvalStackDependencyAnalysisJoin, NoPosition, SimpleDependencyAnalysisJoin, SimpleDependencyAnalysisMerge} +import viper.silver.ast._ import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, DependencyType, StringAnalysisSourceInfo} - case class DependencyAnalysisInfos(sourceInfos: List[AnalysisSourceInfo], dependencyTypes: List[DependencyTypeInfo], mergeInfos: List[DependencyAnalysisMergeInfo], joinInfos: List[DependencyAnalysisJoinInfo], nodes: List[ast.Node]) { def addInfo(info: ast.Info, node: ast.Node): DependencyAnalysisInfos = { + if(!Verifier.config.enableDependencyAnalysis()) return this + val newSourceInfos = sourceInfos ++ info.getUniqueInfo[AnalysisSourceInfo].toList val newDependencyInfos = dependencyTypes ++ info.getUniqueInfo[DependencyTypeInfo].toList val newMergeInfos = mergeInfos ++ info.getUniqueInfo[DependencyAnalysisMergeInfo].toList @@ -16,6 +18,8 @@ case class DependencyAnalysisInfos(sourceInfos: List[AnalysisSourceInfo], depend } def addInfo(info: ast.Info): DependencyAnalysisInfos = { + if(!Verifier.config.enableDependencyAnalysis()) return this + val newSourceInfos = sourceInfos ++ info.getUniqueInfo[AnalysisSourceInfo].toList val newDependencyInfos = dependencyTypes ++ info.getUniqueInfo[DependencyTypeInfo].toList val newMergeInfos = mergeInfos ++ info.getUniqueInfo[DependencyAnalysisMergeInfo].toList @@ -23,15 +27,21 @@ case class DependencyAnalysisInfos(sourceInfos: List[AnalysisSourceInfo], depend DependencyAnalysisInfos(newSourceInfos, newDependencyInfos, newMergeInfos, newJoinInfos, nodes) } - def addInfo(infoString: String, pos: ast.Position, dependencyType: DependencyType): DependencyAnalysisInfos = - this.copy(sourceInfos = sourceInfos ++ List(StringAnalysisSourceInfo(infoString, pos)), dependencyTypes = dependencyTypes ++ List(DependencyTypeInfo(dependencyType))) + def addInfo(infoString: String, pos: ast.Position, dependencyType: DependencyType): DependencyAnalysisInfos = { + if(!Verifier.config.enableDependencyAnalysis()) return this + this.copy(sourceInfos = sourceInfos ++ List(StringAnalysisSourceInfo(infoString, pos)), dependencyTypes = dependencyTypes ++ List(DependencyTypeInfo(dependencyType))) + } + + def withDependencyType(dependencyType: DependencyType): DependencyAnalysisInfos = { + if(!Verifier.config.enableDependencyAnalysis()) return this - def withDependencyType(dependencyType: DependencyType): DependencyAnalysisInfos = { - this.copy(dependencyTypes = DependencyTypeInfo(dependencyType) +: dependencyTypes) + this.copy(dependencyTypes = DependencyTypeInfo(dependencyType) +: dependencyTypes) } def withSource(source: AnalysisSourceInfo): DependencyAnalysisInfos = { - this.copy(sourceInfos = source +: sourceInfos) + if(!Verifier.config.enableDependencyAnalysis()) return this + + this.copy(sourceInfos = source +: sourceInfos) } def getSourceInfo: AnalysisSourceInfo = sourceInfos.head @@ -41,19 +51,23 @@ case class DependencyAnalysisInfos(sourceInfos: List[AnalysisSourceInfo], depend def getMergeInfo: DependencyAnalysisMergeInfo = mergeInfos.headOption.getOrElse(SimpleDependencyAnalysisMerge(getSourceInfo)) def getJoinInfo: List[SimpleDependencyAnalysisJoin] = { - if(joinInfos.isEmpty) return List.empty - val h = joinInfos.head match { - case EvalStackDependencyAnalysisJoin(joinType, edgeType) => SimpleDependencyAnalysisJoin(sourceInfos.last, joinType, edgeType) - case a: SimpleDependencyAnalysisJoin => a - } - List(h) + joinInfos.map { + case EvalStackDependencyAnalysisJoin(joinType, edgeType) => SimpleDependencyAnalysisJoin(sourceInfos.last, joinType, edgeType) + case a: SimpleDependencyAnalysisJoin => a + } } - def withMergeInfo(mergeInfo: DependencyAnalysisMergeInfo): DependencyAnalysisInfos = - this.copy(mergeInfos = mergeInfo +: mergeInfos) + def withMergeInfo(mergeInfo: DependencyAnalysisMergeInfo): DependencyAnalysisInfos = { + if(!Verifier.config.enableDependencyAnalysis()) return this + + this.copy(mergeInfos = mergeInfo +: mergeInfos) + } + + def withJoinInfo(joinInfo: DependencyAnalysisJoinInfo): DependencyAnalysisInfos = { + if(!Verifier.config.enableDependencyAnalysis()) return this - def withJoinInfo(joinInfo: DependencyAnalysisJoinInfo): DependencyAnalysisInfos = - this.copy(joinInfos = joinInfo +: joinInfos) + this.copy(joinInfos = joinInfo +: joinInfos) + } } object DependencyAnalysisInfos { From 8b53c2269d8b78c189f3b6fefd79aa7c53ee2c19 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 31 Mar 2026 16:05:14 +0200 Subject: [PATCH 414/474] fix --- src/test/scala/DependencyAnalysisTests.scala | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/test/scala/DependencyAnalysisTests.scala b/src/test/scala/DependencyAnalysisTests.scala index 08112904f..87146ffd9 100644 --- a/src/test/scala/DependencyAnalysisTests.scala +++ b/src/test/scala/DependencyAnalysisTests.scala @@ -65,9 +65,7 @@ class DependencyAnalysisTests extends AnyFunSuite with DependencyAnalysisTestFra resetFrontend() executeTest(dirName + "/", fileName, frontend) }catch{ - case t: Throwable => - println(t.getMessage) - fail(t) + case t: Throwable => fail(t) } } } From 080116a6596e8b4c6b5ccc8429e8222114a70d67 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 8 Apr 2026 10:04:05 +0200 Subject: [PATCH 415/474] fix Silicon DA tests --- silver | 2 +- .../resources/dependencyAnalysisTests/all/divBy0.vpr | 8 ++++++-- .../dependencyAnalysisTests/all/function-sum.vpr | 6 ++++-- .../resources/dependencyAnalysisTests/all/method-sum.vpr | 9 ++++++--- src/test/scala/DependencyAnalysisTestFramework.scala | 2 +- 5 files changed, 18 insertions(+), 9 deletions(-) diff --git a/silver b/silver index 6fee57cb9..77e6fa189 160000 --- a/silver +++ b/silver @@ -1 +1 @@ -Subproject commit 6fee57cb9fa06cc510cc87272b87618db30fe6f4 +Subproject commit 77e6fa189c308910d08db70fc396f88b7ae9b088 diff --git a/src/test/resources/dependencyAnalysisTests/all/divBy0.vpr b/src/test/resources/dependencyAnalysisTests/all/divBy0.vpr index bae24a324..461c49a55 100644 --- a/src/test/resources/dependencyAnalysisTests/all/divBy0.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/divBy0.vpr @@ -1,7 +1,8 @@ field f: Int method sum(x: Int, y: Int) returns(res: Int) - requires x >= 0 && y >= 0 + requires x >= 0 + requires y >= 0 ensures res == x + y ensures res > 100 { @@ -51,8 +52,11 @@ method sumClient(x: Int, y: Int) // the following stmt reports dependency on n := sum(x, y) because (x < y && n == x + y && n > 100 ==> y != 0) // although you could also prove it via (0 <= x && x < y ==> y != 0) var n2: Int + var arg1: Int + @dependency("SourceCode") + arg1 := x/y @dependency("MethodCall") - n2 := sum(x/y, y) + n2 := sum(arg1, y) @dependency("SourceCode") n := n + n2 diff --git a/src/test/resources/dependencyAnalysisTests/all/function-sum.vpr b/src/test/resources/dependencyAnalysisTests/all/function-sum.vpr index 37c950f23..eca159f1d 100644 --- a/src/test/resources/dependencyAnalysisTests/all/function-sum.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/function-sum.vpr @@ -1,7 +1,8 @@ field f: Int function sum(x: Int, y: Int) : Int - requires x >= 0 && y >= 0 + requires x >= 0 + requires y >= 0 ensures result == x + y ensures result >= 0 { @@ -9,7 +10,8 @@ function sum(x: Int, y: Int) : Int } function sumUnverified(x: Int, y: Int): Int - requires x >= 0 && y >= 0 + requires x >= 0 + requires y >= 0 ensures result == x + y ensures result >= 0 diff --git a/src/test/resources/dependencyAnalysisTests/all/method-sum.vpr b/src/test/resources/dependencyAnalysisTests/all/method-sum.vpr index b5018cb43..7d7bb7220 100644 --- a/src/test/resources/dependencyAnalysisTests/all/method-sum.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/method-sum.vpr @@ -1,7 +1,7 @@ field f: Int method sum(x: Int, y: Int) returns(res: Int) - requires x >= 0 && y >= 0 + requires y >= 0 ensures res == x + y ensures res > 100 { @@ -24,9 +24,12 @@ method sumClient(x: Int, y: Int) // the following stmt reports dependency on n := sum(x, y) because (x < y && n == x + y && n > 100 ==> y != 0) // although you could also prove it via (0 <= x && x < y ==> y != 0) - var n2: Int + var n2: Int + var arg1: Int + @dependency("SourceCode") + arg1 := x/y @dependency("MethodCall") - n2 := sum(x/y, y) + n2 := sum(arg1, y) @dependency("SourceCode") n := n + n2 diff --git a/src/test/scala/DependencyAnalysisTestFramework.scala b/src/test/scala/DependencyAnalysisTestFramework.scala index bdea578d6..3760cd43f 100644 --- a/src/test/scala/DependencyAnalysisTestFramework.scala +++ b/src/test/scala/DependencyAnalysisTestFramework.scala @@ -101,7 +101,7 @@ trait DependencyAnalysisTestFramework { val (newProgram, pruningFactor) = fullGraphInterpreter.getPrunedProgram(crucialNodes, program) val result = baselineFrontend.verifier.verify(newProgram) if(EXPORT_PRUNED_PROGRAMS) exportPrunedProgram(exportFileName, newProgram, pruningFactor, result) - assert(!result.isInstanceOf[verifier.Failure], s"Failed to verify new program ${newProgram.toString()}") + assert(!result.isInstanceOf[verifier.Failure], s"Failed to verify new program. ${result.transformedResult()}\n${newProgram.toString()}") } protected def exportPrunedProgram(exportFileName: String, newProgram: Program, pruningFactor: Double, result: VerificationResult): Unit = { From 38e479f141cf3898ae9497491fd5f9935b073ce2 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 8 Apr 2026 10:04:54 +0200 Subject: [PATCH 416/474] update silver submodule --- silver | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/silver b/silver index 77e6fa189..2a52c524f 160000 --- a/silver +++ b/silver @@ -1 +1 @@ -Subproject commit 77e6fa189c308910d08db70fc396f88b7ae9b088 +Subproject commit 2a52c524f8e78509d65f6c90c56bbf23ba7683fd From 30d9bb3c3839a50b1571818aa01576b311b9927a Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 8 Apr 2026 17:12:06 +0200 Subject: [PATCH 417/474] add DependencyGraphState --- .../DependencyAnalysisReporter.scala | 4 +-- .../DependencyAnalysisResult.scala | 6 ++--- .../DependencyAnalysisUserTool.scala | 4 +-- .../DependencyAnalyzer.scala | 26 +++++++++---------- .../dependencyAnalysis/DependencyGraph.scala | 9 +++++-- .../DependencyGraphImporter.scala | 10 +++---- .../DependencyGraphInterpreter.scala | 4 +-- src/main/scala/interfaces/Verification.scala | 4 +-- 8 files changed, 36 insertions(+), 31 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisReporter.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisReporter.scala index 30660f68f..62b537012 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisReporter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisReporter.scala @@ -3,8 +3,8 @@ package viper.silicon.dependencyAnalysis import viper.silver.reporter.{Message, Reporter} case class DependencyAnalysisReporter(name: String = "dependencyAnalysis_reporter", path: String = "report.csv") extends Reporter { - var dependencyGraphInterpretersPerMember: List[DependencyGraphInterpreter] = List.empty - var joinedDependencyGraphInterpreter: Option[DependencyGraphInterpreter] = None + var dependencyGraphInterpretersPerMember: List[DependencyGraphInterpreter[IntraProcedural]] = List.empty + var joinedDependencyGraphInterpreter: Option[DependencyGraphInterpreter[Final]] = None override def report(msg: Message): Unit = {} } diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisResult.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisResult.scala index 8ea71f7b6..16c40c260 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisResult.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisResult.scala @@ -3,12 +3,12 @@ package viper.silicon.dependencyAnalysis import viper.silver.ast.Program import viper.silver.dependencyAnalysis.AbstractDependencyAnalysisResult -case class DependencyAnalysisResult(programName: String, program: Program, dependencyGraphInterpreters: Set[DependencyGraphInterpreter]) +case class DependencyAnalysisResult(programName: String, program: Program, dependencyGraphInterpreters: Set[DependencyGraphInterpreter[IntraProcedural]]) extends AbstractDependencyAnalysisResult(programName, program, dependencyGraphInterpreters){ - protected lazy val fullDependencyGraphInterpreter: DependencyGraphInterpreter = + protected lazy val fullDependencyGraphInterpreter: DependencyGraphInterpreter[Final] = DependencyAnalyzer.joinGraphsAndGetInterpreter(programName, dependencyGraphInterpreters) - override def getFullDependencyGraphInterpreter: DependencyGraphInterpreter = fullDependencyGraphInterpreter + override def getFullDependencyGraphInterpreter: DependencyGraphInterpreter[Final] = fullDependencyGraphInterpreter } diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala index 6dd0ef447..2e939c110 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala @@ -14,7 +14,7 @@ import scala.io.Source import scala.io.StdIn.readLine import scala.util.matching.Regex -class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterpreter, memberInterpreters: Seq[DependencyGraphInterpreter], +class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterpreter[Final], memberInterpreters: Seq[DependencyGraphInterpreter[IntraProcedural]], program: ast.Program, verificationErrors: List[Failure]) { private val infoString = "Enter " + "\n\t'dep [line numbers]' to print all dependencies of the given line numbers or" + @@ -91,7 +91,7 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete } } - private def handleGraphSizeQuery(interpreter: DependencyGraphInterpreter): Unit = { + private def handleGraphSizeQuery[T <: DependencyGraphState](interpreter: DependencyGraphInterpreter[T]): Unit = { val allAssumptions = interpreter.getNonInternalAssumptionNodes val assumptions = UserLevelDependencyAnalysisNode.from(allAssumptions) val allAssertions = interpreter.getNonInternalAssertionNodes diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index 1928c0d94..89f1df245 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -11,7 +11,7 @@ import scala.collection.mutable trait DependencyAnalyzer { - protected val dependencyGraph: DependencyGraph = new DependencyGraph() + protected val dependencyGraph: DependencyGraph[Init] = new DependencyGraph() def getMember: Option[ast.Member] @@ -47,7 +47,7 @@ trait DependencyAnalyzer { /** * @return the final dependency graph representing all direct and transitive dependencies */ - def buildFinalGraph(): Option[DependencyGraph] + def buildFinalGraph(): Option[DependencyGraph[IntraProcedural]] def addAssertionFailedNode(failedAssertion: Term, analysisInfos: DependencyAnalysisInfos): Option[Int] } @@ -94,11 +94,11 @@ object DependencyAnalyzer { * @param dependencyGraphInterpreters The graphs which should be joined. * @return A dependency graph interpreter operating on a new dependency graph that represents all input graphs and * dependencies between them. - * The new graph is built by adding all existing nodes and edges of all input graphs and joining them via postconditions - * of functions and methods. + * The new graph is built by adding all existing nodes and edges of all input graphs and joining them + * via the join information stored in each node. */ - def joinGraphsAndGetInterpreter(name: String, dependencyGraphInterpreters: Set[DependencyGraphInterpreter]): DependencyGraphInterpreter = { - val newGraph = new DependencyGraph + def joinGraphsAndGetInterpreter(name: String, dependencyGraphInterpreters: Set[DependencyGraphInterpreter[IntraProcedural]]): DependencyGraphInterpreter[Final] = { + val newGraph = new DependencyGraph[Final] newGraph.addAssumptionNodes(dependencyGraphInterpreters.flatMap (_.getGraph.getAssumptionNodes)) newGraph.addAssertionNodes(dependencyGraphInterpreters.flatMap (_.getGraph.getAssertionNodes)) @@ -126,7 +126,7 @@ object DependencyAnalyzer { newGraph.addEdgesConnectingMethodsDownwards(matchingSourceNodes.map(_.id), nodes.map(_.id)) } - val newInterpreter = new DependencyGraphInterpreter(name, newGraph, dependencyGraphInterpreters.toList.flatMap(_.getErrors)) + val newInterpreter = new DependencyGraphInterpreter[Final](name, newGraph, dependencyGraphInterpreters.toList.flatMap(_.getErrors)) newInterpreter } } @@ -262,15 +262,15 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { * source code statement. * Further, this operation removes unnecessary details from the graph by, for example, removing label nodes and merging identical nodes. */ - override def buildFinalGraph(): Option[DependencyGraph] = { + override def buildFinalGraph(): Option[DependencyGraph[IntraProcedural]] = { dependencyGraph.removeLabelNodes() - val mergedGraph = if(Verifier.config.enableDependencyAnalysisDebugging()) dependencyGraph else buildAndGetMergedGraph() + val mergedGraph = /* TODO ake if(Verifier.config.enableDependencyAnalysisDebugging()) dependencyGraph else */ buildAndGetMergedGraph() addTransitiveEdges(mergedGraph) if(!Verifier.config.enableDependencyAnalysisDebugging()) mergedGraph.removeInternalNodes() Some(mergedGraph) } - private def addTransitiveEdges(mergedGraph: DependencyGraph): Unit = { + private def addTransitiveEdges(mergedGraph: DependencyGraph[IntraProcedural]): Unit = { val nodesPerSourceInfo = mergedGraph.getNodes.filter(_.mergeInfo.isMerge).groupBy(_.mergeInfo) nodesPerSourceInfo foreach {case (_, nodes) => val asserts = nodes.filter(_.isInstanceOf[GeneralAssertionNode]) @@ -289,10 +289,10 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { * has no effect on the dependency results but allows to inspect low-level details while debugging and exporting * the low-level graph containing all details. */ - private def buildAndGetMergedGraph(): DependencyGraph = { + private def buildAndGetMergedGraph(): DependencyGraph[IntraProcedural] = { def keepNode(n: DependencyAnalysisNode): Boolean = !n.mergeInfo.isMerge || n.joinInfos.nonEmpty || n.isInstanceOf[InfeasibilityNode] || n.isInstanceOf[AxiomAssumptionNode] - val mergedGraph = new DependencyGraph + val mergedGraph = new DependencyGraph[IntraProcedural] val nodeMap = mutable.HashMap[Int, Int]() dependencyGraph.getAssumptionNodes.filter(keepNode).foreach { n => @@ -365,6 +365,6 @@ class NoDependencyAnalyzer extends DependencyAnalyzer { override def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit = {} override def addDependenciesForAbstractMembers(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp], analysisInfos: DependencyAnalysisInfos): Unit = {} - override def buildFinalGraph(): Option[DependencyGraph] = None + override def buildFinalGraph(): Option[DependencyGraph[IntraProcedural]] = None } \ No newline at end of file diff --git a/src/main/scala/dependencyAnalysis/DependencyGraph.scala b/src/main/scala/dependencyAnalysis/DependencyGraph.scala index d3ec0c309..48d0c485f 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraph.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraph.scala @@ -16,7 +16,12 @@ object DependencyGraphHelper { } } -trait ReadOnlyDependencyGraph extends AbstractReadOnlyDependencyGraph { +trait DependencyGraphState +class Init extends DependencyGraphState +class IntraProcedural extends DependencyGraphState +class Final extends DependencyGraphState + +trait ReadOnlyDependencyGraph[T <: DependencyGraphState] extends AbstractReadOnlyDependencyGraph { def getNodes: Seq[DependencyAnalysisNode] def getAssumptionNodes: Seq[GeneralAssumptionNode] def getAssertionNodes: Seq[GeneralAssertionNode] @@ -34,7 +39,7 @@ trait ReadOnlyDependencyGraph extends AbstractReadOnlyDependencyGraph { def exportGraph(dirName: String): Unit } -class DependencyGraph extends ReadOnlyDependencyGraph { +class DependencyGraph[T <: DependencyGraphState] extends ReadOnlyDependencyGraph[T] { private var assumptionNodes: mutable.Seq[GeneralAssumptionNode] = mutable.Seq() private var assertionNodes: mutable.Seq[GeneralAssertionNode] = mutable.Seq() private val edges: mutable.Map[Int, Set[Int]] = mutable.Map.empty diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala index 21dbf887b..8fbab30ea 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala @@ -39,7 +39,7 @@ object DependencyGraphImporter { // TODO ake: doesn't fully work yet, because the exported program has a different line numbering than the program used for the analysis val program = importProgram(graphFolder) - val interpreter = new DependencyGraphInterpreter("test", graph, List.empty, None) + val interpreter = new DependencyGraphInterpreter[Final]("test", graph, List.empty, None) val userTool = new DependencyAnalysisUserTool(interpreter, Seq.empty, program, List.empty) runUserTool(args, userTool) @@ -68,14 +68,14 @@ object DependencyGraphImporter { } - private def importGraphFromCsv(csvFilePath: String): ReadOnlyDependencyGraph = { - val graph = new DependencyGraph() + private def importGraphFromCsv(csvFilePath: String): ReadOnlyDependencyGraph[Final] = { + val graph = new DependencyGraph[Final]() createNodesFromCsv(graph, csvFilePath) createEdgesFromCsv(graph, csvFilePath) graph } - private def createNodesFromCsv(graph: DependencyGraph, csvFilePath: String): Unit = { + private def createNodesFromCsv(graph: DependencyGraph[Final], csvFilePath: String): Unit = { val bufferedSource = Source.fromFile(csvFilePath + "/nodes.csv") for (line <- bufferedSource.getLines().drop(1)) { @@ -113,7 +113,7 @@ object DependencyGraphImporter { bufferedSource.close() } - private def createEdgesFromCsv(graph: DependencyGraph, csvFilePath: String): Unit = { + private def createEdgesFromCsv(graph: DependencyGraph[Final], csvFilePath: String): Unit = { val bufferedSource = Source.fromFile(csvFilePath + "/edges.csv") for (line <- bufferedSource.getLines().drop(1)) { diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 84cfddb6a..5e0a648b3 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -23,9 +23,9 @@ object DATraversalMode extends Enumeration { } -class DependencyGraphInterpreter(name: String, dependencyGraph: ReadOnlyDependencyGraph, errors: List[Failure], member: Option[ast.Member]=None) extends AbstractDependencyGraphInterpreter{ +class DependencyGraphInterpreter[T <: DependencyGraphState](name: String, dependencyGraph: ReadOnlyDependencyGraph[T], errors: List[Failure], member: Option[ast.Member]=None) extends AbstractDependencyGraphInterpreter{ - def getGraph: ReadOnlyDependencyGraph = dependencyGraph + def getGraph: ReadOnlyDependencyGraph[T] = dependencyGraph def getName: String = name def getMember: Option[ast.Member] = member diff --git a/src/main/scala/interfaces/Verification.scala b/src/main/scala/interfaces/Verification.scala index 42561446b..550be5c94 100644 --- a/src/main/scala/interfaces/Verification.scala +++ b/src/main/scala/interfaces/Verification.scala @@ -8,7 +8,7 @@ package viper.silicon.interfaces import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.{DebugAxiom, DebugExp} -import viper.silicon.dependencyAnalysis.DependencyGraphInterpreter +import viper.silicon.dependencyAnalysis.{DependencyGraphInterpreter, IntraProcedural} import viper.silicon.interfaces.state.Chunk import viper.silicon.reporting._ import viper.silicon.state.terms._ @@ -31,7 +31,7 @@ sealed abstract class VerificationResult { var previous: Vector[VerificationResult] = Vector() //Sets had problems with equality val continueVerification: Boolean = true var isReported: Boolean = false - var dependencyGraphInterpreter: Option[DependencyGraphInterpreter] = None + var dependencyGraphInterpreter: Option[DependencyGraphInterpreter[IntraProcedural]] = None def isFatal: Boolean def &&(other: => VerificationResult): VerificationResult From f995ad47575f041a70f6524c2184c569a317857c Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 9 Apr 2026 10:52:26 +0200 Subject: [PATCH 418/474] fix tests --- .../real-world-examples/listAppend.vpr | 9 ++++++--- .../DependencyAnalysisPrecisionBenchmark.scala | 10 +++++----- .../scala/DependencyAnalysisTestFramework.scala | 14 +++++++------- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/test/resources/dependencyAnalysisTests/real-world-examples/listAppend.vpr b/src/test/resources/dependencyAnalysisTests/real-world-examples/listAppend.vpr index eee87ac15..039a9d5e4 100644 --- a/src/test/resources/dependencyAnalysisTests/real-world-examples/listAppend.vpr +++ b/src/test/resources/dependencyAnalysisTests/real-world-examples/listAppend.vpr @@ -37,7 +37,8 @@ method appendList1(this: Ref, e: Int) @testAssertion("Implicit") fold list(n) } else { - appendList1(this.next, e) + var l_next: Ref := this.next + appendList1(l_next, e) } fold list(this) @@ -67,8 +68,9 @@ method appendList2(this: Ref, e: Int) @irrelevant("Rewrite") fold list(n) } else { + var l_next: Ref := this.next @testAssertion("Implicit") - appendList2(this.next, e) + appendList2(l_next, e) } fold list(this) @@ -98,8 +100,9 @@ method appendListFull(this: Ref, e: Int) @dependency("Rewrite") fold list(n) } else { + var l_next: Ref := this.next @dependency("MethodCall") - appendListFull(this.next, e) + appendListFull(l_next, e) } @testAssertion("Implicit") diff --git a/src/test/scala/DependencyAnalysisPrecisionBenchmark.scala b/src/test/scala/DependencyAnalysisPrecisionBenchmark.scala index 4a0da189a..93d170e22 100644 --- a/src/test/scala/DependencyAnalysisPrecisionBenchmark.scala +++ b/src/test/scala/DependencyAnalysisPrecisionBenchmark.scala @@ -1,6 +1,6 @@ package viper.silicon.tests -import viper.silicon.dependencyAnalysis.{DependencyGraphInterpreter, DependencyAnalysisReporter} +import viper.silicon.dependencyAnalysis._ import viper.silver.ast.Program import viper.silver.verifier import viper.silver.verifier.VerificationResult @@ -54,9 +54,9 @@ object DependencyAnalysisPrecisionBenchmark extends DependencyAnalysisTestFramew } class AnnotatedPrecisionBenchmark(fileName: String, program: Program, - dependencyGraphInterpreters: List[DependencyGraphInterpreter], - fullGraphInterpreter: DependencyGraphInterpreter, - writer: PrintWriter) extends AnnotatedTest(program, dependencyGraphInterpreters, true) { + dependencyGraphInterpreters: List[DependencyGraphInterpreter[IntraProcedural]], + fullGraphInterpreter: DependencyGraphInterpreter[Final], + writer: PrintWriter) extends AnnotatedTest(program, dependencyGraphInterpreters, true) { override def execute(): Unit = { if(!verifyTestSoundness()){ writer.println(s"!!!!!!!!!!!\nFailed to verify soundness of precision test $fileName\n") @@ -70,7 +70,7 @@ object DependencyAnalysisPrecisionBenchmark extends DependencyAnalysisTestFramew } } - protected def computePrecision(dependencyGraphInterpreter: DependencyGraphInterpreter): Double = { + protected def computePrecision[T <: DependencyGraphState](dependencyGraphInterpreter: DependencyGraphInterpreter[T]): Double = { val irrelevantAssumptionNodes = getTestIrrelevantAssumptionNodes(dependencyGraphInterpreter.getNonInternalAssumptionNodes) val irrelevantAssumptionsPerSource = irrelevantAssumptionNodes groupBy (n => extractSourceLine(n.sourceInfo.getPosition)) val assertionNodes = getTestAssertionNodes(dependencyGraphInterpreter.getNonInternalAssertionNodes) diff --git a/src/test/scala/DependencyAnalysisTestFramework.scala b/src/test/scala/DependencyAnalysisTestFramework.scala index 3760cd43f..4eb15c42a 100644 --- a/src/test/scala/DependencyAnalysisTestFramework.scala +++ b/src/test/scala/DependencyAnalysisTestFramework.scala @@ -1,7 +1,7 @@ package viper.silicon.tests import viper.silicon.SiliconFrontend -import viper.silicon.dependencyAnalysis.{DependencyAnalysisNode, DependencyAnalysisReporter, DependencyGraphInterpreter} +import viper.silicon.dependencyAnalysis._ import viper.silver.ast.utility.ViperStrategy import viper.silver.ast.{Infoed, Program} import viper.silver.dependencyAnalysis.AssumptionType @@ -80,7 +80,7 @@ trait DependencyAnalysisTestFramework { * * Statements that are only required as a trigger need to be manually annotated with @trigger() by the user. */ - class PruningTest(fileName: String, program: Program, fullGraphInterpreter: DependencyGraphInterpreter) { + class PruningTest(fileName: String, program: Program, fullGraphInterpreter: DependencyGraphInterpreter[Final]) { def execute(): Unit = { val triggerNodeLines = fullGraphInterpreter.getNodes.filter(node => node.getUserLevelRepresentation.contains("@trigger()")).flatMap(_.sourceInfo.getLineNumber) @@ -113,7 +113,7 @@ trait DependencyAnalysisTestFramework { } } - class PrecisionEvaluation(filePrefix: String, fileName: String, program: Program, fullGraphInterpreter: DependencyGraphInterpreter) { + class PrecisionEvaluation(filePrefix: String, fileName: String, program: Program, fullGraphInterpreter: DependencyGraphInterpreter[Final]) { protected val folderName = s"src/test/resources/precision_groundTruths/$filePrefix" def execute(): Unit = { @@ -190,7 +190,7 @@ trait DependencyAnalysisTestFramework { * but multiple dependency/irrelevant annotations are allowed * */ - class AnnotatedTest(program: Program, dependencyGraphInterpreters: List[DependencyGraphInterpreter], checkPrecision: Boolean) { + class AnnotatedTest(program: Program, dependencyGraphInterpreters: List[DependencyGraphInterpreter[IntraProcedural]], checkPrecision: Boolean) { def execute(): Unit = { val stmtsWithAssumptionAnnotation: Set[Infoed] = extractAnnotatedStmts({ annotationInfo => annotationInfo.values.contains(irrelevantKeyword + "(\"") || annotationInfo.values.contains(dependencyKeyword) }) val allAssumptionNodes = dependencyGraphInterpreters.flatMap(_.getNonInternalAssumptionNodes) @@ -238,7 +238,7 @@ trait DependencyAnalysisTestFramework { } } - protected def checkTestAssertionNodeExists(dependencyGraphInterpreter: DependencyGraphInterpreter): Seq[String] = { + protected def checkTestAssertionNodeExists(dependencyGraphInterpreter: DependencyGraphInterpreter[IntraProcedural]): Seq[String] = { val assumptionNodes = getTestAssumptionNodes(dependencyGraphInterpreter.getNonInternalAssumptionNodes) ++ getTestIrrelevantAssumptionNodes(dependencyGraphInterpreter.getNonInternalAssumptionNodes) val assertionNodes = getTestAssertionNodes(dependencyGraphInterpreter.getNonInternalAssertionNodes) if (assumptionNodes.nonEmpty && assertionNodes.isEmpty) @@ -248,7 +248,7 @@ trait DependencyAnalysisTestFramework { } - protected def checkAllDependencies(dependencyGraphInterpreter: DependencyGraphInterpreter): Seq[String] = { + protected def checkAllDependencies(dependencyGraphInterpreter: DependencyGraphInterpreter[IntraProcedural]): Seq[String] = { val assertionNodes = getTestAssertionNodes(dependencyGraphInterpreter.getNonInternalAssertionNodes) val dependencies = dependencyGraphInterpreter.getAllNonInternalDependencies(assertionNodes.map(_.id))map(_.id) @@ -263,7 +263,7 @@ trait DependencyAnalysisTestFramework { resRelevant ++ resIrrelevant } - protected def checkExplicitDependencies(dependencyGraphInterpreter: DependencyGraphInterpreter): Seq[String] = { + protected def checkExplicitDependencies(dependencyGraphInterpreter: DependencyGraphInterpreter[IntraProcedural]): Seq[String] = { val assertionNodes = getTestAssertionNodes(dependencyGraphInterpreter.getNonInternalAssertionNodes) val dependencies = dependencyGraphInterpreter.getAllExplicitDependencies(assertionNodes.map(_.id)).map(_.id) From dd148b8a4d08301762c4789a2ead08f1aec3945a Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 9 Apr 2026 10:53:27 +0200 Subject: [PATCH 419/474] fix graph export --- .../scala/dependencyAnalysis/DependencyGraph.scala | 2 +- src/main/scala/dependencyAnalysis/neo4j_importer.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyGraph.scala b/src/main/scala/dependencyAnalysis/DependencyGraph.scala index 48d0c485f..6982f0f22 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraph.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraph.scala @@ -253,7 +253,7 @@ class DependencyGraph[T <: DependencyGraphState] extends ReadOnlyDependencyGraph val parts = mutable.Seq(node.id.toString, node.getNodeType, node.assumptionType.toString, node.getNodeString, node.sourceInfo.toString, node.sourceInfo.getPositionString, node.mergeInfo.toString, node.sourceInfo.getDescription) parts.map(_.replace("#", "@")).mkString(sep) } - val headerParts = mutable.Seq("id", "node type", "assumption type", "node info", "source info", "position", "fine grained source", "description") + val headerParts = mutable.Seq("id", "node type", "assumption type", "node info", "source info", "position", "merge info", "description") val builder = new StringBuilder() getNodes foreach (n => builder.append(getNodeExportString(n).replace("\n", " ") + "\n")) diff --git a/src/main/scala/dependencyAnalysis/neo4j_importer.py b/src/main/scala/dependencyAnalysis/neo4j_importer.py index ffb74fa6a..aca7cb099 100644 --- a/src/main/scala/dependencyAnalysis/neo4j_importer.py +++ b/src/main/scala/dependencyAnalysis/neo4j_importer.py @@ -1,11 +1,11 @@ -from neo4j import GraphDatabase import os -from neo4j import Transaction import pandas as pd +from neo4j import GraphDatabase +from neo4j import Transaction -URI = "neo4j+ssc://df418be1.databases.neo4j.io" -AUTH = ("neo4j", os.environ['NEO4J-PW']) +URI = "neo4j+ssc://27de0942.databases.neo4j.io" +AUTH = ("27de0942", os.environ['NEO4J-PW']) ASSUMPTION_NODE_TYPES = """["Inhale", "Assumption", "Infeasible", "Label"]""" @@ -13,7 +13,7 @@ POSTCONDITION_TYPES = """["ExplicitPostcondition", "ImplicitPostcondition"]""" driver = GraphDatabase.driver(URI, auth=AUTH) -session = driver.session(database="neo4j") +session = driver.session(database="27de0942") driver.verify_connectivity() def create_id_uniquness_constraint(tx: Transaction, node_label: str): @@ -51,7 +51,7 @@ def load_nodes(tx: Transaction, file_path: str, node_label: str): SET n.`node info` = row.`node info` SET n.`source info` = row.`source info` SET n.`position` = row.`position` - SET n.`fine grained source` = row.`fine grained source` + SET n.`merge info` = row.`merge info` }}; """ , nodes=nodes From 7fd6ccb0f8345f07b5198e42facf132bb31fb088 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 9 Apr 2026 10:54:43 +0200 Subject: [PATCH 420/474] make method calls more precise by separating it from argument evaluation --- silver | 2 +- .../DependencyAnalysisInfos.scala | 2 + .../DependencyAnalyzer.scala | 12 ++++++ src/main/scala/rules/Executor.scala | 39 ++++++++++++++----- 4 files changed, 44 insertions(+), 11 deletions(-) diff --git a/silver b/silver index 2a52c524f..68cd5f289 160000 --- a/silver +++ b/silver @@ -1 +1 @@ -Subproject commit 2a52c524f8e78509d65f6c90c56bbf23ba7683fd +Subproject commit 68cd5f289228c0a8255d9f9ede68e3a09f0f0b86 diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisInfos.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisInfos.scala index dc8ee96e9..d004cfa4f 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisInfos.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisInfos.scala @@ -68,6 +68,8 @@ case class DependencyAnalysisInfos(sourceInfos: List[AnalysisSourceInfo], depend this.copy(joinInfos = joinInfo +: joinInfos) } + + def removeSource(): DependencyAnalysisInfos = this.copy(sourceInfos = List.empty) } object DependencyAnalysisInfos { diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index 89f1df245..765bcb00a 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -50,6 +50,8 @@ trait DependencyAnalyzer { def buildFinalGraph(): Option[DependencyGraph[IntraProcedural]] def addAssertionFailedNode(failedAssertion: Term, analysisInfos: DependencyAnalysisInfos): Option[Int] + + def addCustomDependenciesBetweenMergeInfos(sourceExps: Seq[Exp], targetExps: Seq[Exp]): Unit } object DependencyAnalyzer { @@ -255,6 +257,14 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { dependencyGraph.addEdges(sourceNodeIds, targetNodes) } + override def addCustomDependenciesBetweenMergeInfos(sourceExps: Seq[Exp], targetExps: Seq[Exp]): Unit = { + val sourceMergeInfos = sourceExps.flatMap(_.info.getUniqueInfo[DependencyAnalysisMergeInfo]).filter(_.isMerge) + val targetMergeInfos = targetExps.flatMap(_.info.getUniqueInfo[DependencyAnalysisMergeInfo]).filter(_.isMerge) + val sourceNodes = getNodes.filter(node => sourceMergeInfos.contains(node.mergeInfo)).map(_.id) + val targetNodes = getNodes.filter(node => targetMergeInfos.contains(node.mergeInfo)).map(_.id) + dependencyGraph.addEdges(sourceNodes, targetNodes) + } + /** * * @return the final dependency graph @@ -367,4 +377,6 @@ class NoDependencyAnalyzer extends DependencyAnalyzer { override def buildFinalGraph(): Option[DependencyGraph[IntraProcedural]] = None + override def addCustomDependenciesBetweenMergeInfos(sourceExps: Seq[Exp], targetExps: Seq[Exp]): Unit = {} + } \ No newline at end of file diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index a845e7da6..88d048a8f 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -21,11 +21,11 @@ import viper.silicon.utils.ast.{BigAnd, extractPTypeFromExp, simplifyVariableNam import viper.silicon.utils.freshSnap import viper.silicon.verifier.Verifier import viper.silver.ast.utility.Statements -import viper.silver.ast.{EdgeType, EvalStackDependencyAnalysisJoin, JoinType} +import viper.silver.ast.{EdgeType, EvalStackDependencyAnalysisJoin, JoinType, SimpleDependencyAnalysisMerge} import viper.silver.cfg.silver.SilverCfg import viper.silver.cfg.silver.SilverCfg.{SilverBlock, SilverEdge} import viper.silver.cfg.{ConditionalEdge, StatementBlock} -import viper.silver.dependencyAnalysis.DependencyType +import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, DependencyType} import viper.silver.verifier.errors._ import viper.silver.verifier.reasons._ import viper.silver.verifier.{CounterexampleTransformer, NullPartialVerificationError, PartialVerificationError} @@ -553,7 +553,9 @@ object executor extends ExecutionRules { val sepIdentifier = v.symbExLog.openScope(mcLog) val paramLog = new CommentRecord("Parameters", s, v.decider.pcs) val paramId = v.symbExLog.openScope(paramLog) - evals(s, eArgs, _ => pveCall, v, analysisInfos)((s1, tArgs, eArgsNew, v1) => { + val eArgsWithDAInfo = SimpleDependencyAnalysisMerge.attachExpMergeInfo(eArgs) + val argsAnalysisInfo = analysisInfos.removeSource() + evals(s, eArgsWithDAInfo, _ => pveCall, v, argsAnalysisInfo)((s1, tArgs, eArgsNew, v1) => { v1.symbExLog.closeScope(paramId) val exampleTrafo = CounterexampleTransformer({ case ce: SiliconCounterexample => ce.withStore(s1.g) @@ -562,21 +564,37 @@ object executor extends ExecutionRules { val pvePre = ErrorWrapperWithExampleTransformer(PreconditionInCallFalse(call).withReasonNodeTransformed(reasonTransformer), exampleTrafo) val preCondLog = new CommentRecord("Precondition", s1, v1.decider.pcs) val preCondId = v1.symbExLog.openScope(preCondLog) - val argsWithExp = if (withExp) - tArgs zip (eArgsNew.get.map(Some(_))) - else - tArgs zip Seq.fill(tArgs.size)(None) - val s2 = s1.copy(g = Store(fargs.zip(argsWithExp)), + val argsWithExp: Seq[(Term, Option[ast.Exp])] = { + if(Verifier.config.enableDependencyAnalysis()){ + tArgs zip eArgsWithDAInfo.map(Some(_)) + } else if (withExp) + tArgs zip (eArgsNew.get.map(Some(_))) + else + tArgs zip Seq.fill(tArgs.size)(None) + } + // encode the method call as a sequence of assignments to fresh variables (one for each argument) and a method call using the fresh variables as arguments + val argsFreshVar = + if(Verifier.config.enableDependencyAnalysis()){ + argsWithExp.map(arg => { + val argNew = v1.decider.fresh(arg._1.sort, None) + v1.decider.assume(Equals(argNew, arg._1), None, argsAnalysisInfo.withSource(AnalysisSourceInfo.createAnalysisSourceInfo(arg._2.get))) + (argNew, None) + }) + }else argsWithExp + val s2 = s1.copy(g = Store(fargs.zip(argsFreshVar)), recordVisited = true) - consumes(s2, meth.pres, false, _ => pvePre, v1, analysisInfos.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Source, EdgeType.Up)))((s3, _, v2) => { + val presWithDAInfo = SimpleDependencyAnalysisMerge.attachExpMergeInfo(meth.pres.flatMap(_.topLevelConjuncts)) + consumes(s2, presWithDAInfo, false, _ => pvePre, v1, analysisInfos.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Source, EdgeType.Up)))((s3, _, v2) => { v2.symbExLog.closeScope(preCondId) val postCondLog = new CommentRecord("Postcondition", s3, v2.decider.pcs) val postCondId = v2.symbExLog.openScope(postCondLog) val outs = meth.formalReturns.map(_.localVar) val gOuts = Store(outs.map(x => (x, v2.decider.fresh(x))).toMap) val s4 = s3.copy(g = s3.g + gOuts, oldHeaps = s3.oldHeaps + (Verifier.PRE_STATE_LABEL -> magicWandSupporter.getEvalHeap(s1))) - produces(s4, freshSnap, meth.posts, _ => pveCallTransformed, v2, analysisInfos.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Sink, EdgeType.Down)))((s5, v3) => { + + val postsWithDAInfo = SimpleDependencyAnalysisMerge.attachExpMergeInfo(meth.posts.flatMap(_.topLevelConjuncts)) + produces(s4, freshSnap, postsWithDAInfo, _ => pveCallTransformed, v2, analysisInfos.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Sink, EdgeType.Down)))((s5, v3) => { v3.symbExLog.closeScope(postCondId) v3.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) val gLhs = Store(lhs.zip(outs) @@ -584,6 +602,7 @@ object executor extends ExecutionRules { val s6 = s5.copy(g = s1.g + gLhs, oldHeaps = s1.oldHeaps, recordVisited = s1.recordVisited) + v3.decider.dependencyAnalyzer.addCustomDependenciesBetweenMergeInfos(presWithDAInfo, postsWithDAInfo) v3.symbExLog.closeScope(sepIdentifier) Q(s6, v3)})})}) From d5609a10c792901198ca6dc0a23284707e1d9b34 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 9 Apr 2026 11:50:22 +0200 Subject: [PATCH 421/474] make function calls more precise by separating it from argument evaluation --- silver | 2 +- src/main/scala/rules/Evaluator.scala | 56 ++++++++++++++++++---------- 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/silver b/silver index 68cd5f289..f1ef66689 160000 --- a/silver +++ b/silver @@ -1 +1 @@ -Subproject commit 68cd5f289228c0a8255d9f9ede68e3a09f0f0b86 +Subproject commit f1ef6668997b902d59fc46bba8aec12e59fc5e1a diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index f66488c06..d27a31f9e 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -24,8 +24,8 @@ import viper.silicon.verifier.Verifier import viper.silicon.{Map, TriggerSets} import viper.silver.ast import viper.silver.ast.utility.Expressions -import viper.silver.ast.{AnnotationInfo, EdgeType, EvalStackDependencyAnalysisJoin, JoinType, LocalVarWithVersion, NoDependencyAnalysisMerge, WeightedQuantifier} -import viper.silver.dependencyAnalysis.DependencyType +import viper.silver.ast.{AnnotationInfo, EdgeType, EvalStackDependencyAnalysisJoin, JoinType, LocalVarWithVersion, NoDependencyAnalysisMerge, SimpleDependencyAnalysisMerge, WeightedQuantifier} +import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, DependencyType} import viper.silver.reporter.{AnnotationWarning, WarningsDuringVerification} import viper.silver.utility.Common.Rational import viper.silver.verifier.errors.{ErrorWrapperWithExampleTransformer, PreconditionInAppFalse} @@ -597,7 +597,20 @@ object evaluator extends EvaluationRules { case fapp @ ast.FuncApp(funcName, eArgs) => val func = s.program.findFunction(funcName) - evals2(s, eArgs, Nil, _ => pve, v, analysisInfos)((s1, tArgs, eArgsNew, v1) => { + val pres = func.pres.map(_.transform { + /* [Malte 2018-08-20] Two examples of the test suite, one of which is the regression + * for Carbon issue #210, fail if the subsequent code that strips out triggers from + * exhaled function preconditions, is commented. The code was originally a work-around + * for Silicon issue #276. Removing triggers from function preconditions is OK-ish + * because they are consumed (exhaled), i.e. asserted. However, the triggers are + * also used to internally generated quantifiers, e.g. related to QPs. My hope is that + * this hack is no longer needed once heap-dependent triggers are supported. + */ + case q: ast.Forall => q.copy(triggers = Nil)(q.pos, q.info, q.errT) + }) + val presWithDAInfo = SimpleDependencyAnalysisMerge.attachExpMergeInfo(pres.flatMap(_.topLevelConjuncts)) + val argsAnalysisInfo = analysisInfos.removeSource() + evals2(s, eArgs, Nil, _ => pve, v, argsAnalysisInfo)((s1, tArgs, eArgsNew, v1) => { // bookkeeper.functionApplications += 1 val joinFunctionArgs = tArgs //++ c2a.quantifiedVariables.filterNot(tArgs.contains) val (debugHeapName, debugLabel) = v1.getDebugOldLabel(s1, fapp.pos) @@ -613,17 +626,7 @@ object evaluator extends EvaluationRules { * although the latter is not necessary. */ joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s1a, v1, analysisInfos)((s2, v2, QB) => { - val pres = func.pres.map(_.transform { - /* [Malte 2018-08-20] Two examples of the test suite, one of which is the regression - * for Carbon issue #210, fail if the subsequent code that strips out triggers from - * exhaled function preconditions, is commented. The code was originally a work-around - * for Silicon issue #276. Removing triggers from function preconditions is OK-ish - * because they are consumed (exhaled), i.e. asserted. However, the triggers are - * also used to internally generated quantifiers, e.g. related to QPs. My hope is that - * this hack is no longer needed once heap-dependent triggers are supported. - */ - case q: ast.Forall => q.copy(triggers = Nil)(q.pos, q.info, q.errT) - }) + /* Formal function arguments are instantiated with the corresponding actual arguments * by adding the corresponding bindings to the store. To avoid formals in error messages * and to report actuals instead, we have two choices: the first is two attach a reason @@ -640,8 +643,19 @@ object evaluator extends EvaluationRules { val pvePre = ErrorWrapperWithExampleTransformer(PreconditionInAppFalse(fapp).withReasonNodeTransformed(reasonOffendingNode => reasonOffendingNode.replace(formalsToActuals)), exampleTrafo) - val argsPairs: Seq[(Term, Option[ast.Exp])] = if (withExp) tArgs.zip(eArgsNew.get.map(Some(_))) else tArgs.zip(Seq.fill(tArgs.size)(None)) - val s3 = s2.copy(g = Store(fargs.zip(argsPairs)), + val argsPairs: Seq[(Term, Option[ast.Exp])] = if(Verifier.config.enableDependencyAnalysis()){ + tArgs zip eArgs.map(Some(_)) + } else if (withExp) tArgs.zip(eArgsNew.get.map(Some(_))) else tArgs.zip(Seq.fill(tArgs.size)(None)) + // encode the function call as a sequence of assignments to fresh variables (one for each argument) and a method call using the fresh variables as arguments + val argsFreshVar = + if(Verifier.config.enableDependencyAnalysis()){ + argsPairs.map(arg => { + val argNew = v1.decider.fresh(arg._1.sort, None) + v1.decider.assume(Equals(argNew, arg._1), None, argsAnalysisInfo.withSource(AnalysisSourceInfo.createAnalysisSourceInfo(arg._2.get))) + (argNew, None) + }) + }else argsPairs + val s3 = s2.copy(g = Store(fargs.zip(argsFreshVar)), recordVisited = true, functionRecorder = s2.functionRecorder.changeDepthBy(+1), /* Temporarily disable the recorder: when recording (to later on @@ -671,7 +685,7 @@ object evaluator extends EvaluationRules { assertReadAccessOnly = if (Verifier.config.respectFunctionPrePermAmounts()) s2.assertReadAccessOnly /* should currently always be false */ else true) val precondAnalysisInfos = analysisInfos.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Source, EdgeType.Up)) - consumes(s3, pres, true, _ => pvePre, v2, precondAnalysisInfos)((s4, snap, v3) => { + consumes(s3, presWithDAInfo, true, _ => pvePre, v2, precondAnalysisInfos)((s4, snap, v3) => { val snap1 = snap.get.convert(sorts.Snap) val preFApp = App(functionSupporter.preconditionVersion(v3.symbolConverter.toFunction(func)), snap1 :: tArgs) val preExp = Option.when(withExp)({ @@ -708,7 +722,11 @@ object evaluator extends EvaluationRules { * joined snapshot could be defined and represented */ })(join(func.typ, s"joined_${func.name}", joinFunctionArgs, Option.when(withExp)(eArgs), v1, analysisInfos))((s6, r, v4) - => Q(s6, r._1, r._2, v4))}) + => { + v4.decider.dependencyAnalyzer.addCustomDependenciesBetweenMergeInfos(presWithDAInfo, + Seq(SimpleDependencyAnalysisMerge.attachExpMergeInfo(fapp), SimpleDependencyAnalysisMerge.attachExpMergeInfo(fapp, analysisInfos.getSourceInfo))) + Q(s6, r._1, r._2, v4) + })}) case ast.Unfolding( acc @ ast.PredicateAccessPredicate(pa @ ast.PredicateAccess(eArgs, predicateName), ePerm), @@ -1464,7 +1482,7 @@ object evaluator extends EvaluationRules { var sJoined = entries.tail.foldLeft(entries.head.s)((sAcc, entry) => sAcc.merge(entry.s)) sJoined = sJoined.copy(functionRecorder = sJoined.functionRecorder.recordPathSymbol(joinSymbol)) - joinDefEqs foreach { case (t, exp, expNew) => v.decider.assume(t, exp, expNew, analysisInfos.withDependencyType(DependencyType.Internal))} + joinDefEqs foreach { case (t, exp, expNew) => v.decider.assume(t, exp, expNew, analysisInfos)} (sJoined, (joinTerm, joinExp)) } From 5f352b2d04fd252546866fc578dbd0e100e1982f Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 9 Apr 2026 12:12:05 +0200 Subject: [PATCH 422/474] add test case for join precision --- .../all/join-precision.vpr | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 src/test/resources/dependencyAnalysisTests/all/join-precision.vpr diff --git a/src/test/resources/dependencyAnalysisTests/all/join-precision.vpr b/src/test/resources/dependencyAnalysisTests/all/join-precision.vpr new file mode 100644 index 000000000..2131f7aff --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/all/join-precision.vpr @@ -0,0 +1,41 @@ + +field f: Int + +function foo2(a: Int): Int + requires a > 0 +{ + 10 +} + + + function add_M(x: Ref, a: Int, b: Int, c: Int): Int + requires acc(x.f) + requires c == a + b + requires b > 0 + requires a >= 0 + ensures result > a + b + { + 10 + c + } + + +method client_meth2(x: Ref, a: Int) + requires acc(x.f) + requires a >= 0 + requires a < 100 +{ + var b: Int, c: Int + assume a != 0 + b := 10 + + var arg1: Int := 1000/a + var arg2: Int := foo2(b) + var arg3a: Int := 1000/a + var arg3: Int := arg3a + foo2(b) + c := add_M(x, arg1, arg2, arg3) + + assert c > 1000/a + b + assert c >= b + exhale acc(x.f) +} + From f869afcc39fc457f4f1da92c6558d1275f8d1233 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 9 Apr 2026 15:57:17 +0200 Subject: [PATCH 423/474] refactor interpreter and user tool --- silver | 2 +- .../DependencyAnalysisDebugSupporter.scala | 44 ++ .../DependencyAnalysisProgressSupporter.scala | 234 +++++++ .../DependencyAnalysisPruningSupporter.scala | 113 +++ .../DependencyAnalysisUserTool.scala | 76 +-- .../dependencyAnalysis/DependencyGraph.scala | 19 - .../DependencyGraphInterpreter.scala | 642 ++++-------------- ...DependencyAnalysisPrecisionBenchmark.scala | 5 +- .../DependencyAnalysisTestFramework.scala | 9 +- .../scala/VerificationProgressRunner.scala | 3 +- 10 files changed, 546 insertions(+), 601 deletions(-) create mode 100644 src/main/scala/dependencyAnalysis/DependencyAnalysisDebugSupporter.scala create mode 100644 src/main/scala/dependencyAnalysis/DependencyAnalysisProgressSupporter.scala create mode 100644 src/main/scala/dependencyAnalysis/DependencyAnalysisPruningSupporter.scala diff --git a/silver b/silver index f1ef66689..f90274bf8 160000 --- a/silver +++ b/silver @@ -1 +1 @@ -Subproject commit f1ef6668997b902d59fc46bba8aec12e59fc5e1a +Subproject commit f90274bf856cd3e89f77b6de84f3e325bcf79523 diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisDebugSupporter.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisDebugSupporter.scala new file mode 100644 index 000000000..ce07abbe3 --- /dev/null +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisDebugSupporter.scala @@ -0,0 +1,44 @@ +package dependencyAnalysis + +import viper.silicon.dependencyAnalysis.{DependencyAnalysisNode, DependencyGraphInterpreter, DependencyGraphState, GeneralAssumptionNode} +import viper.silver.dependencyAnalysis.AnalysisSourceInfo +import viper.silver.dependencyAnalysis.AssumptionType.AssumptionType + +import scala.io.StdIn.readLine + +class DependencyAnalysisDebugSupporter[T <: DependencyGraphState](interpreter: DependencyGraphInterpreter[T]) { + + def run(): Unit = { + try { + val input = readLine().split(" ") + input.head match { + case "assumptionTypes" if input.size == 1 => + println(getAssumptionTypesPerNode().mkString("\n")) + case "assumptionTypes" => + input.tail.foreach(s => println(s"$s: ${getAssumptionTypesByLine(s.toInt)}")) + case "lowLevelNodes" => + input.tail.foreach(s => println(s"$s:\n\t${getLowLevelNodesByLine(s.toInt).mkString("\n\t")}")) + case "q" | "quit" => return + } + }catch { + case _: Throwable => + println("Invalid input.") + } + run() + } + + def getAssumptionTypesByLine(line: Int): Set[AssumptionType] = { + interpreter.getNodesByLine(line).filter(_.isInstanceOf[GeneralAssumptionNode]).map(_.assumptionType) + } + + def getLowLevelNodesByLine(line: Int): Set[DependencyAnalysisNode] = { + interpreter.getNodesByLine(line) + } + + def getAssumptionTypesPerNode(): Map[AnalysisSourceInfo, Set[AssumptionType]] = + getAssumptionTypesPerNode(interpreter.getAssumptionNodes) + + def getAssumptionTypesPerNode(nodes: Set[DependencyAnalysisNode]): Map[AnalysisSourceInfo, Set[AssumptionType]] = + nodes.groupBy(_.sourceInfo).view.mapValues(_.map(_.assumptionType)).toMap + +} diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisProgressSupporter.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisProgressSupporter.scala new file mode 100644 index 000000000..d23d22c61 --- /dev/null +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisProgressSupporter.scala @@ -0,0 +1,234 @@ +package dependencyAnalysis + +import viper.silicon.dependencyAnalysis.DATraversalMode.{DATraversalMode, Downwards, Upwards} +import viper.silicon.dependencyAnalysis._ +import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType} + +import scala.collection.mutable + +class DependencyAnalysisProgressSupporter[T <: DependencyGraphState](interpreter: DependencyGraphInterpreter[T]) { + + private val dependencyGraph = interpreter.getGraph + private lazy val sourceToAssertionNodesMap: Map[AnalysisSourceInfo, Set[DependencyAnalysisNode]] = interpreter.getNonInternalAssertionNodes.groupBy(_.sourceInfo) + + def computeVerificationProgress(): (Double, Double) = { + computeVerificationProgressOptimized() + } + + + /** + * Computes all dependencies of a given dependency node. Intermediate results are cached at procedure-boundaries. + * That is, the dependencies of each pre- and postcondition are cached and can thus be reused for subsequent computations. + * We do not cache intraprocedural dependencies to allow for precise computation of dependencies using the low-level graph. + */ + val deps: DAMemo[(AnalysisSourceInfo, DATraversalMode), Set[CompactUserLevelDependencyAnalysisNode]] = DAMemo { case (assertionNode, mode) => + def computeDependencies(currentNode: AnalysisSourceInfo, visited: Set[(AnalysisSourceInfo, DATraversalMode)], traversalMode: DATraversalMode): Set[CompactUserLevelDependencyAnalysisNode] = { + if (visited.contains((currentNode, traversalMode))) { + return Set.empty // break cycles to avoid infinite loops + } + + if (deps.contains(currentNode, traversalMode)) { + return deps((currentNode, traversalMode)) + } + + val updatedVisited = visited ++ Set((currentNode, traversalMode)) + val allNonInternalAssertions = sourceToAssertionNodesMap.getOrElse(currentNode, Set.empty) + + // compute intraprocedural dependencies without caching any intermediate results + val intraMethodDependencyIds = dependencyGraph.getAllDependencies(allNonInternalAssertions.map(_.id), includeInfeasibilityNodes=true, includeUpwardEdges=false, includeDownwardEdges=false) + val intraMethodDependencies = intraMethodDependencyIds.flatMap(interpreter.nonInternalAssumptionNodesMap.get).filterNot(_.sourceInfo.equals(currentNode)) + + // recursively compute all interprocedural dependencies and cache results at procedure-boundaries + val relevantInterProceduralEdges = traversalMode match { + case Upwards => dependencyGraph.getEdgesConnectingMethodsUpwards + case Downwards => dependencyGraph.getEdgesConnectingMethodsDownwards + } + val interProceduralNodeIds = intraMethodDependencyIds.flatMap(n => relevantInterProceduralEdges.getOrElse(n, Set.empty)) + val interProceduralNodes = interProceduralNodeIds.flatMap(interpreter.nodesMap.get) + val interProceduralDependencies = interProceduralNodes.map(_.sourceInfo).filterNot(_.equals(currentNode)).flatMap(node => computeDependencies(node, updatedVisited, traversalMode)) + + // put together all identified dependencies and cache the result + val result = reduceCompactUserLevelNodes(toCompactUserLevelNodes(intraMethodDependencies ++ interProceduralNodes) ++ interProceduralDependencies) + deps.put((currentNode, traversalMode), result) + result + } + + computeDependencies(assertionNode, Set.empty, mode) + } + + // merges results of several computations by merging low-level nodes belonging to the same source + private def reduceCompactUserLevelNodes(inputNodes: Set[CompactUserLevelDependencyAnalysisNode]): Set[CompactUserLevelDependencyAnalysisNode] = { + + val resultMap: mutable.Map[AnalysisSourceInfo, CompactUserLevelDependencyAnalysisNode] = mutable.Map() + + for (node <- inputNodes) { + val existingNode = resultMap.get(node.source) + + val newNode = existingNode match { + case Some(existing) => + CompactUserLevelDependencyAnalysisNode( + source = node.source, + assumptionTypes = existing.assumptionTypes ++ node.assumptionTypes, + assertionTypes = existing.assertionTypes ++ node.assertionTypes, + hasFailures = existing.hasFailures || node.hasFailures + ) + case None => node + } + + resultMap.update(node.source, newNode) + } + + resultMap.values.toSet + } + + private def toCompactUserLevelNodes(lowLevelNodes: Set[DependencyAnalysisNode]): Set[CompactUserLevelDependencyAnalysisNode] = { + lowLevelNodes.groupBy(_.sourceInfo).map{case (source, nodes) => + val assertionNodes = nodes.filter(_.isInstanceOf[GeneralAssertionNode]) + CompactUserLevelDependencyAnalysisNode(source, + nodes.filter(_.isInstanceOf[GeneralAssumptionNode]).map(_.assumptionType), + assertionNodes.map(_.assumptionType), + assertionNodes.exists(_.asInstanceOf[GeneralAssertionNode].hasFailed) + )}.toSet + } + + /** + * Computes the assertion quality given an assertion node and all of its dependencies. + * The assertion quality is defined as the fraction of non-assumption dependencies over all dependencies. + * The assertion quality is 0.0 if the assertion could not be proven to hold. + */ + private def computeAssertionQuality(allDependencies: Set[CompactUserLevelDependencyAnalysisNode], assertion: AnalysisSourceInfo): Double = { + val assertionNodes = sourceToAssertionNodesMap.getOrElse(assertion, Set.empty).filter(node => node.isInstanceOf[GeneralAssertionNode]) + val failedAssertionNodes = assertionNodes.filter(node => node.asInstanceOf[GeneralAssertionNode].hasFailed || node.assumptionType.equals(AssumptionType.ExplicitPostcondition)) + // assertions with failures have quality = 0.0 + if(failedAssertionNodes.nonEmpty) + return 0.0 + + val explicitDeps = allDependencies.filter(_.assumptionTypes.intersect(AssumptionType.explicitAssumptionTypes).nonEmpty).map(_.source) + val numDepsTotal = allDependencies.size + (numDepsTotal - explicitDeps.size).toDouble / numDepsTotal.toDouble + } + + private def getAssertionsRelevantForProgress: Map[AnalysisSourceInfo, Set[DependencyAnalysisNode]] = sourceToAssertionNodesMap.filter(ass => ass._2.map(_.assumptionType).intersect(AssumptionType.importedTypes).isEmpty) + + /** + * @return the verification progress of the entire program + */ + def computeVerificationProgressOptimized(enableDebugOutput: Boolean = false): (Double, Double) = { + + // compute all dependencies of each proof obligation + val allAssertions = getAssertionsRelevantForProgress.keySet.toList + val allDependenciesPerAssertionNode = allAssertions map (ass => ({ + val ups = deps((ass, Upwards)) + val downs = deps((ass, Downwards)) + reduceCompactUserLevelNodes(ups ++ downs) + }, ass)) + + val specQuality = computeSpecQuality(allDependenciesPerAssertionNode.flatMap(_._1).toSet, enableDebugOutput) + + // for assertion quality, we filter out assertions that do not have any dependencies + val depsOfNonTrivialAssertions = allDependenciesPerAssertionNode filter (_._1.nonEmpty) + val numNonTrivialAssertions = depsOfNonTrivialAssertions.size + + val assertionQualities = depsOfNonTrivialAssertions map (ass => (computeAssertionQuality(ass._1, ass._2), ass._2)) + + // compute Peter's proof quality + val fullyVerifiedAssertions = assertionQualities.filter(_._1 == 1.0) + val proofQualityPeter = if(numNonTrivialAssertions > 0) fullyVerifiedAssertions.size.toDouble / numNonTrivialAssertions.toDouble else 1.0 + + // compute Lea's proof quality + val assertionQualitiesSum = assertionQualities.map(_._1).sum + val proofQualityLea = if(numNonTrivialAssertions > 0) assertionQualitiesSum / numNonTrivialAssertions.toDouble else 1.0 + + if(enableDebugOutput) + println( + s"fullyVerifiedAssertions:\n\t${fullyVerifiedAssertions.mkString("\n\t")}" + + s"assertionQualitiesSum:\n\t${assertionQualities.mkString("\n\t")}" + ) + + println( + s"specQuality = $specQuality\n" + + s"proof quality (Peter): ${fullyVerifiedAssertions.size} / $numNonTrivialAssertions = $proofQualityPeter\n" + + s"proof quality (Lea): $assertionQualitiesSum / $numNonTrivialAssertions = $proofQualityLea\n" + ) + + (specQuality * proofQualityPeter, specQuality * proofQualityLea) + } + + + private def computeSpecQuality(coveredNodes: Set[CompactUserLevelDependencyAnalysisNode], enableDebugOutput: Boolean = false): Double = { + + val explicitAssertions = toCompactUserLevelNodes(interpreter.getExplicitAssertionNodes) + val nonSourceCodeAssumptionTypes = AssumptionType.explicitAssumptionTypes ++ AssumptionType.verificationAnnotationTypes + val allSourceCodeNodes = toCompactUserLevelNodes(interpreter.getNonInternalAssumptionNodes).filter(n => nonSourceCodeAssumptionTypes.intersect(n.assumptionTypes).isEmpty).map(_.source).diff(explicitAssertions.map(_.source)) + + if(allSourceCodeNodes.isEmpty) return 1.0 + + val coveredSourceCodeNodes = coveredNodes.map(_.source).intersect(allSourceCodeNodes) + if(enableDebugOutput) + println( + s"Covered Source Code:\n\t${coveredSourceCodeNodes.toList.sortBy(n => (n.getLineNumber, n.toString())).mkString("\n\t")}" + + s"Uncovered Source Code:\n\t${allSourceCodeNodes.diff(coveredSourceCodeNodes).toList.sortBy(n => (n.getLineNumber, n.toString())).mkString("\n\t")}" + ) + + println(s"Spec Quality = ${coveredSourceCodeNodes.size} / ${allSourceCodeNodes.size}") + coveredSourceCodeNodes.size.toDouble / allSourceCodeNodes.size.toDouble + } + + + /** + * + * @return a list of assumption nodes ordered by their impact on proof quality + */ + def computeAssumptionRanking(): List[(String, Double)] = { + val allAssertions = interpreter.toUserLevelNodes(interpreter.getNonInternalAssertionNodes).filter(ass => ass.assertionTypes.intersect(AssumptionType.importedTypes).isEmpty) + + val relevantDependenciesPerAssertion = allAssertions + .map(ass => (ass, interpreter.toUserLevelNodes(interpreter.getAllNonInternalDependencies(ass.lowerLevelNodes.map(_.id))).diffBySource(Set(ass)))).toMap + .filter{case (assertion, assumptions) => assumptions.nonEmpty || assertion.hasFailures || assertion.assertionTypes.contains(AssumptionType.ExplicitPostcondition)} + val numAssertions = relevantDependenciesPerAssertion.size.toDouble + + val assumptionImpacts= relevantDependenciesPerAssertion.toList.flatMap { case (_, assumptions) => + val explicitDeps = UserLevelDependencyAnalysisNode.extractExplicitAssumptionNodes(assumptions) + explicitDeps.map(node => (node.source, 1.0/assumptions.size/numAssertions)).toList + } + + val unverifiedAssertionImpacts = getAssertionsWithZeroQuality.map(assertion => (assertion, 1.0/numAssertions)).toList + + val totalImpacts1 = (assumptionImpacts ++ unverifiedAssertionImpacts).groupBy(_._1) + val totalImpacts = totalImpacts1.map{case (assumption, impacts) => (assumption.toString, impacts.map(_._2).sum)}.toList + + totalImpacts.sortBy(_._2).reverse + } + + private def getAssertionsWithZeroQuality: Set[AnalysisSourceInfo] = { + val allAssertions = interpreter.toUserLevelNodes(interpreter.getNonInternalAssertionNodes) + allAssertions.filter(assertion => assertion.hasFailures || assertion.assertionTypes.contains(AssumptionType.ExplicitPostcondition)).getSourceSet() + } + + def computeUncoveredStatements(): Int = { + val allAssertions = interpreter.toUserLevelNodes(interpreter.getNonInternalAssertionNodes) + val allDependencies = allAssertions.flatMap(ass => interpreter.toUserLevelNodes(interpreter.getAllNonInternalDependencies(ass.lowerLevelNodes.map(_.id))).diffBySource(Set(ass))).getSourceSet() + + val explicitAssertions = interpreter.toUserLevelNodes(interpreter.getExplicitAssertionNodes) + val allNodes = interpreter.toUserLevelNodes(interpreter.getNonInternalAssumptionNodes) + val allSourceCodeStmts = allNodes.getSourceSet().diff(UserLevelDependencyAnalysisNode.extractByAssumptionType(allNodes, + AssumptionType.explicitAssumptionTypes ++ AssumptionType.verificationAnnotationTypes).getSourceSet()).diff(explicitAssertions.getSourceSet()) + val uncoveredSourceCodeStmts = allSourceCodeStmts.diff(allDependencies) + if(uncoveredSourceCodeStmts.nonEmpty) + println(s"${interpreter.getName}:\n\t${allSourceCodeStmts.diff(allDependencies).toList.sortBy(n => (n.getLineNumber, n.toString())).mkString("\n\t")}") + uncoveredSourceCodeStmts.size + } +} + +case class DAMemo[A,B](f: A => B) extends (A => B) { + private val cache = mutable.Map.empty[A, B] + def apply(x: A): B = cache getOrElseUpdate (x, f(x)) + + def put(a: A, b: B): Option[B] = { + cache.put(a, b) + } + + def contains(a: A): Boolean = { + cache.contains(a) + } +} \ No newline at end of file diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisPruningSupporter.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisPruningSupporter.scala new file mode 100644 index 000000000..c42eefe24 --- /dev/null +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisPruningSupporter.scala @@ -0,0 +1,113 @@ +package dependencyAnalysis + +import viper.silicon.dependencyAnalysis.{DependencyAnalysisNode, DependencyGraphInterpreter, DependencyGraphState} +import viper.silver.ast +import viper.silver.ast.utility.ViperStrategy +import viper.silver.ast.utility.rewriter.Traverse +import viper.silver.ast.{If, Stmt} +import viper.silver.dependencyAnalysis.AnalysisSourceInfo + +import java.io.PrintWriter + +class DependencyAnalysisPruningSupporter[T <: DependencyGraphState](interpreter: DependencyGraphInterpreter[T]) { + + def getPrunedProgram(crucialNodes: Set[DependencyAnalysisNode], program: ast.Program): (ast.Program, Double) = { + + def isCrucialExp(exp: ast.Exp, crucialNodesWithExpInfo: Set[AnalysisSourceInfo]): Boolean = { + crucialNodesWithExpInfo exists (n => n.getPositionString.equals(AnalysisSourceInfo.extractPositionString(exp.pos))) // TODO ake: currently we compare only lines not columns! + } + + def isCrucialStmt(stmt: ast.Stmt, crucialNodesWithStmtInfo: Set[AnalysisSourceInfo]): Boolean = { + crucialNodesWithStmtInfo exists (n => n.getPositionString.equals(AnalysisSourceInfo.extractPositionString(stmt.pos))) + } + + val crucialNodeSourceInfos = crucialNodes map (_.sourceInfo) + var total = 0 + var removed = 0 + var nonDetermBoolCount = 0 + + def getNextNonDetermBool: String = { + nonDetermBoolCount += 1 + s"nonDetermBool_$nonDetermBoolCount" + } + + val newProgram: ast.Program = ViperStrategy.Slim({ + case s@(_: ast.Seqn | _: ast.Goto) => s + case domain@ast.Domain(name, functions, axioms, typVars, interpretations) => + val newAxioms = axioms filter (a => + crucialNodeSourceInfos exists (n => n.getPositionString.equals(AnalysisSourceInfo.extractPositionString(a.exp.pos)) || + n.getPositionString.equals(AnalysisSourceInfo.extractPositionString(a.pos)))) + ast.Domain(name, functions, newAxioms, typVars, interpretations)(domain.pos, domain.info, domain.errT) + case function@ast.Function(name, formalArgs, typ, pres, posts, body) => + val newPres = pres filter (isCrucialExp(_, crucialNodeSourceInfos)) + val newPosts = posts filter (isCrucialExp(_, crucialNodeSourceInfos)) + val newBody = body filter (isCrucialExp(_, crucialNodeSourceInfos)) + ast.Function(name, formalArgs, typ, newPres, newPosts, newBody)(function.pos, function.info, function.errT) + case meth@ast.Method(name, inVars, outVars, pres, posts, body) => + val newPres = pres filter (isCrucialExp(_, crucialNodeSourceInfos)) + val newPosts = posts filter (isCrucialExp(_, crucialNodeSourceInfos)) + total += pres.size + posts.size + removed += (pres.size - newPres.size) + (posts.size - newPosts.size) + ast.Method(name, inVars, outVars, newPres, newPosts, body)(meth.pos, meth.info, meth.errT) + case ifStmt@ast.If(cond, thenBody, elseBody) if !isCrucialExp(cond, crucialNodeSourceInfos) => + total += 1 + removed += 1 + val nonDetermBool = getNextNonDetermBool + ast.Seqn(Seq( + ast.LocalVarDeclStmt(ast.LocalVarDecl(nonDetermBool, ast.Bool)())(), + ast.If(ast.LocalVar(nonDetermBool, ast.Bool)(cond.pos, cond.info, cond.errT), thenBody, elseBody)(ifStmt.pos, ifStmt.info, ifStmt.errT)) + , Seq())(ifStmt.pos, ifStmt.info, ifStmt.errT) + case ifStmt: If => + total += 1 + ifStmt + case whileStmt@ast.While(cond, invs, body) if !isCrucialExp(cond, crucialNodeSourceInfos) => + val newInvs = invs filter (isCrucialExp(_, crucialNodeSourceInfos)) + total += 1 + invs.size + removed += 1 + (invs.size - newInvs.size) + val nonDetermBool = getNextNonDetermBool + ast.Seqn(Seq( + ast.LocalVarDeclStmt(ast.LocalVarDecl(nonDetermBool, ast.Bool)())(), + ast.While(ast.LocalVar(nonDetermBool, ast.Bool)(cond.pos, cond.info, cond.errT), newInvs, body)(whileStmt.pos, whileStmt.info, whileStmt.errT)) + , Seq())(whileStmt.pos, whileStmt.info, whileStmt.errT) + case whileStmt@ast.While(cond, invs, body) => + val newInvs = invs filter (isCrucialExp(_, crucialNodeSourceInfos)) + total += 1 + invs.size + removed += (invs.size - newInvs.size) + ast.While(cond, newInvs, body)(whileStmt.pos, whileStmt.info, whileStmt.errT) + case label@ast.Label(name, invs) => + val newInvs = invs filter (isCrucialExp(_, crucialNodeSourceInfos)) + total += 1 + invs.size + removed += (invs.size - newInvs.size) + ast.Label(name, newInvs)(label.pos, label.info, label.errT) + case s: ast.Package if !isCrucialStmt(s, crucialNodeSourceInfos) => + total += 1 + removed += 1 + ast.Inhale(ast.TrueLit()(s.pos, s.info, s.errT))(s.pos, s.info, s.errT) + case s: Stmt if !isCrucialStmt(s, crucialNodeSourceInfos) => + total += 1 + removed += 1 + ast.Inhale(ast.TrueLit()(s.pos, s.info, s.errT))(s.pos, s.info, s.errT) + case s: Stmt => + total += 1 + s + }, Traverse.BottomUp).execute(program) + (newProgram, removed.toDouble / total.toDouble) + } + + def getCrucialNodes(queriedNodes: Set[DependencyAnalysisNode]): Set[DependencyAnalysisNode] = { + val dependencies = interpreter.getAllNonInternalDependencies(queriedNodes.map(_.id)) + queriedNodes ++ dependencies + } + + def pruneProgramAndExport(queriedNodes: Set[DependencyAnalysisNode], program: ast.Program, exportFileName: String): Unit = { + val writer = new PrintWriter(exportFileName) + + val crucialNodes = getCrucialNodes(queriedNodes) + println(s"Found ${crucialNodes.size} crucial nodes. Pruning...") + + val (newProgram, pruningFactor) = getPrunedProgram(crucialNodes, program) + writer.println("// pruning factor: " + pruningFactor) + writer.println(newProgram.toString()) + writer.close() + } +} diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala index 2e939c110..37e2656d1 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala @@ -1,6 +1,6 @@ package viper.silicon.dependencyAnalysis -import dependencyAnalysis.UserLevelDependencyAnalysisNode +import dependencyAnalysis.{DependencyAnalysisDebugSupporter, DependencyAnalysisProgressSupporter, DependencyAnalysisPruningSupporter, UserLevelDependencyAnalysisNode} import viper.silicon.interfaces.Failure import viper.silver.ast import viper.silver.ast.{AnnotationInfo, AnonymousDomainAxiom, Assume, Goto, If, Inhale, Label, LocalVarDeclStmt, MakeInfoPair, Method, NamedDomainAxiom, Package, Seqn, While} @@ -16,6 +16,10 @@ import scala.util.matching.Regex class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterpreter[Final], memberInterpreters: Seq[DependencyGraphInterpreter[IntraProcedural]], program: ast.Program, verificationErrors: List[Failure]) { + + lazy val progressSupporter = new DependencyAnalysisProgressSupporter[Final](fullGraphInterpreter) + lazy val pruningSupporter = new DependencyAnalysisPruningSupporter[Final](fullGraphInterpreter) + private val infoString = "Enter " + "\n\t'dep [line numbers]' to print all dependencies of the given line numbers or" + "\n\t'downDep [line numbers]' to print all dependents of the given line numbers or" + @@ -61,18 +65,15 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete inputParts.head.toLowerCase match { case "dep" => handleDependencyQuery(inputParts.tail.toSet) case "downdep" => handleDependentsQuery(inputParts.tail.toSet) - case "hasdep" => handleHasDependencyQuery(inputParts.tail.toSet) case "coverage" | "cov" => handleProofCoverageQuery(inputParts.tail) case "covlines" | "covl" => handleProofCoverageLineQuery(inputParts.tail) case "progress" | "prog" => handleVerificationProgressQuery() - case "progressdebug" => handleVerificationProgressDEBUGQuery() - case "progressnaive" => handleVerificationProgressNaiveQuery() case "guidance" | "guide" => handleVerificationGuidanceQuery() - case "guideold" => handleVerificationGuidanceOldQuery() case "prune" => handlePruningRequest(inputParts.tail) case "benchmark" => handleBenchmarkQuery() case "precisioneval" => handlePrecisionEval(inputParts.tail) case "annotate" => handleAnnotateQuery(inputParts.tail) + case "debug" => handleDebugQuery() case "graphsize" => if (inputParts.tail.isEmpty) { handleGraphSizeQuery(fullGraphInterpreter) @@ -153,39 +154,10 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete private def handleVerificationProgressQuery(): Unit = { - val ((optProgressPeter, optProgressLea, optInfo), optTime) = measureTime(fullGraphInterpreter.computeVerificationProgress()) - // println(s"Overall verification progress: $progress") - println(s"$optInfo") - println(s"Peter: $optProgressPeter; Lea: $optProgressLea") - println(s"Finished in ${optTime}ms") - } - - private def handleVerificationProgressDEBUGQuery(): Unit = { + val ((optProgressPeter, optProgressLea), optTime) = measureTime(progressSupporter.computeVerificationProgress()) - println("\nNaive implementation") - val ((naiveProgressPeter, naiveProgressLea, naiveInfo), naiveTime) = measureTime(fullGraphInterpreter.computeVerificationProgressNaive()) -// println(s"Overall verification progress: $progress") - println(s"$naiveInfo") - println(s"Peter: $naiveProgressPeter; Lea: $naiveProgressLea") - println(s"Finished in ${naiveTime}ms") - - println("\nOptimized implementation") - val ((optProgressPeter, optProgressLea, optInfo), optTime) = measureTime(fullGraphInterpreter.computeVerificationProgressOptimized()) - // println(s"Overall verification progress: $progress") - println(s"$optInfo") println(s"Peter: $optProgressPeter; Lea: $optProgressLea") println(s"Finished in ${optTime}ms") - if(Math.abs(naiveProgressPeter - optProgressPeter) > 0.001 || Math.abs(naiveProgressLea - optProgressLea) > 0.001) println("Fail: Progress is not equal!") - else println("Success: Progress is equal!") - } - - private def handleVerificationProgressNaiveQuery(): Unit = { - - val ((naiveProgressPeter, naiveProgressLea, naiveInfo), naiveTime) = measureTime(fullGraphInterpreter.computeVerificationProgressNaive()) - // println(s"Overall verification progress: $progress") - println(s"$naiveInfo") - println(s"Peter: $naiveProgressPeter; Lea: $naiveProgressLea") - println(s"Finished in ${naiveTime}ms") } private def getSourceInfoString(nodes: Set[DependencyAnalysisNode]): String = { @@ -384,16 +356,6 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete println("Done.") } - private def handleHasDependencyQuery(inputs: Set[String]): Unit = { - val queriedNodes = getQueriedNodesFromInput(inputs) - - val (depExists, time) = measureTime[Boolean](fullGraphInterpreter.hasAnyDependency(queriedNodes)) - - println(s"Queried:\n\t${getSourceInfoString(queriedNodes)}") - println(s"Dependency exists? $depExists") - println(s"\nDone in ${time}ms.") - } - private def measureTime[T](function: => T): (T, Double) = { val startAnalysis = System.nanoTime() val res = function @@ -405,13 +367,8 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete private def handlePruningRequest(inputs: Seq[String]): Unit = { println("exportFileName: ") val exportFileName = readLine() - val queriedNodes = getQueriedNodesFromInput(inputs.toSet) - val dependencies = fullGraphInterpreter.getAllNonInternalDependencies(queriedNodes.map(_.id)) - val crucialNodes = queriedNodes ++ dependencies - println(s"Found ${crucialNodes.size} crucial nodes. Pruning...") - - fullGraphInterpreter.pruneProgramAndExport(crucialNodes, program, exportFileName) + pruningSupporter.pruneProgramAndExport(queriedNodes, program, exportFileName) println("Done.") } @@ -454,24 +411,17 @@ class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterprete private def handleVerificationGuidanceQuery(): Unit = { - val assumptionRanking = fullGraphInterpreter.computeAssumptionRanking().filter(_._2 > 0.0) + val assumptionRanking = progressSupporter.computeAssumptionRanking().filter(_._2 > 0.0) println(s"Assumptions/unverified assertions and the number of dependents:\n\t${assumptionRanking.mkString("\n\t")}\n") println("Uncovered source code per method: ") val memberCoverageRanking = memberInterpreters.filter(mInterpreter => mInterpreter.getMember.isDefined && mInterpreter.getMember.get.isInstanceOf[Method]) - .map(mInterpreter => (mInterpreter.getMember.get.name, mInterpreter.computeUncoveredStatements())) + .map(mInterpreter => (mInterpreter.getMember.get.name, new DependencyAnalysisProgressSupporter(mInterpreter).computeUncoveredStatements())) .toList.filter(_._2 > 0).sortBy(_._2).reverse println(s"\nMethods and the number of uncovered statements:\n\t${memberCoverageRanking.mkString("\n\t")}\n") } - private def handleVerificationGuidanceOldQuery(): Unit = { - - val assumptionRanking = fullGraphInterpreter.computeAssumptionRankingOld().filter(_._2 > 0) - println(s"Assumptions and the number of dependents:\n\t${assumptionRanking.mkString("\n\t")}\n") - - val memberCoverageRanking = memberInterpreters.filter(mInterpreter => mInterpreter.getMember.isDefined && mInterpreter.getMember.get.isInstanceOf[Method]) - .map(mInterpreter => (mInterpreter.getMember.get.name, mInterpreter.computeUncoveredStatements())) - .toList.filter(_._2 > 0).sortBy(_._2).reverse - println(s"Methods and the number of uncovered statements:\n\t${memberCoverageRanking.mkString("\n\t")}\n") - } + private def handleDebugQuery(): Unit = { + new DependencyAnalysisDebugSupporter(fullGraphInterpreter).run() + } } diff --git a/src/main/scala/dependencyAnalysis/DependencyGraph.scala b/src/main/scala/dependencyAnalysis/DependencyGraph.scala index 6982f0f22..94207113a 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraph.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraph.scala @@ -31,8 +31,6 @@ trait ReadOnlyDependencyGraph[T <: DependencyGraphState] extends AbstractReadOnl def getAllEdges: Map[Int, Set[Int]] // target -> direct dependencies def getAllEdges(includeUpwardEdges: Boolean, includeDownwardEdges: Boolean): Map[Int, Set[Int]] // target -> direct dependencies - @deprecated // needs to be adapted to the notion of upward and downward edges - def existsAnyDependency(sources: Set[Int], targets: Set[Int], includeInfeasibilityNodes: Boolean): Boolean def getAllDependencies(sources: Set[Int], includeInfeasibilityNodes: Boolean, includeUpwardEdges: Boolean, includeDownwardEdges: Boolean): Set[Int] def getAllDependents(sources: Set[Int], includeInfeasibilityNodes: Boolean, includeUpwardEdges: Boolean, includeDownwardEdges: Boolean): Set[Int] @@ -161,23 +159,6 @@ class DependencyGraph[T <: DependencyGraphState] extends ReadOnlyDependencyGraph vacuousProofs = assertionId +: vacuousProofs } - @deprecated // needs to be adapted to the notion of upward and downward edges - def existsAnyDependency(sources: Set[Int], targets: Set[Int], includeInfeasibilityNodes: Boolean): Boolean = { - val infeasibilityNodeIds: Set[Int] = if(includeInfeasibilityNodes) Set.empty else (getAssumptionNodes filter (_.isInstanceOf[InfeasibilityNode]) map (_.id)).toSet - var visited: Set[Int] = Set.empty - var queue: List[Int] = targets.toList - val allEdges = getAllEdges - while(queue.nonEmpty){ - val curr = queue.head - val newVisits = allEdges.getOrElse(curr, Set()).diff(infeasibilityNodeIds) - if(newVisits.intersect(sources).nonEmpty) - return true - visited = visited ++ Set(curr) - queue = queue.tail ++ (newVisits filter (!visited.contains(_))) - } - false - } - def getAllDependencies(targets: Set[Int], includeInfeasibilityNodes: Boolean, includeUpwardEdges: Boolean, includeDownwardEdges: Boolean): Set[Int] = { val infeasibilityNodeIds: Set[Int] = if(includeInfeasibilityNodes) Set.empty else (getAssumptionNodes filter (_.isInstanceOf[InfeasibilityNode]) map (_.id)).toSet var visited: Set[Int] = Set.empty diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala index 5e0a648b3..9ebdedc4f 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala @@ -1,19 +1,14 @@ package viper.silicon.dependencyAnalysis -import dependencyAnalysis.{CompactUserLevelDependencyAnalysisNode, UserLevelDependencyAnalysisNode} -import viper.silicon.dependencyAnalysis.DATraversalMode.{DATraversalMode, Downwards, Upwards} +import dependencyAnalysis.UserLevelDependencyAnalysisNode import viper.silicon.interfaces.Failure import viper.silicon.verifier.Verifier import viper.silver.ast -import viper.silver.ast.utility.ViperStrategy -import viper.silver.ast.utility.rewriter.Traverse -import viper.silver.ast.{If, JoinType, Program, Stmt} -import viper.silver.dependencyAnalysis.{AbstractDependencyGraphInterpreter, AnalysisSourceInfo, AssumptionType} +import viper.silver.ast.{JoinType, Program} +import viper.silver.dependencyAnalysis.{AbstractDependencyGraphInterpreter, AssumptionType} import java.io.PrintWriter -import java.lang.Double.isNaN import java.nio.file.Paths -import scala.collection.mutable @@ -23,521 +18,142 @@ object DATraversalMode extends Enumeration { } -class DependencyGraphInterpreter[T <: DependencyGraphState](name: String, dependencyGraph: ReadOnlyDependencyGraph[T], errors: List[Failure], member: Option[ast.Member]=None) extends AbstractDependencyGraphInterpreter{ - - def getGraph: ReadOnlyDependencyGraph[T] = dependencyGraph - def getName: String = name - def getMember: Option[ast.Member] = member - - lazy val nodesMap: Map[Int, DependencyAnalysisNode] = getNodes.map(node => (node.id, node)).toMap - lazy val nonInternalAssumptionNodesMap: Map[Int, DependencyAnalysisNode] = getNonInternalAssumptionNodes(getNodes).map(node => (node.id, node)).toMap - lazy val assertionNodesMap: Map[Int, DependencyAnalysisNode] = getAssertionNodes.map(node => (node.id, node)).toMap - def getNodes: Set[DependencyAnalysisNode] = dependencyGraph.getNodes.toSet - def getAssumptionNodes: Set[DependencyAnalysisNode] = dependencyGraph.getAssumptionNodes.toSet - def getAssertionNodes: Set[DependencyAnalysisNode] = dependencyGraph.getAssertionNodes.toSet - def getErrors: List[Failure] = errors - - val joinSinkNodes: Set[DependencyAnalysisNode] = getJoinCandidateNodes(getNodes).filter(_.joinInfos.exists(_.joinType.equals(JoinType.Sink))) - val joinSourceNodes: Set[DependencyAnalysisNode] = getJoinCandidateNodes(getNodes).filter(_.joinInfos.exists(_.joinType.equals(JoinType.Source))) - - private def getJoinCandidateNodes(nodes: Set[DependencyAnalysisNode]): Set[DependencyAnalysisNode] = nodes.filter(node => node.joinInfos.nonEmpty) - - private def toUserLevelNodes(nodes: Iterable[DependencyAnalysisNode]): Set[UserLevelDependencyAnalysisNode] = UserLevelDependencyAnalysisNode.from(nodes) - - def getNodesByLine(line: Int): Set[DependencyAnalysisNode] = - getNodes.filter(n => !AssumptionType.internalTypes.contains(n.assumptionType)).filter(node => node.sourceInfo.getLineNumber.isDefined && node.sourceInfo.getLineNumber.get == line) - - def getNodesByPosition(file: String, line: Int): Set[DependencyAnalysisNode] = - getNodes.filter(n => !AssumptionType.internalTypes.contains(n.assumptionType)).filter(node => node.sourceInfo.getLineNumber.isDefined && node.sourceInfo.getLineNumber.get == line && node.sourceInfo.getPositionString.startsWith(file + ".")) - - - def getNodesByLabel(label: String): Set[DependencyAnalysisNode] = { - val fullAnnotation = ("""@label\(\s*"?""" + java.util.regex.Pattern.quote(label) + """"?\s*\)""").r - getNodes.filter(node => fullAnnotation.findFirstIn(node.toString).isDefined) - } - - def getDirectDependencies(nodeIdsToAnalyze: Set[Int]): Set[DependencyAnalysisNode] = { - var queue = nodeIdsToAnalyze - var result: Set[Int] = Set.empty - val internalNodeIds = getAssumptionNodes.diff(getNonInternalAssumptionNodes).map(_.id) - while(queue.nonEmpty){ - val directDependencyIds = queue flatMap (id => dependencyGraph.getDirectEdges.getOrElse(id, Set.empty)) - queue = internalNodeIds.intersect(directDependencyIds).diff(result) // internal assumptions are hidden -> add their direct dependencies instead - result = result.union(directDependencyIds) - } - - getNonInternalAssumptionNodes.filter(node => result.contains(node.id)) - } - - def getAllNonInternalDependencies(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[DependencyAnalysisNode] = { - val allDependenciesUpwards = dependencyGraph.getAllDependencies(nodeIdsToAnalyze, includeInfeasibilityNodes, includeUpwardEdges=true, includeDownwardEdges=false) - val allDependenciesDownwards = dependencyGraph.getAllDependencies(nodeIdsToAnalyze, includeInfeasibilityNodes, includeUpwardEdges=false, includeDownwardEdges=true) - (allDependenciesUpwards ++ allDependenciesDownwards) flatMap nonInternalAssumptionNodesMap.get - } - - def getAllExplicitDependencies(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[DependencyAnalysisNode] = { - val allDependenciesUpwards = dependencyGraph.getAllDependencies(nodeIdsToAnalyze, includeInfeasibilityNodes, includeUpwardEdges=true, includeDownwardEdges=false) - val allDependenciesDownwards = dependencyGraph.getAllDependencies(nodeIdsToAnalyze, includeInfeasibilityNodes, includeUpwardEdges=false, includeDownwardEdges=true) - getExplicitAssumptionNodes.filter(node => (allDependenciesUpwards ++ allDependenciesDownwards).contains(node.id)) - } - - def getAllNonInternalDependents(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[DependencyAnalysisNode] = { - val allDependentsUpwards = dependencyGraph.getAllDependents(nodeIdsToAnalyze, includeInfeasibilityNodes, includeUpwardEdges=true, includeDownwardEdges=false) - val allDependentsDownwards = dependencyGraph.getAllDependents(nodeIdsToAnalyze, includeInfeasibilityNodes, includeUpwardEdges=false, includeDownwardEdges=true) - getNonInternalAssertionNodes.filter(node => (allDependentsUpwards ++ allDependentsDownwards).contains(node.id)) - } - - def getAllExplicitDependents(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[DependencyAnalysisNode] = { - val allDependentsUpwards = dependencyGraph.getAllDependents(nodeIdsToAnalyze, includeInfeasibilityNodes, includeUpwardEdges=true, includeDownwardEdges=false) - val allDependentsDownwards = dependencyGraph.getAllDependents(nodeIdsToAnalyze, includeInfeasibilityNodes, includeUpwardEdges=false, includeDownwardEdges=true) - getExplicitAssertionNodes.filter(node => (allDependentsUpwards ++ allDependentsDownwards).contains(node.id)) - } - - def getNonInternalAssumptionNodes: Set[DependencyAnalysisNode] = nonInternalAssumptionNodesMap.values.toSet - - def getNonInternalAssumptionNodes(nodes: Set[DependencyAnalysisNode]): Set[DependencyAnalysisNode] = nodes filter (node => - (node.isInstanceOf[GeneralAssumptionNode] && !AssumptionType.internalTypes.contains(node.assumptionType)) - || AssumptionType.postconditionTypes.contains(node.assumptionType) || node.joinInfos.nonEmpty // postconditions act as assumptions for callers - ) - - def getExplicitAssumptionNodes: Set[DependencyAnalysisNode] = getNonInternalAssumptionNodes filter (node => - AssumptionType.explicitAssumptionTypes.contains(node.assumptionType) - ) - - @deprecated // needs to be adapted to the notion of upward and downward edges - def hasAnyDependency(nodesToAnalyze: Set[DependencyAnalysisNode], includeInfeasibilityNodes: Boolean = true): Boolean = - nodesToAnalyze.intersect(getNonInternalAssumptionNodes) - .exists(node => dependencyGraph.existsAnyDependency(Set(node.id), nodesToAnalyze map (_.id) filter (_ != node.id), includeInfeasibilityNodes)) - - - def getNonInternalAssertionNodes: Set[DependencyAnalysisNode] = getAssertionNodes filter (node => - !AssumptionType.internalTypes.contains(node.assumptionType) || node.joinInfos.nonEmpty) - - def getExplicitAssertionNodes: Set[DependencyAnalysisNode] = - getNonInternalAssertionNodes.filter(node => AssumptionType.explicitAssertionTypes.contains(node.assumptionType)) - - def getAssertionNodesWithFailures: Set[GeneralAssertionNode] = - getNonInternalAssertionNodes.filter(_.isInstanceOf[GeneralAssertionNode]).map(_.asInstanceOf[GeneralAssertionNode]).filter(_.hasFailed) - - def exportGraph(program: ast.Program): Unit = { - if(Verifier.config.dependencyAnalysisExportPath.isEmpty) return - val directory = Paths.get(Verifier.config.dependencyAnalysisExportPath()).toFile - directory.mkdir() - dependencyGraph.exportGraph(Verifier.config.dependencyAnalysisExportPath() + "/" + name) - exportProgram(program, Verifier.config.dependencyAnalysisExportPath() + "/" + name) - } - - private def exportProgram(program: Program, path: String): Unit = { - // TODO ake: we should copy the original source file in order to keep the line numbering! - val writer = new PrintWriter(path + "/program.vpr") - writer.println(program.toString()) - writer.close() - } - - private def getNodesWithIdenticalSource(nodes: Set[DependencyAnalysisNode]): Set[DependencyAnalysisNode] = { - val sourceInfos = nodes map (_.sourceInfo) - getNodes filter (node => sourceInfos.contains(node.sourceInfo)) - } - - def computeProofCoverage(): (Double, Set[String]) = { - val explicitAssertionNodes = getNodesWithIdenticalSource(getExplicitAssertionNodes) - computeProofCoverage(explicitAssertionNodes) - } - - def computeProofCoverage(assertionNodes: Set[DependencyAnalysisNode]): (Double, Set[String]) = { - val assertionNodeIds = assertionNodes map (_.id) - val dependencies = dependencyGraph.getAllDependencies(assertionNodeIds, includeInfeasibilityNodes=true, includeUpwardEdges=true, includeDownwardEdges=true) - val coveredNodes = dependencies ++ assertionNodeIds - - val userLevelNodes = toUserLevelNodes(getNonInternalAssumptionNodes.filterNot(_.isInstanceOf[AxiomAssumptionNode])) - if(userLevelNodes.isEmpty) return (Double.NaN, Set()) - - val uncoveredUserLevelNodes = userLevelNodes filter (node => - coveredNodes.intersect(node.lowerLevelNodes.map(_.id)).isEmpty - ) - val proofCoverage = 1.0 - (uncoveredUserLevelNodes.size.toDouble / userLevelNodes.size.toDouble) - (proofCoverage, uncoveredUserLevelNodes.map(_.toString)) - } - - def getPrunedProgram(crucialNodes: Set[DependencyAnalysisNode], program: ast.Program): (ast.Program, Double) = { - - def isCrucialExp(exp: ast.Exp, crucialNodesWithExpInfo: Set[AnalysisSourceInfo]): Boolean = { - crucialNodesWithExpInfo exists (n => n.getPositionString.equals(AnalysisSourceInfo.extractPositionString(exp.pos))) // TODO ake: currently we compare only lines not columns! - } - - def isCrucialStmt(stmt: ast.Stmt, crucialNodesWithStmtInfo: Set[AnalysisSourceInfo]): Boolean = { - crucialNodesWithStmtInfo exists (n => n.getPositionString.equals(AnalysisSourceInfo.extractPositionString(stmt.pos))) - } - - val crucialNodeSourceInfos = crucialNodes map (_.sourceInfo) - var total = 0 - var removed = 0 - var nonDetermBoolCount = 0 - - def getNextNonDetermBool: String = { - nonDetermBoolCount += 1 - s"nonDetermBool_$nonDetermBoolCount" - } - - val newProgram: ast.Program = ViperStrategy.Slim({ - case s @(_: ast.Seqn | _: ast.Goto) => s - case domain@ast.Domain(name, functions, axioms, typVars, interpretations) => - val newAxioms = axioms filter (a => - crucialNodeSourceInfos exists (n => n.getPositionString.equals(AnalysisSourceInfo.extractPositionString(a.exp.pos)) || - n.getPositionString.equals(AnalysisSourceInfo.extractPositionString(a.pos)))) - ast.Domain(name, functions, newAxioms, typVars, interpretations)(domain.pos, domain.info, domain.errT) - case function@ast.Function(name, formalArgs, typ, pres, posts, body) => - val newPres = pres filter (isCrucialExp(_, crucialNodeSourceInfos)) - val newPosts = posts filter (isCrucialExp(_, crucialNodeSourceInfos)) - val newBody = body filter (isCrucialExp(_, crucialNodeSourceInfos)) - ast.Function(name, formalArgs, typ, newPres, newPosts, newBody)(function.pos, function.info, function.errT) - case meth@ast.Method(name, inVars, outVars, pres, posts, body) => - val newPres = pres filter (isCrucialExp(_, crucialNodeSourceInfos)) - val newPosts = posts filter (isCrucialExp(_, crucialNodeSourceInfos)) - total += pres.size + posts.size - removed += (pres.size - newPres.size) + (posts.size - newPosts.size) - ast.Method(name, inVars, outVars, newPres, newPosts, body)(meth.pos, meth.info, meth.errT) - case ifStmt@ast.If(cond, thenBody, elseBody) if !isCrucialExp(cond, crucialNodeSourceInfos) => - total += 1 - removed += 1 - val nonDetermBool = getNextNonDetermBool - ast.Seqn(Seq( - ast.LocalVarDeclStmt(ast.LocalVarDecl(nonDetermBool, ast.Bool)())(), - ast.If(ast.LocalVar(nonDetermBool, ast.Bool)(cond.pos, cond.info, cond.errT), thenBody, elseBody)(ifStmt.pos, ifStmt.info, ifStmt.errT)) - , Seq())(ifStmt.pos, ifStmt.info, ifStmt.errT) - case ifStmt: If => - total += 1 - ifStmt - case whileStmt@ast.While(cond, invs, body) if !isCrucialExp(cond, crucialNodeSourceInfos) => - val newInvs = invs filter (isCrucialExp(_, crucialNodeSourceInfos)) - total += 1 + invs.size - removed += 1 + (invs.size - newInvs.size) - val nonDetermBool = getNextNonDetermBool - ast.Seqn(Seq( - ast.LocalVarDeclStmt(ast.LocalVarDecl(nonDetermBool, ast.Bool)())(), - ast.While(ast.LocalVar(nonDetermBool, ast.Bool)(cond.pos, cond.info, cond.errT), newInvs, body)(whileStmt.pos, whileStmt.info, whileStmt.errT)) - , Seq())(whileStmt.pos, whileStmt.info, whileStmt.errT) - case whileStmt@ast.While(cond, invs, body) => - val newInvs = invs filter (isCrucialExp(_, crucialNodeSourceInfos)) - total += 1 + invs.size - removed += (invs.size - newInvs.size) - ast.While(cond, newInvs, body)(whileStmt.pos, whileStmt.info, whileStmt.errT) - case label@ast.Label(name, invs) => - val newInvs = invs filter (isCrucialExp(_, crucialNodeSourceInfos)) - total += 1 + invs.size - removed += (invs.size - newInvs.size) - ast.Label(name, newInvs)(label.pos, label.info, label.errT) - case s: ast.Package if !isCrucialStmt(s, crucialNodeSourceInfos) => - total += 1 - removed += 1 - ast.Inhale(ast.TrueLit()(s.pos, s.info, s.errT))(s.pos, s.info, s.errT) - case s: Stmt if !isCrucialStmt(s, crucialNodeSourceInfos) => - total += 1 - removed += 1 - ast.Inhale(ast.TrueLit()(s.pos, s.info, s.errT))(s.pos, s.info, s.errT) - case s: Stmt => - total += 1 - s - }, Traverse.BottomUp).execute(program) - (newProgram, removed.toDouble / total.toDouble) - } - - def pruneProgramAndExport(crucialNodes: Set[DependencyAnalysisNode], program: ast.Program, exportFileName: String): Unit = { - val writer = new PrintWriter(exportFileName) - val (newProgram, pruningFactor) = getPrunedProgram(crucialNodes, program) - writer.println("// pruning factor: " + pruningFactor) - writer.println(newProgram.toString()) - writer.close() - } - - def computeVerificationProgress(): (Double, Double, String) = { - computeVerificationProgressOptimized() - } - - private lazy val sourceToAssertionNodes: Map[AnalysisSourceInfo, Set[DependencyAnalysisNode]] = getNonInternalAssertionNodes.groupBy(_.sourceInfo) - - - - val deps: DAMemo[(AnalysisSourceInfo, DATraversalMode), Set[CompactUserLevelDependencyAnalysisNode]] = DAMemo { case (assertionNode, mode) => - def computeDependencies(currentNode: AnalysisSourceInfo, visited: Set[(AnalysisSourceInfo, DATraversalMode)], traversalMode: DATraversalMode): Set[CompactUserLevelDependencyAnalysisNode] = { - if (visited.contains((currentNode, traversalMode))) { - return Set.empty // break cycles to avoid infinite loops - } - - if (deps.contains(currentNode, traversalMode)) { - return deps((currentNode, traversalMode)) - } - - val updatedVisited = visited ++ Set((currentNode, traversalMode)) - val allNonInternalAssertions = sourceToAssertionNodes.getOrElse(currentNode, Set.empty) - - val intraMethodDependencyIds = dependencyGraph.getAllDependencies(allNonInternalAssertions.map(_.id), includeInfeasibilityNodes=true, includeUpwardEdges=false, includeDownwardEdges=false) - val intraMethodDependencies = intraMethodDependencyIds.flatMap(nonInternalAssumptionNodesMap.get).filterNot(_.sourceInfo.equals(currentNode)) - - val postconditionNodeIds = intraMethodDependencyIds.flatMap(n => dependencyGraph.getEdgesConnectingMethodsDownwards.getOrElse(n, Set.empty)) - val postconditionNodes = postconditionNodeIds.flatMap(nodesMap.get) - - val preconditionNodeIds = intraMethodDependencyIds.flatMap(n => dependencyGraph.getEdgesConnectingMethodsUpwards.getOrElse(n, Set.empty)) - val preconditionNodes = preconditionNodeIds.flatMap(nodesMap.get) - - val transDepsDownwards = postconditionNodes.map(_.sourceInfo).filterNot(_.equals(currentNode)).flatMap(node => computeDependencies(node, updatedVisited, DATraversalMode.Downwards)) - val transDepsUpwards = preconditionNodes.map(_.sourceInfo).filterNot(_.equals(currentNode)).flatMap(node => computeDependencies(node, updatedVisited, DATraversalMode.Upwards)) - - val result = reduceCompactUserLevelNodes(toCompactUserLevelNodes(intraMethodDependencies ++ postconditionNodes) ++ transDepsDownwards ++ transDepsUpwards) - - deps.put((currentNode, traversalMode), result) - result - } - - computeDependencies(assertionNode, Set.empty, mode) - } - - private def reduceCompactUserLevelNodes(inputNodes: Set[CompactUserLevelDependencyAnalysisNode]): Set[CompactUserLevelDependencyAnalysisNode] = { - - val resultMap: mutable.Map[AnalysisSourceInfo, CompactUserLevelDependencyAnalysisNode] = mutable.Map() - - for (node <- inputNodes) { - val existingNode = resultMap.get(node.source) - - val newNode = existingNode match { - case Some(existing) => - CompactUserLevelDependencyAnalysisNode( - source = node.source, - assumptionTypes = existing.assumptionTypes ++ node.assumptionTypes, - assertionTypes = existing.assertionTypes ++ node.assertionTypes, - hasFailures = existing.hasFailures || node.hasFailures - ) - case None => node - } - - resultMap.update(node.source, newNode) - } - - resultMap.values.toSet - } - - private def toCompactUserLevelNodes(lowLevelNodes: Set[DependencyAnalysisNode]): Set[CompactUserLevelDependencyAnalysisNode] = { - lowLevelNodes.groupBy(_.sourceInfo).map{case (source, nodes) => - val assertionNodes = nodes.filter(_.isInstanceOf[GeneralAssertionNode]) - CompactUserLevelDependencyAnalysisNode(source, - nodes.filter(_.isInstanceOf[GeneralAssumptionNode]).map(_.assumptionType), - assertionNodes.map(_.assumptionType), - assertionNodes.exists(_.asInstanceOf[GeneralAssertionNode].hasFailed) - )}.toSet - } - - private def computeAssertionQuality(allDependencies: Set[CompactUserLevelDependencyAnalysisNode], assertion: AnalysisSourceInfo): Double = { - val assertionNodes = sourceToAssertionNodes.getOrElse(assertion, Set.empty).filter(node => node.isInstanceOf[GeneralAssertionNode]) - val failedAssertionNodes = assertionNodes.filter(node => node.asInstanceOf[GeneralAssertionNode].hasFailed || node.assumptionType.equals(AssumptionType.ExplicitPostcondition)) - // assertions with failures have quality = 0.0 - if(failedAssertionNodes.nonEmpty) - return 0.0 +class DependencyGraphInterpreter[T <: DependencyGraphState](name: String, dependencyGraph: ReadOnlyDependencyGraph[T], errors: List[Failure], member: Option[ast.Member]=None) extends AbstractDependencyGraphInterpreter { - val explicitDeps = allDependencies.filter(_.assumptionTypes.intersect(AssumptionType.explicitAssumptionTypes).nonEmpty).map(_.source) - val numDepsTotal = allDependencies.map(_.source).size - (numDepsTotal - explicitDeps.size).toDouble / numDepsTotal.toDouble - } + def getGraph: ReadOnlyDependencyGraph[T] = dependencyGraph - private def getAssertionsRelevantForProgress: Map[AnalysisSourceInfo, Set[DependencyAnalysisNode]] = sourceToAssertionNodes.filter(ass => ass._2.map(_.assumptionType).intersect(AssumptionType.importedTypes).isEmpty) + def getName: String = name - def computeVerificationProgressOptimized(): (Double, Double, String) = { + def getMember: Option[ast.Member] = member - val allAssertions = getAssertionsRelevantForProgress.keySet.toList - val assertionDeps = allAssertions map (ass => ({ - val ups = deps((ass, Upwards)) - val downs = deps((ass, Downwards)) - reduceCompactUserLevelNodes(ups ++ downs) - }, ass)) + lazy val nodesMap: Map[Int, DependencyAnalysisNode] = getNodes.map(node => (node.id, node)).toMap + lazy val nonInternalAssumptionNodesMap: Map[Int, DependencyAnalysisNode] = getNonInternalAssumptionNodes(getNodes).map(node => (node.id, node)).toMap + lazy val assertionNodesMap: Map[Int, DependencyAnalysisNode] = getAssertionNodes.map(node => (node.id, node)).toMap - val specQuality = computeSpecQuality(assertionDeps.flatMap(_._1).toSet) + def getNodes: Set[DependencyAnalysisNode] = dependencyGraph.getNodes.toSet - val assertionQualities1 = assertionDeps map (ass => (computeAssertionQuality(ass._1, ass._2), ass._2)) - val assertionQualities = assertionQualities1 filterNot (n => isNaN(n._1)) - val numAssertions = assertionQualities.size - val fullyVerifiedAssertions = assertionQualities.filter(_._1 == 1.0) - val numFullyVerifiedAssertions = fullyVerifiedAssertions.size + def getAssumptionNodes: Set[DependencyAnalysisNode] = dependencyGraph.getAssumptionNodes.toSet - val proofQualityPeter = if(numAssertions > 0) numFullyVerifiedAssertions.toDouble / numAssertions.toDouble else 1.0 - - val assertionQualitiesSum = assertionQualities.map(_._1).sum - val proofQualityLea = if(numAssertions > 0) assertionQualitiesSum / numAssertions.toDouble else 1.0 - - val info = { -// s"Assertions with dependencies on explicit assumptions:\n\t\t${assertionQualities.filterNot(_._1 == 1.0).sortBy(n => (n._2.getLineNumber, n._2.toString())).mkString("\n\t\t")}" + "\n\n" + -// s"Assertions with perfect proof quality:\n\t\t${fullyVerifiedAssertions.map(_._2).sortBy(n => (n.getLineNumber, n.toString())).mkString("\n\t\t")}" + "\n\n" + -// s"Assertion qualities\n\t\t${assertionQualities.sortBy(n => (n._2.getLineNumber, n._2.toString())).mkString("\n\t\t")}" + "\n\n" + - s"specQuality = $specQuality\n" + - s"proof quality (Peter): $numFullyVerifiedAssertions / $numAssertions = $proofQualityPeter\n" + - s"proof quality (Lea): $assertionQualitiesSum / $numAssertions = $proofQualityLea\n" - } - -// println(s"Runtimes:\n\tperMethodDependencyRuntime: ${perMethodDependencyRuntime/1e6}ms\n\t" + -// s"depsToPostcondRuntime: ${depsToPostcondRuntime/1e6}ms\n\t" + -// s"aggregationOfSummaryNodesRuntime: ${aggregationOfSummaryNodesRuntime/1e6}ms\n\t" + -// s"filteringNodesRuntime: ${filteringNodesRuntime/1e6}ms\n\t") - - (specQuality * proofQualityPeter, specQuality * proofQualityLea, info) - } - - - private def computeSpecQuality(coveredNodes: Set[CompactUserLevelDependencyAnalysisNode]): Double = { - - val explicitAssertions = toCompactUserLevelNodes(getExplicitAssertionNodes) - val nonSourceCodeAssumptionTypes = AssumptionType.explicitAssumptionTypes ++ AssumptionType.verificationAnnotationTypes - val allSourceCodeNodes = toCompactUserLevelNodes(getNonInternalAssumptionNodes).filter(n => nonSourceCodeAssumptionTypes.intersect(n.assumptionTypes).isEmpty).map(_.source).diff(explicitAssertions.map(_.source)) - - if(allSourceCodeNodes.isEmpty) return 1.0 - - val coveredSourceCodeNodes = coveredNodes.map(_.source).intersect(allSourceCodeNodes) -// println(s"Covered Source Code:\n\t${coveredSourceCodeNodes.toList.sortBy(n => (n.getLineNumber, n.toString())).mkString("\n\t")}") -// println(s"Uncovered Source Code:\n\t${allSourceCodeNodes.diff(coveredSourceCodeNodes).toList.sortBy(n => (n.getLineNumber, n.toString())).mkString("\n\t")}") - println(s"Spec Quality = ${coveredSourceCodeNodes.size} / ${allSourceCodeNodes.size}") - coveredSourceCodeNodes.size.toDouble / allSourceCodeNodes.size.toDouble - } - - - def computeVerificationProgressNaive(): (Double, Double, String) = { - val allAssertions = toUserLevelNodes(getNonInternalAssertionNodes).filter(ass => ass.assertionTypes.intersect(AssumptionType.importedTypes).isEmpty) - -// println(s"#assertions: ${allAssertions.size}") - - // This is super slow. See optimized progress computation. - val relevantDependenciesPerAssertion = allAssertions - .map(ass => (ass, toUserLevelNodes(getAllNonInternalDependencies(ass.lowerLevelNodes.map(_.id))).diffBySource(Set(ass)))).toMap - .filter{case (ass, assumptions) => assumptions.nonEmpty || ass.hasFailures || ass.assertionTypes.contains(AssumptionType.ExplicitPostcondition)} // filter out trivial assertions like `assert true` - - val relevantDependencies = relevantDependenciesPerAssertion.flatMap(_._2).filter(_.assumptionTypes.nonEmpty).toSet - - val explicitAssertions = toUserLevelNodes(getExplicitAssertionNodes).getSourceSet() - - // covered - val coveredExplicitSources = UserLevelDependencyAnalysisNode.extractExplicitAssumptionNodes(relevantDependencies).getSourceSet() - val coveredVerificationAnnotations = UserLevelDependencyAnalysisNode.extractVerificationAnnotationNodes(relevantDependencies).getSourceSet().diff(coveredExplicitSources) - val coveredSourceCodeStmts = relevantDependencies.getSourceSet().diff(coveredExplicitSources).diff(coveredVerificationAnnotations).diff(explicitAssertions) - - // uncovered - val uncoveredNodes = toUserLevelNodes(getNonInternalAssumptionNodes).diffBySource(relevantDependencies) - val uncoveredExplicitSources = UserLevelDependencyAnalysisNode.extractExplicitAssumptionNodes(uncoveredNodes).getSourceSet() - val uncoveredVerificationAnnotations = UserLevelDependencyAnalysisNode.extractVerificationAnnotationNodes(uncoveredNodes).getSourceSet().diff(uncoveredExplicitSources) ++ explicitAssertions - val uncoveredSourceCodeStmts = uncoveredNodes.getSourceSet().diff(uncoveredExplicitSources).diff(uncoveredVerificationAnnotations) - - // assertions - val relevantAssertions = relevantDependenciesPerAssertion - val assertionsWithFailures = relevantAssertions.filter(assertion => assertion._1.hasFailures).keySet.getSourceSet() - val explicitPostconditions = relevantAssertions.filter(assertion => assertion._1.assertionTypes.contains(AssumptionType.ExplicitPostcondition)).keySet.getSourceSet().diff(assertionsWithFailures) - val assertionsWithZeroProofQuality = assertionsWithFailures.union(explicitPostconditions) - val assertionsWithExplicitDeps = relevantAssertions.filter(deps => !deps._1.hasFailures && deps._2.exists(d => AssumptionType.explicitAssumptionTypes.intersect(d.assumptionTypes).nonEmpty)).keySet.getSourceSet().diff(assertionsWithZeroProofQuality) - val fullyVerifiedAssertions = relevantAssertions.keySet.getSourceSet().diff(assertionsWithExplicitDeps).diff(assertionsWithZeroProofQuality) - - val numRelevantAssertions = relevantAssertions.keySet.size.toDouble - - val numAllSourceCodeStmts = coveredSourceCodeStmts.size.toDouble + uncoveredSourceCodeStmts.size.toDouble - // Peter's metric - val specQuality = if(numAllSourceCodeStmts > 0) coveredSourceCodeStmts.size.toDouble / numAllSourceCodeStmts else 1.0 - val proofQualityPeter = if(numRelevantAssertions > 0) fullyVerifiedAssertions.size.toDouble / numRelevantAssertions else 1.0 - val verificationProgressPeter = specQuality * proofQualityPeter - - // Lea's metric - val proofQualityPerAssertion = relevantAssertions.toList.map { case (assertion, assumptions) => - if(assertionsWithZeroProofQuality.contains(assertion.source)) (0.0, assertion) - else { - val nonExplicitDeps = UserLevelDependencyAnalysisNode.extractNonExplicitAssumptionNodes(assumptions) - (nonExplicitDeps.size.toDouble / assumptions.size.toDouble, assertion) - } - } - - val proofQualityLea = if(numRelevantAssertions > 0) proofQualityPerAssertion.map(_._1).sum / numRelevantAssertions else 1.0 - val verificationProgressLea = specQuality * proofQualityLea - - - def getString(nodes: Set[AnalysisSourceInfo]): String = { - nodes.toList.sortBy(n => (n.getLineNumber, n.toString())).mkString("\n\t\t") - } - - val info = { - s"Covered\n" + - s"\tExplicit Assumptions:\n\t\t${getString(coveredExplicitSources)}" + "\n" + - s"\tVerification Annotations:\n\t\t${getString(coveredVerificationAnnotations)}" + "\n" + - s"\tSource Code:\n\t\t${getString(coveredSourceCodeStmts)}" + "\n" + - "\n" + - s"Uncovered\n" + - s"\tExplicit Assumptions:\n\t\t${getString(uncoveredExplicitSources)}" + "\n" + - s"\tVerification Annotations:\n\t\t${getString(uncoveredVerificationAnnotations)}" + "\n" + - s"\tSource Code:\n\t\t${getString(uncoveredSourceCodeStmts)}" + "\n" + - "\n" + - s"Fully verified assertions:\n\t\t${getString(fullyVerifiedAssertions)}" + "\n\n" + - s"Assertions depending on explicit assumptions:\n\t\t${getString(assertionsWithExplicitDeps)}" + "\n\n" + - s"Assertions with failures:\n\t\t${getString(assertionsWithFailures)}" + "\n\n" + - s"Explicit Postcondition:\n\t\t${getString(explicitPostconditions)}" + "\n\n" + - "\n" + - s"Assertion Qualities:\n\t\t${proofQualityPerAssertion.filterNot(_._1 == 1.0).sortBy(n => (n._2.source.getLineNumber, n._2.toString())).mkString("\n\t\t")}" + "\n\n" + - "\n" + - s"Verification Progress (Peter):\n\t${coveredSourceCodeStmts.size}/${coveredSourceCodeStmts.size + uncoveredSourceCodeStmts.size} * " + - s"${fullyVerifiedAssertions.size}/${relevantAssertions.keySet.size} = ${"%.2f".format(verificationProgressPeter)}" + "\n" + - s"Verification Progress (Lea):\n\t${coveredSourceCodeStmts.size}/${coveredSourceCodeStmts.size + uncoveredSourceCodeStmts.size} * " + - f"${"%.2f".format(proofQualityPerAssertion.map(_._1).sum)}/${relevantAssertions.keys.size} = ${"%.2f".format(verificationProgressLea)}" + "\n" - } - (verificationProgressPeter, verificationProgressLea, info) - } - - /* returns an ordered list of (Assumption, #dependents) */ - def computeAssumptionRanking(): List[(String, Double)] = { - val allAssertions = toUserLevelNodes(getNonInternalAssertionNodes).filter(ass => ass.assertionTypes.intersect(AssumptionType.importedTypes).isEmpty) - - val relevantDependenciesPerAssertion = allAssertions - .map(ass => (ass, toUserLevelNodes(getAllNonInternalDependencies(ass.lowerLevelNodes.map(_.id))).diffBySource(Set(ass)))).toMap - .filter{case (assertion, assumptions) => assumptions.nonEmpty || assertion.hasFailures || assertion.assertionTypes.contains(AssumptionType.ExplicitPostcondition)} - val numAssertions = relevantDependenciesPerAssertion.size.toDouble - - val assumptionImpacts= relevantDependenciesPerAssertion.toList.flatMap { case (_, assumptions) => - val explicitDeps = UserLevelDependencyAnalysisNode.extractExplicitAssumptionNodes(assumptions) - explicitDeps.map(node => (node.source, 1.0/assumptions.size/numAssertions)).toList - } - - val unverifiedAssertionImpacts = getAssertionsWithZeroQuality.map(assertion => (assertion, 1.0/numAssertions)).toList - - val totalImpacts1 = (assumptionImpacts ++ unverifiedAssertionImpacts).groupBy(_._1) - val totalImpacts = totalImpacts1.map{case (assumption, impacts) => (assumption.toString, impacts.map(_._2).sum)}.toList - - totalImpacts.sortBy(_._2).reverse - } - - def computeAssumptionRankingOld(): List[(String, Int)] = { - toUserLevelNodes(getExplicitAssumptionNodes).map(node => (node.toString, toUserLevelNodes(getAllNonInternalDependents(node.lowerLevelNodes.map(_.id))).diff(Set(node)).size)) - .toList.sortBy(_._2).reverse - } - - private def getAssertionsWithZeroQuality: Set[AnalysisSourceInfo] = { - val allAssertions = toUserLevelNodes(getNonInternalAssertionNodes) - allAssertions.filter(assertion => assertion.hasFailures || assertion.assertionTypes.contains(AssumptionType.ExplicitPostcondition)).getSourceSet() - } - - def computeUncoveredStatements(): Int = { - val allAssertions = toUserLevelNodes(getNonInternalAssertionNodes) - val allDependencies = allAssertions.flatMap(ass => toUserLevelNodes(getAllNonInternalDependencies(ass.lowerLevelNodes.map(_.id))).diffBySource(Set(ass))).getSourceSet() - - val explicitAssertions = toUserLevelNodes(getExplicitAssertionNodes) - val allNodes = toUserLevelNodes(getNonInternalAssumptionNodes) - val allSourceCodeStmts = allNodes.getSourceSet().diff(UserLevelDependencyAnalysisNode.extractByAssumptionType(allNodes, - AssumptionType.explicitAssumptionTypes ++ AssumptionType.verificationAnnotationTypes).getSourceSet()).diff(explicitAssertions.getSourceSet()) - val uncoveredSourceCodeStmts = allSourceCodeStmts.diff(allDependencies) - if(uncoveredSourceCodeStmts.nonEmpty) - println(s"$name:\n\t${allSourceCodeStmts.diff(allDependencies).toList.sortBy(n => (n.getLineNumber, n.toString())).mkString("\n\t")}") - uncoveredSourceCodeStmts.size - } -} + def getAssertionNodes: Set[DependencyAnalysisNode] = dependencyGraph.getAssertionNodes.toSet + + def getErrors: List[Failure] = errors + + // TODO ake: join nodes are not needed for the final graph + val joinSinkNodes: Set[DependencyAnalysisNode] = getJoinCandidateNodes(getNodes).filter(_.joinInfos.exists(_.joinType.equals(JoinType.Sink))) + val joinSourceNodes: Set[DependencyAnalysisNode] = getJoinCandidateNodes(getNodes).filter(_.joinInfos.exists(_.joinType.equals(JoinType.Source))) + + private def getJoinCandidateNodes(nodes: Set[DependencyAnalysisNode]): Set[DependencyAnalysisNode] = nodes.filter(node => node.joinInfos.nonEmpty) + + def toUserLevelNodes(nodes: Iterable[DependencyAnalysisNode]): Set[UserLevelDependencyAnalysisNode] = UserLevelDependencyAnalysisNode.from(nodes) + + def getNodesByLine(line: Int): Set[DependencyAnalysisNode] = + getNodes.filter(n => !AssumptionType.internalTypes.contains(n.assumptionType)).filter(node => node.sourceInfo.getLineNumber.isDefined && node.sourceInfo.getLineNumber.get == line) + + def getNodesByPosition(file: String, line: Int): Set[DependencyAnalysisNode] = + getNodes.filter(n => !AssumptionType.internalTypes.contains(n.assumptionType)).filter(node => node.sourceInfo.getLineNumber.isDefined && node.sourceInfo.getLineNumber.get == line && node.sourceInfo.getPositionString.startsWith(file + ".")) + + + def getNodesByLabel(label: String): Set[DependencyAnalysisNode] = { + val fullAnnotation = ("""@label\(\s*"?""" + java.util.regex.Pattern.quote(label) + """"?\s*\)""").r + getNodes.filter(node => fullAnnotation.findFirstIn(node.toString).isDefined) + } + + def getDirectDependencies(nodeIdsToAnalyze: Set[Int]): Set[DependencyAnalysisNode] = { + var queue = nodeIdsToAnalyze + var result: Set[Int] = Set.empty + val internalNodeIds = getAssumptionNodes.diff(getNonInternalAssumptionNodes).map(_.id).union(getAssertionNodes.map(_.id)) + while (queue.nonEmpty) { + val directDependencyIds = queue flatMap (id => dependencyGraph.getDirectEdges.getOrElse(id, Set.empty)) + queue = internalNodeIds.intersect(directDependencyIds).diff(result) // internal assumptions are hidden -> add their direct dependencies instead + result = result.union(directDependencyIds) + } + + getNonInternalAssumptionNodes.filter(node => result.contains(node.id)) + } + + def getAllNonInternalDependencies(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[DependencyAnalysisNode] = { + val allDependenciesUpwards = dependencyGraph.getAllDependencies(nodeIdsToAnalyze, includeInfeasibilityNodes, includeUpwardEdges = true, includeDownwardEdges = false) + val allDependenciesDownwards = dependencyGraph.getAllDependencies(nodeIdsToAnalyze, includeInfeasibilityNodes, includeUpwardEdges = false, includeDownwardEdges = true) + (allDependenciesUpwards ++ allDependenciesDownwards) flatMap nonInternalAssumptionNodesMap.get + } + + def getAllExplicitDependencies(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[DependencyAnalysisNode] = { + val allDependenciesUpwards = dependencyGraph.getAllDependencies(nodeIdsToAnalyze, includeInfeasibilityNodes, includeUpwardEdges = true, includeDownwardEdges = false) + val allDependenciesDownwards = dependencyGraph.getAllDependencies(nodeIdsToAnalyze, includeInfeasibilityNodes, includeUpwardEdges = false, includeDownwardEdges = true) + getExplicitAssumptionNodes.filter(node => (allDependenciesUpwards ++ allDependenciesDownwards).contains(node.id)) + } + + def getAllNonInternalDependents(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[DependencyAnalysisNode] = { + val allDependentsUpwards = dependencyGraph.getAllDependents(nodeIdsToAnalyze, includeInfeasibilityNodes, includeUpwardEdges = true, includeDownwardEdges = false) + val allDependentsDownwards = dependencyGraph.getAllDependents(nodeIdsToAnalyze, includeInfeasibilityNodes, includeUpwardEdges = false, includeDownwardEdges = true) + getNonInternalAssertionNodes.filter(node => (allDependentsUpwards ++ allDependentsDownwards).contains(node.id)) + } + + def getAllExplicitDependents(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[DependencyAnalysisNode] = { + val allDependentsUpwards = dependencyGraph.getAllDependents(nodeIdsToAnalyze, includeInfeasibilityNodes, includeUpwardEdges = true, includeDownwardEdges = false) + val allDependentsDownwards = dependencyGraph.getAllDependents(nodeIdsToAnalyze, includeInfeasibilityNodes, includeUpwardEdges = false, includeDownwardEdges = true) + getExplicitAssertionNodes.filter(node => (allDependentsUpwards ++ allDependentsDownwards).contains(node.id)) + } + + def getNonInternalAssumptionNodes: Set[DependencyAnalysisNode] = nonInternalAssumptionNodesMap.values.toSet + + def getNonInternalAssumptionNodes(nodes: Set[DependencyAnalysisNode]): Set[DependencyAnalysisNode] = nodes filter (node => + (node.isInstanceOf[GeneralAssumptionNode] && !AssumptionType.internalTypes.contains(node.assumptionType)) + || AssumptionType.postconditionTypes.contains(node.assumptionType) || node.joinInfos.nonEmpty // postconditions act as assumptions for callers + ) + + def getExplicitAssumptionNodes: Set[DependencyAnalysisNode] = getNonInternalAssumptionNodes filter (node => + AssumptionType.explicitAssumptionTypes.contains(node.assumptionType) + ) + + def getNonInternalAssertionNodes: Set[DependencyAnalysisNode] = getAssertionNodes filter (node => + !AssumptionType.internalTypes.contains(node.assumptionType) || node.joinInfos.nonEmpty) -case class DAMemo[A,B](f: A => B) extends (A => B) { - private val cache = mutable.Map.empty[A, B] - def apply(x: A): B = cache getOrElseUpdate (x, f(x)) + def getExplicitAssertionNodes: Set[DependencyAnalysisNode] = + getNonInternalAssertionNodes.filter(node => AssumptionType.explicitAssertionTypes.contains(node.assumptionType)) - def put(a: A, b: B): Option[B] = { - cache.put(a, b) - } + def getAssertionNodesWithFailures: Set[GeneralAssertionNode] = + getNonInternalAssertionNodes.filter(_.isInstanceOf[GeneralAssertionNode]).map(_.asInstanceOf[GeneralAssertionNode]).filter(_.hasFailed) - def contains(a: A): Boolean = { - cache.contains(a) - } + def exportGraph(program: ast.Program): Unit = { + if (Verifier.config.dependencyAnalysisExportPath.isEmpty) return + val directory = Paths.get(Verifier.config.dependencyAnalysisExportPath()).toFile + directory.mkdir() + dependencyGraph.exportGraph(Verifier.config.dependencyAnalysisExportPath() + "/" + name) + exportProgram(program, Verifier.config.dependencyAnalysisExportPath() + "/" + name) + } + + private def exportProgram(program: Program, path: String): Unit = { + // TODO ake: we should copy the original source file in order to keep the line numbering! + val writer = new PrintWriter(path + "/program.vpr") + writer.println(program.toString()) + writer.close() + } + + private def getNodesWithIdenticalSource(nodes: Set[DependencyAnalysisNode]): Set[DependencyAnalysisNode] = { + val sourceInfos = nodes map (_.sourceInfo) + getNodes filter (node => sourceInfos.contains(node.sourceInfo)) + } + + def computeProofCoverage(): (Double, Set[String]) = { + val explicitAssertionNodes = getNodesWithIdenticalSource(getExplicitAssertionNodes) + computeProofCoverage(explicitAssertionNodes) + } + + def computeProofCoverage(assertionNodes: Set[DependencyAnalysisNode]): (Double, Set[String]) = { + val assertionNodeIds = assertionNodes map (_.id) + val dependencies = dependencyGraph.getAllDependencies(assertionNodeIds, includeInfeasibilityNodes = true, includeUpwardEdges = true, includeDownwardEdges = true) + val coveredNodes = dependencies ++ assertionNodeIds + + val userLevelNodes = toUserLevelNodes(getNonInternalAssumptionNodes.filterNot(_.isInstanceOf[AxiomAssumptionNode])) + if (userLevelNodes.isEmpty) return (Double.NaN, Set()) + + val uncoveredUserLevelNodes = userLevelNodes filter (node => + coveredNodes.intersect(node.lowerLevelNodes.map(_.id)).isEmpty + ) + val proofCoverage = 1.0 - (uncoveredUserLevelNodes.size.toDouble / userLevelNodes.size.toDouble) + (proofCoverage, uncoveredUserLevelNodes.map(_.toString)) + } } diff --git a/src/test/scala/DependencyAnalysisPrecisionBenchmark.scala b/src/test/scala/DependencyAnalysisPrecisionBenchmark.scala index 93d170e22..e0de9bdf3 100644 --- a/src/test/scala/DependencyAnalysisPrecisionBenchmark.scala +++ b/src/test/scala/DependencyAnalysisPrecisionBenchmark.scala @@ -1,5 +1,6 @@ package viper.silicon.tests +import dependencyAnalysis.DependencyAnalysisPruningSupporter import viper.silicon.dependencyAnalysis._ import viper.silver.ast.Program import viper.silver.verifier @@ -57,6 +58,8 @@ object DependencyAnalysisPrecisionBenchmark extends DependencyAnalysisTestFramew dependencyGraphInterpreters: List[DependencyGraphInterpreter[IntraProcedural]], fullGraphInterpreter: DependencyGraphInterpreter[Final], writer: PrintWriter) extends AnnotatedTest(program, dependencyGraphInterpreters, true) { + lazy val pruningSupporter = new DependencyAnalysisPruningSupporter[Final](fullGraphInterpreter) + override def execute(): Unit = { if(!verifyTestSoundness()){ writer.println(s"!!!!!!!!!!!\nFailed to verify soundness of precision test $fileName\n") @@ -97,7 +100,7 @@ object DependencyAnalysisPrecisionBenchmark extends DependencyAnalysisTestFramew protected def pruneAndVerify(relevantLines: Set[Int], exportFileName: String): Boolean = { val crucialNodes = relevantLines.flatMap(line => fullGraphInterpreter.getNodesByLine(line)) - val (newProgram, pruningFactor) = fullGraphInterpreter.getPrunedProgram(crucialNodes, program) + val (newProgram, pruningFactor) = pruningSupporter.getPrunedProgram(crucialNodes, program) val result = frontend.verifier.verify(newProgram) // exportPrunedProgram(exportFileName, newProgram, pruningFactor, result) // can be used for debugging !result.isInstanceOf[verifier.Failure] diff --git a/src/test/scala/DependencyAnalysisTestFramework.scala b/src/test/scala/DependencyAnalysisTestFramework.scala index 4eb15c42a..f1d0c6b71 100644 --- a/src/test/scala/DependencyAnalysisTestFramework.scala +++ b/src/test/scala/DependencyAnalysisTestFramework.scala @@ -1,5 +1,6 @@ package viper.silicon.tests +import dependencyAnalysis.DependencyAnalysisPruningSupporter import viper.silicon.SiliconFrontend import viper.silicon.dependencyAnalysis._ import viper.silver.ast.utility.ViperStrategy @@ -81,6 +82,7 @@ trait DependencyAnalysisTestFramework { * Statements that are only required as a trigger need to be manually annotated with @trigger() by the user. */ class PruningTest(fileName: String, program: Program, fullGraphInterpreter: DependencyGraphInterpreter[Final]) { + lazy val pruningSupporter = new DependencyAnalysisPruningSupporter(fullGraphInterpreter) def execute(): Unit = { val triggerNodeLines = fullGraphInterpreter.getNodes.filter(node => node.getUserLevelRepresentation.contains("@trigger()")).flatMap(_.sourceInfo.getLineNumber) @@ -98,7 +100,7 @@ trait DependencyAnalysisTestFramework { val dependencies = fullGraphInterpreter.getAllNonInternalDependencies(relevantNodes.map(_.id)) val crucialNodes = relevantNodes ++ dependencies - val (newProgram, pruningFactor) = fullGraphInterpreter.getPrunedProgram(crucialNodes, program) + val (newProgram, pruningFactor) = pruningSupporter.getPrunedProgram(crucialNodes, program) val result = baselineFrontend.verifier.verify(newProgram) if(EXPORT_PRUNED_PROGRAMS) exportPrunedProgram(exportFileName, newProgram, pruningFactor, result) assert(!result.isInstanceOf[verifier.Failure], s"Failed to verify new program. ${result.transformedResult()}\n${newProgram.toString()}") @@ -114,6 +116,7 @@ trait DependencyAnalysisTestFramework { } class PrecisionEvaluation(filePrefix: String, fileName: String, program: Program, fullGraphInterpreter: DependencyGraphInterpreter[Final]) { + lazy val pruningSupporter = new DependencyAnalysisPruningSupporter(fullGraphInterpreter) protected val folderName = s"src/test/resources/precision_groundTruths/$filePrefix" def execute(): Unit = { @@ -154,7 +157,7 @@ trait DependencyAnalysisTestFramework { } } - val (minProgram, _) = fullGraphInterpreter.getPrunedProgram(bestDepSet ++ relevantNodes, program) + val (minProgram, _) = pruningSupporter.getPrunedProgram(bestDepSet ++ relevantNodes, program) exportPrunedProgram(s"${fileName}_${relevantLines.mkString("_")}.vpr", minProgram) val precision = minNumDeps.toDouble / reportedDependencies.size @@ -162,7 +165,7 @@ trait DependencyAnalysisTestFramework { } protected def pruneAndVerify(crucialNodes: Set[DependencyAnalysisNode]): Boolean = { - val (newProgram, _) = fullGraphInterpreter.getPrunedProgram(crucialNodes, program) + val (newProgram, _) = pruningSupporter.getPrunedProgram(crucialNodes, program) val result = baselineFrontend.verifier.verify(newProgram) !result.isInstanceOf[verifier.Failure] } diff --git a/src/test/scala/VerificationProgressRunner.scala b/src/test/scala/VerificationProgressRunner.scala index 3b8c297f9..b5b44dcbb 100644 --- a/src/test/scala/VerificationProgressRunner.scala +++ b/src/test/scala/VerificationProgressRunner.scala @@ -1,5 +1,6 @@ package viper.silicon.tests +import dependencyAnalysis.DependencyAnalysisProgressSupporter import viper.silicon.dependencyAnalysis.DependencyAnalysisReporter import viper.silver.ast.Program import viper.silver.frontend.SilFrontend @@ -60,7 +61,7 @@ object VerificationProgressRunner extends DependencyAnalysisTestFramework { val joinedDependencyGraphInterpreter = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].joinedDependencyGraphInterpreter - val (progressPeter, progressLea, _) = joinedDependencyGraphInterpreter.get.computeVerificationProgress() + val (progressPeter, progressLea, _) = new DependencyAnalysisProgressSupporter(joinedDependencyGraphInterpreter.get).computeVerificationProgress() writer.println(f"$fileName\t$progressPeter%.3f\t$progressLea%.3f\t${!hasFailures}") println(f"$fileName\t$progressPeter%.3f\t$progressLea%.3f\t${!hasFailures}") From d5f2fa52b4a6b203d3db98e3c92db56cd072e4ee Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 10 Apr 2026 09:09:29 +0200 Subject: [PATCH 424/474] refactor interpreter and user tool --- .../DependencyAnalysisDebugSupporter.scala | 44 -- .../DependencyAnalysisReporter.scala | 1 + .../DependencyAnalysisResult.scala | 1 + .../DependencyAnalysisUserTool.scala | 427 ------------------ .../DependencyAnalyzer.scala | 1 + .../UserLevelDependencyAnalysisNode.scala | 29 +- .../AbstractDependencyAnalysisCliTool.scala | 55 +++ ...chmarkDependencyAnalysisCliExtension.scala | 251 ++++++++++ .../DebugDependencyAnalysisCliExtension.scala | 49 ++ .../cliTool/DependencyAnalysisCliTool.scala | 194 ++++++++ .../DependencyGraphImporter.scala | 8 +- .../DependencyAnalysisProgressSupporter.scala | 17 +- .../DependencyAnalysisPruningSupporter.scala | 4 +- .../DependencyGraphInterpreter.scala | 3 +- src/main/scala/interfaces/Verification.scala | 3 +- .../scala/supporters/MethodSupporter.scala | 1 + .../functions/FunctionVerificationUnit.scala | 1 + .../scala/verifier/DefaultMainVerifier.scala | 4 +- ...DependencyAnalysisPrecisionBenchmark.scala | 2 +- .../DependencyAnalysisTestFramework.scala | 2 +- src/test/scala/DependencyAnalysisTests.scala | 1 + .../scala/VerificationProgressRunner.scala | 2 +- 22 files changed, 582 insertions(+), 518 deletions(-) delete mode 100644 src/main/scala/dependencyAnalysis/DependencyAnalysisDebugSupporter.scala delete mode 100644 src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala create mode 100644 src/main/scala/dependencyAnalysis/cliTool/AbstractDependencyAnalysisCliTool.scala create mode 100644 src/main/scala/dependencyAnalysis/cliTool/BenchmarkDependencyAnalysisCliExtension.scala create mode 100644 src/main/scala/dependencyAnalysis/cliTool/DebugDependencyAnalysisCliExtension.scala create mode 100644 src/main/scala/dependencyAnalysis/cliTool/DependencyAnalysisCliTool.scala rename src/main/scala/dependencyAnalysis/{ => cliTool}/DependencyGraphImporter.scala (96%) rename src/main/scala/dependencyAnalysis/{ => graphInterpretation}/DependencyAnalysisProgressSupporter.scala (94%) rename src/main/scala/dependencyAnalysis/{ => graphInterpretation}/DependencyAnalysisPruningSupporter.scala (98%) rename src/main/scala/dependencyAnalysis/{ => graphInterpretation}/DependencyGraphInterpreter.scala (98%) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisDebugSupporter.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisDebugSupporter.scala deleted file mode 100644 index ce07abbe3..000000000 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisDebugSupporter.scala +++ /dev/null @@ -1,44 +0,0 @@ -package dependencyAnalysis - -import viper.silicon.dependencyAnalysis.{DependencyAnalysisNode, DependencyGraphInterpreter, DependencyGraphState, GeneralAssumptionNode} -import viper.silver.dependencyAnalysis.AnalysisSourceInfo -import viper.silver.dependencyAnalysis.AssumptionType.AssumptionType - -import scala.io.StdIn.readLine - -class DependencyAnalysisDebugSupporter[T <: DependencyGraphState](interpreter: DependencyGraphInterpreter[T]) { - - def run(): Unit = { - try { - val input = readLine().split(" ") - input.head match { - case "assumptionTypes" if input.size == 1 => - println(getAssumptionTypesPerNode().mkString("\n")) - case "assumptionTypes" => - input.tail.foreach(s => println(s"$s: ${getAssumptionTypesByLine(s.toInt)}")) - case "lowLevelNodes" => - input.tail.foreach(s => println(s"$s:\n\t${getLowLevelNodesByLine(s.toInt).mkString("\n\t")}")) - case "q" | "quit" => return - } - }catch { - case _: Throwable => - println("Invalid input.") - } - run() - } - - def getAssumptionTypesByLine(line: Int): Set[AssumptionType] = { - interpreter.getNodesByLine(line).filter(_.isInstanceOf[GeneralAssumptionNode]).map(_.assumptionType) - } - - def getLowLevelNodesByLine(line: Int): Set[DependencyAnalysisNode] = { - interpreter.getNodesByLine(line) - } - - def getAssumptionTypesPerNode(): Map[AnalysisSourceInfo, Set[AssumptionType]] = - getAssumptionTypesPerNode(interpreter.getAssumptionNodes) - - def getAssumptionTypesPerNode(nodes: Set[DependencyAnalysisNode]): Map[AnalysisSourceInfo, Set[AssumptionType]] = - nodes.groupBy(_.sourceInfo).view.mapValues(_.map(_.assumptionType)).toMap - -} diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisReporter.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisReporter.scala index 62b537012..ca906ac06 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisReporter.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisReporter.scala @@ -1,5 +1,6 @@ package viper.silicon.dependencyAnalysis +import viper.silicon.dependencyAnalysis.graphInterpretation.DependencyGraphInterpreter import viper.silver.reporter.{Message, Reporter} case class DependencyAnalysisReporter(name: String = "dependencyAnalysis_reporter", path: String = "report.csv") extends Reporter { diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisResult.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisResult.scala index 16c40c260..494e4e1fa 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisResult.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisResult.scala @@ -1,5 +1,6 @@ package viper.silicon.dependencyAnalysis +import viper.silicon.dependencyAnalysis.graphInterpretation.DependencyGraphInterpreter import viper.silver.ast.Program import viper.silver.dependencyAnalysis.AbstractDependencyAnalysisResult diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala deleted file mode 100644 index 37e2656d1..000000000 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisUserTool.scala +++ /dev/null @@ -1,427 +0,0 @@ -package viper.silicon.dependencyAnalysis - -import dependencyAnalysis.{DependencyAnalysisDebugSupporter, DependencyAnalysisProgressSupporter, DependencyAnalysisPruningSupporter, UserLevelDependencyAnalysisNode} -import viper.silicon.interfaces.Failure -import viper.silver.ast -import viper.silver.ast.{AnnotationInfo, AnonymousDomainAxiom, Assume, Goto, If, Inhale, Label, LocalVarDeclStmt, MakeInfoPair, Method, NamedDomainAxiom, Package, Seqn, While} - -import java.io.{BufferedWriter, FileWriter, PrintWriter} -import java.nio.file.{Path, Paths} -import java.time.LocalDateTime -import java.time.format.DateTimeFormatter -import scala.annotation.tailrec -import scala.io.Source -import scala.io.StdIn.readLine -import scala.util.matching.Regex - -class DependencyAnalysisUserTool(fullGraphInterpreter: DependencyGraphInterpreter[Final], memberInterpreters: Seq[DependencyGraphInterpreter[IntraProcedural]], - program: ast.Program, verificationErrors: List[Failure]) { - - lazy val progressSupporter = new DependencyAnalysisProgressSupporter[Final](fullGraphInterpreter) - lazy val pruningSupporter = new DependencyAnalysisPruningSupporter[Final](fullGraphInterpreter) - - private val infoString = "Enter " + - "\n\t'dep [line numbers]' to print all dependencies of the given line numbers or" + - "\n\t'downDep [line numbers]' to print all dependents of the given line numbers or" + - "\n\t'hasDep [line numbers]' to print whether there exists any dependency between any pair of the given lines or" + - "\n\t'cov [members]' to print proof coverage of given member or" + - "\n\t'covL member [line numbers]' to print proof coverage of given lines of given member or" + - "\n\t'progress' to compute the verification progress of the program or" + - "\n\t'guide' to compute verification guidance or" + - "\n\t'prune [line numbers]' to prune the program with respect to the given line numbers and export the new program or" + - "\n\t'q' to quit" - - def run(): Unit = { - println("Dependency Analysis Tool started.") - println(infoString) - runInternal() - } - - def run(commandStr: String): Unit = { - handleUserInput(commandStr) - } - - @tailrec - private def runInternal(): Unit = { - try { - val userInput = readLine() - if (userInput.equalsIgnoreCase("q") || userInput.equalsIgnoreCase("quit")) { - return - } - if (userInput.nonEmpty) { - handleUserInput(userInput) - } else { - println(infoString) - } - }catch { - case e: Exception => println("Error:\n" + e.getMessage) - } - runInternal() - } - - private def handleUserInput(userInput: String): Unit = { - val inputParts = userInput.split(" ").toSeq - if (inputParts.nonEmpty) { - inputParts.head.toLowerCase match { - case "dep" => handleDependencyQuery(inputParts.tail.toSet) - case "downdep" => handleDependentsQuery(inputParts.tail.toSet) - case "coverage" | "cov" => handleProofCoverageQuery(inputParts.tail) - case "covlines" | "covl" => handleProofCoverageLineQuery(inputParts.tail) - case "progress" | "prog" => handleVerificationProgressQuery() - case "guidance" | "guide" => handleVerificationGuidanceQuery() - case "prune" => handlePruningRequest(inputParts.tail) - case "benchmark" => handleBenchmarkQuery() - case "precisioneval" => handlePrecisionEval(inputParts.tail) - case "annotate" => handleAnnotateQuery(inputParts.tail) - case "debug" => handleDebugQuery() - case "graphsize" => - if (inputParts.tail.isEmpty) { - handleGraphSizeQuery(fullGraphInterpreter) - } else { - memberInterpreters.filter(aa => aa.getMember.isDefined && - aa.getMember.exists { - case meth: Method => meth.body.isDefined && inputParts.tail.contains(meth.name); - case func: ast.Function => func.body.isDefined && inputParts.tail.contains(func.name); - case _ => false }) - .foreach(aa => handleGraphSizeQuery(aa)) - } - case _ => println("Invalid input."); println(infoString) - } - } else { - println("Invalid input."); println(infoString) - } - } - - private def handleGraphSizeQuery[T <: DependencyGraphState](interpreter: DependencyGraphInterpreter[T]): Unit = { - val allAssumptions = interpreter.getNonInternalAssumptionNodes - val assumptions = UserLevelDependencyAnalysisNode.from(allAssumptions) - val allAssertions = interpreter.getNonInternalAssertionNodes - val assertions = UserLevelDependencyAnalysisNode.from(allAssertions) - val nodes = UserLevelDependencyAnalysisNode.from(allAssertions.union(allAssumptions)) - println(s"#Assumptions = ${assumptions.size}") - println(s"#Assertions = ${assertions.size}") - println(s"#Nodes = ${nodes.size}") - println(s"#low-level Assumptions (non-internal) = ${allAssumptions.size}") - println(s"#low-level Assumptions (all) = ${interpreter.getAssumptionNodes.size}") - println(s"#low-level Assertions (non-internal) = ${allAssertions.size}") - println(s"#low-level Assertions (all) = ${interpreter.getAssertionNodes.size}") - println("Done.") - } - - private def handleProofCoverageQuery(memberNames: Seq[String]): Unit = { - println("Proof Coverage") - memberInterpreters.filter(aa => aa.getMember.isDefined && aa.getMember.exists { - case meth: Method => meth.body.isDefined && (memberNames.isEmpty || memberNames.contains(meth.name)) - case func: ast.Function => func.body.isDefined && (memberNames.isEmpty || memberNames.contains(func.name)) - case _ => false - }) - .foreach(aa => { - val ((coverage, uncoveredSources), time) = measureTime(aa.computeProofCoverage()) - println(s"${aa.getMember.map(_.name).getOrElse("")} (${time}ms)") - println(s"coverage: $coverage") - if (!coverage.equals(1.0)) - println(s"uncovered nodes:\n\t${uncoveredSources.mkString("\n\t")}") - println(s"#uncovered nodes:\n\t${uncoveredSources.size}") - }) - println("Done.") - } - - private def handleProofCoverageLineQuery(memberNames: Seq[String]): Unit = { - if(memberNames.isEmpty) return // TODO ake: invalid input handling - - println("Proof Coverage") - val lines = memberNames.tail.flatMap(_.toIntOption) - memberInterpreters.filter(aa => aa.getMember.isDefined && aa.getMember.exists { - case meth: Method => meth.body.isDefined && meth.name.equalsIgnoreCase(memberNames.head) - case func: ast.Function => func.body.isDefined && func.name.equalsIgnoreCase(memberNames.head) - case _ => false - }) - .foreach(aa => { - val ((coverage, uncoveredSources), time) = if(lines.nonEmpty){ - val assertions = lines flatMap aa.getNodesByLine - measureTime(aa.computeProofCoverage(assertions.toSet)) - }else{ - measureTime(aa.computeProofCoverage()) - } - println(s"${aa.getMember.map(_.name).getOrElse("")} (${time}ms)") - println(s"coverage: $coverage") - if (!coverage.equals(1.0)) - println(s"uncovered nodes:\n\t${uncoveredSources.mkString("\n\t")}") - println(s"#uncovered nodes:\n\t${uncoveredSources.size}") - }) - println("Done.") - } - - private def handleVerificationProgressQuery(): Unit = { - - val ((optProgressPeter, optProgressLea), optTime) = measureTime(progressSupporter.computeVerificationProgress()) - - println(s"Peter: $optProgressPeter; Lea: $optProgressLea") - println(s"Finished in ${optTime}ms") - } - - private def getSourceInfoString(nodes: Set[DependencyAnalysisNode]): String = { - UserLevelDependencyAnalysisNode.mkUserLevelString(nodes, "\n\t") - } - - private def getQueriedNodesFromInput(inputs: Set[String]): Set[DependencyAnalysisNode] = { - inputs flatMap (input => { - val parts = input.split("@") - if(parts.size == 2) - parts(1).toIntOption.map(fullGraphInterpreter.getNodesByPosition(parts(0), _)).getOrElse(Set.empty) - else if(parts.size == 1){ - parts(0).toIntOption map fullGraphInterpreter.getNodesByLine getOrElse Set.empty - }else{ - Set.empty - } - }) - } - - private def handleDependencyQuery(inputs: Set[String]): Unit = { - val queriedNodes = getQueriedNodesFromInput(inputs) - val queriedAssertions = queriedNodes.filter(node => node.isInstanceOf[GeneralAssertionNode]) - - val (directDependencies, timeDirect) = measureTime[Set[DependencyAnalysisNode]](fullGraphInterpreter.getDirectDependencies(queriedAssertions.map(_.id))) - val (allDependencies, timeAll) = measureTime[Set[DependencyAnalysisNode]](fullGraphInterpreter.getAllNonInternalDependencies(queriedAssertions.map(_.id))) - val (allDependenciesWithoutInfeasibility, timeWithoutInfeasibility) = measureTime[Set[DependencyAnalysisNode]](fullGraphInterpreter.getAllNonInternalDependencies(queriedAssertions.map(_.id), includeInfeasibilityNodes=false)) - val (explicitDependencies, timeExplicit) = measureTime[Set[DependencyAnalysisNode]](fullGraphInterpreter.getAllExplicitDependencies(queriedAssertions.map(_.id))) - - println(s"Queried:\n\t${getSourceInfoString(queriedNodes)}") - - println(s"\nDirect Dependencies (${timeDirect}ms):\n\t${getSourceInfoString(directDependencies.diff(queriedNodes))}") - println(s"\nAll Dependencies (${timeAll}ms):\n\t${getSourceInfoString(allDependencies.diff(queriedNodes))}") - println(s"\nDependencies without infeasibility (${timeWithoutInfeasibility}ms):\n\t${getSourceInfoString(allDependenciesWithoutInfeasibility.diff(queriedNodes))}") - println(s"\nExplicit Dependencies (${timeExplicit}ms):\n\t${getSourceInfoString(explicitDependencies.diff(queriedNodes))}") - - if(queriedAssertions.exists(_.asInstanceOf[GeneralAssertionNode].hasFailed)) println("\nQueried assertions (partially) FAILED!\n") - println("Done.") - } - - private def handlePrecisionEval(inputs: Seq[String]): Unit = { - val labelPattern: Regex = """@label\(\s*("?)([^")\s]+)\1\s*\)""".r - val header = "Assertion Label,Sound?,#True Dependencies,#Reported Dependencies,#False-Positives,Call Graph Size,Runtime,Noise" - - def readFile(path: String): Map[String, Set[String]] = { - val src = Source.fromFile(path) - try { - src.getLines() - .filter(_.trim.nonEmpty) // skip empty lines - .map { line => - val Array(left, right) = line.split("=", 2) // split into key and rest - val key = left.trim - val values = right.split(",").map(_.trim).toSet - key -> values - } - .toMap - } finally { - src.close() - } - } - - def addOutput(bw: BufferedWriter, output: String): Unit = { - bw.write(output) - bw.newLine() - println(output) - } - - def evalSingleAssertion(assertionLabel: String, groundTruthLabels: Set[String], callGraphLabels: Set[String], bw: BufferedWriter): Unit = { - val startAnalysis = System.nanoTime() - val queriedAssertions = fullGraphInterpreter.getNodesByLabel(assertionLabel) - val allDependencies = fullGraphInterpreter.getAllNonInternalDependencies(queriedAssertions.map(_.id)) - val sourceDependencies = UserLevelDependencyAnalysisNode.from(allDependencies).getSourceSet().diff(UserLevelDependencyAnalysisNode.from(queriedAssertions).getSourceSet()) - - val endAnalysis = System.nanoTime() - val durationMs = (endAnalysis - startAnalysis) / 1e6 - - val labelsInReportedDeps: Set[Set[String]] = sourceDependencies.map(node => labelPattern.findAllMatchIn(node.toString).map(_.group(2)).toSet) - - val actualLabelInReportedDeps = labelsInReportedDeps.filter(_.size == 1).flatten - val noise = labelsInReportedDeps.filterNot(_.size == 1) - - val isSound = groundTruthLabels.diff(actualLabelInReportedDeps).isEmpty - val imprecise = actualLabelInReportedDeps.diff(groundTruthLabels) - - assert(!isSound || groundTruthLabels.size + imprecise.size == actualLabelInReportedDeps.size, s"Imprecision calculation is wrong.") - assert(actualLabelInReportedDeps.size <= callGraphLabels.size, "Call graph size is smaller than reported dependencies.") - - addOutput(bw, s"$assertionLabel,${if(isSound) "YES" else "NO"},${groundTruthLabels.size},${actualLabelInReportedDeps.size},${imprecise.size},${callGraphLabels.size},${durationMs}ms,${noise.size}") - -// println(s"Queried:\n\t${getSourceInfoString(queriedAssertions)}") -// println(s"\nAll Dependencies (${timeAll}ms):\n\t$sourceDependenciesString") -// -// if(queriedAssertions.exists(_.asInstanceOf[GeneralAssertionNode].hasFailed)) println("\nQueried assertions (partially) FAILED!\n") - } - - assert(inputs.size == 1) - - val dir: Path = Paths.get(inputs.head) - - val pathToGroundTruth = dir.resolve("ground-truth.txt") - val pathToCallGraphs = dir.resolve("call-graphs.txt") - - val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HHmmss") - val timestamp = LocalDateTime.now().format(formatter) - - val output: Path = dir.resolve( s"result_$timestamp.csv") - val bw = new BufferedWriter(new FileWriter(output.toUri.getPath)) - - try { - val groundTruths = readFile(pathToGroundTruth.toUri.getPath) - val callGraphs = readFile(pathToCallGraphs.toUri.getPath) - addOutput(bw, header) - callGraphs.foreach { case (assertionLabel, callGraphLabels) => evalSingleAssertion(assertionLabel, groundTruths(assertionLabel), callGraphLabels, bw) } - - bw.close() - println("Done.") - }catch { - case e: Throwable => println(s"Failed. ${e.getMessage}") - }finally { - bw.close() - } - } - - private def handleAnnotateQuery(inputs: Seq[String]): Unit = { - var n = 0 - def nextN: Int = { - n = n + 1 - n - } - - def newInfo(info: ast.Info): ast.Info = MakeInfoPair(AnnotationInfo(Map(("label", Seq(s"L$nextN")))), info) - - - def annotateConjungts(exp: ast.Exp): ast.Exp = { - exp match { - case ast.And(l, r) => ast.And(annotateConjungts(l), annotateConjungts(r))(exp.pos, exp.info, exp.errT) - case _ => annotateExp(exp) - } - } - - def annotateExp(exp: ast.Exp): ast.Exp = exp.withMeta((exp.pos, newInfo(exp.info), exp.errT)) - - def annotateSeqn(seqn: ast.Seqn):ast.Seqn = Seqn(seqn.ss.map(annotateStmt), seqn.scopedSeqnDeclarations)(seqn.pos, seqn.info, seqn.errT) - - def annotateStmt(stmt: ast.Stmt): ast.Stmt = { - stmt match { - case Inhale(exp) => Inhale(annotateConjungts(exp))(exp.pos, exp.info, exp.errT) - case Assume(exp) => Assume(annotateConjungts(exp))(exp.pos, exp.info, exp.errT) - case seqn: Seqn => annotateSeqn(seqn) - case If(cond, thn, els) => If(annotateExp(cond), annotateSeqn(thn), annotateSeqn(els))(stmt.pos, stmt.info, stmt.errT) - case While(cond, invs, body) => While(annotateExp(cond), invs.map(annotateConjungts), annotateSeqn(body))(stmt.pos, stmt.info, stmt.errT) - case Label(name, invs) => Label(name, invs.map(annotateConjungts))(stmt.pos, stmt.info, stmt.errT) - case _: Goto | _: LocalVarDeclStmt => stmt - case Package(wand, proofScript) => Package(wand, annotateSeqn(proofScript))(stmt.pos, newInfo(stmt.info), stmt.errT) - case _ => stmt.withMeta((stmt.pos, newInfo(stmt.info), stmt.errT)) - } - } - - def annotateDomain(domain: ast.Domain): ast.Domain = { - def annotateAxiom(axiom: ast.DomainAxiom): ast.DomainAxiom = axiom match { - case NamedDomainAxiom(name, exp) => NamedDomainAxiom(name, annotateExp(exp))(axiom.pos, axiom.info, axiom.domainName, axiom.errT) - case AnonymousDomainAxiom(exp) => AnonymousDomainAxiom(annotateExp(exp))(axiom.pos, axiom.info, axiom.domainName, axiom.errT) - } - domain.copy(axioms = domain.axioms.map(annotateAxiom))(domain.pos, domain.info, domain.errT) - } - - def annotateFunction(function: ast.Function): ast.Function = - function.copy(pres=function.pres.map(annotateConjungts), posts=function.posts.map(annotateConjungts), body=function.body.map(annotateExp))(function.pos, function.info, function.errT) - - def annotateMethod(method: ast.Method): ast.Method = - method.copy(pres=method.pres.map(annotateConjungts), posts=method.posts.map(annotateConjungts), body=method.body.map(annotateSeqn))(method.pos, method.info, method.errT) - - val newProgram: ast.Program = program.copy(domains=program.domains.map(annotateDomain), functions=program.functions.map(annotateFunction), - methods=program.methods.map(annotateMethod))(program.pos, program.info, program.errT) - - val writer = new PrintWriter(inputs.head) - writer.println(newProgram.toString()) - writer.close() - println("Done.") - - } - - - private def handleDependentsQuery(inputs: Set[String]): Unit = { - - val queriedNodes = getQueriedNodesFromInput(inputs).intersect(fullGraphInterpreter.getNonInternalAssumptionNodes) - - val (allDependents, timeAll) = measureTime[Set[DependencyAnalysisNode]](fullGraphInterpreter.getAllNonInternalDependents(queriedNodes.map(_.id))) - val (dependentsWithoutInfeasibility, timeWithoutInfeasibility) = measureTime[Set[DependencyAnalysisNode]](fullGraphInterpreter.getAllNonInternalDependents(queriedNodes.map(_.id), includeInfeasibilityNodes=false)) - val (explicitDependents, timeExplicit) = measureTime[Set[DependencyAnalysisNode]](fullGraphInterpreter.getAllExplicitDependents(queriedNodes.map(_.id))) - - println(s"Queried:\n\t${getSourceInfoString(queriedNodes)}") - - println(s"\nAll Dependents (${timeAll}ms):\n\t${getSourceInfoString(allDependents)}") - println(s"\nDependents without infeasibility (${timeWithoutInfeasibility}ms):\n\t${getSourceInfoString(dependentsWithoutInfeasibility)}") - println(s"\nExplicit Dependents (${timeExplicit}ms):\n\t${getSourceInfoString(explicitDependents)}") - println("Done.") - } - - private def measureTime[T](function: => T): (T, Double) = { - val startAnalysis = System.nanoTime() - val res = function - val endAnalysis = System.nanoTime() - val durationMs = (endAnalysis - startAnalysis) / 1e6 - (res, durationMs) - } - - private def handlePruningRequest(inputs: Seq[String]): Unit = { - println("exportFileName: ") - val exportFileName = readLine() - val queriedNodes = getQueriedNodesFromInput(inputs.toSet) - pruningSupporter.pruneProgramAndExport(queriedNodes, program, exportFileName) - println("Done.") - } - - private def handleBenchmarkQuery(): Unit = { - val N = 12 - var check = true - println("Result file name: ") - val exportFileName = readLine() - val writer = new PrintWriter(exportFileName) - writer.println("queried line,#lowLevelDeps,#deps,runtimes [ms]") - - while(check){ - println("enter line number(s) for query or 'q' to quit") - val userInput = readLine() - if(userInput.equalsIgnoreCase("q")){ - println("Quit.") - check = false - }else{ - val inputs = userInput.split(" ").toSet - - val queriedNodes = getQueriedNodesFromInput(inputs) - var allTimes = Seq.empty[Double] - var numDeps = 0 - var numLowLevelDeps = 0 - - for (_ <- 0 to N) { - val (allDependencies, time) = measureTime[Set[DependencyAnalysisNode]](fullGraphInterpreter.getAllNonInternalDependencies(queriedNodes.map(_.id))) - allTimes = allTimes :+ time - numLowLevelDeps = allDependencies.size - numDeps = UserLevelDependencyAnalysisNode.from(allDependencies).size - } - - writer.println(s"$userInput,$numLowLevelDeps,$numDeps,${allTimes.mkString(",")}") - println(s"Avg: ${allTimes.sum/allTimes.size}") - } - } - - writer.close() - } - - private def handleVerificationGuidanceQuery(): Unit = { - - val assumptionRanking = progressSupporter.computeAssumptionRanking().filter(_._2 > 0.0) - println(s"Assumptions/unverified assertions and the number of dependents:\n\t${assumptionRanking.mkString("\n\t")}\n") - - println("Uncovered source code per method: ") - val memberCoverageRanking = memberInterpreters.filter(mInterpreter => mInterpreter.getMember.isDefined && mInterpreter.getMember.get.isInstanceOf[Method]) - .map(mInterpreter => (mInterpreter.getMember.get.name, new DependencyAnalysisProgressSupporter(mInterpreter).computeUncoveredStatements())) - .toList.filter(_._2 > 0).sortBy(_._2).reverse - println(s"\nMethods and the number of uncovered statements:\n\t${memberCoverageRanking.mkString("\n\t")}\n") - } - - private def handleDebugQuery(): Unit = { - new DependencyAnalysisDebugSupporter(fullGraphInterpreter).run() - } -} diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index 765bcb00a..069daa3cd 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -1,5 +1,6 @@ package viper.silicon.dependencyAnalysis +import viper.silicon.dependencyAnalysis.graphInterpretation.DependencyGraphInterpreter import viper.silicon.interfaces.state.{Chunk, GeneralChunk} import viper.silicon.state.terms.{NoPerm, _} import viper.silicon.verifier.Verifier diff --git a/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala b/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala index 52aaa937d..6d67fd14d 100644 --- a/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala +++ b/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala @@ -3,8 +3,8 @@ package dependencyAnalysis import viper.silicon.dependencyAnalysis._ import viper.silicon.state.terms.{And, Term} import viper.silver.ast.Position -import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, StringAnalysisSourceInfo} import viper.silver.dependencyAnalysis.AssumptionType.AssumptionType +import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, StringAnalysisSourceInfo} object UserLevelDependencyAnalysisNode { @@ -17,36 +17,12 @@ object UserLevelDependencyAnalysisNode { res } - def extractExplicitAssumptionNodes(nodes: Set[UserLevelDependencyAnalysisNode]): Set[UserLevelDependencyAnalysisNode] = { - extractByAssumptionType(nodes, AssumptionType.explicitAssumptionTypes) - } - - def extractNonExplicitAssumptionNodes(nodes: Set[UserLevelDependencyAnalysisNode]): Set[UserLevelDependencyAnalysisNode] = { - nodes.diff(extractExplicitAssumptionNodes(nodes)) - } - - def extractVerificationAnnotationNodes(nodes: Set[UserLevelDependencyAnalysisNode]): Set[UserLevelDependencyAnalysisNode] = { - extractByAssumptionType(nodes, AssumptionType.verificationAnnotationTypes) - } - - def extractSourceCodeNodes(nodes: Set[UserLevelDependencyAnalysisNode]): Set[UserLevelDependencyAnalysisNode] = { - nodes.diff(extractExplicitAssumptionNodes(nodes)).diff(extractVerificationAnnotationNodes(nodes)) - } - def extractByAssumptionType(nodes: Set[UserLevelDependencyAnalysisNode], assumptionTypes: Set[AssumptionType]): Set[UserLevelDependencyAnalysisNode] = { nodes.filter(node => assumptionTypes.intersect(node.assumptionTypes).nonEmpty) } - def extractByAssertionType(nodes: Set[UserLevelDependencyAnalysisNode], assertionTypes: Set[AssumptionType]): Set[UserLevelDependencyAnalysisNode] = { - nodes.filter(node => assertionTypes.intersect(node.assertionTypes).nonEmpty) - } - - def mkString(nodes: Set[UserLevelDependencyAnalysisNode], sep: String = "\n"): String = { - nodes.toList.sortBy(n => (n.source.getLineNumber, n.source.toString)).mkString(sep) - } - def mkUserLevelString(nodes: Set[DependencyAnalysisNode], sep: String = "\n"): String = { - mkString(from(nodes), sep) + from(nodes).toList.sortBy(n => (n.source.getLineNumber, n.source.toString)).mkString(sep) } implicit class SetNodeOps(private val left: Set[UserLevelDependencyAnalysisNode]) extends AnyVal { @@ -81,7 +57,6 @@ case class UserLevelDependencyAnalysisNode(source: AnalysisSourceInfo, lowerLeve def groupingCondition: (String, Position) = (source.toString, position) - } case class CompactUserLevelDependencyAnalysisNode(source: AnalysisSourceInfo, assumptionTypes: Set[AssumptionType], assertionTypes: Set[AssumptionType], hasFailures: Boolean) { diff --git a/src/main/scala/dependencyAnalysis/cliTool/AbstractDependencyAnalysisCliTool.scala b/src/main/scala/dependencyAnalysis/cliTool/AbstractDependencyAnalysisCliTool.scala new file mode 100644 index 000000000..a63c3666e --- /dev/null +++ b/src/main/scala/dependencyAnalysis/cliTool/AbstractDependencyAnalysisCliTool.scala @@ -0,0 +1,55 @@ +package dependencyAnalysis.cliTool + +import dependencyAnalysis.UserLevelDependencyAnalysisNode +import viper.silicon.dependencyAnalysis.graphInterpretation.DependencyGraphInterpreter +import viper.silicon.dependencyAnalysis.{DependencyAnalysisNode, Final} + +trait AbstractDependencyAnalysisCliTool { + val interpreter: DependencyGraphInterpreter[Final] + + protected def getSourceInfoString(nodes: Set[DependencyAnalysisNode]): String = { + UserLevelDependencyAnalysisNode.mkUserLevelString(nodes, "\n\t") + } + + protected def getQueriedNodesFromInput(inputs: Set[String]): Set[DependencyAnalysisNode] = { + inputs flatMap (input => { + val parts = input.split("@") + if(parts.size == 2) + parts(1).toIntOption.map(interpreter.getNodesByPosition(parts(0), _)).getOrElse(Set.empty) + else if(parts.size == 1){ + parts(0).toIntOption map interpreter.getNodesByLine getOrElse Set.empty + }else{ + Set.empty + } + }) + } + + protected def measureTime[T](function: => T): (T, Double) = { + val startAnalysis = System.nanoTime() + val res = function + val endAnalysis = System.nanoTime() + val durationMs = (endAnalysis - startAnalysis) / 1e6 + (res, durationMs) + } +} + + +trait DependencyAnalysisCliToolExtension extends AbstractDependencyAnalysisCliTool { + val name: String + val commands: List[DependencyAnalysisCliCommand] + + def getInfoString(separator: String): String = s"$name$separator\t${commands.map(_.description).mkString(s"$separator\t")}" + + def visit(inputs: Seq[String]): Unit = commands foreach (_.visit(inputs)) +} + +trait DependencyAnalysisCliCommand { + val cmdName: String + val cmd: Seq[String] => Unit + val description: String + + def accept(inputs: Seq[String]): Boolean = inputs.nonEmpty && inputs.head.equals(cmdName) + + def visit(inputs: Seq[String]): Unit = if(accept(inputs)) cmd(inputs.tail) + +} diff --git a/src/main/scala/dependencyAnalysis/cliTool/BenchmarkDependencyAnalysisCliExtension.scala b/src/main/scala/dependencyAnalysis/cliTool/BenchmarkDependencyAnalysisCliExtension.scala new file mode 100644 index 000000000..202eb7cee --- /dev/null +++ b/src/main/scala/dependencyAnalysis/cliTool/BenchmarkDependencyAnalysisCliExtension.scala @@ -0,0 +1,251 @@ +package viper.silicon.dependencyAnalysis.cliTool + +import dependencyAnalysis.UserLevelDependencyAnalysisNode +import dependencyAnalysis.cliTool.{DependencyAnalysisCliCommand, DependencyAnalysisCliToolExtension} +import viper.silicon.dependencyAnalysis.graphInterpretation.DependencyGraphInterpreter +import viper.silicon.dependencyAnalysis.{DependencyAnalysisNode, Final} +import viper.silver.ast +import viper.silver.ast.{AnnotationInfo, AnonymousDomainAxiom, Assume, Goto, If, Inhale, Label, LocalVarDeclStmt, MakeInfoPair, NamedDomainAxiom, Package, Seqn, While} + +import java.io.{BufferedWriter, FileWriter, PrintWriter} +import java.nio.file.{Path, Paths} +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter +import scala.io.Source +import scala.io.StdIn.readLine +import scala.util.matching.Regex + +class BenchmarkDependencyAnalysisCliExtension(override val interpreter: DependencyGraphInterpreter[Final], program: ast.Program) extends DependencyAnalysisCliToolExtension { + + override val name: String = "Benchmark Features" + override val commands: List[DependencyAnalysisCliCommand] = List( + new PerformanceBenchmarkCommand, + new GraphSizeCommand, + new AnnotateProgramCommand, + new PrecisionEvaluationCommand + ) + + class PerformanceBenchmarkCommand extends DependencyAnalysisCliCommand { + override val cmdName: String = "benchmark" + override val cmd: Seq[String] => Unit = _ => handleBenchmarkQuery() + override val description: String = s"'$cmdName' to run the performance benchmark" + + private def handleBenchmarkQuery(): Unit = { + val N = 12 + var check = true + println("Result file name: ") + val exportFileName = readLine() + val writer = new PrintWriter(exportFileName) + writer.println("queried line,#lowLevelDeps,#deps,runtimes [ms]") + + while(check){ + println("enter line number(s) for query or 'q' to quit") + val userInput = readLine() + if(userInput.equalsIgnoreCase("q")){ + println("Quit.") + check = false + }else{ + val inputs = userInput.split(" ").toSet + + val queriedNodes = getQueriedNodesFromInput(inputs) + var allTimes = Seq.empty[Double] + var numDeps = 0 + var numLowLevelDeps = 0 + + for (_ <- 0 to N) { + val (allDependencies, time) = measureTime[Set[DependencyAnalysisNode]](interpreter.getAllNonInternalDependencies(queriedNodes.map(_.id))) + allTimes = allTimes :+ time + numLowLevelDeps = allDependencies.size + numDeps = UserLevelDependencyAnalysisNode.from(allDependencies).size + } + + writer.println(s"$userInput,$numLowLevelDeps,$numDeps,${allTimes.mkString(",")}") + println(s"Avg: ${allTimes.sum/allTimes.size}") + } + } + + writer.close() + } + } + + class PrecisionEvaluationCommand extends DependencyAnalysisCliCommand { + override val cmdName: String = "precisionEval" + override val cmd: Seq[String] => Unit = inputs => handlePrecisionEval(inputs.head) + override val description: String = s"'$cmdName [folder]' to run precision evaluation with respect to the ground truth and call graphs specified in the provided folder" + + override def accept(inputs: Seq[String]): Boolean = super.accept(inputs) && inputs.size >= 2 + + + private def handlePrecisionEval(pathToTestFolder: String): Unit = { + val labelPattern: Regex = """@label\(\s*("?)([^")\s]+)\1\s*\)""".r + val header = "Assertion Label,Sound?,#True Dependencies,#Reported Dependencies,#False-Positives,Call Graph Size,Runtime,Noise" + + def readFile(path: String): Map[String, Set[String]] = { + val src = Source.fromFile(path) + try { + src.getLines() + .filter(_.trim.nonEmpty) // skip empty lines + .map { line => + val Array(left, right) = line.split("=", 2) // split into key and rest + val key = left.trim + val values = right.split(",").map(_.trim).toSet + key -> values + } + .toMap + } finally { + src.close() + } + } + + def addOutput(bw: BufferedWriter, output: String): Unit = { + bw.write(output) + bw.newLine() + println(output) + } + + def evalSingleAssertion(assertionLabel: String, groundTruthLabels: Set[String], callGraphLabels: Set[String], bw: BufferedWriter): Unit = { + val startAnalysis = System.nanoTime() + val queriedAssertions = interpreter.getNodesByLabel(assertionLabel) + val allDependencies = interpreter.getAllNonInternalDependencies(queriedAssertions.map(_.id)) + val sourceDependencies = UserLevelDependencyAnalysisNode.from(allDependencies).getSourceSet().diff(UserLevelDependencyAnalysisNode.from(queriedAssertions).getSourceSet()) + + val endAnalysis = System.nanoTime() + val durationMs = (endAnalysis - startAnalysis) / 1e6 + + val labelsInReportedDeps: Set[Set[String]] = sourceDependencies.map(node => labelPattern.findAllMatchIn(node.toString).map(_.group(2)).toSet) + + val actualLabelInReportedDeps = labelsInReportedDeps.filter(_.size == 1).flatten + val noise = labelsInReportedDeps.filterNot(_.size == 1) + + val isSound = groundTruthLabels.diff(actualLabelInReportedDeps).isEmpty + val imprecise = actualLabelInReportedDeps.diff(groundTruthLabels) + + assert(!isSound || groundTruthLabels.size + imprecise.size == actualLabelInReportedDeps.size, s"Imprecision calculation is wrong.") + assert(actualLabelInReportedDeps.size <= callGraphLabels.size, "Call graph size is smaller than reported dependencies.") + + addOutput(bw, s"$assertionLabel,${if (isSound) "YES" else "NO"},${groundTruthLabels.size},${actualLabelInReportedDeps.size},${imprecise.size},${callGraphLabels.size},${durationMs}ms,${noise.size}") + + // println(s"Queried:\n\t${getSourceInfoString(queriedAssertions)}") + // println(s"\nAll Dependencies (${timeAll}ms):\n\t$sourceDependenciesString") + // + // if(queriedAssertions.exists(_.asInstanceOf[GeneralAssertionNode].hasFailed)) println("\nQueried assertions (partially) FAILED!\n") + } + + val dir: Path = Paths.get(pathToTestFolder) + + val pathToGroundTruth = dir.resolve("ground-truth.txt") + val pathToCallGraphs = dir.resolve("call-graphs.txt") + + val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HHmmss") + val timestamp = LocalDateTime.now().format(formatter) + + val output: Path = dir.resolve(s"result_$timestamp.csv") + val bw = new BufferedWriter(new FileWriter(output.toUri.getPath)) + + try { + val groundTruths = readFile(pathToGroundTruth.toUri.getPath) + val callGraphs = readFile(pathToCallGraphs.toUri.getPath) + addOutput(bw, header) + callGraphs.foreach { case (assertionLabel, callGraphLabels) => evalSingleAssertion(assertionLabel, groundTruths(assertionLabel), callGraphLabels, bw) } + + bw.close() + println("Done.") + } catch { + case e: Throwable => println(s"Failed. ${e.getMessage}") + } finally { + bw.close() + } + } + } + + class AnnotateProgramCommand extends DependencyAnalysisCliCommand { + override val cmdName: String = "annotate" + override val cmd: Seq[String] => Unit = inputs => handleAnnotateQuery(inputs.head) + override val description: String = s"'$cmdName [file name] to annotate each statement with a label and write the resulting program to the provided file" + + override def accept(inputs: Seq[String]): Boolean = super.accept(inputs) && inputs.size >= 2 + + + private def handleAnnotateQuery(resultFileName: String): Unit = { + var n = 0 + def nextN: Int = { + n = n + 1 + n + } + + def newInfo(info: ast.Info): ast.Info = MakeInfoPair(AnnotationInfo(Map(("label", Seq(s"L$nextN")))), info) + + + def annotateConjungts(exp: ast.Exp): ast.Exp = { + exp match { + case ast.And(l, r) => ast.And(annotateConjungts(l), annotateConjungts(r))(exp.pos, exp.info, exp.errT) + case _ => annotateExp(exp) + } + } + + def annotateExp(exp: ast.Exp): ast.Exp = exp.withMeta((exp.pos, newInfo(exp.info), exp.errT)) + + def annotateSeqn(seqn: ast.Seqn):ast.Seqn = Seqn(seqn.ss.map(annotateStmt), seqn.scopedSeqnDeclarations)(seqn.pos, seqn.info, seqn.errT) + + def annotateStmt(stmt: ast.Stmt): ast.Stmt = { + stmt match { + case Inhale(exp) => Inhale(annotateConjungts(exp))(exp.pos, exp.info, exp.errT) + case Assume(exp) => Assume(annotateConjungts(exp))(exp.pos, exp.info, exp.errT) + case seqn: Seqn => annotateSeqn(seqn) + case If(cond, thn, els) => If(annotateExp(cond), annotateSeqn(thn), annotateSeqn(els))(stmt.pos, stmt.info, stmt.errT) + case While(cond, invs, body) => While(annotateExp(cond), invs.map(annotateConjungts), annotateSeqn(body))(stmt.pos, stmt.info, stmt.errT) + case Label(name, invs) => Label(name, invs.map(annotateConjungts))(stmt.pos, stmt.info, stmt.errT) + case _: Goto | _: LocalVarDeclStmt => stmt + case Package(wand, proofScript) => Package(wand, annotateSeqn(proofScript))(stmt.pos, newInfo(stmt.info), stmt.errT) + case _ => stmt.withMeta((stmt.pos, newInfo(stmt.info), stmt.errT)) + } + } + + def annotateDomain(domain: ast.Domain): ast.Domain = { + def annotateAxiom(axiom: ast.DomainAxiom): ast.DomainAxiom = axiom match { + case NamedDomainAxiom(name, exp) => NamedDomainAxiom(name, annotateExp(exp))(axiom.pos, axiom.info, axiom.domainName, axiom.errT) + case AnonymousDomainAxiom(exp) => AnonymousDomainAxiom(annotateExp(exp))(axiom.pos, axiom.info, axiom.domainName, axiom.errT) + } + domain.copy(axioms = domain.axioms.map(annotateAxiom))(domain.pos, domain.info, domain.errT) + } + + def annotateFunction(function: ast.Function): ast.Function = + function.copy(pres=function.pres.map(annotateConjungts), posts=function.posts.map(annotateConjungts), body=function.body.map(annotateExp))(function.pos, function.info, function.errT) + + def annotateMethod(method: ast.Method): ast.Method = + method.copy(pres=method.pres.map(annotateConjungts), posts=method.posts.map(annotateConjungts), body=method.body.map(annotateSeqn))(method.pos, method.info, method.errT) + + val newProgram: ast.Program = program.copy(domains=program.domains.map(annotateDomain), functions=program.functions.map(annotateFunction), + methods=program.methods.map(annotateMethod))(program.pos, program.info, program.errT) + + val writer = new PrintWriter(resultFileName) + writer.println(newProgram.toString()) + writer.close() + println("Done.") + + } + } + + class GraphSizeCommand extends DependencyAnalysisCliCommand { + override val cmdName: String = "graphSize" + override val cmd: Seq[String] => Unit = _ => handleGraphSizeQuery() + override val description: String = s"'$cmdName' to print the size of the graph" + + private def handleGraphSizeQuery(): Unit = { + val allAssumptions = interpreter.getNonInternalAssumptionNodes + val assumptions = UserLevelDependencyAnalysisNode.from(allAssumptions) + val allAssertions = interpreter.getNonInternalAssertionNodes + val assertions = UserLevelDependencyAnalysisNode.from(allAssertions) + val nodes = UserLevelDependencyAnalysisNode.from(allAssertions.union(allAssumptions)) + println(s"#Assumptions = ${assumptions.size}") + println(s"#Assertions = ${assertions.size}") + println(s"#Nodes = ${nodes.size}") + println(s"#low-level Assumptions (non-internal) = ${allAssumptions.size}") + println(s"#low-level Assumptions (all) = ${interpreter.getAssumptionNodes.size}") + println(s"#low-level Assertions (non-internal) = ${allAssertions.size}") + println(s"#low-level Assertions (all) = ${interpreter.getAssertionNodes.size}") + println("Done.") + } + } + +} diff --git a/src/main/scala/dependencyAnalysis/cliTool/DebugDependencyAnalysisCliExtension.scala b/src/main/scala/dependencyAnalysis/cliTool/DebugDependencyAnalysisCliExtension.scala new file mode 100644 index 000000000..7b5d49706 --- /dev/null +++ b/src/main/scala/dependencyAnalysis/cliTool/DebugDependencyAnalysisCliExtension.scala @@ -0,0 +1,49 @@ +package viper.silicon.dependencyAnalysis.cliTool + +import dependencyAnalysis.cliTool.{DependencyAnalysisCliCommand, DependencyAnalysisCliToolExtension} +import viper.silicon.dependencyAnalysis._ +import viper.silicon.dependencyAnalysis.graphInterpretation.DependencyGraphInterpreter +import viper.silver.dependencyAnalysis.AnalysisSourceInfo +import viper.silver.dependencyAnalysis.AssumptionType.AssumptionType + +class DebugDependencyAnalysisCliExtension(override val interpreter: DependencyGraphInterpreter[Final]) extends DependencyAnalysisCliToolExtension{ + override val name: String = "Debug Features" + override val commands: List[DependencyAnalysisCliCommand] = List( + new AssumptionTypesCommand, + new LowLevelNodesCommand + ) + + class AssumptionTypesCommand extends DependencyAnalysisCliCommand { + override val cmdName: String = "assumptionTypes" + override val cmd: Seq[String] => Unit = { inputs => + if(inputs.isEmpty) + println(getAssumptionTypesPerNode().mkString("\n")) + else + inputs.flatMap(_.toIntOption).foreach(i => println(s"$i: ${getAssumptionTypesByLine(i)}")) + } + override val description: String = s"'$cmdName [line numbers]' to print the assumption types of all nodes or just the provided lines" + + def getAssumptionTypesByLine(line: Int): Set[AssumptionType] = { + interpreter.getNodesByLine(line).filter(_.isInstanceOf[GeneralAssumptionNode]).map(_.assumptionType) + } + + def getAssumptionTypesPerNode(): Map[AnalysisSourceInfo, Set[AssumptionType]] = + getAssumptionTypesPerNode(interpreter.getAssumptionNodes) + + def getAssumptionTypesPerNode(nodes: Set[DependencyAnalysisNode]): Map[AnalysisSourceInfo, Set[AssumptionType]] = + nodes.groupBy(_.sourceInfo).view.mapValues(_.map(_.assumptionType)).toMap + } + + class LowLevelNodesCommand extends DependencyAnalysisCliCommand { + override val cmdName: String = "lowLevelNodes" + override val cmd: Seq[String] => Unit = inputs => + inputs.flatMap(_.toIntOption).foreach(i => println(s"$i:\n\t${getLowLevelNodesByLine(i).mkString("\n\t")}")) + override val description: String = s"'$cmdName [line numbers]' to print all low-level nodes of the provided lines" + + override def accept(inputs: Seq[String]): Boolean = super.accept(inputs) && inputs.tail.nonEmpty + + def getLowLevelNodesByLine(line: Int): Set[DependencyAnalysisNode] = { + interpreter.getNodesByLine(line) + } + } +} diff --git a/src/main/scala/dependencyAnalysis/cliTool/DependencyAnalysisCliTool.scala b/src/main/scala/dependencyAnalysis/cliTool/DependencyAnalysisCliTool.scala new file mode 100644 index 000000000..5ce0a2e0a --- /dev/null +++ b/src/main/scala/dependencyAnalysis/cliTool/DependencyAnalysisCliTool.scala @@ -0,0 +1,194 @@ +package viper.silicon.dependencyAnalysis.cliTool + +import dependencyAnalysis.cliTool.{AbstractDependencyAnalysisCliTool, DependencyAnalysisCliToolExtension} +import viper.silicon.dependencyAnalysis._ +import viper.silicon.dependencyAnalysis.graphInterpretation.{DependencyAnalysisProgressSupporter, DependencyAnalysisPruningSupporter, DependencyGraphInterpreter} +import viper.silicon.interfaces.Failure +import viper.silver.ast +import viper.silver.ast.Method + +import scala.annotation.tailrec +import scala.io.StdIn.readLine + +class DependencyAnalysisCliTool(fullGraphInterpreter: DependencyGraphInterpreter[Final], memberInterpreters: Seq[DependencyGraphInterpreter[IntraProcedural]], + program: ast.Program, verificationErrors: List[Failure]) extends AbstractDependencyAnalysisCliTool { + + val extensions: List[DependencyAnalysisCliToolExtension] = List( + new DebugDependencyAnalysisCliExtension(fullGraphInterpreter), + new BenchmarkDependencyAnalysisCliExtension(fullGraphInterpreter, program) + ) + + lazy val progressSupporter = new DependencyAnalysisProgressSupporter[Final](fullGraphInterpreter) + lazy val pruningSupporter = new DependencyAnalysisPruningSupporter[Final](fullGraphInterpreter) + + private val infoString = "Enter " + + "\n\t'dep [line numbers]' to print all dependencies of the given line numbers or" + + "\n\t'downDep [line numbers]' to print all dependents of the given line numbers or" + + "\n\t'cov [members]' to print proof coverage of given member or" + + "\n\t'covL member [line numbers]' to print proof coverage of given lines of given member or" + + "\n\t'progress' to compute the verification progress of the program or" + + "\n\t'guide' to compute verification guidance or" + + "\n\t'prune [line numbers]' to prune the program with respect to the given line numbers and export the new program or" + + (if(extensions.nonEmpty) "\n\t" else "") + + extensions.map(_.getInfoString("\n\t")).mkString("\n\t") + + "\n\t'q' to quit" + + def run(): Unit = { + println("Dependency Analysis Tool started.") + println(infoString) + runInternal() + } + + def run(commandStr: String): Unit = { + handleUserInput(commandStr) + } + + @tailrec + private def runInternal(): Unit = { + try { + val userInput = readLine() + if (userInput.equalsIgnoreCase("q") || userInput.equalsIgnoreCase("quit")) { + return + } + if (userInput.nonEmpty) { + handleUserInput(userInput) + } else { + println(infoString) + } + }catch { + case e: Exception => println("Error:\n" + e.getMessage) + } + runInternal() + } + + private def handleUserInput(userInput: String): Unit = { + val inputParts = userInput.split(" ").toSeq + if (inputParts.nonEmpty) { + inputParts.head.toLowerCase match { + case "help" => println(infoString) + case "dep" => handleDependencyQuery(inputParts.tail.toSet) + case "downdep" => handleDependentsQuery(inputParts.tail.toSet) + case "coverage" | "cov" => handleProofCoverageQuery(inputParts.tail) + case "covlines" | "covl" => handleProofCoverageLineQuery(inputParts.tail) + case "progress" | "prog" => handleVerificationProgressQuery() + case "guidance" | "guide" => handleVerificationGuidanceQuery() + case "prune" => handlePruningRequest(inputParts.tail) + case _ => extensions.foreach(_.visit(inputParts)) + } + } else { + println("Invalid input."); println(infoString) + } + } + + + private def handleProofCoverageQuery(memberNames: Seq[String]): Unit = { + println("Proof Coverage") + memberInterpreters.filter(aa => aa.getMember.isDefined && aa.getMember.exists { + case meth: Method => meth.body.isDefined && (memberNames.isEmpty || memberNames.contains(meth.name)) + case func: ast.Function => func.body.isDefined && (memberNames.isEmpty || memberNames.contains(func.name)) + case _ => false + }) + .foreach(aa => { + val ((coverage, uncoveredSources), time) = measureTime(aa.computeProofCoverage()) + println(s"${aa.getMember.map(_.name).getOrElse("")} (${time}ms)") + println(s"coverage: $coverage") + if (!coverage.equals(1.0)) + println(s"uncovered nodes:\n\t${uncoveredSources.mkString("\n\t")}") + println(s"#uncovered nodes:\n\t${uncoveredSources.size}") + }) + println("Done.") + } + + private def handleProofCoverageLineQuery(memberNames: Seq[String]): Unit = { + if(memberNames.isEmpty) return // TODO ake: invalid input handling + + println("Proof Coverage") + val lines = memberNames.tail.flatMap(_.toIntOption) + memberInterpreters.filter(aa => aa.getMember.isDefined && aa.getMember.exists { + case meth: Method => meth.body.isDefined && meth.name.equalsIgnoreCase(memberNames.head) + case func: ast.Function => func.body.isDefined && func.name.equalsIgnoreCase(memberNames.head) + case _ => false + }) + .foreach(aa => { + val ((coverage, uncoveredSources), time) = if(lines.nonEmpty){ + val assertions = lines flatMap aa.getNodesByLine + measureTime(aa.computeProofCoverage(assertions.toSet)) + }else{ + measureTime(aa.computeProofCoverage()) + } + println(s"${aa.getMember.map(_.name).getOrElse("")} (${time}ms)") + println(s"coverage: $coverage") + if (!coverage.equals(1.0)) + println(s"uncovered nodes:\n\t${uncoveredSources.mkString("\n\t")}") + println(s"#uncovered nodes:\n\t${uncoveredSources.size}") + }) + println("Done.") + } + + private def handleVerificationProgressQuery(): Unit = { + + val ((optProgressPeter, optProgressLea), optTime) = measureTime(progressSupporter.computeVerificationProgress()) + + println(s"Peter: $optProgressPeter; Lea: $optProgressLea") + println(s"Finished in ${optTime}ms") + } + + private def handleDependencyQuery(inputs: Set[String]): Unit = { + val queriedNodes = getQueriedNodesFromInput(inputs) + val queriedAssertions = queriedNodes.filter(node => node.isInstanceOf[GeneralAssertionNode]) + + val (directDependencies, timeDirect) = measureTime[Set[DependencyAnalysisNode]](fullGraphInterpreter.getDirectDependencies(queriedAssertions.map(_.id))) + val (allDependencies, timeAll) = measureTime[Set[DependencyAnalysisNode]](fullGraphInterpreter.getAllNonInternalDependencies(queriedAssertions.map(_.id))) + val (allDependenciesWithoutInfeasibility, timeWithoutInfeasibility) = measureTime[Set[DependencyAnalysisNode]](fullGraphInterpreter.getAllNonInternalDependencies(queriedAssertions.map(_.id), includeInfeasibilityNodes=false)) + val (explicitDependencies, timeExplicit) = measureTime[Set[DependencyAnalysisNode]](fullGraphInterpreter.getAllExplicitDependencies(queriedAssertions.map(_.id))) + + println(s"Queried:\n\t${getSourceInfoString(queriedNodes)}") + + println(s"\nDirect Dependencies (${timeDirect}ms):\n\t${getSourceInfoString(directDependencies.diff(queriedNodes))}") + println(s"\nAll Dependencies (${timeAll}ms):\n\t${getSourceInfoString(allDependencies.diff(queriedNodes))}") + println(s"\nDependencies without infeasibility (${timeWithoutInfeasibility}ms):\n\t${getSourceInfoString(allDependenciesWithoutInfeasibility.diff(queriedNodes))}") + println(s"\nExplicit Dependencies (${timeExplicit}ms):\n\t${getSourceInfoString(explicitDependencies.diff(queriedNodes))}") + + if(queriedAssertions.exists(_.asInstanceOf[GeneralAssertionNode].hasFailed)) println("\nQueried assertions (partially) FAILED!\n") + println("Done.") + } + + private def handleDependentsQuery(inputs: Set[String]): Unit = { + + val queriedNodes = getQueriedNodesFromInput(inputs).intersect(fullGraphInterpreter.getNonInternalAssumptionNodes) + + val (allDependents, timeAll) = measureTime[Set[DependencyAnalysisNode]](fullGraphInterpreter.getAllNonInternalDependents(queriedNodes.map(_.id))) + val (dependentsWithoutInfeasibility, timeWithoutInfeasibility) = measureTime[Set[DependencyAnalysisNode]](fullGraphInterpreter.getAllNonInternalDependents(queriedNodes.map(_.id), includeInfeasibilityNodes=false)) + val (explicitDependents, timeExplicit) = measureTime[Set[DependencyAnalysisNode]](fullGraphInterpreter.getAllExplicitDependents(queriedNodes.map(_.id))) + + println(s"Queried:\n\t${getSourceInfoString(queriedNodes)}") + + println(s"\nAll Dependents (${timeAll}ms):\n\t${getSourceInfoString(allDependents)}") + println(s"\nDependents without infeasibility (${timeWithoutInfeasibility}ms):\n\t${getSourceInfoString(dependentsWithoutInfeasibility)}") + println(s"\nExplicit Dependents (${timeExplicit}ms):\n\t${getSourceInfoString(explicitDependents)}") + println("Done.") + } + + private def handlePruningRequest(inputs: Seq[String]): Unit = { + println("exportFileName: ") + val exportFileName = readLine() + val queriedNodes = getQueriedNodesFromInput(inputs.toSet) + pruningSupporter.pruneProgramAndExport(queriedNodes, program, exportFileName) + println("Done.") + } + + private def handleVerificationGuidanceQuery(): Unit = { + + val assumptionRanking = progressSupporter.computeAssumptionRanking().filter(_._2 > 0.0) + println(s"Assumptions/unverified assertions and the number of dependents:\n\t${assumptionRanking.mkString("\n\t")}\n") + + println("Uncovered source code per method: ") + val memberCoverageRanking = memberInterpreters.filter(mInterpreter => mInterpreter.getMember.isDefined && mInterpreter.getMember.get.isInstanceOf[Method]) + .map(mInterpreter => (mInterpreter.getMember.get.name, new DependencyAnalysisProgressSupporter(mInterpreter).computeUncoveredStatements())) + .toList.filter(_._2 > 0).sortBy(_._2).reverse + println(s"\nMethods and the number of uncovered statements:\n\t${memberCoverageRanking.mkString("\n\t")}\n") + } + + override val interpreter: DependencyGraphInterpreter[Final] = fullGraphInterpreter +} + diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala b/src/main/scala/dependencyAnalysis/cliTool/DependencyGraphImporter.scala similarity index 96% rename from src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala rename to src/main/scala/dependencyAnalysis/cliTool/DependencyGraphImporter.scala index 8fbab30ea..af75d0c8f 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphImporter.scala +++ b/src/main/scala/dependencyAnalysis/cliTool/DependencyGraphImporter.scala @@ -1,7 +1,9 @@ -package viper.silicon.dependencyAnalysis +package viper.silicon.dependencyAnalysis.cliTool import viper.silicon import viper.silicon.SiliconFrontend +import viper.silicon.dependencyAnalysis._ +import viper.silicon.dependencyAnalysis.graphInterpretation.DependencyGraphInterpreter import viper.silicon.interfaces.state.Chunk import viper.silicon.state.SimpleIdentifier import viper.silicon.state.terms.sorts.Bool @@ -40,7 +42,7 @@ object DependencyGraphImporter { val program = importProgram(graphFolder) val interpreter = new DependencyGraphInterpreter[Final]("test", graph, List.empty, None) - val userTool = new DependencyAnalysisUserTool(interpreter, Seq.empty, program, List.empty) + val userTool = new DependencyAnalysisCliTool(interpreter, Seq.empty, program, List.empty) runUserTool(args, userTool) } @@ -53,7 +55,7 @@ object DependencyGraphImporter { throw new IllegalArgumentException("Error: --graphFolder argument is required but not found.") } - private def runUserTool(args: Array[String], userTool: DependencyAnalysisUserTool): Unit = { + private def runUserTool(args: Array[String], userTool: DependencyAnalysisCliTool): Unit = { val cmdsIndex = args.indexOf("--cmds") val cmds = if (0 <= cmdsIndex && cmdsIndex < args.length - 1) Some(args(cmdsIndex + 1).split(";").map(_.trim)) else None diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisProgressSupporter.scala b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisProgressSupporter.scala similarity index 94% rename from src/main/scala/dependencyAnalysis/DependencyAnalysisProgressSupporter.scala rename to src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisProgressSupporter.scala index d23d22c61..fef61af4b 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisProgressSupporter.scala +++ b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisProgressSupporter.scala @@ -1,7 +1,8 @@ -package dependencyAnalysis +package viper.silicon.dependencyAnalysis.graphInterpretation -import viper.silicon.dependencyAnalysis.DATraversalMode.{DATraversalMode, Downwards, Upwards} +import dependencyAnalysis.{CompactUserLevelDependencyAnalysisNode, UserLevelDependencyAnalysisNode} import viper.silicon.dependencyAnalysis._ +import viper.silicon.dependencyAnalysis.graphInterpretation.DATraversalMode.DATraversalMode import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType} import scala.collection.mutable @@ -40,8 +41,8 @@ class DependencyAnalysisProgressSupporter[T <: DependencyGraphState](interpreter // recursively compute all interprocedural dependencies and cache results at procedure-boundaries val relevantInterProceduralEdges = traversalMode match { - case Upwards => dependencyGraph.getEdgesConnectingMethodsUpwards - case Downwards => dependencyGraph.getEdgesConnectingMethodsDownwards + case DATraversalMode.Upwards => dependencyGraph.getEdgesConnectingMethodsUpwards + case DATraversalMode.Downwards => dependencyGraph.getEdgesConnectingMethodsDownwards } val interProceduralNodeIds = intraMethodDependencyIds.flatMap(n => relevantInterProceduralEdges.getOrElse(n, Set.empty)) val interProceduralNodes = interProceduralNodeIds.flatMap(interpreter.nodesMap.get) @@ -118,8 +119,8 @@ class DependencyAnalysisProgressSupporter[T <: DependencyGraphState](interpreter // compute all dependencies of each proof obligation val allAssertions = getAssertionsRelevantForProgress.keySet.toList val allDependenciesPerAssertionNode = allAssertions map (ass => ({ - val ups = deps((ass, Upwards)) - val downs = deps((ass, Downwards)) + val ups = deps((ass, DATraversalMode.Upwards)) + val downs = deps((ass, DATraversalMode.Downwards)) reduceCompactUserLevelNodes(ups ++ downs) }, ass)) @@ -169,7 +170,7 @@ class DependencyAnalysisProgressSupporter[T <: DependencyGraphState](interpreter s"Covered Source Code:\n\t${coveredSourceCodeNodes.toList.sortBy(n => (n.getLineNumber, n.toString())).mkString("\n\t")}" + s"Uncovered Source Code:\n\t${allSourceCodeNodes.diff(coveredSourceCodeNodes).toList.sortBy(n => (n.getLineNumber, n.toString())).mkString("\n\t")}" ) - + println(s"Spec Quality = ${coveredSourceCodeNodes.size} / ${allSourceCodeNodes.size}") coveredSourceCodeNodes.size.toDouble / allSourceCodeNodes.size.toDouble } @@ -188,7 +189,7 @@ class DependencyAnalysisProgressSupporter[T <: DependencyGraphState](interpreter val numAssertions = relevantDependenciesPerAssertion.size.toDouble val assumptionImpacts= relevantDependenciesPerAssertion.toList.flatMap { case (_, assumptions) => - val explicitDeps = UserLevelDependencyAnalysisNode.extractExplicitAssumptionNodes(assumptions) + val explicitDeps = UserLevelDependencyAnalysisNode.extractByAssumptionType(assumptions, AssumptionType.explicitAssumptionTypes) explicitDeps.map(node => (node.source, 1.0/assumptions.size/numAssertions)).toList } diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisPruningSupporter.scala b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisPruningSupporter.scala similarity index 98% rename from src/main/scala/dependencyAnalysis/DependencyAnalysisPruningSupporter.scala rename to src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisPruningSupporter.scala index c42eefe24..c3f6fc935 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisPruningSupporter.scala +++ b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisPruningSupporter.scala @@ -1,6 +1,6 @@ -package dependencyAnalysis +package viper.silicon.dependencyAnalysis.graphInterpretation -import viper.silicon.dependencyAnalysis.{DependencyAnalysisNode, DependencyGraphInterpreter, DependencyGraphState} +import viper.silicon.dependencyAnalysis.{DependencyAnalysisNode, DependencyGraphState} import viper.silver.ast import viper.silver.ast.utility.ViperStrategy import viper.silver.ast.utility.rewriter.Traverse diff --git a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala similarity index 98% rename from src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala rename to src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala index 9ebdedc4f..07dcc44ae 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala @@ -1,6 +1,7 @@ -package viper.silicon.dependencyAnalysis +package viper.silicon.dependencyAnalysis.graphInterpretation import dependencyAnalysis.UserLevelDependencyAnalysisNode +import viper.silicon.dependencyAnalysis._ import viper.silicon.interfaces.Failure import viper.silicon.verifier.Verifier import viper.silver.ast diff --git a/src/main/scala/interfaces/Verification.scala b/src/main/scala/interfaces/Verification.scala index 550be5c94..7ead45d1c 100644 --- a/src/main/scala/interfaces/Verification.scala +++ b/src/main/scala/interfaces/Verification.scala @@ -8,7 +8,8 @@ package viper.silicon.interfaces import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.{DebugAxiom, DebugExp} -import viper.silicon.dependencyAnalysis.{DependencyGraphInterpreter, IntraProcedural} +import viper.silicon.dependencyAnalysis.IntraProcedural +import viper.silicon.dependencyAnalysis.graphInterpretation.DependencyGraphInterpreter import viper.silicon.interfaces.state.Chunk import viper.silicon.reporting._ import viper.silicon.state.terms._ diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index 5e3de394b..3180fe11f 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -10,6 +10,7 @@ import com.typesafe.scalalogging.Logger import viper.silicon.Map import viper.silicon.decider.Decider import viper.silicon.dependencyAnalysis._ +import viper.silicon.dependencyAnalysis.graphInterpretation.DependencyGraphInterpreter import viper.silicon.interfaces._ import viper.silicon.logger.records.data.WellformednessCheckRecord import viper.silicon.rules.{consumer, executionFlowController, executor, producer} diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index 06cd5a27d..6d5c7b61c 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -11,6 +11,7 @@ import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.DebugExp import viper.silicon.decider.Decider import viper.silicon.dependencyAnalysis._ +import viper.silicon.dependencyAnalysis.graphInterpretation.DependencyGraphInterpreter import viper.silicon.interfaces._ import viper.silicon.interfaces.decider.ProverLike import viper.silicon.rules.{consumer, evaluator, executionFlowController, producer} diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index 571574ef0..a41027b8e 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -12,6 +12,7 @@ import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.debugger.SiliconDebugger import viper.silicon.decider.SMTLib2PreambleReader import viper.silicon.dependencyAnalysis._ +import viper.silicon.dependencyAnalysis.cliTool.DependencyAnalysisCliTool import viper.silicon.extensions.ConditionalPermissionRewriter import viper.silicon.interfaces._ import viper.silicon.interfaces.decider.ProverLike @@ -21,7 +22,6 @@ import viper.silicon.state._ import viper.silicon.state.terms.{Decl, Sort, Term, sorts} import viper.silicon.supporters._ import viper.silicon.supporters.functions.{DefaultFunctionVerificationUnitProvider, FunctionData} -import viper.silicon.supporters.{AnnotationSupporter, DefaultDomainsContributor, DefaultMapsContributor, DefaultMultisetsContributor, DefaultPredicateVerificationUnitProvider, DefaultSequencesContributor, DefaultSetsContributor, MagicWandSnapFunctionsContributor, PredicateData} import viper.silicon.supporters.qps._ import viper.silicon.utils.Counter import viper.silver.ast @@ -667,7 +667,7 @@ class DefaultMainVerifier(config: Config, } if (Verifier.config.startDependencyAnalysisTool()) { - val commandLineTool = new DependencyAnalysisUserTool(result.getFullDependencyGraphInterpreter, dependencyGraphInterpreters, program, verificationErrors) + val commandLineTool = new DependencyAnalysisCliTool(result.getFullDependencyGraphInterpreter, dependencyGraphInterpreters, program, verificationErrors) commandLineTool.run() } diff --git a/src/test/scala/DependencyAnalysisPrecisionBenchmark.scala b/src/test/scala/DependencyAnalysisPrecisionBenchmark.scala index e0de9bdf3..53731840f 100644 --- a/src/test/scala/DependencyAnalysisPrecisionBenchmark.scala +++ b/src/test/scala/DependencyAnalysisPrecisionBenchmark.scala @@ -1,6 +1,6 @@ package viper.silicon.tests -import dependencyAnalysis.DependencyAnalysisPruningSupporter +import dependencyAnalysis.graphInterpretation.{DependencyAnalysisPruningSupporter, DependencyGraphInterpreter} import viper.silicon.dependencyAnalysis._ import viper.silver.ast.Program import viper.silver.verifier diff --git a/src/test/scala/DependencyAnalysisTestFramework.scala b/src/test/scala/DependencyAnalysisTestFramework.scala index f1d0c6b71..42cc016d9 100644 --- a/src/test/scala/DependencyAnalysisTestFramework.scala +++ b/src/test/scala/DependencyAnalysisTestFramework.scala @@ -1,6 +1,6 @@ package viper.silicon.tests -import dependencyAnalysis.DependencyAnalysisPruningSupporter +import dependencyAnalysis.graphInterpretation.{DependencyAnalysisPruningSupporter, DependencyGraphInterpreter} import viper.silicon.SiliconFrontend import viper.silicon.dependencyAnalysis._ import viper.silver.ast.utility.ViperStrategy diff --git a/src/test/scala/DependencyAnalysisTests.scala b/src/test/scala/DependencyAnalysisTests.scala index 87146ffd9..41faf6c6f 100644 --- a/src/test/scala/DependencyAnalysisTests.scala +++ b/src/test/scala/DependencyAnalysisTests.scala @@ -1,6 +1,7 @@ package viper.silicon.tests import dependencyAnalysis.UserLevelDependencyAnalysisNode +import dependencyAnalysis.cliTool.DependencyAnalysisUserTool import org.scalatest.funsuite.AnyFunSuite import viper.silicon.dependencyAnalysis._ import viper.silver.ast._ diff --git a/src/test/scala/VerificationProgressRunner.scala b/src/test/scala/VerificationProgressRunner.scala index b5b44dcbb..41ec7b866 100644 --- a/src/test/scala/VerificationProgressRunner.scala +++ b/src/test/scala/VerificationProgressRunner.scala @@ -1,6 +1,6 @@ package viper.silicon.tests -import dependencyAnalysis.DependencyAnalysisProgressSupporter +import dependencyAnalysis.graphInterpretation.DependencyAnalysisProgressSupporter import viper.silicon.dependencyAnalysis.DependencyAnalysisReporter import viper.silver.ast.Program import viper.silver.frontend.SilFrontend From 329e7dcea80497f919cd74ac20cb43eedfe30e8e Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 10 Apr 2026 09:18:05 +0200 Subject: [PATCH 425/474] fix tests --- src/test/scala/DependencyAnalysisPrecisionBenchmark.scala | 2 +- src/test/scala/DependencyAnalysisTestFramework.scala | 2 +- src/test/scala/DependencyAnalysisTests.scala | 4 ++-- src/test/scala/VerificationProgressRunner.scala | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/test/scala/DependencyAnalysisPrecisionBenchmark.scala b/src/test/scala/DependencyAnalysisPrecisionBenchmark.scala index 53731840f..613c6e670 100644 --- a/src/test/scala/DependencyAnalysisPrecisionBenchmark.scala +++ b/src/test/scala/DependencyAnalysisPrecisionBenchmark.scala @@ -1,7 +1,7 @@ package viper.silicon.tests -import dependencyAnalysis.graphInterpretation.{DependencyAnalysisPruningSupporter, DependencyGraphInterpreter} import viper.silicon.dependencyAnalysis._ +import viper.silicon.dependencyAnalysis.graphInterpretation.{DependencyAnalysisPruningSupporter, DependencyGraphInterpreter} import viper.silver.ast.Program import viper.silver.verifier import viper.silver.verifier.VerificationResult diff --git a/src/test/scala/DependencyAnalysisTestFramework.scala b/src/test/scala/DependencyAnalysisTestFramework.scala index 42cc016d9..e109db140 100644 --- a/src/test/scala/DependencyAnalysisTestFramework.scala +++ b/src/test/scala/DependencyAnalysisTestFramework.scala @@ -1,8 +1,8 @@ package viper.silicon.tests -import dependencyAnalysis.graphInterpretation.{DependencyAnalysisPruningSupporter, DependencyGraphInterpreter} import viper.silicon.SiliconFrontend import viper.silicon.dependencyAnalysis._ +import viper.silicon.dependencyAnalysis.graphInterpretation.{DependencyAnalysisPruningSupporter, DependencyGraphInterpreter} import viper.silver.ast.utility.ViperStrategy import viper.silver.ast.{Infoed, Program} import viper.silver.dependencyAnalysis.AssumptionType diff --git a/src/test/scala/DependencyAnalysisTests.scala b/src/test/scala/DependencyAnalysisTests.scala index 41faf6c6f..e9d735d2e 100644 --- a/src/test/scala/DependencyAnalysisTests.scala +++ b/src/test/scala/DependencyAnalysisTests.scala @@ -1,9 +1,9 @@ package viper.silicon.tests import dependencyAnalysis.UserLevelDependencyAnalysisNode -import dependencyAnalysis.cliTool.DependencyAnalysisUserTool import org.scalatest.funsuite.AnyFunSuite import viper.silicon.dependencyAnalysis._ +import viper.silicon.dependencyAnalysis.cliTool.DependencyAnalysisCliTool import viper.silver.ast._ import viper.silver.frontend.SilFrontend import viper.silver.verifier @@ -92,7 +92,7 @@ class DependencyAnalysisTests extends AnyFunSuite with DependencyAnalysisTestFra directory.mkdir() val directoryTestCase = new File(s"$basePathAnnotatedPrograms/$fileName") directoryTestCase.mkdir() - val dependencyAnalysisUserTool = new DependencyAnalysisUserTool(joinedDependencyGraphInterpreter.get, dependencyGraphInterpreters, program, List()) + val dependencyAnalysisUserTool = new DependencyAnalysisCliTool(joinedDependencyGraphInterpreter.get, dependencyGraphInterpreters, program, List()) dependencyAnalysisUserTool.run(s"annotate $basePathAnnotatedPrograms/$fileName/$fileName.vpr") } new AnnotatedTest(program, dependencyGraphInterpreters, CHECK_PRECISION).execute() diff --git a/src/test/scala/VerificationProgressRunner.scala b/src/test/scala/VerificationProgressRunner.scala index 41ec7b866..0b646e77d 100644 --- a/src/test/scala/VerificationProgressRunner.scala +++ b/src/test/scala/VerificationProgressRunner.scala @@ -1,7 +1,7 @@ package viper.silicon.tests -import dependencyAnalysis.graphInterpretation.DependencyAnalysisProgressSupporter import viper.silicon.dependencyAnalysis.DependencyAnalysisReporter +import viper.silicon.dependencyAnalysis.graphInterpretation.DependencyAnalysisProgressSupporter import viper.silver.ast.Program import viper.silver.frontend.SilFrontend import viper.silver.verifier @@ -61,7 +61,7 @@ object VerificationProgressRunner extends DependencyAnalysisTestFramework { val joinedDependencyGraphInterpreter = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].joinedDependencyGraphInterpreter - val (progressPeter, progressLea, _) = new DependencyAnalysisProgressSupporter(joinedDependencyGraphInterpreter.get).computeVerificationProgress() + val (progressPeter, progressLea) = new DependencyAnalysisProgressSupporter(joinedDependencyGraphInterpreter.get).computeVerificationProgress() writer.println(f"$fileName\t$progressPeter%.3f\t$progressLea%.3f\t${!hasFailures}") println(f"$fileName\t$progressPeter%.3f\t$progressLea%.3f\t${!hasFailures}") From 7ed09185590783562fa2156063befe262c0b9dea Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 10 Apr 2026 09:58:46 +0200 Subject: [PATCH 426/474] fix state consolidation --- .../dependencyAnalysis/DependencyAnalysisInfos.scala | 3 +++ .../scala/dependencyAnalysis/DependencyAnalyzer.scala | 10 +++++----- src/main/scala/rules/Evaluator.scala | 8 ++++---- src/main/scala/rules/Executor.scala | 5 ++--- src/main/scala/rules/QuantifiedChunkSupport.scala | 6 +++--- src/main/scala/rules/StateConsolidator.scala | 8 +++----- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisInfos.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisInfos.scala index d004cfa4f..4e8e38c33 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisInfos.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisInfos.scala @@ -86,6 +86,9 @@ object DependencyAnalysisInfos { def create(infoString: String, dependencyType: DependencyType): DependencyAnalysisInfos = create(StringAnalysisSourceInfo(infoString, NoPosition), dependencyType) + + def createUnique(infoString: String, dependencyType: DependencyType): DependencyAnalysisInfos = + create(StringAnalysisSourceInfo(s"$infoString-${DependencyGraphHelper.nextId()}", NoPosition), dependencyType) } diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index 069daa3cd..8e7b8a795 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -205,9 +205,9 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { override def createAssertOrCheckNode(term: Term, analysisInfos: DependencyAnalysisInfos, isCheck: Boolean): Option[GeneralAssertionNode] = { if(isCheck) - Some(SimpleCheckNode(term, analysisInfos.getSourceInfo, analysisInfos.getDependencyType.assumptionType, analysisInfos.getMergeInfo, analysisInfos.getJoinInfo)) + Some(SimpleCheckNode(term, analysisInfos.getSourceInfo, analysisInfos.getDependencyType.assertionType, analysisInfos.getMergeInfo, analysisInfos.getJoinInfo)) else - Some(SimpleAssertionNode(term, analysisInfos.getSourceInfo, analysisInfos.getDependencyType.assumptionType, analysisInfos.getMergeInfo, analysisInfos.getJoinInfo)) + Some(SimpleAssertionNode(term, analysisInfos.getSourceInfo, analysisInfos.getDependencyType.assertionType, analysisInfos.getMergeInfo, analysisInfos.getJoinInfo)) } private def addAssertNode(term: Term, analysisInfos: DependencyAnalysisInfos): Option[Int] = { @@ -229,8 +229,8 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { } override def addAssertionFailedNode(failedAssertion: Term, analysisInfos: DependencyAnalysisInfos): Option[Int] = { - val assumeNode = SimpleAssumptionNode(failedAssertion, None, analysisInfos.getSourceInfo, analysisInfos.getDependencyType.assumptionType, analysisInfos.getMergeInfo, analysisInfos.getJoinInfo) - val assertFailedNode = SimpleAssertionNode(failedAssertion, analysisInfos.getSourceInfo, analysisInfos.getDependencyType.assumptionType, analysisInfos.getMergeInfo, analysisInfos.getJoinInfo, hasFailed=true) + val assumeNode = SimpleAssumptionNode(failedAssertion, None, analysisInfos.getSourceInfo, analysisInfos.getDependencyType.assertionType, analysisInfos.getMergeInfo, analysisInfos.getJoinInfo) + val assertFailedNode = SimpleAssertionNode(failedAssertion, analysisInfos.getSourceInfo, analysisInfos.getDependencyType.assertionType, analysisInfos.getMergeInfo, analysisInfos.getJoinInfo, hasFailed=true) dependencyGraph.addNode(assumeNode) dependencyGraph.addNode(assertFailedNode) dependencyGraph.addEdges(Set(assumeNode.id), assertFailedNode.id) @@ -275,7 +275,7 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { */ override def buildFinalGraph(): Option[DependencyGraph[IntraProcedural]] = { dependencyGraph.removeLabelNodes() - val mergedGraph = /* TODO ake if(Verifier.config.enableDependencyAnalysisDebugging()) dependencyGraph else */ buildAndGetMergedGraph() + val mergedGraph = if(Verifier.config.enableDependencyAnalysisDebugging()) dependencyGraph.asInstanceOf[DependencyGraph[IntraProcedural]] else buildAndGetMergedGraph() addTransitiveEdges(mergedGraph) if(!Verifier.config.enableDependencyAnalysisDebugging()) mergedGraph.removeInternalNodes() Some(mergedGraph) diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index d27a31f9e..86c2f34be 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -559,7 +559,7 @@ object evaluator extends EvaluationRules { val auxNonGlobalsExp = auxExps.map(_._2) val commentGlobal = "Nested auxiliary terms: globals (aux)" v1.decider.prover.comment(commentGlobal) - val auxAnalysisInfos = analysisInfos.withDependencyType(DependencyType.Internal).withMergeInfo(NoDependencyAnalysisMerge()) + val auxAnalysisInfos = analysisInfos.withDependencyType(DependencyType.Internal).withMergeInfo(NoDependencyAnalysisMerge()) // TODO ake: review v1.decider.assume(tAuxGlobal, Option.when(withExp)(DebugExp.createInstance(description=commentGlobal, children=auxGlobalsExp.get)), enforceAssumption = false, auxAnalysisInfos) val commentNonGlobals = "Nested auxiliary terms: non-globals (aux)" v1.decider.prover.comment(commentNonGlobals) @@ -609,8 +609,8 @@ object evaluator extends EvaluationRules { case q: ast.Forall => q.copy(triggers = Nil)(q.pos, q.info, q.errT) }) val presWithDAInfo = SimpleDependencyAnalysisMerge.attachExpMergeInfo(pres.flatMap(_.topLevelConjuncts)) - val argsAnalysisInfo = analysisInfos.removeSource() - evals2(s, eArgs, Nil, _ => pve, v, argsAnalysisInfo)((s1, tArgs, eArgsNew, v1) => { + val eArgsWithDAInfO = SimpleDependencyAnalysisMerge.attachExpMergeInfo(eArgs) + evals2(s, eArgsWithDAInfO, Nil, _ => pve, v, analysisInfos)((s1, tArgs, eArgsNew, v1) => { // bookkeeper.functionApplications += 1 val joinFunctionArgs = tArgs //++ c2a.quantifiedVariables.filterNot(tArgs.contains) val (debugHeapName, debugLabel) = v1.getDebugOldLabel(s1, fapp.pos) @@ -651,7 +651,7 @@ object evaluator extends EvaluationRules { if(Verifier.config.enableDependencyAnalysis()){ argsPairs.map(arg => { val argNew = v1.decider.fresh(arg._1.sort, None) - v1.decider.assume(Equals(argNew, arg._1), None, argsAnalysisInfo.withSource(AnalysisSourceInfo.createAnalysisSourceInfo(arg._2.get))) + v1.decider.assume(Equals(argNew, arg._1), None, analysisInfos.withMergeInfo(SimpleDependencyAnalysisMerge(AnalysisSourceInfo.createAnalysisSourceInfo(arg._2.get)))) (argNew, None) }) }else argsPairs diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 88d048a8f..74d58c6af 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -554,8 +554,7 @@ object executor extends ExecutionRules { val paramLog = new CommentRecord("Parameters", s, v.decider.pcs) val paramId = v.symbExLog.openScope(paramLog) val eArgsWithDAInfo = SimpleDependencyAnalysisMerge.attachExpMergeInfo(eArgs) - val argsAnalysisInfo = analysisInfos.removeSource() - evals(s, eArgsWithDAInfo, _ => pveCall, v, argsAnalysisInfo)((s1, tArgs, eArgsNew, v1) => { + evals(s, eArgsWithDAInfo, _ => pveCall, v, analysisInfos)((s1, tArgs, eArgsNew, v1) => { v1.symbExLog.closeScope(paramId) val exampleTrafo = CounterexampleTransformer({ case ce: SiliconCounterexample => ce.withStore(s1.g) @@ -577,7 +576,7 @@ object executor extends ExecutionRules { if(Verifier.config.enableDependencyAnalysis()){ argsWithExp.map(arg => { val argNew = v1.decider.fresh(arg._1.sort, None) - v1.decider.assume(Equals(argNew, arg._1), None, argsAnalysisInfo.withSource(AnalysisSourceInfo.createAnalysisSourceInfo(arg._2.get))) + v1.decider.assume(Equals(argNew, arg._1), None, analysisInfos.withMergeInfo(SimpleDependencyAnalysisMerge(AnalysisSourceInfo.createAnalysisSourceInfo(arg._2.get)))) (argNew, None) }) }else argsWithExp diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 2215f347b..23b941def 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -674,7 +674,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { smDef: SnapshotMapDefinition, v: Verifier) : (PermMapDefinition, PmCache) = { - val analysisInfos = DependencyAnalysisInfos.create("summarizing heap", DependencyType.Internal, NoDependencyAnalysisMerge()) + val analysisInfos = DependencyAnalysisInfos.createUnique("summarizing heap", DependencyType.Internal) val res = Verifier.config.mapCache(s.pmCache.get(resource, relevantChunks)) match { case Some(pmDef) => v.decider.assume(pmDef.valueDefinitions, Option.when(withExp)(DebugExp.createInstance("value definitions", isInternal_ = true)), enforceAssumption = false, analysisInfos=analysisInfos) @@ -713,7 +713,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { optSmDomainDefinitionCondition: Option[Term] = None, optQVarsInstantiations: Option[Seq[Term]] = None) : (SnapshotMapDefinition, SnapshotMapCache) = { - val analysisInfos = DependencyAnalysisInfos.create("summarizing heap", DependencyType.Internal, NoDependencyAnalysisMerge()) + val analysisInfos = DependencyAnalysisInfos.createUnique("summarizing heap", DependencyType.Internal) def emitSnapshotMapDefinition(s: State, smDef: SnapshotMapDefinition, @@ -933,7 +933,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val commentGlobals = "Nested auxiliary terms: globals" v.decider.prover.comment(commentGlobals) - val analysisInfosGlobals = DependencyAnalysisInfos.create(commentGlobals, DependencyType.Internal, NoDependencyAnalysisMerge()) + val analysisInfosGlobals = DependencyAnalysisInfos.create(commentGlobals, DependencyType.Internal, NoDependencyAnalysisMerge()) // TODO ake: review v.decider.assume(auxGlobals, Option.when(withExp)(DebugExp.createInstance(description=commentGlobals, children=auxGlobalsExp.get)), enforceAssumption = false, analysisInfos=analysisInfosGlobals) diff --git a/src/main/scala/rules/StateConsolidator.scala b/src/main/scala/rules/StateConsolidator.scala index 20670460c..5b08fa5ce 100644 --- a/src/main/scala/rules/StateConsolidator.scala +++ b/src/main/scala/rules/StateConsolidator.scala @@ -20,7 +20,6 @@ import viper.silicon.state.terms.predef.`?r` import viper.silicon.supporters.functions.FunctionRecorder import viper.silicon.verifier.Verifier import viper.silver.ast -import viper.silver.ast.NoDependencyAnalysisMerge import viper.silver.dependencyAnalysis.DependencyType import scala.annotation.unused @@ -64,7 +63,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol val comLog = new CommentRecord("state consolidation", s, v.decider.pcs) val sepIdentifier = v.symbExLog.openScope(comLog) - val analysisInfos = DependencyAnalysisInfos.create("state consolidation", DependencyType.Internal, NoDependencyAnalysisMerge()) // TODO ake: review + val analysisInfos = DependencyAnalysisInfos.createUnique("state consolidation", DependencyType.Internal) v.decider.prover.comment("[state consolidation]") v.decider.prover.saturate(config.proverSaturationTimeouts.beforeIteration) @@ -178,7 +177,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol * nextChunk: current chunk from the sequence of new chunks/of chunks to merge into the * sequence of destination chunks */ - val analysisInfos = DependencyAnalysisInfos.create("stat_consolidation", DependencyType.Internal, NoDependencyAnalysisMerge()) // TODO ake: review + val analysisInfos = DependencyAnalysisInfos.createUnique("state_consolidation", DependencyType.Internal) val res = findMatchingChunk(accMergedChunks, nextChunk, v, analysisInfos) match { case Some(ch) => val resMerge = mergeChunks(fr1, ch, nextChunk, qvars, v, analysisInfos) @@ -212,7 +211,6 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol private def mergeChunks(fr1: FunctionRecorder, chunk1: Chunk, chunk2: Chunk, qvars: Seq[Var], v: Verifier, analysisInfos: DependencyAnalysisInfos): Option[(FunctionRecorder, Chunk, Term)] = { val result = mergeChunks1(fr1, chunk1, chunk2, qvars, v, analysisInfos) result.map({case (fRec, ch, snapEq) => -// v.decider.dependencyAnalyzer.addPermissionDependencies(Set(chunk1, chunk2), Set(), ch) (fRec, ch, v.decider.wrapWithDependencyAnalysisLabel(snapEq, Set(chunk1, chunk2)))}) } @@ -437,7 +435,7 @@ class MoreComplexExhaleStateConsolidator(config: Config) extends DefaultStateCon // silver\src\test\resources\quantifiedpermissions\sets\generalised_shape.sil // to fail. - val analysisInfos = DependencyAnalysisInfos.create("state consolidation", DependencyType.Internal, NoDependencyAnalysisMerge()) // TODO ake: review + val analysisInfos = DependencyAnalysisInfos.createUnique("state consolidation", DependencyType.Internal) if (s.retrying) { // TODO: apply to all heaps (s.h +: s.reserveHeaps, as done below) From 9f7f3562339cdacdbcc3cbf9734dcf64c811fe7c Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 10 Apr 2026 14:08:53 +0200 Subject: [PATCH 427/474] fix for method and function calls --- silver | 2 +- .../DependencyAnalyzer.scala | 21 +++++++++--- .../cliTool/DependencyAnalysisCliTool.scala | 7 ++-- .../DependencyAnalysisProgressSupporter.scala | 8 ++--- .../DependencyGraphInterpreter.scala | 2 +- src/main/scala/rules/Evaluator.scala | 11 +++--- src/main/scala/rules/Executor.scala | 10 +++--- .../tests/function-progress.vpr | 27 +++++++++++++++ .../tests/gaussian-progress.vpr | 21 ++++++++++++ .../verificationProgress/tests/progress1.vpr | 34 +++++++++++++++++++ .../DependencyAnalysisTestFramework.scala | 3 +- 11 files changed, 121 insertions(+), 25 deletions(-) create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgress/tests/function-progress.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgress/tests/gaussian-progress.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgress/tests/progress1.vpr diff --git a/silver b/silver index f90274bf8..75f02f321 160000 --- a/silver +++ b/silver @@ -1 +1 @@ -Subproject commit f90274bf856cd3e89f77b6de84f3e325bcf79523 +Subproject commit 75f02f321e60619b49892720493d83d1ac1ca28a diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index 8e7b8a795..5b119da1e 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -137,6 +137,8 @@ object DependencyAnalyzer { class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { protected var proofCoverage: Double = 0.0 + protected var customMergeDependencies: Set[(Set[DependencyAnalysisMergeInfo], Set[DependencyAnalysisMergeInfo])] = Set.empty + override def getMember: Option[ast.Member] = Some(member) override def getNodes: Iterable[DependencyAnalysisNode] = dependencyGraph.getNodes @@ -259,11 +261,18 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { } override def addCustomDependenciesBetweenMergeInfos(sourceExps: Seq[Exp], targetExps: Seq[Exp]): Unit = { - val sourceMergeInfos = sourceExps.flatMap(_.info.getUniqueInfo[DependencyAnalysisMergeInfo]).filter(_.isMerge) - val targetMergeInfos = targetExps.flatMap(_.info.getUniqueInfo[DependencyAnalysisMergeInfo]).filter(_.isMerge) - val sourceNodes = getNodes.filter(node => sourceMergeInfos.contains(node.mergeInfo)).map(_.id) - val targetNodes = getNodes.filter(node => targetMergeInfos.contains(node.mergeInfo)).map(_.id) - dependencyGraph.addEdges(sourceNodes, targetNodes) + val sourceMergeInfos = sourceExps.flatMap(_.info.getUniqueInfo[DependencyAnalysisMergeInfo]).filter(_.isMerge).toSet + val targetMergeInfos = targetExps.flatMap(_.info.getUniqueInfo[DependencyAnalysisMergeInfo]).filter(_.isMerge).toSet + + customMergeDependencies = Set((sourceMergeInfos, targetMergeInfos)) ++ customMergeDependencies + } + + protected def addCustomMergeDependencies(): Unit = { + customMergeDependencies.foreach{ case (sourceMergeInfos, targetMergeInfos) => + val sourceNodes = getNodes.filter(node => sourceMergeInfos.contains(node.mergeInfo)).map(_.id) + val targetNodes = getNodes.filter(node => targetMergeInfos.contains(node.mergeInfo)).map(_.id) + dependencyGraph.addEdges(sourceNodes, targetNodes) + } } /** @@ -291,6 +300,8 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { val notChecks = nodes.filter(n => !n.isInstanceOf[SimpleCheckNode]) mergedGraph.addEdges(checks.map(_.id), notChecks.map(_.id)) // TODO ake: why do we need this? } + + addCustomMergeDependencies() } /** diff --git a/src/main/scala/dependencyAnalysis/cliTool/DependencyAnalysisCliTool.scala b/src/main/scala/dependencyAnalysis/cliTool/DependencyAnalysisCliTool.scala index 5ce0a2e0a..6c416d943 100644 --- a/src/main/scala/dependencyAnalysis/cliTool/DependencyAnalysisCliTool.scala +++ b/src/main/scala/dependencyAnalysis/cliTool/DependencyAnalysisCliTool.scala @@ -70,7 +70,7 @@ class DependencyAnalysisCliTool(fullGraphInterpreter: DependencyGraphInterpreter case "downdep" => handleDependentsQuery(inputParts.tail.toSet) case "coverage" | "cov" => handleProofCoverageQuery(inputParts.tail) case "covlines" | "covl" => handleProofCoverageLineQuery(inputParts.tail) - case "progress" | "prog" => handleVerificationProgressQuery() + case "progress" | "prog" => handleVerificationProgressQuery(inputParts.tail) case "guidance" | "guide" => handleVerificationGuidanceQuery() case "prune" => handlePruningRequest(inputParts.tail) case _ => extensions.foreach(_.visit(inputParts)) @@ -125,9 +125,10 @@ class DependencyAnalysisCliTool(fullGraphInterpreter: DependencyGraphInterpreter println("Done.") } - private def handleVerificationProgressQuery(): Unit = { + private def handleVerificationProgressQuery(inputs: Seq[String]): Unit = { + val enableDebugging = inputs.nonEmpty && inputs.head.equals("debug") - val ((optProgressPeter, optProgressLea), optTime) = measureTime(progressSupporter.computeVerificationProgress()) + val ((optProgressPeter, optProgressLea), optTime) = measureTime(progressSupporter.computeVerificationProgress(enableDebugging)) println(s"Peter: $optProgressPeter; Lea: $optProgressLea") println(s"Finished in ${optTime}ms") diff --git a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisProgressSupporter.scala b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisProgressSupporter.scala index fef61af4b..9a53ba69b 100644 --- a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisProgressSupporter.scala +++ b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisProgressSupporter.scala @@ -12,8 +12,8 @@ class DependencyAnalysisProgressSupporter[T <: DependencyGraphState](interpreter private val dependencyGraph = interpreter.getGraph private lazy val sourceToAssertionNodesMap: Map[AnalysisSourceInfo, Set[DependencyAnalysisNode]] = interpreter.getNonInternalAssertionNodes.groupBy(_.sourceInfo) - def computeVerificationProgress(): (Double, Double) = { - computeVerificationProgressOptimized() + def computeVerificationProgress(enableDebugging: Boolean=false): (Double, Double) = { + computeVerificationProgressOptimized(enableDebugging) } @@ -142,7 +142,7 @@ class DependencyAnalysisProgressSupporter[T <: DependencyGraphState](interpreter if(enableDebugOutput) println( - s"fullyVerifiedAssertions:\n\t${fullyVerifiedAssertions.mkString("\n\t")}" + + s"fullyVerifiedAssertions:\n\t${fullyVerifiedAssertions.mkString("\n\t")}\n" + s"assertionQualitiesSum:\n\t${assertionQualities.mkString("\n\t")}" ) @@ -167,7 +167,7 @@ class DependencyAnalysisProgressSupporter[T <: DependencyGraphState](interpreter val coveredSourceCodeNodes = coveredNodes.map(_.source).intersect(allSourceCodeNodes) if(enableDebugOutput) println( - s"Covered Source Code:\n\t${coveredSourceCodeNodes.toList.sortBy(n => (n.getLineNumber, n.toString())).mkString("\n\t")}" + + s"Covered Source Code:\n\t${coveredSourceCodeNodes.toList.sortBy(n => (n.getLineNumber, n.toString())).mkString("\n\t")}\n" + s"Uncovered Source Code:\n\t${allSourceCodeNodes.diff(coveredSourceCodeNodes).toList.sortBy(n => (n.getLineNumber, n.toString())).mkString("\n\t")}" ) diff --git a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala index 07dcc44ae..1fea58709 100644 --- a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala @@ -100,7 +100,7 @@ class DependencyGraphInterpreter[T <: DependencyGraphState](name: String, depend def getNonInternalAssumptionNodes(nodes: Set[DependencyAnalysisNode]): Set[DependencyAnalysisNode] = nodes filter (node => (node.isInstanceOf[GeneralAssumptionNode] && !AssumptionType.internalTypes.contains(node.assumptionType)) - || AssumptionType.postconditionTypes.contains(node.assumptionType) || node.joinInfos.nonEmpty // postconditions act as assumptions for callers + || AssumptionType.postconditionTypes.contains(node.assumptionType) || node.joinInfos.nonEmpty // TODO ake: review ) def getExplicitAssumptionNodes: Set[DependencyAnalysisNode] = getNonInternalAssumptionNodes filter (node => diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 86c2f34be..bfc66d890 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -24,7 +24,7 @@ import viper.silicon.verifier.Verifier import viper.silicon.{Map, TriggerSets} import viper.silver.ast import viper.silver.ast.utility.Expressions -import viper.silver.ast.{AnnotationInfo, EdgeType, EvalStackDependencyAnalysisJoin, JoinType, LocalVarWithVersion, NoDependencyAnalysisMerge, SimpleDependencyAnalysisMerge, WeightedQuantifier} +import viper.silver.ast.{AnnotationInfo, DependencyAnalysisMergeInfo, EdgeType, EvalStackDependencyAnalysisJoin, JoinType, LocalVarWithVersion, NoDependencyAnalysisMerge, SimpleDependencyAnalysisMerge, WeightedQuantifier} import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, DependencyType} import viper.silver.reporter.{AnnotationWarning, WarningsDuringVerification} import viper.silver.utility.Common.Rational @@ -608,8 +608,9 @@ object evaluator extends EvaluationRules { */ case q: ast.Forall => q.copy(triggers = Nil)(q.pos, q.info, q.errT) }) - val presWithDAInfo = SimpleDependencyAnalysisMerge.attachExpMergeInfo(pres.flatMap(_.topLevelConjuncts)) - val eArgsWithDAInfO = SimpleDependencyAnalysisMerge.attachExpMergeInfo(eArgs) + val fappSourceInfo = AnalysisSourceInfo.createAnalysisSourceInfo(fapp) + val presWithDAInfo = DependencyAnalysisMergeInfo.attachExpMergeInfo(pres.flatMap(_.topLevelConjuncts), Some(fappSourceInfo)) + val eArgsWithDAInfO = DependencyAnalysisMergeInfo.attachExpMergeInfo(eArgs, None) evals2(s, eArgsWithDAInfO, Nil, _ => pve, v, analysisInfos)((s1, tArgs, eArgsNew, v1) => { // bookkeeper.functionApplications += 1 val joinFunctionArgs = tArgs //++ c2a.quantifiedVariables.filterNot(tArgs.contains) @@ -691,7 +692,7 @@ object evaluator extends EvaluationRules { val preExp = Option.when(withExp)({ DebugExp.createInstance(Some(s"precondition of ${func.name}(${eArgsNew.get.mkString(", ")}) holds"), None, None, InsertionOrderedSet.empty) }) - v3.decider.assume(preFApp, preExp, precondAnalysisInfos) + v3.decider.assume(preFApp, preExp, analysisInfos) val funcAnn = func.info.getUniqueInfo[AnnotationInfo] val tFApp = funcAnn match { case Some(a) if a.values.contains("opaque") => @@ -724,7 +725,7 @@ object evaluator extends EvaluationRules { })(join(func.typ, s"joined_${func.name}", joinFunctionArgs, Option.when(withExp)(eArgs), v1, analysisInfos))((s6, r, v4) => { v4.decider.dependencyAnalyzer.addCustomDependenciesBetweenMergeInfos(presWithDAInfo, - Seq(SimpleDependencyAnalysisMerge.attachExpMergeInfo(fapp), SimpleDependencyAnalysisMerge.attachExpMergeInfo(fapp, analysisInfos.getSourceInfo))) + Seq(DependencyAnalysisMergeInfo.attachExpMergeInfo(fapp, None), DependencyAnalysisMergeInfo.attachExpMergeInfo(fapp, analysisInfos.getSourceInfo, None))) Q(s6, r._1, r._2, v4) })}) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 74d58c6af..14ac52a6e 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -21,7 +21,7 @@ import viper.silicon.utils.ast.{BigAnd, extractPTypeFromExp, simplifyVariableNam import viper.silicon.utils.freshSnap import viper.silicon.verifier.Verifier import viper.silver.ast.utility.Statements -import viper.silver.ast.{EdgeType, EvalStackDependencyAnalysisJoin, JoinType, SimpleDependencyAnalysisMerge} +import viper.silver.ast.{Literal, _} import viper.silver.cfg.silver.SilverCfg import viper.silver.cfg.silver.SilverCfg.{SilverBlock, SilverEdge} import viper.silver.cfg.{ConditionalEdge, StatementBlock} @@ -425,7 +425,7 @@ object executor extends ExecutionRules { v.decider.assume(tRcvr !== Null, debugExp, debugExpSubst, analysisInfos) val eRcvr = Option.when(withExp)(Seq(x)) - val p = FullPerm + val p = terms.FullPerm val pExp = Option.when(withExp)(ast.FullPerm()(stmt.pos, stmt.info, stmt.errT)) def addFieldPerms(s: State, flds: Seq[ast.Field], v: Verifier) @@ -553,7 +553,7 @@ object executor extends ExecutionRules { val sepIdentifier = v.symbExLog.openScope(mcLog) val paramLog = new CommentRecord("Parameters", s, v.decider.pcs) val paramId = v.symbExLog.openScope(paramLog) - val eArgsWithDAInfo = SimpleDependencyAnalysisMerge.attachExpMergeInfo(eArgs) + val eArgsWithDAInfo = DependencyAnalysisMergeInfo.attachExpMergeInfo(eArgs, None) evals(s, eArgsWithDAInfo, _ => pveCall, v, analysisInfos)((s1, tArgs, eArgsNew, v1) => { v1.symbExLog.closeScope(paramId) val exampleTrafo = CounterexampleTransformer({ @@ -583,7 +583,7 @@ object executor extends ExecutionRules { val s2 = s1.copy(g = Store(fargs.zip(argsFreshVar)), recordVisited = true) - val presWithDAInfo = SimpleDependencyAnalysisMerge.attachExpMergeInfo(meth.pres.flatMap(_.topLevelConjuncts)) + val presWithDAInfo = DependencyAnalysisMergeInfo.attachExpMergeInfo(meth.pres.flatMap(_.topLevelConjuncts), Some(analysisInfos.getSourceInfo)) consumes(s2, presWithDAInfo, false, _ => pvePre, v1, analysisInfos.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Source, EdgeType.Up)))((s3, _, v2) => { v2.symbExLog.closeScope(preCondId) val postCondLog = new CommentRecord("Postcondition", s3, v2.decider.pcs) @@ -592,7 +592,7 @@ object executor extends ExecutionRules { val gOuts = Store(outs.map(x => (x, v2.decider.fresh(x))).toMap) val s4 = s3.copy(g = s3.g + gOuts, oldHeaps = s3.oldHeaps + (Verifier.PRE_STATE_LABEL -> magicWandSupporter.getEvalHeap(s1))) - val postsWithDAInfo = SimpleDependencyAnalysisMerge.attachExpMergeInfo(meth.posts.flatMap(_.topLevelConjuncts)) + val postsWithDAInfo = DependencyAnalysisMergeInfo.attachExpMergeInfo(meth.posts.flatMap(_.topLevelConjuncts), Some(analysisInfos.getSourceInfo)) produces(s4, freshSnap, postsWithDAInfo, _ => pveCallTransformed, v2, analysisInfos.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Sink, EdgeType.Down)))((s5, v3) => { v3.symbExLog.closeScope(postCondId) v3.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgress/tests/function-progress.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgress/tests/function-progress.vpr new file mode 100644 index 000000000..f18a00a3e --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgress/tests/function-progress.vpr @@ -0,0 +1,27 @@ + + +function div100(n: Int): Int + requires n > 0 + ensures result >= 0 +{ + 100/n +} + +method client() +{ + var n: Int, n2: Int, res: Int, d: Int + + n2 := 100 + assume n > 10 + + res := div100(n) + d := div100(n2) + res := res + d + + assert res >= 0 + + d := div100(n) + assert res == d + 1 + + res := res + div100(n) +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgress/tests/gaussian-progress.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgress/tests/gaussian-progress.vpr new file mode 100644 index 000000000..ca1f29ec9 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgress/tests/gaussian-progress.vpr @@ -0,0 +1,21 @@ +// spec quality: 5/5 +// proof quality (Peter): 1/3 +// proof quality (Lea): 2.625/3 +// progress (Peter): 0.333 +// progress (Lea): 0.875 + +method gaussianSimple(n: Int) returns (res: Int) + ensures res == n * (n + 1) / 2 +{ + assume 0 <= n + res := 0 + var i: Int := 0 + + while(i <= n) + invariant i <= (n + 1) + invariant res == (i - 1) * i / 2 + { + res := res + i + i := i + 1 + } +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgress/tests/progress1.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgress/tests/progress1.vpr new file mode 100644 index 000000000..23e189ffa --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgress/tests/progress1.vpr @@ -0,0 +1,34 @@ +// spec quality: 5/6 +// proof quality (Peter): 3/6 +// proof quality (Lea): 4.464 / 6 +// progress (Peter): 0.416 +// progress (Lea): 0.620 + +method progressTest(a: Int, b: Int) returns (res: Int) + requires a > 0 + ensures res == a + b + ensures res > 0 +{ + assume b >= 0 + res := a + b +} + +method client() +{ + var a: Int, b: Int, res: Int + assume a > 0 + assume b > 0 + + res := progressTest(a, b) + + var a2: Int, b2: Int, res2: Int + + a2 := 1 + + a2 := 10 + b2 := 10 + + res2 := progressTest(a2, b2) + + assert res2 == 20 +} \ No newline at end of file diff --git a/src/test/scala/DependencyAnalysisTestFramework.scala b/src/test/scala/DependencyAnalysisTestFramework.scala index e109db140..cbc374b03 100644 --- a/src/test/scala/DependencyAnalysisTestFramework.scala +++ b/src/test/scala/DependencyAnalysisTestFramework.scala @@ -23,7 +23,8 @@ trait DependencyAnalysisTestFramework { val ignores: Seq[String] var baseCommandLineArguments: Seq[String] = Seq("--timeout", "300" /* seconds */) var analysisCommandLineArguments: Seq[String] = - baseCommandLineArguments ++ Seq("--enableDependencyAnalysis", "--disableInfeasibilityChecks", "--proverArgs", "proof=true unsat-core=true") + baseCommandLineArguments ++ Seq("--enableDependencyAnalysis", "--disableInfeasibilityChecks", "--proverArgs", "proof=true unsat-core=true", + "--enableTempDirectory") def visitFiles(dirName: String, function: (String, String) => Unit): Unit = { val path = Paths.get(getClass.getClassLoader.getResource(dirName).toURI) From 5cf82c03cfbc172b049398dbbdea18382f88e045 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 10 Apr 2026 14:44:58 +0200 Subject: [PATCH 428/474] add some documentation --- src/main/scala/dependencyAnalysis/DependencyGraph.scala | 8 ++++---- .../DependencyAnalysisProgressSupporter.scala | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyGraph.scala b/src/main/scala/dependencyAnalysis/DependencyGraph.scala index 94207113a..aa3a09e61 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraph.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraph.scala @@ -26,8 +26,8 @@ trait ReadOnlyDependencyGraph[T <: DependencyGraphState] extends AbstractReadOnl def getAssumptionNodes: Seq[GeneralAssumptionNode] def getAssertionNodes: Seq[GeneralAssertionNode] def getDirectEdges: Map[Int, Set[Int]] // target -> direct dependencies - def getEdgesConnectingMethodsDownwards: Map[Int, Set[Int]] - def getEdgesConnectingMethodsUpwards: Map[Int, Set[Int]] + def getEdgesConnectingMethodsDownwards: Map[Int, Set[Int]] // e.g. edges connecting POSTcondition with method/function calls + def getEdgesConnectingMethodsUpwards: Map[Int, Set[Int]] // e.g. edges connecting PREconditions with method/function calls def getAllEdges: Map[Int, Set[Int]] // target -> direct dependencies def getAllEdges(includeUpwardEdges: Boolean, includeDownwardEdges: Boolean): Map[Int, Set[Int]] // target -> direct dependencies @@ -41,8 +41,8 @@ class DependencyGraph[T <: DependencyGraphState] extends ReadOnlyDependencyGraph private var assumptionNodes: mutable.Seq[GeneralAssumptionNode] = mutable.Seq() private var assertionNodes: mutable.Seq[GeneralAssertionNode] = mutable.Seq() private val edges: mutable.Map[Int, Set[Int]] = mutable.Map.empty - private val edgesConnectingMethodsDownwards: mutable.Map[Int, Set[Int]] = mutable.Map.empty // keep this, it's relevant for computing verification progress - private val edgesConnectingMethodsUpwards: mutable.Map[Int, Set[Int]] = mutable.Map.empty // keep this, it's relevant for computing verification progress + private val edgesConnectingMethodsDownwards: mutable.Map[Int, Set[Int]] = mutable.Map.empty // e.g. edges connecting POSTcondition with method/function calls + private val edgesConnectingMethodsUpwards: mutable.Map[Int, Set[Int]] = mutable.Map.empty // e.g. edges connecting PREconditions with method/function calls private var vacuousProofs: mutable.Seq[Int] = mutable.Seq() def getNodes: Seq[DependencyAnalysisNode] = getAssumptionNodes ++ getAssertionNodes diff --git a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisProgressSupporter.scala b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisProgressSupporter.scala index 9a53ba69b..63b0a1ca2 100644 --- a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisProgressSupporter.scala +++ b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisProgressSupporter.scala @@ -41,7 +41,7 @@ class DependencyAnalysisProgressSupporter[T <: DependencyGraphState](interpreter // recursively compute all interprocedural dependencies and cache results at procedure-boundaries val relevantInterProceduralEdges = traversalMode match { - case DATraversalMode.Upwards => dependencyGraph.getEdgesConnectingMethodsUpwards + case DATraversalMode.Upwards => dependencyGraph.getEdgesConnectingMethodsUpwards // TODO ake: increase precision, we are not using the low-level nodes properly here case DATraversalMode.Downwards => dependencyGraph.getEdgesConnectingMethodsDownwards } val interProceduralNodeIds = intraMethodDependencyIds.flatMap(n => relevantInterProceduralEdges.getOrElse(n, Set.empty)) From 505944c14dae246a947f5cfc79338852011613d8 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 10 Apr 2026 15:15:16 +0200 Subject: [PATCH 429/474] fix tests --- .../scala/dependencyAnalysis/DependencyAnalyzer.scala | 10 +++++----- src/test/scala/DependencyAnalysisTestFramework.scala | 5 ++--- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index 5b119da1e..29f6e6184 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -267,11 +267,11 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { customMergeDependencies = Set((sourceMergeInfos, targetMergeInfos)) ++ customMergeDependencies } - protected def addCustomMergeDependencies(): Unit = { + protected def addCustomMergeDependencies(mergedGraph: DependencyGraph[IntraProcedural]): Unit = { customMergeDependencies.foreach{ case (sourceMergeInfos, targetMergeInfos) => - val sourceNodes = getNodes.filter(node => sourceMergeInfos.contains(node.mergeInfo)).map(_.id) - val targetNodes = getNodes.filter(node => targetMergeInfos.contains(node.mergeInfo)).map(_.id) - dependencyGraph.addEdges(sourceNodes, targetNodes) + val sourceNodes = mergedGraph.getNodes.filter(node => sourceMergeInfos.contains(node.mergeInfo)).map(_.id) + val targetNodes = mergedGraph.getNodes.filter(node => targetMergeInfos.contains(node.mergeInfo)).map(_.id) + mergedGraph.addEdges(sourceNodes, targetNodes) } } @@ -301,7 +301,7 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { mergedGraph.addEdges(checks.map(_.id), notChecks.map(_.id)) // TODO ake: why do we need this? } - addCustomMergeDependencies() + addCustomMergeDependencies(mergedGraph) } /** diff --git a/src/test/scala/DependencyAnalysisTestFramework.scala b/src/test/scala/DependencyAnalysisTestFramework.scala index cbc374b03..969376ca1 100644 --- a/src/test/scala/DependencyAnalysisTestFramework.scala +++ b/src/test/scala/DependencyAnalysisTestFramework.scala @@ -23,8 +23,7 @@ trait DependencyAnalysisTestFramework { val ignores: Seq[String] var baseCommandLineArguments: Seq[String] = Seq("--timeout", "300" /* seconds */) var analysisCommandLineArguments: Seq[String] = - baseCommandLineArguments ++ Seq("--enableDependencyAnalysis", "--disableInfeasibilityChecks", "--proverArgs", "proof=true unsat-core=true", - "--enableTempDirectory") + baseCommandLineArguments ++ Seq("--enableDependencyAnalysis", "--disableInfeasibilityChecks", "--proverArgs", "proof=true unsat-core=true") def visitFiles(dirName: String, function: (String, String) => Unit): Unit = { val path = Paths.get(getClass.getClassLoader.getResource(dirName).toURI) @@ -254,7 +253,7 @@ trait DependencyAnalysisTestFramework { protected def checkAllDependencies(dependencyGraphInterpreter: DependencyGraphInterpreter[IntraProcedural]): Seq[String] = { val assertionNodes = getTestAssertionNodes(dependencyGraphInterpreter.getNonInternalAssertionNodes) - val dependencies = dependencyGraphInterpreter.getAllNonInternalDependencies(assertionNodes.map(_.id))map(_.id) + val dependencies = dependencyGraphInterpreter.getAllNonInternalDependencies(assertionNodes.map(_.id)).map(_.id) val relevantAssumptionNodes = getTestAssumptionNodes(dependencyGraphInterpreter.getNonInternalAssumptionNodes) val resRelevant: Seq[String] = checkDependenciesAndGetErrorMsgs(relevantAssumptionNodes, dependencies, isDependencyExpected = true, "Missing dependency") From 6c69f8b62d000629ecd1db2cb65b47d2dac86820 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 10 Apr 2026 17:02:19 +0200 Subject: [PATCH 430/474] add documentation and cleanup unused code --- silver | 2 +- .../DependencyAnalysisInfos.scala | 6 +- .../DependencyAnalysisNode.scala | 16 +++-- .../DependencyAnalyzer.scala | 33 +++++++--- .../dependencyAnalysis/DependencyGraph.scala | 64 ++++++++++++++++--- .../DependencyAnalysisProgressSupporter.scala | 18 ++++-- .../DependencyAnalysisPruningSupporter.scala | 8 +++ .../DependencyGraphInterpreter.scala | 7 +- 8 files changed, 122 insertions(+), 32 deletions(-) diff --git a/silver b/silver index 75f02f321..f63ebf53b 160000 --- a/silver +++ b/silver @@ -1 +1 @@ -Subproject commit 75f02f321e60619b49892720493d83d1ac1ca28a +Subproject commit f63ebf53b4114d5c8b9672a4424f2be9cbb114b7 diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisInfos.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisInfos.scala index 4e8e38c33..46c2f8e18 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisInfos.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisInfos.scala @@ -5,6 +5,10 @@ import viper.silver.ast import viper.silver.ast._ import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, DependencyType, StringAnalysisSourceInfo} +/** + * Stores all information about the currently evaluated statement/expression such that the dependency analysis can + * correctly add nodes and edges to the graph. + */ case class DependencyAnalysisInfos(sourceInfos: List[AnalysisSourceInfo], dependencyTypes: List[DependencyTypeInfo], mergeInfos: List[DependencyAnalysisMergeInfo], joinInfos: List[DependencyAnalysisJoinInfo], nodes: List[ast.Node]) { def addInfo(info: ast.Info, node: ast.Node): DependencyAnalysisInfos = { @@ -68,8 +72,6 @@ case class DependencyAnalysisInfos(sourceInfos: List[AnalysisSourceInfo], depend this.copy(joinInfos = joinInfo +: joinInfos) } - - def removeSource(): DependencyAnalysisInfos = this.copy(sourceInfos = List.empty) } object DependencyAnalysisInfos { diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala index dc4c6db9c..e6f082da0 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala @@ -3,11 +3,11 @@ package viper.silicon.dependencyAnalysis import viper.silicon.interfaces.state.Chunk import viper.silicon.state.terms.{False, Term, Var} import viper.silver.ast.{DependencyAnalysisMergeInfo, NoDependencyAnalysisMerge, Position, SimpleDependencyAnalysisJoin} -import viper.silver.dependencyAnalysis.{AbstractDependencyAnalysisNode, AnalysisSourceInfo, AssumptionType, NoAnalysisSourceInfo} import viper.silver.dependencyAnalysis.AssumptionType.AssumptionType +import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, NoAnalysisSourceInfo} -trait DependencyAnalysisNode extends AbstractDependencyAnalysisNode { +trait DependencyAnalysisNode { /** * The unique node id, which is also given to the SMT solver such that unsat cores can be mapped back to dependency nodes. @@ -29,9 +29,15 @@ trait DependencyAnalysisNode extends AbstractDependencyAnalysisNode { */ val assumptionType: AssumptionType + /** + * The merge info determines which nodes should be "merged" when lifting the graph to the user level. In reality, + * the nodes are connected by edges instead and are only partially merged. + */ val mergeInfo: DependencyAnalysisMergeInfo - + /** + * The join infos specify how the node should be joined with nodes of other verification component's graphs. + */ val joinInfos: List[SimpleDependencyAnalysisJoin] /** @@ -43,7 +49,7 @@ trait DependencyAnalysisNode extends AbstractDependencyAnalysisNode { def getUserLevelRepresentation: String = sourceInfo.toString def getSourceCodePosition: Position = sourceInfo.getPosition - /* + /** Some string representations, mainly used for debugging purposes. The strings represented to users are obtained via sourceInfo.toString and do not contain any low-level information about the node (such as the id or term). @@ -116,7 +122,7 @@ case class PermissionExhaleNode(chunk: Chunk, term: Term, sourceInfo: AnalysisSo } /** - * Label nodes are internally used nodes, mostly used to improve precision of the dependency analysis. + * Label nodes are internally-used nodes, mostly used to improve precision of the dependency analysis. * They are completely hidden from users. */ case class LabelNode(term: Var, _id: Option[Int]=None) extends GeneralAssumptionNode { diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index 29f6e6184..2d7b8c62e 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -30,8 +30,19 @@ trait DependencyAnalyzer { def createAssertOrCheckNode(term: Term, analysisInfos: DependencyAnalysisInfos, isCheck: Boolean): Option[GeneralAssertionNode] def addAssertFalseNode(isCheck: Boolean, analysisInfos: DependencyAnalysisInfos): Option[Int] def addInfeasibilityNode(isCheck: Boolean, analysisInfos: DependencyAnalysisInfos): Option[Int] + def addAssertionFailedNode(failedAssertion: Term, analysisInfos: DependencyAnalysisInfos): Option[Int] + /** + * Adds a dependency between all pairs of source and dest node ids. + */ def addDependency(source: Option[Int], dest: Option[Int]): Unit + + /** + * @param dep The UNSAT core as reported by Z3. + * @param assertionLabel the label of the assertion that was proven using the UNSAT core + * + * Parses the UNSAT core and adds all its components as dependencies of the provided assertion. + */ def processUnsatCoreAndAddDependencies(dep: String, assertionLabel: String): Unit /** @@ -41,17 +52,20 @@ trait DependencyAnalyzer { def addDependenciesForAbstractMembers(sourceExps: Seq[ast.Exp], targetExps: Seq[ast.Exp], analysisInfos: DependencyAnalysisInfos): Unit /** - * Adds an assertion and assumption node with the given analysis source info and dependencies to the current infeasibility node. + * Adds an assertion and assumption node with the given analysis source info including dependencies to the current infeasibility node. */ def addAssertionWithDepToInfeasNode(infeasNodeId: Option[Int], analysisInfos: DependencyAnalysisInfos): Unit = {} /** - * @return the final dependency graph representing all direct and transitive dependencies + * @return the final dependency graph representing all (direct and transitive) intraprocedural dependencies */ def buildFinalGraph(): Option[DependencyGraph[IntraProcedural]] - def addAssertionFailedNode(failedAssertion: Term, analysisInfos: DependencyAnalysisInfos): Option[Int] - + /** + * Stores the information that all nodes having a merge info in sourceExps should be connected to all nodes having a + * merge info in targetExps. + * These edges are eventually added when building the final graph ([[viper.silicon.dependencyAnalysis.DependencyAnalyzer#buildFinalGraph]]). + */ def addCustomDependenciesBetweenMergeInfos(sourceExps: Seq[Exp], targetExps: Seq[Exp]): Unit } @@ -94,9 +108,9 @@ object DependencyAnalyzer { /** * * @param name Optional name for the result graph. - * @param dependencyGraphInterpreters The graphs which should be joined. + * @param dependencyGraphInterpreters The graphs to be joined. * @return A dependency graph interpreter operating on a new dependency graph that represents all input graphs and - * dependencies between them. + * all dependencies between them. * The new graph is built by adding all existing nodes and edges of all input graphs and joining them * via the join information stored in each node. */ @@ -135,8 +149,6 @@ object DependencyAnalyzer { } class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { - protected var proofCoverage: Double = 0.0 - protected var customMergeDependencies: Set[(Set[DependencyAnalysisMergeInfo], Set[DependencyAnalysisMergeInfo])] = Set.empty override def getMember: Option[ast.Member] = Some(member) @@ -290,6 +302,11 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { Some(mergedGraph) } + /** + * Adds edges between the nodes with identical merge info and the ones to be connected as given by the custom merge dependencies + * to the merged graph. This step is necessary to ensure that the low-level graph is connected and encodes all direct + * and indirect dependencies. + */ private def addTransitiveEdges(mergedGraph: DependencyGraph[IntraProcedural]): Unit = { val nodesPerSourceInfo = mergedGraph.getNodes.filter(_.mergeInfo.isMerge).groupBy(_.mergeInfo) nodesPerSourceInfo foreach {case (_, nodes) => diff --git a/src/main/scala/dependencyAnalysis/DependencyGraph.scala b/src/main/scala/dependencyAnalysis/DependencyGraph.scala index aa3a09e61..9a3a52328 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraph.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraph.scala @@ -1,6 +1,6 @@ package viper.silicon.dependencyAnalysis -import viper.silver.dependencyAnalysis.{AbstractReadOnlyDependencyGraph, AssumptionType} +import viper.silver.dependencyAnalysis.AssumptionType import java.io.PrintWriter import java.nio.file.Paths @@ -11,6 +11,9 @@ import scala.collection.mutable object DependencyGraphHelper { private val idCounter: AtomicInteger = new AtomicInteger(0) + /** + * Helper function used to ensure uniqueness of all dependency node ids. + */ def nextId(): Int = { idCounter.getAndIncrement() } @@ -21,19 +24,55 @@ class Init extends DependencyGraphState class IntraProcedural extends DependencyGraphState class Final extends DependencyGraphState -trait ReadOnlyDependencyGraph[T <: DependencyGraphState] extends AbstractReadOnlyDependencyGraph { +trait ReadOnlyDependencyGraph[T <: DependencyGraphState] { def getNodes: Seq[DependencyAnalysisNode] def getAssumptionNodes: Seq[GeneralAssumptionNode] def getAssertionNodes: Seq[GeneralAssertionNode] - def getDirectEdges: Map[Int, Set[Int]] // target -> direct dependencies - def getEdgesConnectingMethodsDownwards: Map[Int, Set[Int]] // e.g. edges connecting POSTcondition with method/function calls - def getEdgesConnectingMethodsUpwards: Map[Int, Set[Int]] // e.g. edges connecting PREconditions with method/function calls - def getAllEdges: Map[Int, Set[Int]] // target -> direct dependencies - def getAllEdges(includeUpwardEdges: Boolean, includeDownwardEdges: Boolean): Map[Int, Set[Int]] // target -> direct dependencies + /** + * @return a map from node to the set of direct dependencies in the intraprocedural low-level graph + */ + def getDirectEdges: Map[Int, Set[Int]] + + /** + * @return all interprocedural downward edges in the graph as a map from node to all its direct downward dependencies. + * A downward edge connects a node representing the proof of a property to a node representing the assumption of + * said property in another verification component. + * For example, a downward edge may connect a postcondition with a corresponding method call. + */ + def getEdgesConnectingMethodsDownwards: Map[Int, Set[Int]] + + /** + * @return all interprocedural upwards edges in the graph as a map from node to all its direct upwards dependencies. + * An upwards edge connects a node justifying an assumption (by proving it) to a node representing the specification + * element depending on it in another verification component. + * For example, an upwards edge may connect a method call with a corresponding precondition. + */ + def getEdgesConnectingMethodsUpwards: Map[Int, Set[Int]] // e.g. edges connecting PREconditions with method/function calls + def getAllEdges: Map[Int, Set[Int]] + def getAllEdges(includeUpwardEdges: Boolean, includeDownwardEdges: Boolean): Map[Int, Set[Int]] + + /** + * @param sources a set of node ids + * @param includeInfeasibilityNodes if set to true, dependencies found via infeasibility nodes are included in the result + * @param includeUpwardEdges if set to true, interprocedural upward edges are taken into account + * @param includeDownwardEdges if set to true, interprocedural downward edges are taken into account + * @return the set of dependencies of the provided sources + */ def getAllDependencies(sources: Set[Int], includeInfeasibilityNodes: Boolean, includeUpwardEdges: Boolean, includeDownwardEdges: Boolean): Set[Int] + + /** + * @param sources a set of node ids + * @param includeInfeasibilityNodes if set to true, dependents found via infeasibility nodes are included in the result + * @param includeUpwardEdges if set to true, interprocedural upward edges are taken into account + * @param includeDownwardEdges if set to true, interprocedural downward edges are taken into account + * @return the set of dependents of the provided sources + */ def getAllDependents(sources: Set[Int], includeInfeasibilityNodes: Boolean, includeUpwardEdges: Boolean, includeDownwardEdges: Boolean): Set[Int] + /** + * Exports the graph to the folder 'dirName'. + */ def exportGraph(dirName: String): Unit } @@ -43,7 +82,7 @@ class DependencyGraph[T <: DependencyGraphState] extends ReadOnlyDependencyGraph private val edges: mutable.Map[Int, Set[Int]] = mutable.Map.empty private val edgesConnectingMethodsDownwards: mutable.Map[Int, Set[Int]] = mutable.Map.empty // e.g. edges connecting POSTcondition with method/function calls private val edgesConnectingMethodsUpwards: mutable.Map[Int, Set[Int]] = mutable.Map.empty // e.g. edges connecting PREconditions with method/function calls - private var vacuousProofs: mutable.Seq[Int] = mutable.Seq() + private var vacuousProofs: mutable.Seq[Int] = mutable.Seq() def getNodes: Seq[DependencyAnalysisNode] = getAssumptionNodes ++ getAssertionNodes def getAssumptionNodes: Seq[GeneralAssumptionNode] = assumptionNodes.toSeq @@ -186,6 +225,9 @@ class DependencyGraph[T <: DependencyGraphState] extends ReadOnlyDependencyGraph visited } + /** + * Removes the provided nodes while perceiving the transitive closure by adding edges between the predecessors and successors. + */ private def removeAllEdgesForNode(node: DependencyAnalysisNode): Unit = { val id = node.id val predecessors = (edges filter { case (_, t) => t.contains(id) }).keys @@ -195,6 +237,9 @@ class DependencyGraph[T <: DependencyGraphState] extends ReadOnlyDependencyGraph } + /** + * Removes all label nodes while perceiving the transitive closure by adding edges between the predecessors and successors. + */ def removeLabelNodes(): Unit = { def filterCriteria(n: DependencyAnalysisNode) = n.isInstanceOf[LabelNode] @@ -202,6 +247,9 @@ class DependencyGraph[T <: DependencyGraphState] extends ReadOnlyDependencyGraph assumptionNodes = assumptionNodes filterNot filterCriteria } + /** + * Removes internal nodes while perceiving the transitive closure by adding edges between the predecessors and successors. + */ def removeInternalNodes(): Unit = { def filterCriteria(n: DependencyAnalysisNode) = AssumptionType.internalTypes.contains(n.assumptionType) && !AssumptionType.CustomInternal.equals(n.assumptionType) diff --git a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisProgressSupporter.scala b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisProgressSupporter.scala index 63b0a1ca2..1eb962563 100644 --- a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisProgressSupporter.scala +++ b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisProgressSupporter.scala @@ -16,13 +16,13 @@ class DependencyAnalysisProgressSupporter[T <: DependencyGraphState](interpreter computeVerificationProgressOptimized(enableDebugging) } - /** * Computes all dependencies of a given dependency node. Intermediate results are cached at procedure-boundaries. * That is, the dependencies of each pre- and postcondition are cached and can thus be reused for subsequent computations. * We do not cache intraprocedural dependencies to allow for precise computation of dependencies using the low-level graph. */ val deps: DAMemo[(AnalysisSourceInfo, DATraversalMode), Set[CompactUserLevelDependencyAnalysisNode]] = DAMemo { case (assertionNode, mode) => + // TODO ake: maybe this should be moved to the DependencyGraphInterpreter such that other queries can be optimized as well. def computeDependencies(currentNode: AnalysisSourceInfo, visited: Set[(AnalysisSourceInfo, DATraversalMode)], traversalMode: DATraversalMode): Set[CompactUserLevelDependencyAnalysisNode] = { if (visited.contains((currentNode, traversalMode))) { return Set.empty // break cycles to avoid infinite loops @@ -112,7 +112,11 @@ class DependencyAnalysisProgressSupporter[T <: DependencyGraphState](interpreter private def getAssertionsRelevantForProgress: Map[AnalysisSourceInfo, Set[DependencyAnalysisNode]] = sourceToAssertionNodesMap.filter(ass => ass._2.map(_.assumptionType).intersect(AssumptionType.importedTypes).isEmpty) /** - * @return the verification progress of the entire program + * @return the verification progress of the entire program. + * Verification progress is defined as the product of specification quality and proof quality. + * Specification quality is the fraction of source code statements that is a dependency of any proof obligation. + * Proof quality is defined as the average assertion quality over all proof obligations. + * Assertion quality of an assertion a is the fraction of non-assumption dependencies over all dependencies of the assertion a. */ def computeVerificationProgressOptimized(enableDebugOutput: Boolean = false): (Double, Double) = { @@ -156,6 +160,10 @@ class DependencyAnalysisProgressSupporter[T <: DependencyGraphState](interpreter } + /** + * Computes the specification quality given the covered nodes. Specification quality is defined as the fraction of + * all (user-level) nodes in the graph that are covered. + */ private def computeSpecQuality(coveredNodes: Set[CompactUserLevelDependencyAnalysisNode], enableDebugOutput: Boolean = false): Double = { val explicitAssertions = toCompactUserLevelNodes(interpreter.getExplicitAssertionNodes) @@ -177,8 +185,7 @@ class DependencyAnalysisProgressSupporter[T <: DependencyGraphState](interpreter /** - * - * @return a list of assumption nodes ordered by their impact on proof quality + * @return a list of assumption nodes ordered by their impact on proof quality. */ def computeAssumptionRanking(): List[(String, Double)] = { val allAssertions = interpreter.toUserLevelNodes(interpreter.getNonInternalAssertionNodes).filter(ass => ass.assertionTypes.intersect(AssumptionType.importedTypes).isEmpty) @@ -206,6 +213,9 @@ class DependencyAnalysisProgressSupporter[T <: DependencyGraphState](interpreter allAssertions.filter(assertion => assertion.hasFailures || assertion.assertionTypes.contains(AssumptionType.ExplicitPostcondition)).getSourceSet() } + /** + * Prints all uncovered source code statements and returns the number of uncovered source code statements. + */ def computeUncoveredStatements(): Int = { val allAssertions = interpreter.toUserLevelNodes(interpreter.getNonInternalAssertionNodes) val allDependencies = allAssertions.flatMap(ass => interpreter.toUserLevelNodes(interpreter.getAllNonInternalDependencies(ass.lowerLevelNodes.map(_.id))).diffBySource(Set(ass))).getSourceSet() diff --git a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisPruningSupporter.scala b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisPruningSupporter.scala index c3f6fc935..937634f61 100644 --- a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisPruningSupporter.scala +++ b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisPruningSupporter.scala @@ -11,6 +11,9 @@ import java.io.PrintWriter class DependencyAnalysisPruningSupporter[T <: DependencyGraphState](interpreter: DependencyGraphInterpreter[T]) { + /** + * Prunes the program. That is, all statements and expressions that are not a crucial node are removed from the program. + */ def getPrunedProgram(crucialNodes: Set[DependencyAnalysisNode], program: ast.Program): (ast.Program, Double) = { def isCrucialExp(exp: ast.Exp, crucialNodesWithExpInfo: Set[AnalysisSourceInfo]): Boolean = { @@ -99,6 +102,11 @@ class DependencyAnalysisPruningSupporter[T <: DependencyGraphState](interpreter: queriedNodes ++ dependencies } + /** + * Prunes the given program with respect to the queries nodes. That is, all statements and expressions that are neither + * one of the queried nodes nor a dependencies of a queried node are removed from the program. The program is exported + * to the given export file. + */ def pruneProgramAndExport(queriedNodes: Set[DependencyAnalysisNode], program: ast.Program, exportFileName: String): Unit = { val writer = new PrintWriter(exportFileName) diff --git a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala index 1fea58709..61c1b276a 100644 --- a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala @@ -11,14 +11,11 @@ import viper.silver.dependencyAnalysis.{AbstractDependencyGraphInterpreter, Assu import java.io.PrintWriter import java.nio.file.Paths - - object DATraversalMode extends Enumeration { type DATraversalMode = Value val Upwards, Downwards = Value } - class DependencyGraphInterpreter[T <: DependencyGraphState](name: String, dependencyGraph: ReadOnlyDependencyGraph[T], errors: List[Failure], member: Option[ast.Member]=None) extends AbstractDependencyGraphInterpreter { def getGraph: ReadOnlyDependencyGraph[T] = dependencyGraph @@ -39,7 +36,7 @@ class DependencyGraphInterpreter[T <: DependencyGraphState](name: String, depend def getErrors: List[Failure] = errors - // TODO ake: join nodes are not needed for the final graph + // TODO ake: join nodes are not needed for the final graph. Maybe we can outsource this to a dedicated intraprocedural graph interpreter class. val joinSinkNodes: Set[DependencyAnalysisNode] = getJoinCandidateNodes(getNodes).filter(_.joinInfos.exists(_.joinType.equals(JoinType.Sink))) val joinSourceNodes: Set[DependencyAnalysisNode] = getJoinCandidateNodes(getNodes).filter(_.joinInfos.exists(_.joinType.equals(JoinType.Source))) @@ -136,11 +133,13 @@ class DependencyGraphInterpreter[T <: DependencyGraphState](name: String, depend getNodes filter (node => sourceInfos.contains(node.sourceInfo)) } + // TODO ake: might be deprecated def computeProofCoverage(): (Double, Set[String]) = { val explicitAssertionNodes = getNodesWithIdenticalSource(getExplicitAssertionNodes) computeProofCoverage(explicitAssertionNodes) } + // TODO ake: might be deprecated def computeProofCoverage(assertionNodes: Set[DependencyAnalysisNode]): (Double, Set[String]) = { val assertionNodeIds = assertionNodes map (_.id) val dependencies = dependencyGraph.getAllDependencies(assertionNodeIds, includeInfeasibilityNodes = true, includeUpwardEdges = true, includeDownwardEdges = true) From 4ae1874715b3c0a5b54805595b2be157e864ff84 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 10 Apr 2026 18:22:53 +0200 Subject: [PATCH 431/474] adapt Gobra DA implementation to new design --- .../dependencyAnalysis/DependencyAnalysisInfos.scala | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisInfos.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisInfos.scala index 46c2f8e18..179506c06 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisInfos.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisInfos.scala @@ -3,7 +3,7 @@ package viper.silicon.dependencyAnalysis import viper.silicon.verifier.Verifier import viper.silver.ast import viper.silver.ast._ -import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, DependencyType, StringAnalysisSourceInfo} +import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyType, StringAnalysisSourceInfo} /** * Stores all information about the currently evaluated statement/expression such that the dependency analysis can @@ -48,9 +48,11 @@ case class DependencyAnalysisInfos(sourceInfos: List[AnalysisSourceInfo], depend this.copy(sourceInfos = source +: sourceInfos) } - def getSourceInfo: AnalysisSourceInfo = sourceInfos.head + def getSourceInfo: AnalysisSourceInfo = + sourceInfos.headOption.getOrElse(nodes.headOption.map(AnalysisSourceInfo.createAnalysisSourceInfo).getOrElse(StringAnalysisSourceInfo("Unknown", NoPosition))) - def getDependencyType: DependencyType = dependencyTypes.head.dependencyType + def getDependencyType: DependencyType = + dependencyTypes.headOption.map(_.dependencyType).getOrElse(DependencyType.make(AssumptionType.Unknown)) def getMergeInfo: DependencyAnalysisMergeInfo = mergeInfos.headOption.getOrElse(SimpleDependencyAnalysisMerge(getSourceInfo)) From 86c8f939b22049b9ceceafb923b5767c61eaa133 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 10 Apr 2026 18:23:20 +0200 Subject: [PATCH 432/474] update submodules --- silver | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/silver b/silver index f63ebf53b..b2584ff4d 160000 --- a/silver +++ b/silver @@ -1 +1 @@ -Subproject commit f63ebf53b4114d5c8b9672a4424f2be9cbb114b7 +Subproject commit b2584ff4d43bcb62639d585e9377a275d7068c54 From a195706b7a4c02093610dfbf21a204cfb227c6ac Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 10 Apr 2026 21:40:33 +0200 Subject: [PATCH 433/474] adapt solution for Gobra interfaces --- silver | 2 +- src/main/scala/decider/Decider.scala | 19 +++++++++++++-- .../DependencyAnalysisInfos.scala | 2 +- .../DependencyAnalysisNode.scala | 4 ++-- .../DependencyAnalyzer.scala | 3 ++- .../cliTool/DependencyGraphImporter.scala | 2 +- .../DependencyGraphInterpreter.scala | 4 ++-- src/main/scala/rules/Brancher.scala | 9 +++---- src/main/scala/rules/Consumer.scala | 2 +- src/main/scala/rules/Evaluator.scala | 6 ++--- src/main/scala/rules/Executor.scala | 12 +++++----- src/main/scala/rules/HeapSupporter.scala | 3 +-- src/main/scala/rules/Producer.scala | 2 +- .../scala/rules/QuantifiedChunkSupport.scala | 3 +-- .../scala/supporters/MethodSupporter.scala | 23 +----------------- .../supporters/functions/FunctionData.scala | 4 ++-- .../functions/FunctionVerificationUnit.scala | 24 ++----------------- 17 files changed, 49 insertions(+), 75 deletions(-) diff --git a/silver b/silver index b2584ff4d..80c867d06 160000 --- a/silver +++ b/silver @@ -1 +1 @@ -Subproject commit b2584ff4d43bcb62639d585e9377a275d7068c54 +Subproject commit 80c867d06c7523d65c3a7d780ff6a9dcc7672b3e diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 521516662..40d96c5d8 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -20,8 +20,9 @@ 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, Member, NoPosition} +import viper.silver.ast.{Info, LocalVarWithVersion, Member, NoPosition} import viper.silver.components.StatefulComponent +import viper.silver.dependencyAnalysis.{AdditionalAssertionNode, AdditionalAssumptionNode, AdditionalDependencyNodeInfo} import viper.silver.parser.{PKw, PPrimitiv, PReserved, PType} import viper.silver.reporter.{ConfigurationConfirmation, InternalWarningMessage} import viper.silver.verifier.{DependencyNotFoundError, Model} @@ -80,7 +81,9 @@ trait Decider { * 2. The implementation reacts to a failing assertion by e.g. a state consolidation */ - def assert(t: Term, analysisInfos: DependencyAnalysisInfos)(Q: Boolean => VerificationResult): VerificationResult + def handleAndGetUpdatedAnalysisInfos(analysisInfos: DependencyAnalysisInfos, info: Info, node: ast.Node): DependencyAnalysisInfos + + def assert(t: Term, analysisInfos: DependencyAnalysisInfos)(Q: Boolean => VerificationResult): VerificationResult def assert(t: Term, analysisInfos: DependencyAnalysisInfos, timeout: Option[Int])(Q: Boolean => VerificationResult): VerificationResult def fresh(id: String, sort: Sort, ptype: Option[PType]): Var @@ -526,6 +529,18 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } } + def handleAndGetUpdatedAnalysisInfos(analysisInfos: DependencyAnalysisInfos, info: Info, node: ast.Node): DependencyAnalysisInfos = { + val newAnalysisInfos = analysisInfos.addInfo(info, node) + info.getAllInfos[AdditionalDependencyNodeInfo].foreach { + case AdditionalAssertionNode() => dependencyAnalyzer.createAssertOrCheckNode(True, newAnalysisInfos, isCheck = false).foreach(n => { + dependencyAnalyzer.addAssertionNode(n) + if(isPathInfeasible) dependencyAnalyzer.addDependency(pcs.getCurrentInfeasibilityNode, Some(n.id)) + }) + case AdditionalAssumptionNode() => dependencyAnalyzer.addAssumption(True, newAnalysisInfos) + } + newAnalysisInfos + } + def check(t: Term, timeout: Int, analysisInfos: DependencyAnalysisInfos): Boolean = { deciderAssert(t, analysisInfos, Some(timeout), isCheck=true)._1 } diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisInfos.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisInfos.scala index 179506c06..f968fe3ce 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisInfos.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisInfos.scala @@ -3,7 +3,7 @@ package viper.silicon.dependencyAnalysis import viper.silicon.verifier.Verifier import viper.silver.ast import viper.silver.ast._ -import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyType, StringAnalysisSourceInfo} +import viper.silver.dependencyAnalysis._ /** * Stores all information about the currently evaluated statement/expression such that the dependency analysis can diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala index e6f082da0..d471da068 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala @@ -2,9 +2,9 @@ package viper.silicon.dependencyAnalysis import viper.silicon.interfaces.state.Chunk import viper.silicon.state.terms.{False, Term, Var} -import viper.silver.ast.{DependencyAnalysisMergeInfo, NoDependencyAnalysisMerge, Position, SimpleDependencyAnalysisJoin} +import viper.silver.ast.Position import viper.silver.dependencyAnalysis.AssumptionType.AssumptionType -import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, NoAnalysisSourceInfo} +import viper.silver.dependencyAnalysis._ trait DependencyAnalysisNode { diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index 2d7b8c62e..b7b2cb047 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -5,8 +5,9 @@ import viper.silicon.interfaces.state.{Chunk, GeneralChunk} import viper.silicon.state.terms.{NoPerm, _} import viper.silicon.verifier.Verifier import viper.silver.ast -import viper.silver.ast.JoinType.JoinType import viper.silver.ast._ +import viper.silver.dependencyAnalysis.JoinType.JoinType +import viper.silver.dependencyAnalysis.{DependencyAnalysisMergeInfo, EdgeType, EvalStackDependencyAnalysisJoin, JoinType} import scala.collection.mutable diff --git a/src/main/scala/dependencyAnalysis/cliTool/DependencyGraphImporter.scala b/src/main/scala/dependencyAnalysis/cliTool/DependencyGraphImporter.scala index af75d0c8f..e98caf2f6 100644 --- a/src/main/scala/dependencyAnalysis/cliTool/DependencyGraphImporter.scala +++ b/src/main/scala/dependencyAnalysis/cliTool/DependencyGraphImporter.scala @@ -10,7 +10,7 @@ import viper.silicon.state.terms.sorts.Bool import viper.silicon.state.terms.{NoPerm, Term, True, Var} import viper.silver.ast import viper.silver.ast._ -import viper.silver.dependencyAnalysis.{AssumptionType, StringAnalysisSourceInfo} +import viper.silver.dependencyAnalysis.{AssumptionType, SimpleDependencyAnalysisJoin, SimpleDependencyAnalysisMerge, StringAnalysisSourceInfo} import viper.silver.frontend.SilFrontend import java.nio.file.Paths diff --git a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala index 61c1b276a..b6faae7ec 100644 --- a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala @@ -5,8 +5,8 @@ import viper.silicon.dependencyAnalysis._ import viper.silicon.interfaces.Failure import viper.silicon.verifier.Verifier import viper.silver.ast -import viper.silver.ast.{JoinType, Program} -import viper.silver.dependencyAnalysis.{AbstractDependencyGraphInterpreter, AssumptionType} +import viper.silver.ast.Program +import viper.silver.dependencyAnalysis.{AbstractDependencyGraphInterpreter, AssumptionType, JoinType} import java.io.PrintWriter import java.nio.file.Paths diff --git a/src/main/scala/rules/Brancher.scala b/src/main/scala/rules/Brancher.scala index 5d4f495be..e04ec4001 100644 --- a/src/main/scala/rules/Brancher.scala +++ b/src/main/scala/rules/Brancher.scala @@ -8,9 +8,9 @@ package viper.silicon.rules import viper.silicon.common.concurrency._ import viper.silicon.decider.PathConditionStack -import viper.silicon.dependencyAnalysis.{DependencyAnalysisInfos, DependencyAnalyzer} +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfos import viper.silicon.interfaces.{Unreachable, VerificationResult} -import viper.silicon.reporting.{condenseToViperResult, convertToViperResult} +import viper.silicon.reporting.condenseToViperResult import viper.silicon.state.State import viper.silicon.state.terms.{FunctionDecl, MacroDecl, Not, Term} import viper.silicon.verifier.Verifier @@ -47,7 +47,7 @@ object brancher extends BranchingRules { : VerificationResult = { if(v.decider.isPathInfeasible){ - val analysisInfos1 = analysisInfos.addInfo(conditionExp._1.info, conditionExp._1) + val analysisInfos1 = v.decider.handleAndGetUpdatedAnalysisInfos(analysisInfos, conditionExp._1.info, conditionExp._1) // FIXME ake: infeasible path // val assertionNodesForJoin = DependencyAnalyzer.extractAssertionsForJoin(conditionExp._1, s.program) // assertionNodesForJoin.foreach(n => v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, CompositeAnalysisSourceInfo(v.decider.analysisSourceInfoStack.getFullSourceInfo, AnalysisSourceInfo.createAnalysisSourceInfo(n)), v.decider.analysisSourceInfoStack.getDependencyType, isJoinNode=true)) @@ -73,7 +73,8 @@ object brancher extends BranchingRules { && s.quantifiedVariables.map(_._1).exists(condition.freeVariables.contains)) ) - val analysisInfos1 = analysisInfos.addInfo(conditionExp._1.info, conditionExp._1) + val analysisInfos1 = v.decider.handleAndGetUpdatedAnalysisInfos(analysisInfos, conditionExp._1.info, conditionExp._1) + /* True if the then-branch is to be explored */ val executeThenBranch = ( skipPathFeasibilityCheck diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index 294b5746f..e5ccba532 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -180,7 +180,7 @@ object consumer extends ConsumptionRules { val s1 = s0.copy(h = s.h) /* s1 is s, but the retrying flag might be set */ val sepIdentifier = v1.symbExLog.openScope(new ConsumeRecord(a, s1, v.decider.pcs)) - val analysisInfos1 = analysisInfos.addInfo(a.info, a) + val analysisInfos1 = v1.decider.handleAndGetUpdatedAnalysisInfos(analysisInfos, a.info, a) consumeTlc(s1, h0, a, returnSnap, pve, v1, analysisInfos1)((s2, h2, snap2, v2) => { v2.symbExLog.closeScope(sepIdentifier) diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index bfc66d890..d6ed3770b 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -24,8 +24,8 @@ import viper.silicon.verifier.Verifier import viper.silicon.{Map, TriggerSets} import viper.silver.ast import viper.silver.ast.utility.Expressions -import viper.silver.ast.{AnnotationInfo, DependencyAnalysisMergeInfo, EdgeType, EvalStackDependencyAnalysisJoin, JoinType, LocalVarWithVersion, NoDependencyAnalysisMerge, SimpleDependencyAnalysisMerge, WeightedQuantifier} -import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, DependencyType} +import viper.silver.ast.{AnnotationInfo, LocalVarWithVersion, WeightedQuantifier} +import viper.silver.dependencyAnalysis._ import viper.silver.reporter.{AnnotationWarning, WarningsDuringVerification} import viper.silver.utility.Common.Rational import viper.silver.verifier.errors.{ErrorWrapperWithExampleTransformer, PreconditionInAppFalse} @@ -89,7 +89,7 @@ object evaluator extends EvaluationRules { : VerificationResult = { val sepIdentifier = v.symbExLog.openScope(new EvaluateRecord(e, s, v.decider.pcs)) - val analysisInfos1 = analysisInfos.addInfo(e.info, e) + val analysisInfos1 = v.decider.handleAndGetUpdatedAnalysisInfos(analysisInfos, e.info, e) eval3(s, e, pve, v, analysisInfos1)((s1, t, eNew, v1) => { v1.symbExLog.closeScope(sepIdentifier) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 14ac52a6e..80afbd7d4 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -20,12 +20,12 @@ import viper.silicon.state.terms._ import viper.silicon.utils.ast.{BigAnd, extractPTypeFromExp, simplifyVariableName} import viper.silicon.utils.freshSnap import viper.silicon.verifier.Verifier +import viper.silver.ast.Literal import viper.silver.ast.utility.Statements -import viper.silver.ast.{Literal, _} import viper.silver.cfg.silver.SilverCfg import viper.silver.cfg.silver.SilverCfg.{SilverBlock, SilverEdge} import viper.silver.cfg.{ConditionalEdge, StatementBlock} -import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, DependencyType} +import viper.silver.dependencyAnalysis._ import viper.silver.verifier.errors._ import viper.silver.verifier.reasons._ import viper.silver.verifier.{CounterexampleTransformer, NullPartialVerificationError, PartialVerificationError} @@ -70,7 +70,7 @@ object executor extends ExecutionRules { val condEdgeRecord = new ConditionalEdgeRecord(ce.condition, s, v.decider.pcs) val sepIdentifier = v.symbExLog.openScope(condEdgeRecord) val s1 = handleOutEdge(s, edge, v) - val analysisInfos = DefaultDependencyAnalysisInfos.addInfo(ce.condition.info, ce.condition) + val analysisInfos = v.decider.handleAndGetUpdatedAnalysisInfos(DefaultDependencyAnalysisInfos, ce.condition.info, ce.condition) eval(s1, ce.condition, IfFailed(ce.condition), v, analysisInfos)((s2, tCond, condNew, v1) => /* Using branch(...) here ensures that the edge condition is recorded * as a branch condition on the pathcondition stack. @@ -150,7 +150,7 @@ object executor extends ExecutionRules { case _ => false }) - val analysisInfos = DefaultDependencyAnalysisInfos.addInfo(cedge1.condition.info, cedge1.condition) + val analysisInfos = v.decider.handleAndGetUpdatedAnalysisInfos(DefaultDependencyAnalysisInfos, cedge1.condition.info, cedge1.condition) eval(s, cedge1.condition, pvef(cedge1.condition), v, analysisInfos)((s1, t0, condNew, v1) => // The type arguments here are Null because there is no need to pass any join data. @@ -186,7 +186,7 @@ object executor extends ExecutionRules { if Verifier.config.parallelizeBranches() && cond2 == ast.Not(cond1)() => val condEdgeRecord = new ConditionalEdgeRecord(thenEdge.condition, s, v.decider.pcs) val sepIdentifier = v.symbExLog.openScope(condEdgeRecord) - val analysisInfos = DefaultDependencyAnalysisInfos.addInfo(thenEdge.condition.info, thenEdge.condition) + val analysisInfos = v.decider.handleAndGetUpdatedAnalysisInfos(DefaultDependencyAnalysisInfos, thenEdge.condition.info, thenEdge.condition) val res = eval(s, thenEdge.condition, IfFailed(thenEdge.condition), v, analysisInfos)((s2, tCond, eCondNew, v1) => brancher.branch(s2, tCond, (thenEdge.condition, eCondNew), v1, analysisInfos)( (s3, v3) => { @@ -342,7 +342,7 @@ object executor extends ExecutionRules { (Q: (State, Verifier) => VerificationResult) : VerificationResult = { val sepIdentifier = v.symbExLog.openScope(new ExecuteRecord(stmt, s, v.decider.pcs)) - val analysisInfos = DefaultDependencyAnalysisInfos.addInfo(stmt.info, stmt) + val analysisInfos = v.decider.handleAndGetUpdatedAnalysisInfos(DefaultDependencyAnalysisInfos, stmt.info, stmt) exec2(s, stmt, v, analysisInfos)((s1, v1) => { v1.symbExLog.closeScope(sepIdentifier) Q(s1, v1) diff --git a/src/main/scala/rules/HeapSupporter.scala b/src/main/scala/rules/HeapSupporter.scala index 0d5b47d84..6b3057a70 100644 --- a/src/main/scala/rules/HeapSupporter.scala +++ b/src/main/scala/rules/HeapSupporter.scala @@ -24,8 +24,7 @@ import viper.silicon.utils.ast.{BigAnd, replaceVarsInExp} import viper.silicon.utils.freshSnap import viper.silicon.verifier.Verifier import viper.silver.ast -import viper.silver.ast.SimpleDependencyAnalysisMerge -import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, DependencyType} +import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, DependencyType, SimpleDependencyAnalysisMerge} import viper.silver.parser.PUnknown import viper.silver.verifier.reasons.{InsufficientPermission, MagicWandChunkNotFound} import viper.silver.verifier.{ErrorReason, PartialVerificationError, VerificationError} diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index a9a7c3f99..ca69370b5 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -151,7 +151,7 @@ object producer extends ProductionRules { val a = as.head.whenInhaling val pve = pves.head - val analysisInfos1 = analysisInfos.addInfo(a.info, a) + val analysisInfos1 = v.decider.handleAndGetUpdatedAnalysisInfos(analysisInfos, a.info, a) if (as.tail.isEmpty) wrappedProduceTlc(s, sf, a, pve, v, analysisInfos1)((s1, v1) => { Q(s1, v1) diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 23b941def..b780f4363 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -27,8 +27,7 @@ import viper.silicon.utils.freshSnap import viper.silicon.utils.notNothing.NotNothing import viper.silicon.verifier.Verifier import viper.silver.ast -import viper.silver.ast.NoDependencyAnalysisMerge -import viper.silver.dependencyAnalysis.DependencyType +import viper.silver.dependencyAnalysis.{DependencyType, NoDependencyAnalysisMerge} import viper.silver.parser.PUnknown import viper.silver.reporter.InternalWarningMessage import viper.silver.verifier.reasons.{InsufficientPermission, MagicWandChunkNotFound} diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index 3180fe11f..3f7b12af5 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -20,9 +20,8 @@ import viper.silicon.state.{State, Store} import viper.silicon.utils.freshSnap import viper.silicon.verifier.{Verifier, VerifierComponent} import viper.silver.ast -import viper.silver.ast._ import viper.silver.components.StatefulComponent -import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyAnalysisJoinNodeInfo} +import viper.silver.dependencyAnalysis._ import viper.silver.verifier.errors._ /* TODO: Consider changing the DefaultMethodVerificationUnitProvider into a SymbolicExecutionRule */ @@ -87,26 +86,6 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif val analysisInfosPrecondition = DependencyAnalysisInfos.DefaultDependencyAnalysisInfos.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Sink, EdgeType.Up)) val analysisInfosPostcondition = DependencyAnalysisInfos.DefaultDependencyAnalysisInfos.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Source, EdgeType.Down)) - - val daJoinNodeInfoOpt = method.info.getUniqueInfo[DependencyAnalysisJoinNodeInfo] - // TODO ake: frontend join -// if(daJoinNodeInfoOpt.isDefined){ -// val infodaJoinNodeInfo = daJoinNodeInfoOpt.get -// // v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(infodaJoinNodeInfo.sourceInfo, DependencyType.make(AssumptionType.CustomInternal)) -// val postCondNodes = (posts ++ pres).flatMap(_.topLevelConjuncts).map(pc => SimpleAssumptionNode(True, None, AnalysisSourceInfo.createAnalysisSourceInfo(pc), AssumptionType.ImplicitPostcondition, isClosed=false, isJoinNode=true)) -// val postCondAssertNodes = (posts ++ pres).flatMap(_.topLevelConjuncts).map(pc => SimpleAssertionNode(True, AnalysisSourceInfo.createAnalysisSourceInfo(pc), AssumptionType.ImplicitPostcondition, isClosed=false, isJoinNode=true)) -// val customJoinNode = infodaJoinNodeInfo.getAssertionNode -// postCondNodes foreach v.decider.dependencyAnalyzer.addAssumptionNode -// postCondAssertNodes foreach v.decider.dependencyAnalyzer.addAssertionNode -// -// v.decider.dependencyAnalyzer.addAssertionNode(customJoinNode) -// -// postCondNodes foreach (n => v.decider.dependencyAnalyzer.addDependency(Some(customJoinNode.id), Some(n.id))) -// postCondNodes foreach (n => v.decider.dependencyAnalyzer.addDependency(Some(n.id), Some(customJoinNode.id))) -// postCondAssertNodes foreach (n => v.decider.dependencyAnalyzer.addDependency(Some(customJoinNode.id), Some(n.id))) -// postCondAssertNodes foreach (n => v.decider.dependencyAnalyzer.addDependency(Some(n.id), Some(customJoinNode.id))) -// } - errorsReportedSoFar.set(0) val result = /* Combined the well-formedness check and the execution of the body, which are two separate diff --git a/src/main/scala/supporters/functions/FunctionData.scala b/src/main/scala/supporters/functions/FunctionData.scala index 6f7ac947d..9e4c761f5 100644 --- a/src/main/scala/supporters/functions/FunctionData.scala +++ b/src/main/scala/supporters/functions/FunctionData.scala @@ -19,9 +19,9 @@ import viper.silicon.utils.ast.simplifyVariableName import viper.silicon.verifier.Verifier import viper.silicon.{Config, Map, toMap} import viper.silver.ast -import viper.silver.ast.{EdgeType, JoinType, LocalVarWithVersion, SimpleDependencyAnalysisJoin} +import viper.silver.ast.LocalVarWithVersion import viper.silver.ast.utility.Functions -import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyType} +import viper.silver.dependencyAnalysis._ import viper.silver.parser.PUnknown import viper.silver.reporter.{InternalWarningMessage, Reporter} diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index 6d5c7b61c..f556f2906 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -28,7 +28,7 @@ import viper.silver.ast import viper.silver.ast._ import viper.silver.ast.utility.Functions import viper.silver.components.StatefulComponent -import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyAnalysisJoinNodeInfo, DependencyType} +import viper.silver.dependencyAnalysis._ import viper.silver.parser.PType import viper.silver.verifier.errors.{ContractNotWellformed, FunctionNotWellformed, PostconditionViolated} @@ -284,29 +284,9 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver val precondAnalysisSourceInfos = DependencyAnalysisInfos.create("preconditions", DependencyType.Internal) val analysisInfosPostcondition = DependencyAnalysisInfos.DefaultDependencyAnalysisInfos.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Source, EdgeType.Down)) - val analysisInfosBody = DependencyAnalysisInfos.DefaultDependencyAnalysisInfos.addInfo(body.info, body) + val analysisInfosBody = v.decider.handleAndGetUpdatedAnalysisInfos(DependencyAnalysisInfos.DefaultDependencyAnalysisInfos, body.info, body) .withJoinInfo(SimpleDependencyAnalysisJoin(AnalysisSourceInfo.createAnalysisSourceInfo(body), JoinType.Source, EdgeType.Down)) - val daJoinNodeInfoOpt = function.info.getUniqueInfo[DependencyAnalysisJoinNodeInfo] - // TODO ake: frontend join -// if(daJoinNodeInfoOpt.isDefined){ -// val infodaJoinNodeInfo = daJoinNodeInfoOpt.get -//// v.decider.analysisSourceInfoStack.addAnalysisSourceInfo(infodaJoinNodeInfo.sourceInfo, DependencyType.make(AssumptionType.CustomInternal)) -// val postCondNodes = (posts ++ function.pres).flatMap(_.topLevelConjuncts).map(pc => SimpleAssumptionNode(True, None, AnalysisSourceInfo.createAnalysisSourceInfo(pc), AssumptionType.ImplicitPostcondition, isClosed=false, isJoinNode=true)) -// val postCondAssertNodes = (posts ++ function.pres).flatMap(_.topLevelConjuncts).map(pc => SimpleAssertionNode(True, AnalysisSourceInfo.createAnalysisSourceInfo(pc), AssumptionType.ImplicitPostcondition, isClosed=false, isJoinNode=true)) -// val customJoinNode = infodaJoinNodeInfo.getAssertionNode -// -// postCondNodes foreach v.decider.dependencyAnalyzer.addAssumptionNode -// postCondAssertNodes foreach v.decider.dependencyAnalyzer.addAssertionNode -// v.decider.dependencyAnalyzer.addAssertionNode(customJoinNode) -// -// postCondNodes foreach (n => v.decider.dependencyAnalyzer.addDependency(Some(customJoinNode.id), Some(n.id))) -// postCondNodes foreach (n => v.decider.dependencyAnalyzer.addDependency(Some(n.id), Some(customJoinNode.id))) -// postCondAssertNodes foreach (n => v.decider.dependencyAnalyzer.addDependency(Some(customJoinNode.id), Some(n.id))) -// postCondAssertNodes foreach (n => v.decider.dependencyAnalyzer.addDependency(Some(n.id), Some(customJoinNode.id))) -// } - - val result = phase1data.foldLeft(Success(): VerificationResult) { case (fatalResult: FatalResult, _) => fatalResult case (intermediateResult, Phase1Data(sPre, bcsPre, bcsPreExp, pcsPre, pcsPreExp)) => From 69c7064ee9cbc2a0ede17ac7ebf091cc4328d440 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sun, 12 Apr 2026 17:14:53 +0200 Subject: [PATCH 434/474] fixes --- silver | 2 +- src/main/scala/decider/Decider.scala | 17 ----------------- .../DependencyGraphInterpreter.scala | 2 +- src/main/scala/rules/Evaluator.scala | 4 ++-- 4 files changed, 4 insertions(+), 21 deletions(-) diff --git a/silver b/silver index 80c867d06..acb3d686e 160000 --- a/silver +++ b/silver @@ -1 +1 @@ -Subproject commit 80c867d06c7523d65c3a7d780ff6a9dcc7672b3e +Subproject commit acb3d686e93cac74863f40640d43e16f17784a7e diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 40d96c5d8..318f2ad17 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -358,23 +358,6 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } } -// private def pushAnalysisSourceInfo(sourceInfo: AnalysisSourceInfo, info: ast.Info, dependencyType: Option[DependencyType]): Unit = { -// analysisSourceInfoStack.addAnalysisSourceInfo(sourceInfo, dependencyType.getOrElse(analysisSourceInfoStack.getDependencyType)) - // TODO ake: frontend join -// if (info.getUniqueInfo[DependencyAnalysisJoinNodeInfo].isDefined) { -// // add assertions and assumptions nodes that can be used for a graph join -// val joinNodeInfo = info.getUniqueInfo[DependencyAnalysisJoinNodeInfo].get -// val currentTopLevelSource = analysisSourceInfoStack.getFullSourceInfo -// val assertionNode = joinNodeInfo.getAssertionNode(currentTopLevelSource, analysisSourceInfoStack.getDependencyType.assertionType) -// val assumptionNode = joinNodeInfo.getAssumptionNode(currentTopLevelSource) -// dependencyAnalyzer.addAssertionNode(assertionNode) -// dependencyAnalyzer.addAssumptionNode(assumptionNode) -// dependencyAnalyzer.addDependency(Some(assumptionNode.id), Some(assertionNode.id)) -// } -// } - - - def assume(t: Term, e: Option[ast.Exp], finalExp: Option[ast.Exp], analysisInfos: DependencyAnalysisInfos): Unit = { if (finalExp.isDefined) { assume(assumptions=InsertionOrderedSet((t, Some(DebugExp.createInstance(e.get, finalExp.get)))), enforceAssumption = false, isDefinition = false, analysisInfos) diff --git a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala index b6faae7ec..d64fe2709 100644 --- a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala @@ -97,7 +97,7 @@ class DependencyGraphInterpreter[T <: DependencyGraphState](name: String, depend def getNonInternalAssumptionNodes(nodes: Set[DependencyAnalysisNode]): Set[DependencyAnalysisNode] = nodes filter (node => (node.isInstanceOf[GeneralAssumptionNode] && !AssumptionType.internalTypes.contains(node.assumptionType)) - || AssumptionType.postconditionTypes.contains(node.assumptionType) || node.joinInfos.nonEmpty // TODO ake: review + || AssumptionType.postconditionTypes.contains(node.assumptionType) || node.joinInfos.nonEmpty // TODO ake: find a better way to include the join nodes ) def getExplicitAssumptionNodes: Set[DependencyAnalysisNode] = getNonInternalAssumptionNodes filter (node => diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index d6ed3770b..4800bb658 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -725,7 +725,7 @@ object evaluator extends EvaluationRules { })(join(func.typ, s"joined_${func.name}", joinFunctionArgs, Option.when(withExp)(eArgs), v1, analysisInfos))((s6, r, v4) => { v4.decider.dependencyAnalyzer.addCustomDependenciesBetweenMergeInfos(presWithDAInfo, - Seq(DependencyAnalysisMergeInfo.attachExpMergeInfo(fapp, None), DependencyAnalysisMergeInfo.attachExpMergeInfo(fapp, analysisInfos.getSourceInfo, None))) + Seq(DependencyAnalysisMergeInfo.attachExpMergeInfo(fapp, analysisInfos.getMergeInfo))) Q(s6, r._1, r._2, v4) })}) @@ -828,7 +828,7 @@ object evaluator extends EvaluationRules { => Q(s4, r4._1, r4._2, v4)) case ast.Asserting(eAss, eIn) => - consume(s, eAss, false, pve, v, analysisInfos /* TODO ake: explicit assertion? */)((s2, _, v2) => { + consume(s, eAss, false, pve, v, analysisInfos )((s2, _, v2) => { val s3 = s2.copy(g = s.g, h = s.h) eval(s3, eIn, pve, v2, analysisInfos)(Q) }) From bcbe1b6be5b3e6148e546a42af3e7d2d756bed9f Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Sun, 12 Apr 2026 18:59:48 +0200 Subject: [PATCH 435/474] fix infeasibility node --- src/main/scala/decider/Decider.scala | 2 +- src/main/scala/dependencyAnalysis/DependencyGraph.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 318f2ad17..46125dcd6 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -494,7 +494,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => // THIS WOULD BE UNSOUND! Unsoundness is introduced when infeasibility is introduced while executing a package statements and pontentially in other cases as well. // assumeWithoutSmokeChecks(InsertionOrderedSet((False, DependencyAnalyzer.createAssumptionLabel(infeasibleNodeId)))) dependencyAnalyzer.addDependency(checkNode.map(_.id), infeasibleNodeId) - pcs.setCurrentInfeasibilityNode(checkNode.map(_.id)) + pcs.setCurrentInfeasibilityNode(infeasibleNodeId) }else if(isAssert){ checkNode foreach (node => dependencyAnalyzer.addAssertionNode(node.getAssertFailedNode)) } diff --git a/src/main/scala/dependencyAnalysis/DependencyGraph.scala b/src/main/scala/dependencyAnalysis/DependencyGraph.scala index 9a3a52328..1c97903d0 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraph.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraph.scala @@ -207,7 +207,7 @@ class DependencyGraph[T <: DependencyGraphState] extends ReadOnlyDependencyGraph val curr = queue.head val newVisits = allEdges.getOrElse(curr, Set()).diff(infeasibilityNodeIds) visited = visited ++ Set(curr) - queue = queue.tail ++ (newVisits filter (!visited.contains(_))) + queue = queue.tail ++ newVisits.diff(visited) } visited } From d6eec6b182e833c7418f66314d383cccb04ea0b5 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 20 Apr 2026 10:30:27 +0200 Subject: [PATCH 436/474] fix withMeta for BackendFuncApp --- silver | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/silver b/silver index acb3d686e..13725ae1c 160000 --- a/silver +++ b/silver @@ -1 +1 @@ -Subproject commit acb3d686e93cac74863f40640d43e16f17784a7e +Subproject commit 13725ae1cf6301623fb78ffaefae337e9efa8767 From 8bc735daf9e0cfd4190967d56d3aa97527d9fae9 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 20 Apr 2026 10:41:49 +0200 Subject: [PATCH 437/474] fix withMeta for Adt plugin --- silver | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/silver b/silver index 13725ae1c..65e523a6f 160000 --- a/silver +++ b/silver @@ -1 +1 @@ -Subproject commit 13725ae1cf6301623fb78ffaefae337e9efa8767 +Subproject commit 65e523a6fe967cf33a261b16d84aa2815b3ddf85 From 61c8fa676827847509947d91aabc9f9c90c1d4c3 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 21 Apr 2026 09:03:45 +0200 Subject: [PATCH 438/474] fix DA tests --- src/test/scala/DependencyAnalysisTestFramework.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test/scala/DependencyAnalysisTestFramework.scala b/src/test/scala/DependencyAnalysisTestFramework.scala index 969376ca1..b836aa2fc 100644 --- a/src/test/scala/DependencyAnalysisTestFramework.scala +++ b/src/test/scala/DependencyAnalysisTestFramework.scala @@ -101,6 +101,7 @@ trait DependencyAnalysisTestFramework { val crucialNodes = relevantNodes ++ dependencies val (newProgram, pruningFactor) = pruningSupporter.getPrunedProgram(crucialNodes, program) + resetBaselineFrontend() val result = baselineFrontend.verifier.verify(newProgram) if(EXPORT_PRUNED_PROGRAMS) exportPrunedProgram(exportFileName, newProgram, pruningFactor, result) assert(!result.isInstanceOf[verifier.Failure], s"Failed to verify new program. ${result.transformedResult()}\n${newProgram.toString()}") @@ -166,6 +167,7 @@ trait DependencyAnalysisTestFramework { protected def pruneAndVerify(crucialNodes: Set[DependencyAnalysisNode]): Boolean = { val (newProgram, _) = pruningSupporter.getPrunedProgram(crucialNodes, program) + resetBaselineFrontend() val result = baselineFrontend.verifier.verify(newProgram) !result.isInstanceOf[verifier.Failure] } From edf51daa574124ebf2bd1cd20ecf843beab0244f Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 21 Apr 2026 09:50:19 +0200 Subject: [PATCH 439/474] fix dependency queries regarding join edges --- .../DependencyGraphInterpreter.scala | 8 +-- .../dependencyAnalysisTests/all/join-test.vpr | 66 +++++++++++++++++++ 2 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 src/test/resources/dependencyAnalysisTests/all/join-test.vpr diff --git a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala index d64fe2709..27204a339 100644 --- a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala @@ -71,25 +71,25 @@ class DependencyGraphInterpreter[T <: DependencyGraphState](name: String, depend def getAllNonInternalDependencies(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[DependencyAnalysisNode] = { val allDependenciesUpwards = dependencyGraph.getAllDependencies(nodeIdsToAnalyze, includeInfeasibilityNodes, includeUpwardEdges = true, includeDownwardEdges = false) - val allDependenciesDownwards = dependencyGraph.getAllDependencies(nodeIdsToAnalyze, includeInfeasibilityNodes, includeUpwardEdges = false, includeDownwardEdges = true) + val allDependenciesDownwards = dependencyGraph.getAllDependencies(nodeIdsToAnalyze ++ allDependenciesUpwards, includeInfeasibilityNodes, includeUpwardEdges = false, includeDownwardEdges = true) (allDependenciesUpwards ++ allDependenciesDownwards) flatMap nonInternalAssumptionNodesMap.get } def getAllExplicitDependencies(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[DependencyAnalysisNode] = { val allDependenciesUpwards = dependencyGraph.getAllDependencies(nodeIdsToAnalyze, includeInfeasibilityNodes, includeUpwardEdges = true, includeDownwardEdges = false) - val allDependenciesDownwards = dependencyGraph.getAllDependencies(nodeIdsToAnalyze, includeInfeasibilityNodes, includeUpwardEdges = false, includeDownwardEdges = true) + val allDependenciesDownwards = dependencyGraph.getAllDependencies(nodeIdsToAnalyze ++ allDependenciesUpwards, includeInfeasibilityNodes, includeUpwardEdges = false, includeDownwardEdges = true) getExplicitAssumptionNodes.filter(node => (allDependenciesUpwards ++ allDependenciesDownwards).contains(node.id)) } def getAllNonInternalDependents(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[DependencyAnalysisNode] = { val allDependentsUpwards = dependencyGraph.getAllDependents(nodeIdsToAnalyze, includeInfeasibilityNodes, includeUpwardEdges = true, includeDownwardEdges = false) - val allDependentsDownwards = dependencyGraph.getAllDependents(nodeIdsToAnalyze, includeInfeasibilityNodes, includeUpwardEdges = false, includeDownwardEdges = true) + val allDependentsDownwards = dependencyGraph.getAllDependents(nodeIdsToAnalyze ++ allDependentsUpwards, includeInfeasibilityNodes, includeUpwardEdges = false, includeDownwardEdges = true) getNonInternalAssertionNodes.filter(node => (allDependentsUpwards ++ allDependentsDownwards).contains(node.id)) } def getAllExplicitDependents(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[DependencyAnalysisNode] = { val allDependentsUpwards = dependencyGraph.getAllDependents(nodeIdsToAnalyze, includeInfeasibilityNodes, includeUpwardEdges = true, includeDownwardEdges = false) - val allDependentsDownwards = dependencyGraph.getAllDependents(nodeIdsToAnalyze, includeInfeasibilityNodes, includeUpwardEdges = false, includeDownwardEdges = true) + val allDependentsDownwards = dependencyGraph.getAllDependents(nodeIdsToAnalyze ++ allDependentsUpwards, includeInfeasibilityNodes, includeUpwardEdges = false, includeDownwardEdges = true) getExplicitAssertionNodes.filter(node => (allDependentsUpwards ++ allDependentsDownwards).contains(node.id)) } diff --git a/src/test/resources/dependencyAnalysisTests/all/join-test.vpr b/src/test/resources/dependencyAnalysisTests/all/join-test.vpr new file mode 100644 index 000000000..81d75b480 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/all/join-test.vpr @@ -0,0 +1,66 @@ +method foo(n: Int) returns (res: Int) + requires n > 0 + ensures res > 0 +{ + res := n + var a: Int + a := 10 / res + while(true) + invariant res > 0 + { + res := foo(res) + } +} + +method bar(n: Int) returns (res: Int) + requires n > 0 + ensures res > 0 +{ + res := n +} + +method barClient(n: Int) returns (res: Int) + requires n > 0 +{ + res := bar(n) +} + +method client() +{ + var n: Int + n := 1 + while(n < 100) + invariant n > 0 + { + n := n + 1 + n := bar(n) + } + assert n > 0 +} + +method recWrapped(n: Int) returns (res: Int) + requires n >= 0 + ensures res >= 0 +{ + res := rec(n) +} + + +method rec(n: Int) returns (res: Int) + requires n >= 0 + ensures res >= 0 +{ + var i: Int + i := 0 + res := 0 + while(i < n) + invariant res >= 0 + invariant i >= 0 + invariant i <= n + { + var tmp: Int + tmp := recWrapped(n) + res := res + tmp + i := i + 1 + } +} \ No newline at end of file From 567c4800ed8d634f7d249a18ae4b16e8ee4d88c8 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 21 Apr 2026 10:44:38 +0200 Subject: [PATCH 440/474] refactor dependency queries --- .../DependencyGraphInterpreter.scala | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala index 27204a339..8d95ae585 100644 --- a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala @@ -69,28 +69,35 @@ class DependencyGraphInterpreter[T <: DependencyGraphState](name: String, depend getNonInternalAssumptionNodes.filter(node => result.contains(node.id)) } - def getAllNonInternalDependencies(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[DependencyAnalysisNode] = { + private def getAllDependencies(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true) = { val allDependenciesUpwards = dependencyGraph.getAllDependencies(nodeIdsToAnalyze, includeInfeasibilityNodes, includeUpwardEdges = true, includeDownwardEdges = false) val allDependenciesDownwards = dependencyGraph.getAllDependencies(nodeIdsToAnalyze ++ allDependenciesUpwards, includeInfeasibilityNodes, includeUpwardEdges = false, includeDownwardEdges = true) - (allDependenciesUpwards ++ allDependenciesDownwards) flatMap nonInternalAssumptionNodesMap.get + allDependenciesUpwards ++ allDependenciesDownwards + } + + def getAllNonInternalDependencies(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[DependencyAnalysisNode] = { + getAllDependencies(nodeIdsToAnalyze, includeInfeasibilityNodes) flatMap nonInternalAssumptionNodesMap.get } def getAllExplicitDependencies(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[DependencyAnalysisNode] = { - val allDependenciesUpwards = dependencyGraph.getAllDependencies(nodeIdsToAnalyze, includeInfeasibilityNodes, includeUpwardEdges = true, includeDownwardEdges = false) - val allDependenciesDownwards = dependencyGraph.getAllDependencies(nodeIdsToAnalyze ++ allDependenciesUpwards, includeInfeasibilityNodes, includeUpwardEdges = false, includeDownwardEdges = true) - getExplicitAssumptionNodes.filter(node => (allDependenciesUpwards ++ allDependenciesDownwards).contains(node.id)) + val allDeps = getAllDependencies(nodeIdsToAnalyze, includeInfeasibilityNodes) + getExplicitAssumptionNodes.filter(node => allDeps.contains(node.id)) + } + + private def getAllDependents(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true) = { + val allDependentsDownwards = dependencyGraph.getAllDependents(nodeIdsToAnalyze, includeInfeasibilityNodes, includeUpwardEdges = false, includeDownwardEdges = true) + val allDependentsUpwards = dependencyGraph.getAllDependents(nodeIdsToAnalyze ++ allDependentsDownwards, includeInfeasibilityNodes, includeUpwardEdges = true, includeDownwardEdges = false) + allDependentsUpwards ++ allDependentsDownwards } def getAllNonInternalDependents(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[DependencyAnalysisNode] = { - val allDependentsUpwards = dependencyGraph.getAllDependents(nodeIdsToAnalyze, includeInfeasibilityNodes, includeUpwardEdges = true, includeDownwardEdges = false) - val allDependentsDownwards = dependencyGraph.getAllDependents(nodeIdsToAnalyze ++ allDependentsUpwards, includeInfeasibilityNodes, includeUpwardEdges = false, includeDownwardEdges = true) - getNonInternalAssertionNodes.filter(node => (allDependentsUpwards ++ allDependentsDownwards).contains(node.id)) + val allDeps = getAllDependents(nodeIdsToAnalyze, includeInfeasibilityNodes) + getNonInternalAssertionNodes.filter(node => allDeps.contains(node.id)) } def getAllExplicitDependents(nodeIdsToAnalyze: Set[Int], includeInfeasibilityNodes: Boolean = true): Set[DependencyAnalysisNode] = { - val allDependentsUpwards = dependencyGraph.getAllDependents(nodeIdsToAnalyze, includeInfeasibilityNodes, includeUpwardEdges = true, includeDownwardEdges = false) - val allDependentsDownwards = dependencyGraph.getAllDependents(nodeIdsToAnalyze ++ allDependentsUpwards, includeInfeasibilityNodes, includeUpwardEdges = false, includeDownwardEdges = true) - getExplicitAssertionNodes.filter(node => (allDependentsUpwards ++ allDependentsDownwards).contains(node.id)) + val allDeps = getAllDependents(nodeIdsToAnalyze, includeInfeasibilityNodes) + getExplicitAssertionNodes.filter(node => allDeps.contains(node.id)) } def getNonInternalAssumptionNodes: Set[DependencyAnalysisNode] = nonInternalAssumptionNodesMap.values.toSet From 7fd12133113698ce34d6296b46734084471c004f Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 21 Apr 2026 15:37:40 +0200 Subject: [PATCH 441/474] refactor progress and pruning supporter --- .../cliTool/DependencyAnalysisCliTool.scala | 11 ++++------- .../DependencyGraphInterpreter.scala | 3 +++ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/cliTool/DependencyAnalysisCliTool.scala b/src/main/scala/dependencyAnalysis/cliTool/DependencyAnalysisCliTool.scala index 6c416d943..62f53748f 100644 --- a/src/main/scala/dependencyAnalysis/cliTool/DependencyAnalysisCliTool.scala +++ b/src/main/scala/dependencyAnalysis/cliTool/DependencyAnalysisCliTool.scala @@ -2,7 +2,7 @@ package viper.silicon.dependencyAnalysis.cliTool import dependencyAnalysis.cliTool.{AbstractDependencyAnalysisCliTool, DependencyAnalysisCliToolExtension} import viper.silicon.dependencyAnalysis._ -import viper.silicon.dependencyAnalysis.graphInterpretation.{DependencyAnalysisProgressSupporter, DependencyAnalysisPruningSupporter, DependencyGraphInterpreter} +import viper.silicon.dependencyAnalysis.graphInterpretation.{DependencyAnalysisProgressSupporter, DependencyGraphInterpreter} import viper.silicon.interfaces.Failure import viper.silver.ast import viper.silver.ast.Method @@ -18,9 +18,6 @@ class DependencyAnalysisCliTool(fullGraphInterpreter: DependencyGraphInterpreter new BenchmarkDependencyAnalysisCliExtension(fullGraphInterpreter, program) ) - lazy val progressSupporter = new DependencyAnalysisProgressSupporter[Final](fullGraphInterpreter) - lazy val pruningSupporter = new DependencyAnalysisPruningSupporter[Final](fullGraphInterpreter) - private val infoString = "Enter " + "\n\t'dep [line numbers]' to print all dependencies of the given line numbers or" + "\n\t'downDep [line numbers]' to print all dependents of the given line numbers or" + @@ -128,7 +125,7 @@ class DependencyAnalysisCliTool(fullGraphInterpreter: DependencyGraphInterpreter private def handleVerificationProgressQuery(inputs: Seq[String]): Unit = { val enableDebugging = inputs.nonEmpty && inputs.head.equals("debug") - val ((optProgressPeter, optProgressLea), optTime) = measureTime(progressSupporter.computeVerificationProgress(enableDebugging)) + val ((optProgressPeter, optProgressLea), optTime) = measureTime(fullGraphInterpreter.progressSupporter.computeVerificationProgress(enableDebugging)) println(s"Peter: $optProgressPeter; Lea: $optProgressLea") println(s"Finished in ${optTime}ms") @@ -174,13 +171,13 @@ class DependencyAnalysisCliTool(fullGraphInterpreter: DependencyGraphInterpreter println("exportFileName: ") val exportFileName = readLine() val queriedNodes = getQueriedNodesFromInput(inputs.toSet) - pruningSupporter.pruneProgramAndExport(queriedNodes, program, exportFileName) + fullGraphInterpreter.pruningSupporter.pruneProgramAndExport(queriedNodes, program, exportFileName) println("Done.") } private def handleVerificationGuidanceQuery(): Unit = { - val assumptionRanking = progressSupporter.computeAssumptionRanking().filter(_._2 > 0.0) + val assumptionRanking = fullGraphInterpreter.progressSupporter.computeAssumptionRanking().filter(_._2 > 0.0) println(s"Assumptions/unverified assertions and the number of dependents:\n\t${assumptionRanking.mkString("\n\t")}\n") println("Uncovered source code per method: ") diff --git a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala index 8d95ae585..a7a19447d 100644 --- a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala @@ -17,6 +17,9 @@ object DATraversalMode extends Enumeration { } class DependencyGraphInterpreter[T <: DependencyGraphState](name: String, dependencyGraph: ReadOnlyDependencyGraph[T], errors: List[Failure], member: Option[ast.Member]=None) extends AbstractDependencyGraphInterpreter { + val pruningSupporter: DependencyAnalysisPruningSupporter[T] = new DependencyAnalysisPruningSupporter[T](this) + val progressSupporter: DependencyAnalysisProgressSupporter[T] = new DependencyAnalysisProgressSupporter[T](this) + def getGraph: ReadOnlyDependencyGraph[T] = dependencyGraph From a5017216aa4952d23a4adc2b1db99b546db6bfdd Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 23 Apr 2026 17:37:42 +0200 Subject: [PATCH 442/474] print warnings when DA info is missing --- .../DependencyAnalysisInfos.scala | 40 ++++++++++++++++--- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisInfos.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisInfos.scala index f968fe3ce..5817fff85 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisInfos.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisInfos.scala @@ -1,5 +1,6 @@ package viper.silicon.dependencyAnalysis +import viper.silicon.SiliconRunner import viper.silicon.verifier.Verifier import viper.silver.ast import viper.silver.ast._ @@ -48,13 +49,42 @@ case class DependencyAnalysisInfos(sourceInfos: List[AnalysisSourceInfo], depend this.copy(sourceInfos = source +: sourceInfos) } - def getSourceInfo: AnalysisSourceInfo = - sourceInfos.headOption.getOrElse(nodes.headOption.map(AnalysisSourceInfo.createAnalysisSourceInfo).getOrElse(StringAnalysisSourceInfo("Unknown", NoPosition))) + private def getNodeInfo(n: ast.Node): String = { + n match { + case np: Positioned => + s"${n.toString()} (${np.pos})" + case _ => + s"${n.toString()} (???)" + } + } - def getDependencyType: DependencyType = - dependencyTypes.headOption.map(_.dependencyType).getOrElse(DependencyType.make(AssumptionType.Unknown)) + private def getDebugInfo: String = { + val sourceInfo = sourceInfos.headOption.map("source info: " + _.toString + " ").getOrElse("") + val nodeInfo = if(nodes.nonEmpty) "nodes: " + nodes.map(getNodeInfo).mkString(", ") else "" + s"$sourceInfo$nodeInfo" + } + + def getSourceInfo: AnalysisSourceInfo = { + val sourceInfoOpt = sourceInfos.headOption + if(sourceInfoOpt.isDefined){ + sourceInfoOpt.get + }else{ + SiliconRunner.logger.warn(s"WARN: Missing source info for $getDebugInfo") + nodes.headOption.map(AnalysisSourceInfo.createAnalysisSourceInfo).getOrElse(StringAnalysisSourceInfo("Unknown", NoPosition)) + } + } + + def getDependencyType: DependencyType = { + val dependencyTypeOpt = dependencyTypes.headOption.map(_.dependencyType) + if(dependencyTypeOpt.isDefined) { + dependencyTypeOpt.get + }else { + SiliconRunner.logger.warn(s"WARN: Missing dependency type for $getDebugInfo") + DependencyType.make(AssumptionType.Unknown) + } + } - def getMergeInfo: DependencyAnalysisMergeInfo = mergeInfos.headOption.getOrElse(SimpleDependencyAnalysisMerge(getSourceInfo)) + def getMergeInfo: DependencyAnalysisMergeInfo = mergeInfos.headOption.getOrElse(SimpleDependencyAnalysisMerge(getSourceInfo)) def getJoinInfo: List[SimpleDependencyAnalysisJoin] = { joinInfos.map { From f84414a02720ae015206901adad56ee5b58c88ca Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 23 Apr 2026 18:16:57 +0200 Subject: [PATCH 443/474] do not collect builtin axioms --- .../DependencyAnalysisInfos.scala | 13 ++++--------- src/main/scala/interfaces/decider/Prover.scala | 10 ++++++---- src/main/scala/rules/Evaluator.scala | 3 +-- src/main/scala/supporters/Domains.scala | 4 +--- .../supporters/functions/FunctionData.scala | 18 ++++++++++-------- 5 files changed, 22 insertions(+), 26 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisInfos.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisInfos.scala index 5817fff85..c187826c3 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisInfos.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisInfos.scala @@ -10,7 +10,7 @@ import viper.silver.dependencyAnalysis._ * Stores all information about the currently evaluated statement/expression such that the dependency analysis can * correctly add nodes and edges to the graph. */ -case class DependencyAnalysisInfos(sourceInfos: List[AnalysisSourceInfo], dependencyTypes: List[DependencyTypeInfo], mergeInfos: List[DependencyAnalysisMergeInfo], joinInfos: List[DependencyAnalysisJoinInfo], nodes: List[ast.Node]) { +case class DependencyAnalysisInfos(sourceInfos: List[AnalysisSourceInfo], dependencyTypes: List[DependencyTypeInfo], mergeInfos: List[DependencyAnalysisMergeInfo], joinInfos: List[DependencyAnalysisJoinInfo], nodes: List[ast.Node], analysisEnabled: Boolean = true) { def addInfo(info: ast.Info, node: ast.Node): DependencyAnalysisInfos = { if(!Verifier.config.enableDependencyAnalysis()) return this @@ -104,6 +104,8 @@ case class DependencyAnalysisInfos(sourceInfos: List[AnalysisSourceInfo], depend this.copy(joinInfos = joinInfo +: joinInfos) } + + def withEnabled(analysisEnabled: Boolean): DependencyAnalysisInfos = this.copy(analysisEnabled=analysisEnabled) } object DependencyAnalysisInfos { @@ -123,11 +125,4 @@ object DependencyAnalysisInfos { def createUnique(infoString: String, dependencyType: DependencyType): DependencyAnalysisInfos = create(StringAnalysisSourceInfo(s"$infoString-${DependencyGraphHelper.nextId()}", NoPosition), dependencyType) -} - - - - -class CustomDependencyAnalysisNode(description: String, sourceInfoOpt: Option[AnalysisSourceInfo], dependencyTypeOpt: Option[DependencyType], - createAssertionNode: Boolean, createAssumptionNode: Boolean, - mergeInfoOpt: Option[DependencyAnalysisMergeInfo], joinInfoOpt: Option[DependencyAnalysisJoinInfo]) \ No newline at end of file +} \ No newline at end of file diff --git a/src/main/scala/interfaces/decider/Prover.scala b/src/main/scala/interfaces/decider/Prover.scala index 5ef06b68e..d4e513afe 100644 --- a/src/main/scala/interfaces/decider/Prover.scala +++ b/src/main/scala/interfaces/decider/Prover.scala @@ -15,8 +15,6 @@ import viper.silicon.verifier.Verifier import viper.silicon.{Config, Map} import viper.silver.ast import viper.silver.components.StatefulComponent -import viper.silver.dependencyAnalysis.AssumptionType.AssumptionType -import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, DependencyType} import viper.silver.verifier.Model sealed abstract class Result @@ -47,8 +45,12 @@ trait ProverLike { if(Verifier.config.enableDependencyAnalysis()){ axioms.foreach(axiom => { val analysisInfos = axiom._2 - val id = preambleDependencyAnalyzer.addAxiom(axiom._1, analysisInfos) - assume(axiom._1, DependencyAnalyzer.createAxiomLabel(id)) + if(analysisInfos.analysisEnabled){ + val id = preambleDependencyAnalyzer.addAxiom(axiom._1, analysisInfos) + assume(axiom._1, DependencyAnalyzer.createAxiomLabel(id)) + }else { + assume(axiom._1) + } }) } else{ axioms.foreach(t => assume(t._1)) diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 4800bb658..457c50f73 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -724,8 +724,7 @@ object evaluator extends EvaluationRules { */ })(join(func.typ, s"joined_${func.name}", joinFunctionArgs, Option.when(withExp)(eArgs), v1, analysisInfos))((s6, r, v4) => { - v4.decider.dependencyAnalyzer.addCustomDependenciesBetweenMergeInfos(presWithDAInfo, - Seq(DependencyAnalysisMergeInfo.attachExpMergeInfo(fapp, analysisInfos.getMergeInfo))) + if(v4.decider.isDependencyAnalysisEnabled) v4.decider.dependencyAnalyzer.addCustomDependenciesBetweenMergeInfos(presWithDAInfo, Seq(DependencyAnalysisMergeInfo.attachExpMergeInfo(fapp, analysisInfos.getMergeInfo))) Q(s6, r._1, r._2, v4) })}) diff --git a/src/main/scala/supporters/Domains.scala b/src/main/scala/supporters/Domains.scala index b0e199781..6877d63cf 100644 --- a/src/main/scala/supporters/Domains.scala +++ b/src/main/scala/supporters/Domains.scala @@ -16,8 +16,6 @@ import viper.silicon.state.{FunctionPreconditionTransformer, SymbolConverter, te import viper.silicon.toMap import viper.silver.ast import viper.silver.ast.NamedDomainAxiom -import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType, DependencyType} -import viper.silver.dependencyAnalysis.AssumptionType.AssumptionType trait DomainsContributor[SO, SY, AX, UA] extends PreambleContributor[SO, SY, AX] { def uniquenessAssumptionsAfterAnalysis: Iterable[UA] @@ -107,7 +105,7 @@ class DefaultDomainsContributor(symbolConverter: SymbolConverter, val tAx = domainTranslator.translateAxiom(axiom, symbolConverter.toSort) val tAxPres = FunctionPreconditionTransformer.transform(tAx, program) val enableAnalysis = DependencyAnalyzer.extractEnableAnalysisFromInfo(axiom.info).getOrElse(isAnalysisForDomainEnabled) - collectedAxioms = collectedAxioms.incl((terms.And(tAxPres, tAx), DependencyAnalysisInfos.DefaultDependencyAnalysisInfos.addInfo(axiom.exp.info, axiom.exp))) + collectedAxioms = collectedAxioms.incl((terms.And(tAxPres, tAx), DependencyAnalysisInfos.DefaultDependencyAnalysisInfos.addInfo(axiom.exp.info, axiom.exp).withEnabled(enableAnalysis))) }) }) } diff --git a/src/main/scala/supporters/functions/FunctionData.scala b/src/main/scala/supporters/functions/FunctionData.scala index 9e4c761f5..a91fb99e0 100644 --- a/src/main/scala/supporters/functions/FunctionData.scala +++ b/src/main/scala/supporters/functions/FunctionData.scala @@ -140,21 +140,23 @@ class FunctionData(val programFunction: ast.Function, val preconditionFunctionApplication = App(preconditionFunction, `?s` +: formalArgs.values.toSeq) - private val bodyAnalysisInfos: DependencyAnalysisInfos = if(programFunction.body.isDefined) - DependencyAnalysisInfos.DefaultDependencyAnalysisInfos.addInfo(programFunction.body.get.info, programFunction.body.get) - .withJoinInfo(SimpleDependencyAnalysisJoin(AnalysisSourceInfo.createAnalysisSourceInfo(programFunction.body.get), JoinType.Sink, EdgeType.Down)) - else DependencyAnalysisInfos.create("unverified function body", DependencyType.Internal) + private val bodyAnalysisInfos: DependencyAnalysisInfos = + if(programFunction.body.isDefined) + DependencyAnalysisInfos.DefaultDependencyAnalysisInfos.addInfo(programFunction.body.get.info, programFunction.body.get) + .withJoinInfo(SimpleDependencyAnalysisJoin(AnalysisSourceInfo.createAnalysisSourceInfo(programFunction.body.get), JoinType.Sink, EdgeType.Down)) + .withEnabled(isAnalysisEnabled) + else DependencyAnalysisInfos.create("unverified function body", DependencyType.Internal).withEnabled(isAnalysisEnabled) val limitedAxiom: (Quantification, DependencyAnalysisInfos) = (Forall(arguments, BuiltinEquals(limitedFunctionApplication, functionApplication), Trigger(functionApplication)), - DependencyAnalysisInfos.create("Limited Axiom", DependencyType.Internal)) + DependencyAnalysisInfos.create("Limited Axiom", DependencyType.Internal).withEnabled(isAnalysisEnabled)) val triggerAxiom: (Quantification, DependencyAnalysisInfos) = (Forall(arguments, triggerFunctionApplication, Trigger(limitedFunctionApplication)), - DependencyAnalysisInfos.create("Trigger Axiom", DependencyType.Trigger)) + DependencyAnalysisInfos.create("Trigger Axiom", DependencyType.Trigger).withEnabled(isAnalysisEnabled)) /* @@ -243,7 +245,7 @@ class FunctionData(val programFunction: ast.Function, val bodyBindings: Map[Var, Term] = Map(formalResult -> limitedFunctionApplication) def wrapBody(body: Term): Term = Let(toMap(bodyBindings), body) - val analysisInfos = DependencyAnalysisInfos.DefaultDependencyAnalysisInfos + val analysisInfos = DependencyAnalysisInfos.DefaultDependencyAnalysisInfos.withEnabled(isAnalysisEnabled) if(isAnalysisEnabled){ (Forall(arguments, wrapBody(And(generateNestedDefinitionalAxioms)), Trigger(limitedFunctionApplication)), bodyAnalysisInfos) +: @@ -345,7 +347,7 @@ class FunctionData(val programFunction: ast.Function, val bodyBindings: Map[Var, Term] = Map(formalResult -> limitedFunctionApplication) val bodies = translatedPosts.map(tPost => Let(bodyBindings, Implies(pre, FunctionPreconditionTransformer.transform(tPost, program)))) bodies.map(b => (Forall(arguments, b, Seq(Trigger(limitedFunctionApplication))), - DependencyAnalysisInfos.create("postPreconditionPropagationAxiom", DependencyType.Internal))) // TODO ake + DependencyAnalysisInfos.create("postPreconditionPropagationAxiom", DependencyType.Internal).withEnabled(isAnalysisEnabled))) } else Seq() postPreconditions } From c80c852fada52edeb51e4411fc5bb0c53b608624 Mon Sep 17 00:00:00 2001 From: DovydasVad Date: Fri, 20 Mar 2026 08:38:43 +0100 Subject: [PATCH 444/474] Add flags for VSC extension --- src/main/scala/Config.scala | 38 +++++++++++++++++++ .../cliTool/DependencyAnalysisCliTool.scala | 21 +++++++--- .../scala/verifier/DefaultMainVerifier.scala | 15 +++++++- 3 files changed, 67 insertions(+), 7 deletions(-) diff --git a/src/main/scala/Config.scala b/src/main/scala/Config.scala index 769d6bc54..98f8cdc87 100644 --- a/src/main/scala/Config.scala +++ b/src/main/scala/Config.scala @@ -745,6 +745,30 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { noshort = true ) + val pruneLines: ScallopOption[List[Int]] = opt[List[Int]]("pruneLines", + descr = "Line numbers to prune the program with respect to. Part of the dependency analysis tool.", + default = None, + noshort = true + ) + + val pruneExportFileName: ScallopOption[String] = opt[String]("pruneExportFileName", + descr = "Export file name for the pruned program (used with --pruneLines)", + default = Some("prunedExport.vpr"), + noshort = true + ) + + val computeVerificationProgress: ScallopOption[Boolean] = opt[Boolean]("computeVerificationProgress", + descr = "Computes verification progress of the program", + default = Some(false), + noshort = true + ) + + val computeVerificationProgressFileName: ScallopOption[String] = opt[String]("computeVerificationProgressFileName", + descr = "Export file name for the verification progress output (used with --computeVerificationProgress)", + default = Some("progressExport.vpr"), + noshort = true + ) + /* Option validation (trailing file argument is validated by parent class) */ validateOpt(prover) { @@ -822,6 +846,20 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { Left(s"Option ${startDependencyAnalysisTool.name} requires option ${enableDependencyAnalysis.name}") } + validateOpt(pruneLines, enableDependencyAnalysis) { + case (None, _) => Right(()) + case (Some(_), Some(true)) => Right(()) + case (Some(_), _) => + Left(s"Option ${pruneLines.name} requires option ${enableDependencyAnalysis.name}") + } + + validateOpt(computeVerificationProgress, enableDependencyAnalysis) { + case (Some(false), _) => Right(()) + case (_, Some(true)) => Right(()) + case (_, _) => + Left(s"Option ${computeVerificationProgress.name} requires option ${enableDependencyAnalysis.name}") + } + validateOpt(startDebuggerAutomatically, enableDebugging) { case (Some(false), _) => Right(()) case (Some(true), Some(true)) => Right(()) diff --git a/src/main/scala/dependencyAnalysis/cliTool/DependencyAnalysisCliTool.scala b/src/main/scala/dependencyAnalysis/cliTool/DependencyAnalysisCliTool.scala index 62f53748f..bdf64c8fd 100644 --- a/src/main/scala/dependencyAnalysis/cliTool/DependencyAnalysisCliTool.scala +++ b/src/main/scala/dependencyAnalysis/cliTool/DependencyAnalysisCliTool.scala @@ -7,6 +7,7 @@ import viper.silicon.interfaces.Failure import viper.silver.ast import viper.silver.ast.Method +import java.io.PrintWriter import scala.annotation.tailrec import scala.io.StdIn.readLine @@ -122,13 +123,19 @@ class DependencyAnalysisCliTool(fullGraphInterpreter: DependencyGraphInterpreter println("Done.") } - private def handleVerificationProgressQuery(inputs: Seq[String]): Unit = { + def handleVerificationProgressQuery(inputs: Seq[String], exportFileNameOpt: Option[String] = None): Unit = { val enableDebugging = inputs.nonEmpty && inputs.head.equals("debug") val ((optProgressPeter, optProgressLea), optTime) = measureTime(fullGraphInterpreter.progressSupporter.computeVerificationProgress(enableDebugging)) - println(s"Peter: $optProgressPeter; Lea: $optProgressLea") - println(s"Finished in ${optTime}ms") + val output = s"Peter: $optProgressPeter; Lea: $optProgressLea\nFinished in ${optTime}ms" + println(output) + + if (exportFileNameOpt.isDefined) { + val writer = new PrintWriter(exportFileNameOpt.get) + writer.println(output) + writer.close() + } } private def handleDependencyQuery(inputs: Set[String]): Unit = { @@ -167,9 +174,11 @@ class DependencyAnalysisCliTool(fullGraphInterpreter: DependencyGraphInterpreter println("Done.") } - private def handlePruningRequest(inputs: Seq[String]): Unit = { - println("exportFileName: ") - val exportFileName = readLine() + def handlePruningRequest(inputs: Seq[String], exportFileNameOpt: Option[String] = None): Unit = { + val exportFileName = exportFileNameOpt.getOrElse { + println("exportFileName: ") + readLine() + } val queriedNodes = getQueriedNodesFromInput(inputs.toSet) fullGraphInterpreter.pruningSupporter.pruneProgramAndExport(queriedNodes, program, exportFileName) println("Done.") diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index a41027b8e..4cba96031 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -20,9 +20,9 @@ import viper.silicon.logger.{MemberSymbExLogger, SymbExLogger} import viper.silicon.reporting.{MultiRunRecorders, condenseToViperResult} import viper.silicon.state._ import viper.silicon.state.terms.{Decl, Sort, Term, sorts} -import viper.silicon.supporters._ import viper.silicon.supporters.functions.{DefaultFunctionVerificationUnitProvider, FunctionData} import viper.silicon.supporters.qps._ +import viper.silicon.supporters._ import viper.silicon.utils.Counter import viper.silver.ast import viper.silver.ast.utility.rewriter.Traverse @@ -666,6 +666,19 @@ class DefaultMainVerifier(config: Config, result.getFullDependencyGraphInterpreter.exportGraph(program) } + if (Verifier.config.pruneLines.isDefined) { + val commandLineTool = new DependencyAnalysisCliTool(result.getFullDependencyGraphInterpreter, dependencyGraphInterpreters, program, verificationErrors) + val lineInputs = Verifier.config.pruneLines().map(_.toString) + val exportFileName = Verifier.config.pruneExportFileName() + commandLineTool.handlePruningRequest(lineInputs, Some(exportFileName)) + } + + if (Verifier.config.computeVerificationProgress()) { + val commandLineTool = new DependencyAnalysisCliTool(result.getFullDependencyGraphInterpreter, dependencyGraphInterpreters, program, verificationErrors) + val exportFileName = Verifier.config.computeVerificationProgressFileName() + commandLineTool.handleVerificationProgressQuery(Seq.empty, Some(exportFileName)) + } + if (Verifier.config.startDependencyAnalysisTool()) { val commandLineTool = new DependencyAnalysisCliTool(result.getFullDependencyGraphInterpreter, dependencyGraphInterpreters, program, verificationErrors) commandLineTool.run() From bf7211a0070c0271ba52e4761e20fe1b30ab8555 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 7 May 2026 16:14:58 +0200 Subject: [PATCH 445/474] mark infeasibility checks at branches as internal --- .../scala/dependencyAnalysis/DependencyGraph.scala | 5 ++++- src/main/scala/rules/Brancher.scala | 10 +++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyGraph.scala b/src/main/scala/dependencyAnalysis/DependencyGraph.scala index 1c97903d0..35c328be5 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraph.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraph.scala @@ -251,7 +251,10 @@ class DependencyGraph[T <: DependencyGraphState] extends ReadOnlyDependencyGraph * Removes internal nodes while perceiving the transitive closure by adding edges between the predecessors and successors. */ def removeInternalNodes(): Unit = { - def filterCriteria(n: DependencyAnalysisNode) = AssumptionType.internalTypes.contains(n.assumptionType) && !AssumptionType.CustomInternal.equals(n.assumptionType) + def filterCriteria(n: DependencyAnalysisNode) = { + AssumptionType.internalTypes.contains(n.assumptionType) && !AssumptionType.CustomInternal.equals(n.assumptionType) && + !n.isInstanceOf[InfeasibilityNode] + } assumptionNodes filter filterCriteria foreach removeAllEdgesForNode assumptionNodes = assumptionNodes filterNot filterCriteria diff --git a/src/main/scala/rules/Brancher.scala b/src/main/scala/rules/Brancher.scala index e04ec4001..86631541b 100644 --- a/src/main/scala/rules/Brancher.scala +++ b/src/main/scala/rules/Brancher.scala @@ -16,7 +16,7 @@ import viper.silicon.state.terms.{FunctionDecl, MacroDecl, Not, Term} import viper.silicon.verifier.Verifier import viper.silver.ast import viper.silver.ast.utility.Expressions -import viper.silver.dependencyAnalysis.DependencyType +import viper.silver.dependencyAnalysis.{DependencyType, NoDependencyAnalysisMerge} import viper.silver.reporter.BranchFailureMessage import viper.silver.verifier.Failure @@ -78,13 +78,13 @@ object brancher extends BranchingRules { /* True if the then-branch is to be explored */ val executeThenBranch = ( skipPathFeasibilityCheck - || !v.decider.check(negatedCondition, Verifier.config.checkTimeout(), analysisInfos1.withDependencyType(DependencyType.Internal))) + || !v.decider.check(negatedCondition, Verifier.config.checkTimeout(), analysisInfos1.withDependencyType(DependencyType.Internal).withMergeInfo(NoDependencyAnalysisMerge()))) /* False if the then-branch is to be explored */ val executeElseBranch = ( !executeThenBranch /* Assumes that ast least one branch is feasible */ || skipPathFeasibilityCheck - || !v.decider.check(condition, Verifier.config.checkTimeout(), analysisInfos1.withDependencyType(DependencyType.Internal))) + || !v.decider.check(condition, Verifier.config.checkTimeout(), analysisInfos1.withDependencyType(DependencyType.Internal).withMergeInfo(NoDependencyAnalysisMerge()))) val parallelizeElseBranch = s.parallelizeBranches && executeThenBranch && executeElseBranch @@ -163,7 +163,7 @@ object brancher extends BranchingRules { executionFlowController.locally(s, v0)((s1, v1) => { v1.decider.prover.comment(s"[else-branch: $cnt | $negatedCondition]") v1.decider.setCurrentBranchCondition(negatedCondition, (negatedConditionExp, negatedConditionExpNew), analysisInfos1) - if(v.decider.isDependencyAnalysisEnabled && !executeElseBranch) v.decider.checkSmoke(analysisInfos1) + if(v.decider.isDependencyAnalysisEnabled && !executeElseBranch) v.decider.checkSmoke(analysisInfos1.withDependencyType(DependencyType.Internal).withMergeInfo(NoDependencyAnalysisMerge())) var functionsOfElseBranchdDeciderBefore: Set[FunctionDecl] = null var nMacrosOfElseBranchDeciderBefore: Int = 0 @@ -214,7 +214,7 @@ object brancher extends BranchingRules { executionFlowController.locally(s, v)((s1, v1) => { v1.decider.prover.comment(s"[then-branch: $cnt | $condition]") v1.decider.setCurrentBranchCondition(condition, conditionExp, analysisInfos1) - if(v.decider.isDependencyAnalysisEnabled && !executeThenBranch) v.decider.checkSmoke(analysisInfos1) + if(v.decider.isDependencyAnalysisEnabled && !executeThenBranch) v.decider.checkSmoke(analysisInfos1.withDependencyType(DependencyType.Internal).withMergeInfo(NoDependencyAnalysisMerge())) fThen(v1.stateConsolidator(s1).consolidateOptionally(s1, v1), v1) }) From de0c85b054a8eb6fa02417d32f2aef8cd52b1410 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 8 May 2026 13:41:20 +0200 Subject: [PATCH 446/474] ignore preconditions for progress and guidance --- .../DependencyAnalysisProgressSupporter.scala | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisProgressSupporter.scala b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisProgressSupporter.scala index 1eb962563..cc25135ee 100644 --- a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisProgressSupporter.scala +++ b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisProgressSupporter.scala @@ -109,7 +109,10 @@ class DependencyAnalysisProgressSupporter[T <: DependencyGraphState](interpreter (numDepsTotal - explicitDeps.size).toDouble / numDepsTotal.toDouble } - private def getAssertionsRelevantForProgress: Map[AnalysisSourceInfo, Set[DependencyAnalysisNode]] = sourceToAssertionNodesMap.filter(ass => ass._2.map(_.assumptionType).intersect(AssumptionType.importedTypes).isEmpty) + private def getAssertionsRelevantForProgress: Map[AnalysisSourceInfo, Set[DependencyAnalysisNode]] = { + val excludedAssertionTypes = AssumptionType.importedTypes ++ Set(AssumptionType.Precondition) + sourceToAssertionNodesMap.filter(ass => ass._2.map(_.assumptionType).intersect(excludedAssertionTypes).isEmpty) + } /** * @return the verification progress of the entire program. @@ -188,7 +191,7 @@ class DependencyAnalysisProgressSupporter[T <: DependencyGraphState](interpreter * @return a list of assumption nodes ordered by their impact on proof quality. */ def computeAssumptionRanking(): List[(String, Double)] = { - val allAssertions = interpreter.toUserLevelNodes(interpreter.getNonInternalAssertionNodes).filter(ass => ass.assertionTypes.intersect(AssumptionType.importedTypes).isEmpty) + val allAssertions = interpreter.toUserLevelNodes(getAssertionsRelevantForProgress.values.flatten) val relevantDependenciesPerAssertion = allAssertions .map(ass => (ass, interpreter.toUserLevelNodes(interpreter.getAllNonInternalDependencies(ass.lowerLevelNodes.map(_.id))).diffBySource(Set(ass)))).toMap @@ -217,7 +220,7 @@ class DependencyAnalysisProgressSupporter[T <: DependencyGraphState](interpreter * Prints all uncovered source code statements and returns the number of uncovered source code statements. */ def computeUncoveredStatements(): Int = { - val allAssertions = interpreter.toUserLevelNodes(interpreter.getNonInternalAssertionNodes) + val allAssertions = interpreter.toUserLevelNodes(getAssertionsRelevantForProgress.values.flatten) val allDependencies = allAssertions.flatMap(ass => interpreter.toUserLevelNodes(interpreter.getAllNonInternalDependencies(ass.lowerLevelNodes.map(_.id))).diffBySource(Set(ass))).getSourceSet() val explicitAssertions = interpreter.toUserLevelNodes(interpreter.getExplicitAssertionNodes) From 0548f8083de309c82aa1d4d6b685c37e3b959b75 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 8 May 2026 15:38:32 +0200 Subject: [PATCH 447/474] fix join nodes on infeasible paths --- src/main/scala/decider/Decider.scala | 7 ++++++- .../DependencyGraphInterpreter.scala | 2 +- src/main/scala/rules/Brancher.scala | 7 ------- src/main/scala/rules/Evaluator.scala | 15 ------------- src/main/scala/rules/Executor.scala | 21 ++++--------------- 5 files changed, 11 insertions(+), 41 deletions(-) diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index 46125dcd6..d0a1024d8 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -479,11 +479,16 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => /* Asserting facts */ def checkSmoke(analysisInfos: DependencyAnalysisInfos, isAssert: Boolean=false): Boolean = { - if(isPathInfeasible) return true val checkNode = dependencyAnalyzer.createAssertOrCheckNode(False, analysisInfos, !isAssert) val label = DependencyAnalyzer.createAssertionLabel(checkNode.map(_.id)) + if(isPathInfeasible) { + checkNode foreach dependencyAnalyzer.addAssertionNode + dependencyAnalyzer.addDependency(pcs.getCurrentInfeasibilityNode, checkNode.map(_.id)) + return true + } + val timeout = if (isAssert) Verifier.config.assertTimeout.toOption else Verifier.config.checkTimeout.toOption val result = prover.check(timeout, label) == Unsat diff --git a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala index a7a19447d..9b592d37d 100644 --- a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala @@ -111,7 +111,7 @@ class DependencyGraphInterpreter[T <: DependencyGraphState](name: String, depend ) def getExplicitAssumptionNodes: Set[DependencyAnalysisNode] = getNonInternalAssumptionNodes filter (node => - AssumptionType.explicitAssumptionTypes.contains(node.assumptionType) + node.isInstanceOf[GeneralAssumptionNode] && AssumptionType.explicitAssumptionTypes.contains(node.assumptionType) ) def getNonInternalAssertionNodes: Set[DependencyAnalysisNode] = getAssertionNodes filter (node => diff --git a/src/main/scala/rules/Brancher.scala b/src/main/scala/rules/Brancher.scala index 86631541b..c0f424137 100644 --- a/src/main/scala/rules/Brancher.scala +++ b/src/main/scala/rules/Brancher.scala @@ -15,7 +15,6 @@ import viper.silicon.state.State import viper.silicon.state.terms.{FunctionDecl, MacroDecl, Not, Term} import viper.silicon.verifier.Verifier import viper.silver.ast -import viper.silver.ast.utility.Expressions import viper.silver.dependencyAnalysis.{DependencyType, NoDependencyAnalysisMerge} import viper.silver.reporter.BranchFailureMessage import viper.silver.verifier.Failure @@ -48,12 +47,6 @@ object brancher extends BranchingRules { if(v.decider.isPathInfeasible){ val analysisInfos1 = v.decider.handleAndGetUpdatedAnalysisInfos(analysisInfos, conditionExp._1.info, conditionExp._1) -// FIXME ake: infeasible path - // val assertionNodesForJoin = DependencyAnalyzer.extractAssertionsForJoin(conditionExp._1, s.program) -// assertionNodesForJoin.foreach(n => v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, CompositeAnalysisSourceInfo(v.decider.analysisSourceInfoStack.getFullSourceInfo, AnalysisSourceInfo.createAnalysisSourceInfo(n)), v.decider.analysisSourceInfoStack.getDependencyType, isJoinNode=true)) - if(!Expressions.isKnownWellDefined(conditionExp._1, Some(s.program))){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfos1) - } v.decider.dependencyAnalyzer.addAssumption(condition, analysisInfos1) return fThen(s, v).combine(fElse(s, v)) } diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 457c50f73..c4a609bdd 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -23,7 +23,6 @@ import viper.silicon.utils.toSf import viper.silicon.verifier.Verifier import viper.silicon.{Map, TriggerSets} import viper.silver.ast -import viper.silver.ast.utility.Expressions import viper.silver.ast.{AnnotationInfo, LocalVarWithVersion, WeightedQuantifier} import viper.silver.dependencyAnalysis._ import viper.silver.reporter.{AnnotationWarning, WarningsDuringVerification} @@ -100,20 +99,6 @@ object evaluator extends EvaluationRules { (Q: (State, Term, Option[ast.Exp], Verifier) => VerificationResult) : VerificationResult = { - if(v.decider.isPathInfeasible){ -// FIXME ake: infeasible paths -// val assertionNodesForJoin = DependencyAnalyzer.extractAssertionsForJoin(e, s.program) -// assertionNodesForJoin.foreach(n => v.decider.dependencyAnalyzer.addAssumption(True, CompositeAnalysisSourceInfo(v.decider.analysisSourceInfoStack.getFullSourceInfo, AnalysisSourceInfo.createAnalysisSourceInfo(n)), v.decider.analysisSourceInfoStack.getAssumptionType, isJoinNode=true)) - - if(!Expressions.isKnownWellDefined(e, Some(s.program))){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfos) - } - val sort = v.symbolConverter.toSort(e.typ) - val newVar = v.decider.fresh(sort, None) // just make sure the returned term typechecks - return Q(s, newVar, None, v) - } - - /* For debugging only */ e match { case _: ast.TrueLit | _: ast.FalseLit | _: ast.NullLit | _: ast.IntLit | _: ast.FullPerm | _: ast.NoPerm diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 80afbd7d4..cfc3efd53 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -21,7 +21,6 @@ import viper.silicon.utils.ast.{BigAnd, extractPTypeFromExp, simplifyVariableNam import viper.silicon.utils.freshSnap import viper.silicon.verifier.Verifier import viper.silver.ast.Literal -import viper.silver.ast.utility.Statements import viper.silver.cfg.silver.SilverCfg import viper.silver.cfg.silver.SilverCfg.{SilverBlock, SilverEdge} import viper.silver.cfg.{ConditionalEdge, StatementBlock} @@ -353,20 +352,6 @@ object executor extends ExecutionRules { (continuation: (State, Verifier) => VerificationResult) : VerificationResult = { - if(v.decider.isPathInfeasible){ - // FIXME ake: infeasbile path -// val assertionNodesForJoin = DependencyAnalyzer.extractAssertionsForJoin(stmt, state.program) -// assertionNodesForJoin.foreach(n => v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, CompositeAnalysisSourceInfo(v.decider.analysisSourceInfoStack.getFullSourceInfo, AnalysisSourceInfo.createAnalysisSourceInfo(n)), v.decider.analysisSourceInfoStack.getDependencyType, isJoinNode=true)) - - if(Statements.hasProofObligations(stmt, state.program)){ - v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfos) - } - if(Statements.introducesSmtAssumptions(stmt)){ - v.decider.dependencyAnalyzer.addAssumption(True, analysisInfos) - } - return continuation(state, v) - } - val s = state.copy(h = magicWandSupporter.getExecutionHeap(state)) val Q: (State, Verifier) => VerificationResult = (s, v) => { continuation(magicWandSupporter.moveToReserveHeap(s, v), v)} @@ -583,7 +568,9 @@ object executor extends ExecutionRules { val s2 = s1.copy(g = Store(fargs.zip(argsFreshVar)), recordVisited = true) - val presWithDAInfo = DependencyAnalysisMergeInfo.attachExpMergeInfo(meth.pres.flatMap(_.topLevelConjuncts), Some(analysisInfos.getSourceInfo)) + + val presWithDAInfo = if(!v1.decider.isDependencyAnalysisEnabled) meth.pres else DependencyAnalysisMergeInfo.attachExpMergeInfo(meth.pres.flatMap(_.topLevelConjuncts), Some(analysisInfos.getSourceInfo)) + consumes(s2, presWithDAInfo, false, _ => pvePre, v1, analysisInfos.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Source, EdgeType.Up)))((s3, _, v2) => { v2.symbExLog.closeScope(preCondId) val postCondLog = new CommentRecord("Postcondition", s3, v2.decider.pcs) @@ -592,7 +579,7 @@ object executor extends ExecutionRules { val gOuts = Store(outs.map(x => (x, v2.decider.fresh(x))).toMap) val s4 = s3.copy(g = s3.g + gOuts, oldHeaps = s3.oldHeaps + (Verifier.PRE_STATE_LABEL -> magicWandSupporter.getEvalHeap(s1))) - val postsWithDAInfo = DependencyAnalysisMergeInfo.attachExpMergeInfo(meth.posts.flatMap(_.topLevelConjuncts), Some(analysisInfos.getSourceInfo)) + val postsWithDAInfo = if(!v1.decider.isDependencyAnalysisEnabled) meth.posts else DependencyAnalysisMergeInfo.attachExpMergeInfo(meth.posts.flatMap(_.topLevelConjuncts), Some(analysisInfos.getSourceInfo)) produces(s4, freshSnap, postsWithDAInfo, _ => pveCallTransformed, v2, analysisInfos.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Sink, EdgeType.Down)))((s5, v3) => { v3.symbExLog.closeScope(postCondId) v3.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) From 49d90fd6cb19a98e54ea2bee8017a60f6077bc88 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 8 May 2026 15:53:18 +0200 Subject: [PATCH 448/474] add assertionTypes query --- .../DebugDependencyAnalysisCliExtension.scala | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/main/scala/dependencyAnalysis/cliTool/DebugDependencyAnalysisCliExtension.scala b/src/main/scala/dependencyAnalysis/cliTool/DebugDependencyAnalysisCliExtension.scala index 7b5d49706..06750098a 100644 --- a/src/main/scala/dependencyAnalysis/cliTool/DebugDependencyAnalysisCliExtension.scala +++ b/src/main/scala/dependencyAnalysis/cliTool/DebugDependencyAnalysisCliExtension.scala @@ -10,6 +10,7 @@ class DebugDependencyAnalysisCliExtension(override val interpreter: DependencyGr override val name: String = "Debug Features" override val commands: List[DependencyAnalysisCliCommand] = List( new AssumptionTypesCommand, + new AssertionTypesCommand, new LowLevelNodesCommand ) @@ -34,6 +35,27 @@ class DebugDependencyAnalysisCliExtension(override val interpreter: DependencyGr nodes.groupBy(_.sourceInfo).view.mapValues(_.map(_.assumptionType)).toMap } + class AssertionTypesCommand extends DependencyAnalysisCliCommand { + override val cmdName: String = "assertionTypes" + override val cmd: Seq[String] => Unit = { inputs => + if(inputs.isEmpty) + println(getAssertionTypesPerNode().mkString("\n")) + else + inputs.flatMap(_.toIntOption).foreach(i => println(s"$i: ${getAssertionTypesByLine(i)}")) + } + override val description: String = s"'$cmdName [line numbers]' to print the assertion types of all nodes or just the provided lines" + + def getAssertionTypesByLine(line: Int): Set[AssumptionType] = { + interpreter.getNodesByLine(line).filter(_.isInstanceOf[GeneralAssertionNode]).map(_.assumptionType) + } + + def getAssertionTypesPerNode(): Map[AnalysisSourceInfo, Set[AssumptionType]] = + getAssertionTypesPerNode(interpreter.getAssertionNodes) + + def getAssertionTypesPerNode(nodes: Set[DependencyAnalysisNode]): Map[AnalysisSourceInfo, Set[AssumptionType]] = + nodes.groupBy(_.sourceInfo).view.mapValues(_.map(_.assumptionType)).toMap + } + class LowLevelNodesCommand extends DependencyAnalysisCliCommand { override val cmdName: String = "lowLevelNodes" override val cmd: Seq[String] => Unit = inputs => From 9c469a5414a73308b7bf604f3dc62dc8ba742cd5 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 8 May 2026 16:26:39 +0200 Subject: [PATCH 449/474] create one node per top-level conjuncts for assertions --- src/main/scala/rules/Executor.scala | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index cfc3efd53..870206eb6 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -450,7 +450,8 @@ object executor extends ExecutionRules { case exhale @ ast.Exhale(a) => val pve = ExhaleFailed(exhale) - consume(s, a, false, pve, v, analysisInfos)((s1, _, v1) => + val analysisInfos1 = if(a.topLevelConjuncts.size > 1) analysisInfos.copy(sourceInfos = List.empty) else analysisInfos // needed to ensure that each top-level conjunct gets a dedicated assertion node + consume(s, a, false, pve, v, analysisInfos1)((s1, _, v1) => Q(s1, v1)) case assert @ ast.Assert(a: ast.FalseLit) if !s.isInPackage => @@ -471,8 +472,9 @@ object executor extends ExecutionRules { ) case assert @ ast.Assert(a) if Verifier.config.disableSubsumption() => - val r = - consume(s, a, false, AssertFailed(assert), v, analysisInfos)((_, _, _) => + val analysisInfos1 = if(a.topLevelConjuncts.size > 1) analysisInfos.copy(sourceInfos = List.empty) else analysisInfos // needed to ensure that each top-level conjunct gets a dedicated assertion node + val r = + consume(s, a, false, AssertFailed(assert), v, analysisInfos1)((_, _, _) => Success()) r combine Q(s, v) @@ -480,6 +482,8 @@ object executor extends ExecutionRules { case assert @ ast.Assert(a) => val pve = AssertFailed(assert) + val analysisInfos1 = if(a.topLevelConjuncts.size > 1) analysisInfos.copy(sourceInfos = List.empty) else analysisInfos // needed to ensure that each top-level conjunct gets a dedicated assertion node + if (s.exhaleExt) { Predef.assert(s.h.values.isEmpty) Predef.assert(s.reserveHeaps.head.values.isEmpty) @@ -488,11 +492,11 @@ object executor extends ExecutionRules { * hUsed (reserveHeaps.head) instead of consuming them. hUsed is later discarded and replaced * by s.h. By copying hUsed to s.h the contained permissions remain available inside the wand. */ - consume(s, a, false, pve, v, analysisInfos)((s2, _, v1) => { + consume(s, a, false, pve, v, analysisInfos1)((s2, _, v1) => { Q(s2.copy(h = s2.reserveHeaps.head), v1) }) } else - consume(s, a, false, pve, v, analysisInfos)((s1, _, v1) => { + consume(s, a, false, pve, v, analysisInfos1)((s1, _, v1) => { val s2 = s1.copy(h = s.h, reserveHeaps = s.reserveHeaps) Q(s2, v1)}) From b2bd2bd3f13455bd43e581393a32494452a8386f Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 12 May 2026 08:42:06 +0200 Subject: [PATCH 450/474] clean up silver --- src/main/scala/dependencyAnalysis/DependencyGraph.scala | 3 +-- .../DependencyAnalysisProgressSupporter.scala | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyGraph.scala b/src/main/scala/dependencyAnalysis/DependencyGraph.scala index 35c328be5..1b41246df 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraph.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraph.scala @@ -252,8 +252,7 @@ class DependencyGraph[T <: DependencyGraphState] extends ReadOnlyDependencyGraph */ def removeInternalNodes(): Unit = { def filterCriteria(n: DependencyAnalysisNode) = { - AssumptionType.internalTypes.contains(n.assumptionType) && !AssumptionType.CustomInternal.equals(n.assumptionType) && - !n.isInstanceOf[InfeasibilityNode] + AssumptionType.internalTypes.contains(n.assumptionType) && !n.isInstanceOf[InfeasibilityNode] } assumptionNodes filter filterCriteria foreach removeAllEdgesForNode diff --git a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisProgressSupporter.scala b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisProgressSupporter.scala index cc25135ee..747a82efd 100644 --- a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisProgressSupporter.scala +++ b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisProgressSupporter.scala @@ -110,7 +110,7 @@ class DependencyAnalysisProgressSupporter[T <: DependencyGraphState](interpreter } private def getAssertionsRelevantForProgress: Map[AnalysisSourceInfo, Set[DependencyAnalysisNode]] = { - val excludedAssertionTypes = AssumptionType.importedTypes ++ Set(AssumptionType.Precondition) + val excludedAssertionTypes = AssumptionType.importedTypes ++ AssumptionType.preconditionTypes sourceToAssertionNodesMap.filter(ass => ass._2.map(_.assumptionType).intersect(excludedAssertionTypes).isEmpty) } From 52c63db6a747c8ab9c8bcd8d8b32982ad452276f Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 12 May 2026 08:44:37 +0200 Subject: [PATCH 451/474] clean up silver --- silver | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/silver b/silver index 65e523a6f..5c1eeb66a 160000 --- a/silver +++ b/silver @@ -1 +1 @@ -Subproject commit 65e523a6fe967cf33a261b16d84aa2815b3ddf85 +Subproject commit 5c1eeb66a0b7033ee0de117f4b2a352d8551c874 From bcc8d7e0269cf63afc66786b7addf02a02043b8d Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 12 May 2026 09:01:21 +0200 Subject: [PATCH 452/474] remove benchmark artifacts --- .../viper_perf_benchmark_plotter.py | 89 ------------------ .../mce/imprecisions.vpr | 48 ---------- .../dependencyAnalysisTests/mce/mce_tests.vpr | 23 ----- .../minimalExamples-precision/assert.vpr | 25 ----- .../minimalExamples-precision/debug.vpr | 43 --------- .../minimalExamples-precision/domains.vpr | 36 ------- .../infeasibility.vpr | 56 ----------- ..._2025-07-24_09-53-19_absolute_runtimes.png | Bin 71405 -> 0 bytes .../result_2025-07-24_09-53-19_overhead.png | Bin 68691 -> 0 bytes ...t_2025-07-24_09-53-19_overhead_vs_size.png | Bin 27767 -> 0 bytes ..._2025-07-24_15-48-37_absolute_runtimes.png | Bin 40630 -> 0 bytes .../result_2025-07-24_15-48-37_overhead.png | Bin 36132 -> 0 bytes ...t_2025-07-24_15-48-37_overhead_vs_size.png | Bin 24058 -> 0 bytes .../precisionTests/results/result_table.out | 31 ------ .../verificationFailures/failures.vpr | 76 --------------- .../dependencyAnalysisTests/viperTest.vpr | 63 ------------- .../dependencyAnalysisTests/viperTest2.vpr | 24 ----- 17 files changed, 514 deletions(-) delete mode 100644 src/test/resources/dependencyAnalysisTests/benchmark_scripts/viper_perf_benchmark_plotter.py delete mode 100644 src/test/resources/dependencyAnalysisTests/mce/imprecisions.vpr delete mode 100644 src/test/resources/dependencyAnalysisTests/mce/mce_tests.vpr delete mode 100644 src/test/resources/dependencyAnalysisTests/minimalExamples-precision/assert.vpr delete mode 100644 src/test/resources/dependencyAnalysisTests/minimalExamples-precision/debug.vpr delete mode 100644 src/test/resources/dependencyAnalysisTests/minimalExamples-precision/domains.vpr delete mode 100644 src/test/resources/dependencyAnalysisTests/minimalExamples-precision/infeasibility.vpr delete mode 100644 src/test/resources/dependencyAnalysisTests/performanceBenchmark/result_2025-07-24_09-53-19_absolute_runtimes.png delete mode 100644 src/test/resources/dependencyAnalysisTests/performanceBenchmark/result_2025-07-24_09-53-19_overhead.png delete mode 100644 src/test/resources/dependencyAnalysisTests/performanceBenchmark/result_2025-07-24_09-53-19_overhead_vs_size.png delete mode 100644 src/test/resources/dependencyAnalysisTests/performanceBenchmark/result_2025-07-24_15-48-37_absolute_runtimes.png delete mode 100644 src/test/resources/dependencyAnalysisTests/performanceBenchmark/result_2025-07-24_15-48-37_overhead.png delete mode 100644 src/test/resources/dependencyAnalysisTests/performanceBenchmark/result_2025-07-24_15-48-37_overhead_vs_size.png delete mode 100644 src/test/resources/dependencyAnalysisTests/precisionTests/results/result_table.out delete mode 100644 src/test/resources/dependencyAnalysisTests/verificationFailures/failures.vpr delete mode 100644 src/test/resources/dependencyAnalysisTests/viperTest.vpr delete mode 100644 src/test/resources/dependencyAnalysisTests/viperTest2.vpr diff --git a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/viper_perf_benchmark_plotter.py b/src/test/resources/dependencyAnalysisTests/benchmark_scripts/viper_perf_benchmark_plotter.py deleted file mode 100644 index f02468bff..000000000 --- a/src/test/resources/dependencyAnalysisTests/benchmark_scripts/viper_perf_benchmark_plotter.py +++ /dev/null @@ -1,89 +0,0 @@ -import matplotlib.pyplot as plt -import numpy as np - -def import_result(file: str): - with open(file, mode="r") as f: - contents = f.readlines() - header = [c.strip() for c in contents[0].split(",")] - results = {} - for line in contents[1:]: - parts = line.split(',') - test_name = parts[0].strip() - runtimes = [float(x.strip()) for x in parts[1:]] - results[test_name] = runtimes - return header, results - -def plot_absolute_runtimes(header: list[str], test_results: dict[str, list[float]], out_file: str): - names = [] - result1 = [] - result2 = [] - for name, result in test_results.items(): - names.append(name.removeprefix("dependencyAnalysisTests/")) - result1.append(result[0]) - result2.append(result[1]) - - x = np.arange(len(names)) - width = 0.35 - - fig, ax = plt.subplots(figsize=(12, 6)) - ax.bar(x - width/2, result1, width, label=header[1]) - ax.bar(x + width/2, result2, width, label=header[2]) - - ax.set_ylabel('Runtime (ms)') - ax.set_title('Absolute Runtimes in ms') - ax.set_xticks(x) - ax.set_xticklabels(names, rotation=90) - ax.legend() - - plt.tight_layout() - # plt.show() - plt.savefig(out_file.replace(".out", "_absolute_runtimes.png")) - # plt.show() - -def plot_overhead(header: list[str], test_results: dict[str, list[float]], out_file: str): - names = [] - result1 = [] - for name, result in test_results.items(): - names.append(name.removeprefix("dependencyAnalysisTests/")) - result1.append(result[2]) - - fig, ax = plt.subplots(figsize=(12, 6)) - ax.bar(names, result1) - - ax.set_ylabel('analysis runtime/baseline runtime') - ax.set_title('Overhead of the Analysis') - ax.set_xticklabels(names, rotation=90) - ax.legend() - - plt.tight_layout() - plt.savefig(out_file.replace(".out", "_overhead.png")) - # plt.show() - -def plot_overhead_vs_program_size(header: list[str], test_results: dict[str, list[float]], out_file: str): - names = [] - result1 = [] - result2 = [] - for name, result in test_results.items(): - names.append(name) - result1.append(result[2]) - result2.append(result[3]) - - plt.figure(figsize=(12,6)) - plt.scatter(result2, result1) - - plt.ylabel('analysis runtime/baseline runtime') - plt.xlabel('program size (#lines of code)') - plt.title('Overhead of the Analysis vs Program Size') - plt.legend() - - plt.grid(True) - plt.tight_layout() - # plt.show() - plt.savefig(out_file.replace(".out", "_overhead_vs_size.png")) - -input_file = input("Input file: ") -header, result = import_result(input_file) - -plot_absolute_runtimes(header, result, input_file) -plot_overhead(header, result, input_file) -plot_overhead_vs_program_size(header, result, input_file) \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/mce/imprecisions.vpr b/src/test/resources/dependencyAnalysisTests/mce/imprecisions.vpr deleted file mode 100644 index c0987ca6d..000000000 --- a/src/test/resources/dependencyAnalysisTests/mce/imprecisions.vpr +++ /dev/null @@ -1,48 +0,0 @@ -field f: Int - -method exhaleInhale(a: Ref) // inhaleExhale/assignment_field - requires @dependency("Precondition")(acc(a.f)) -{ - exhale acc(a.f, 1/2) - inhale acc(a.f, 1/2) - -var gen_RO_INT_1: Int - -var gen_dummy_int: Int -{ - @irrelevant("Implicit") - a.f := a.f + gen_RO_INT_1 // reported as dependency on mce, why? -} - - @testAssertion("Explicit") - assert perm(a.f) == write -} - -method exhale1(){ // inhaleExhale/magicWand -> reports dependency to gen_x access inhale (aliasing checks?) - var x: Ref - - @dependency("Explicit") - inhale acc(x.f) - @dependency("Explicit") - inhale x.f > 0 - - exhale acc(x.f, 1/2) - - -var gen_dummy_int: Int -{ - var gen_i: Int - var gen_x: Ref - @irrelevant("Explicit") - inhale gen_i > 0 - @irrelevant("Explicit") - inhale acc(gen_x.f) && gen_x.f > 0 - @irrelevant("Implicit") - package gen_i >= 0 --* acc(gen_x.f) && gen_x.f > 0 - @irrelevant("Implicit") - apply gen_i >= 0 --* acc(gen_x.f) && gen_x.f > 0 -} - - @testAssertion("Explicit") - assert x.f > 0 -} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/mce/mce_tests.vpr b/src/test/resources/dependencyAnalysisTests/mce/mce_tests.vpr deleted file mode 100644 index 009297bf8..000000000 --- a/src/test/resources/dependencyAnalysisTests/mce/mce_tests.vpr +++ /dev/null @@ -1,23 +0,0 @@ -field f: Int - -method mce_test_1(a: Ref, b: Ref, c: Ref) -{ - var x: Int := 0 - - - @dependency("Explicit") - inhale acc(a.f) // assumption 1 - @irrelevant("Explicit") - inhale acc(b.f) // assumption 2 - - if (@dependency("PathCondition")(c == a || c == b)) { - @dependency("Implicit") - x := c.f - } - - @dependency("Explicit") - assume c == a // assumption 3 - - @testAssertion("Explicit") - assert x == a.f -} diff --git a/src/test/resources/dependencyAnalysisTests/minimalExamples-precision/assert.vpr b/src/test/resources/dependencyAnalysisTests/minimalExamples-precision/assert.vpr deleted file mode 100644 index a377af179..000000000 --- a/src/test/resources/dependencyAnalysisTests/minimalExamples-precision/assert.vpr +++ /dev/null @@ -1,25 +0,0 @@ -field f: Int - -method inhale1(){ - var x: Ref - - @dependency("Explicit") - inhale acc(x.f, 1/2) - @dependency("Explicit") - inhale x.f > 0 - - inhale acc(x.f, 1/2) - - -var gen_dummy_int: Int -{ - var gen_i: Int - @irrelevant("Explicit") - inhale gen_i > 0 // dependency of the assertion - @irrelevant() - assert x.f>0 && gen_i > 0 // reported as internal dependency -} - - @testAssertion("Explicit") - assert x.f > 0 -} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/minimalExamples-precision/debug.vpr b/src/test/resources/dependencyAnalysisTests/minimalExamples-precision/debug.vpr deleted file mode 100644 index 947e7691e..000000000 --- a/src/test/resources/dependencyAnalysisTests/minimalExamples-precision/debug.vpr +++ /dev/null @@ -1,43 +0,0 @@ -field f: Int - -method incr(a: Ref) - requires acc(a.f) - ensures acc(a.f) - ensures a.f == old(a.f) + 1 -{ - a.f := a.f + 1 -} -predicate implPred(a: Int, x: Ref){ - a > 0 ==> acc(x.f) -} - - -method unfoldingWithImpl(a: Int, x: Ref) - requires @dependency("Precondition")(implPred(a, x)) -{ - var res: Int - if(@dependency("PathCondition")(a > 5)){ - - -var gen_dummy_int: Int -{ - var gen_xs: Seq[Ref] - @irrelevant("Explicit") - inhale |gen_xs| > 2 - @irrelevant("Explicit") - inhale forall gen_x: Ref :: gen_x in gen_xs ==> acc(gen_x.f) - @irrelevant("Explicit") - inhale forall gen_x: Ref :: gen_x in gen_xs ==> gen_x.f < 0 - @irrelevant("Implicit") - gen_xs[0].f := gen_xs[1].f + a - res - @irrelevant("Implicit") - res := gen_xs[0].f + gen_xs[1].f -} - - @testAssertion("Implicit") - res := unfolding implPred(a, x) in x.f - }else{ - @irrelevant("Implicit") - res := a - } -} diff --git a/src/test/resources/dependencyAnalysisTests/minimalExamples-precision/domains.vpr b/src/test/resources/dependencyAnalysisTests/minimalExamples-precision/domains.vpr deleted file mode 100644 index 4def0282f..000000000 --- a/src/test/resources/dependencyAnalysisTests/minimalExamples-precision/domains.vpr +++ /dev/null @@ -1,36 +0,0 @@ -field f: Int -field val: Int -define access(a) - (forall j: Int :: 0 <= j && j < len(a) ==> acc(slot(a,j).val)) - - -domain IArray { - function slot(a: IArray, i: Int): Ref - function len(a: IArray): Int - function first(r: Ref): IArray - function second(r: Ref): Int - - axiom all_diff { - forall a: IArray, i: Int :: { slot(a,i) } - first(slot(a,i)) == a && second(slot(a,i)) == i - } - - axiom len_nonneg { - forall a: IArray :: { len(a) } - len(a) >= 0 - } -} - - -method domain_functions() -{ - var a: IArray - inhale len(a) == 3 - inhale access(a) - - slot(a,1).val := 1 - - slot(a,0).val := 0 - - assert slot(a,0).val >= 0 // test assertion - precise -} diff --git a/src/test/resources/dependencyAnalysisTests/minimalExamples-precision/infeasibility.vpr b/src/test/resources/dependencyAnalysisTests/minimalExamples-precision/infeasibility.vpr deleted file mode 100644 index 3d031f49a..000000000 --- a/src/test/resources/dependencyAnalysisTests/minimalExamples-precision/infeasibility.vpr +++ /dev/null @@ -1,56 +0,0 @@ -field f: Int - -predicate gen_confuse_with_impl(i: Int, x: Ref){ - i >= 0 ==> acc(x.f) -} -function gen_add_positive(a: Int, b: Int): Int - requires a >= 0 && b >= 0 - ensures result == a + b - ensures result >= 0 - -method branch1(){ - var x: Int, y: Int - - x := 10 - - inhale y > 0 - - var gen_dummy_int: Int - var gen_x: Ref - inhale acc(gen_x.f) - - fold gen_confuse_with_impl(y, gen_x) - unfold gen_confuse_with_impl(y, gen_x) // causes branching, one branch is infeasible, reported as dependency - - assert x > 0 -} - -method branch2(){ - var x: Int, y: Int - - x := 10 - - // inhale y > 0 // ONLY DIFFERENCE!!! - - var gen_dummy_int: Int - var gen_x: Ref - inhale acc(gen_x.f) - - inhale gen_confuse_with_impl(y, gen_x) - unfold gen_confuse_with_impl(y, gen_x) // causes branching, all branches feasible, not reported as dependency - - assert x > 0 -} - -method branch_implication(){ - var x: Int, y: Int - - if(x > 0){ - inhale y > 0 - }else{ - inhale y > 0 - } - - assert y > 0 - assert x > 0 ==> y > 0 // precise -} diff --git a/src/test/resources/dependencyAnalysisTests/performanceBenchmark/result_2025-07-24_09-53-19_absolute_runtimes.png b/src/test/resources/dependencyAnalysisTests/performanceBenchmark/result_2025-07-24_09-53-19_absolute_runtimes.png deleted file mode 100644 index ed180ed940d83d84689cef7b5a0e5570ea19ac82..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 71405 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 z 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 Z8LY&^#I}0-$e=Gk<&>IImZF*O{{R)oy|@4X diff --git a/src/test/resources/dependencyAnalysisTests/performanceBenchmark/result_2025-07-24_09-53-19_overhead_vs_size.png b/src/test/resources/dependencyAnalysisTests/performanceBenchmark/result_2025-07-24_09-53-19_overhead_vs_size.png deleted file mode 100644 index 8fe299098edefdb025f261fe4b334be692ff79b2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27767 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 diff --git a/src/test/resources/dependencyAnalysisTests/performanceBenchmark/result_2025-07-24_15-48-37_absolute_runtimes.png b/src/test/resources/dependencyAnalysisTests/performanceBenchmark/result_2025-07-24_15-48-37_absolute_runtimes.png deleted file mode 100644 index 95239ab753b522f5072e75176e78e3ae3757dc16..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 40630 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 MZZx`W;2iaT06q)49smFU diff --git a/src/test/resources/dependencyAnalysisTests/performanceBenchmark/result_2025-07-24_15-48-37_overhead.png b/src/test/resources/dependencyAnalysisTests/performanceBenchmark/result_2025-07-24_15-48-37_overhead.png deleted file mode 100644 index 75f56d4872cc85b0ddaf5370664b61e15d27eb5b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 36132 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 z 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 diff --git a/src/test/resources/dependencyAnalysisTests/precisionTests/results/result_table.out b/src/test/resources/dependencyAnalysisTests/precisionTests/results/result_table.out deleted file mode 100644 index 1df286803..000000000 --- a/src/test/resources/dependencyAnalysisTests/precisionTests/results/result_table.out +++ /dev/null @@ -1,31 +0,0 @@ - & A-inhaleExhale & B-permissions & C-permWildcard & D-quantifiedPermissions & E-function-call & F-method-call & G-predicates & H-magicWands & I-branches & J-loops & K-domain & L-misc -00_baseline & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 -01_inhale_invariant & 0.96 & 1.00 & 1.00 & 0.92 & 1.00 & 1.00 & 0.82 & 1.00 & 1.00 & 1.00 & 0.96 & 1.00 -02_2_assert_invariant & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 0.97 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 -02_assert_invariant & 0.97 & 1.00 & 1.00 & 0.94 & 1.00 & 1.00 & 0.87 & 1.00 & 1.00 & 1.00 & 0.97 & 1.00 -03_assignment_pure & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 -04_2_assignment_field_disjoint_field & 1.00 & 1.00 & 1.00 & 0.98 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 -04_assignment_field & 1.00 & 1.00 & NaN & NaN & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 -05_assignment_disjoint_ref & 1.00 & 1.00 & 1.00 & 0.96 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 -06_assignment_disjoint_ref_disjoint_field & 1.00 & 1.00 & 1.00 & 0.96 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 -07_assignment_disjoint_ref_quantified & 0.89 & 0.88 & 0.91 & 0.86 & 1.00 & 1.00 & 0.99 & 0.98 & 1.00 & 1.00 & 1.00 & 0.94 -08_assignment_disjoint_ref_disjoint_field_quantified & 1.00 & 1.00 & 1.00 & 0.96 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 -09_1_fold_unfold & 0.95 & 1.00 & 1.00 & 0.93 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 -09_fold_unfold_with_implication & 0.89 & 0.76 & 0.83 & 0.73 & 0.89 & 0.90 & 0.76 & 0.84 & 0.85 & 0.86 & 0.89 & 0.95 -10_function_add_pure & 1.00 & 1.00 & 1.00 & 0.95 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 -11_function_add_impure & 1.00 & 1.00 & NaN & NaN & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 -12_method_call_pure & 1.00 & 1.00 & 1.00 & NaN & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 -13_method_call_impure & 1.00 & 1.00 & 1.00 & 0.98 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 -14_branch & 1.00 & 1.00 & 1.00 & 0.96 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 -15_branch_nested & 1.00 & 1.00 & 1.00 & 0.88 & 1.00 & 1.00 & 0.92 & 1.00 & 0.93 & 1.00 & 1.00 & 1.00 -16_branch_infeasible & 0.92 & 0.77 & 0.85 & 0.80 & 0.93 & 0.93 & 0.80 & 0.88 & 0.87 & 0.89 & 0.89 & 0.96 -17_branch_function & 0.94 & 1.00 & 0.92 & 0.86 & 1.00 & 1.00 & 0.88 & 1.00 & 0.76 & 0.94 & 1.00 & 1.00 -18_while_pure & 1.00 & 1.00 & 1.00 & NaN & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & NaN & 1.00 & 1.00 -19_while_perm & 1.00 & 1.00 & NaN & NaN & 1.00 & 1.00 & NaN & NaN & 1.00 & 1.00 & 1.00 & 1.00 -20_magic_wand & 0.94 & 0.95 & 1.00 & 0.94 & 1.00 & 1.00 & 1.00 & 0.96 & 1.00 & 1.00 & 1.00 & 1.00 -21_2_quasihavoc_disjoint_field & 1.00 & 1.00 & 1.00 & 0.98 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 -21_quasihavoc & 0.90 & 0.74 & NaN & NaN & 1.00 & 1.00 & 0.99 & 0.96 & 1.00 & 1.00 & 0.96 & 0.96 -22_quasihavoc_disjoint_ref & 0.87 & 0.68 & 0.78 & 0.69 & 1.00 & 1.00 & 0.98 & 0.96 & 1.00 & 1.00 & 1.00 & 0.94 -23_quasihavoc_disjoint_ref_disjoint_field & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 -24_goto_pure & 1.00 & 1.00 & 1.00 & NaN & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & NaN & 1.00 & 1.00 -25_unrelated_sync_points & 1.00 & 1.00 & 1.00 & 0.95 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 & 1.00 diff --git a/src/test/resources/dependencyAnalysisTests/verificationFailures/failures.vpr b/src/test/resources/dependencyAnalysisTests/verificationFailures/failures.vpr deleted file mode 100644 index d0e129318..000000000 --- a/src/test/resources/dependencyAnalysisTests/verificationFailures/failures.vpr +++ /dev/null @@ -1,76 +0,0 @@ -field f: Int - - -method foo(x: Ref) - requires acc(x.f, 1/2) - ensures acc(x.f, 1/2) -{ - var a: Int - a := x.f - if(x.f > 0){ - x.f := 1 - x.f := 2 - assert a == 0 - } - - assert a == x.f -} - - -method divBy0(a: Int) - requires a < 100 -{ - - var b: Int - if(a > 0){ - b := 10 - } - - b := 100 / a - - assert a < 100 - - assert a != 0 - - assert b >= 0 - -} - -method divBy0_2(a: Int, x: Ref) - requires a < 100 -{ - - var b: Int - b := 100 / a - - x.f := 0 - - assert a < 100 - - assert a != 0 - - assert b >= 0 - -} - - -method client() -{ - var x: Ref := new(f) - x.f := 0 - - foo(x) - - assert x.f == 0 - exhale acc(x.f) -} - - -method permissions(x: Ref, y: Ref, z: Ref) - requires acc(x.f, 1/2) && acc(y.f, 1/2) && acc(z.f, 1/2) -{ - assume x == y - - assert z != y - -} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/viperTest.vpr b/src/test/resources/dependencyAnalysisTests/viperTest.vpr deleted file mode 100644 index 2dac2bf9f..000000000 --- a/src/test/resources/dependencyAnalysisTests/viperTest.vpr +++ /dev/null @@ -1,63 +0,0 @@ -field f: Int - -method foo(a: Int, x: Ref) returns (res: Int) - ensures res >= 0 -{ - if(a < 0){ - res := x.f - }else{ - res := a - } -} - -method aTest() - ensures false -{ - assert false -} - -method bar() -{ - var a: Int - assert a > 0 - - assert a >= 0 - - a := 0 - - assert a == 0 -} - -method permTest(x: Ref) - requires acc(x.f) -{ - x.f := 0 -} - -predicate p(a: Int, x: Ref){ - acc(x.f, 1/2) && a == x.f -} - -method pTest(x: Ref, a: Int) - requires p(a, x) - requires a == 0 -{ - unfold p(a, x) - - assert a == x.f - - assert a > 0 - - var i: Int - i := 0 - assert i == 0 -} - -method unreachTest(a: Int, x: Ref) - requires a > 0 -{ - if(a < 0){ - x.f := a - assert false - } -} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/viperTest2.vpr b/src/test/resources/dependencyAnalysisTests/viperTest2.vpr deleted file mode 100644 index 1a0796467..000000000 --- a/src/test/resources/dependencyAnalysisTests/viperTest2.vpr +++ /dev/null @@ -1,24 +0,0 @@ - - -method foo(a: Int) returns (res: Int) - requires a > 0 - ensures res > 0 -{ - assume false -} - - -method bar(a: Int) returns (res: Int) - requires a > 0 -{ - assume a < 100 - res := what(a+1) - assert res >= 0 - assert res < 100 - assert res < 100 -} - - -method what(a: Int) returns (res: Int) - requires a > 0 - ensures res > 0 From 395e36fab047e19989bd455981d4cd84fef51c00 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 12 May 2026 09:11:43 +0200 Subject: [PATCH 453/474] clean up silver --- silver | 2 +- src/main/scala/Silicon.scala | 22 ++++++++----------- .../DependencyAnalysisResult.scala | 6 ++--- .../DependencyGraphInterpreter.scala | 4 ++-- .../scala/verifier/DefaultMainVerifier.scala | 5 +---- 5 files changed, 15 insertions(+), 24 deletions(-) diff --git a/silver b/silver index 5c1eeb66a..0f67b51b6 160000 --- a/silver +++ b/silver @@ -1 +1 @@ -Subproject commit 5c1eeb66a0b7033ee0de117f4b2a352d8551c874 +Subproject commit 0f67b51b6c59f69d026e0229c0911c84b3ee5dac diff --git a/src/main/scala/Silicon.scala b/src/main/scala/Silicon.scala index f91f2158a..8ae57e73e 100644 --- a/src/main/scala/Silicon.scala +++ b/src/main/scala/Silicon.scala @@ -6,28 +6,26 @@ package viper.silicon -import java.text.SimpleDateFormat -import java.util.concurrent.{Callable, Executors, TimeUnit, TimeoutException} -import scala.collection.immutable.ArraySeq -import scala.util.{Left, Right} import ch.qos.logback.classic.{Level, Logger} import com.typesafe.scalalogging.LazyLogging import org.slf4j.LoggerFactory -import viper.silver.ast -import viper.silver.frontend.{DefaultStates, MinimalViperFrontendAPI, SilFrontend, ViperFrontendAPI} -import viper.silver.reporter._ -import viper.silver.verifier.{AbstractVerificationError => SilAbstractVerificationError, Failure => SilFailure, Success => SilSuccess, TimeoutOccurred => SilTimeoutOccurred, VerificationResult => SilVerificationResult, Verifier => SilVerifier} +import viper.silicon.decider.{Cvc5ProverStdIO, Z3ProverStdIO} import viper.silicon.interfaces.Failure import viper.silicon.logger.{MemberSymbExLogger, SymbExLogger} import viper.silicon.reporting.{MultiRunRecorders, condenseToViperResult} import viper.silicon.verifier.DefaultMainVerifier -import viper.silicon.decider.{Cvc5ProverStdIO, Z3ProverStdIO} +import viper.silver.ast import viper.silver.cfg.silver.SilverCfg -import viper.silver.dependencyAnalysis.AbstractDependencyAnalysisResult +import viper.silver.frontend.{DefaultStates, MinimalViperFrontendAPI, SilFrontend, ViperFrontendAPI} import viper.silver.logger.ViperStdOutLogger +import viper.silver.reporter._ import viper.silver.utility.FileProgramSubmitter +import viper.silver.verifier.{AbstractVerificationError => SilAbstractVerificationError, Failure => SilFailure, Success => SilSuccess, TimeoutOccurred => SilTimeoutOccurred, VerificationResult => SilVerificationResult, Verifier => SilVerifier} -import scala.util.chaining._ +import java.text.SimpleDateFormat +import java.util.concurrent.{Callable, Executors, TimeUnit, TimeoutException} +import scala.collection.immutable.ArraySeq +import scala.util.{Left, Right} object Silicon { val name = BuildInfo.projectName @@ -315,8 +313,6 @@ class Silicon(val reporter: Reporter, private var debugInfo: Seq[(String, Any)] logger.setLevel(Level.toLevel(loggerLevelString)) } } - - override def getDependencyAnalysisResult: Option[AbstractDependencyAnalysisResult] = verifier.dependencyAnalysisResult } class SiliconFrontend(override val reporter: Reporter, diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisResult.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisResult.scala index 494e4e1fa..68a04512a 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisResult.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisResult.scala @@ -2,14 +2,12 @@ package viper.silicon.dependencyAnalysis import viper.silicon.dependencyAnalysis.graphInterpretation.DependencyGraphInterpreter import viper.silver.ast.Program -import viper.silver.dependencyAnalysis.AbstractDependencyAnalysisResult -case class DependencyAnalysisResult(programName: String, program: Program, dependencyGraphInterpreters: Set[DependencyGraphInterpreter[IntraProcedural]]) - extends AbstractDependencyAnalysisResult(programName, program, dependencyGraphInterpreters){ +case class DependencyAnalysisResult(programName: String, program: Program, dependencyGraphInterpreters: Set[DependencyGraphInterpreter[IntraProcedural]]){ protected lazy val fullDependencyGraphInterpreter: DependencyGraphInterpreter[Final] = DependencyAnalyzer.joinGraphsAndGetInterpreter(programName, dependencyGraphInterpreters) - override def getFullDependencyGraphInterpreter: DependencyGraphInterpreter[Final] = fullDependencyGraphInterpreter + def getFullDependencyGraphInterpreter: DependencyGraphInterpreter[Final] = fullDependencyGraphInterpreter } diff --git a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala index 9b592d37d..f33068ad7 100644 --- a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala @@ -6,7 +6,7 @@ import viper.silicon.interfaces.Failure import viper.silicon.verifier.Verifier import viper.silver.ast import viper.silver.ast.Program -import viper.silver.dependencyAnalysis.{AbstractDependencyGraphInterpreter, AssumptionType, JoinType} +import viper.silver.dependencyAnalysis.{AssumptionType, JoinType} import java.io.PrintWriter import java.nio.file.Paths @@ -16,7 +16,7 @@ object DATraversalMode extends Enumeration { val Upwards, Downwards = Value } -class DependencyGraphInterpreter[T <: DependencyGraphState](name: String, dependencyGraph: ReadOnlyDependencyGraph[T], errors: List[Failure], member: Option[ast.Member]=None) extends AbstractDependencyGraphInterpreter { +class DependencyGraphInterpreter[T <: DependencyGraphState](name: String, dependencyGraph: ReadOnlyDependencyGraph[T], errors: List[Failure], member: Option[ast.Member]=None) { val pruningSupporter: DependencyAnalysisPruningSupporter[T] = new DependencyAnalysisPruningSupporter[T](this) val progressSupporter: DependencyAnalysisProgressSupporter[T] = new DependencyAnalysisProgressSupporter[T](this) diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index 4cba96031..7c955639f 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -20,9 +20,9 @@ import viper.silicon.logger.{MemberSymbExLogger, SymbExLogger} import viper.silicon.reporting.{MultiRunRecorders, condenseToViperResult} import viper.silicon.state._ import viper.silicon.state.terms.{Decl, Sort, Term, sorts} +import viper.silicon.supporters._ import viper.silicon.supporters.functions.{DefaultFunctionVerificationUnitProvider, FunctionData} import viper.silicon.supporters.qps._ -import viper.silicon.supporters._ import viper.silicon.utils.Counter import viper.silver.ast import viper.silver.ast.utility.rewriter.Traverse @@ -95,8 +95,6 @@ class DefaultMainVerifier(config: Config, MultiRunRecorders /* In lieu of a better place, include MultiRunRecorders singleton here */ ) - var dependencyAnalysisResult: Option[DependencyAnalysisResult] = None - /* Lifetime */ override def start(): Unit = { @@ -660,7 +658,6 @@ class DefaultMainVerifier(config: Config, // TODO ake: make sure we can access the name of frontend programs (instead of naming it "joined") val result = DependencyAnalysisResult(inputFile.map(_.replaceAll("\\\\", "_").replaceAll("/", "_").replaceAll(".vpr", "")).getOrElse("joined"), program, dependencyGraphInterpreters.toSet) - dependencyAnalysisResult = Some(result) if (Verifier.config.dependencyAnalysisExportPath.isDefined) { result.getFullDependencyGraphInterpreter.exportGraph(program) From bf8b403c0c359f184f9e48065039b952b09b1584 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 12 May 2026 10:36:23 +0200 Subject: [PATCH 454/474] clean up silicon --- src/main/scala/Config.scala | 21 +++++-------------- src/main/scala/decider/Decider.scala | 12 ++--------- .../DependencyAnalyzer.scala | 2 +- .../cliTool/DependencyGraphImporter.scala | 2 +- .../DependencyAnalysisProgressSupporter.scala | 2 +- .../DependencyGraphInterpreter.scala | 2 -- .../dependencyAnalysis/neo4j_importer.py | 2 +- src/main/scala/interfaces/state/Chunks.scala | 2 +- 8 files changed, 12 insertions(+), 33 deletions(-) diff --git a/src/main/scala/Config.scala b/src/main/scala/Config.scala index 98f8cdc87..3d9dbfbe2 100644 --- a/src/main/scala/Config.scala +++ b/src/main/scala/Config.scala @@ -6,16 +6,17 @@ package viper.silicon -import java.nio.file.{Files, Path, Paths} -import scala.collection.immutable.ArraySeq -import scala.util.matching.Regex -import scala.util.Properties._ import org.rogach.scallop._ import viper.silicon.Config.JoinMode.JoinMode import viper.silicon.Config.StateConsolidationMode.StateConsolidationMode import viper.silicon.decider.{Cvc5ProverStdIO, Z3ProverAPI, Z3ProverStdIO} import viper.silver.frontend.SilFrontendConfig +import java.nio.file.{Files, Path, Paths} +import scala.collection.immutable.ArraySeq +import scala.util.Properties._ +import scala.util.matching.Regex + class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { import Config._ @@ -733,18 +734,6 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { noshort = true ) - val dependencyAnalysisPostProcessingMode: ScallopOption[Int] = opt[Int]("dependencyAnalysisPostProcessingMode", - descr = "Postprocessing mode: 0=default, 1=disable memory footprint optimizations, 2=disable joining of graphs and all of 1 (does not compute dependencies between methods), 3=disable transitive edges and all of 2 (UNSOUND)", - default = Some(0), - noshort = false - ) - - val enableDependencyAnalysisProfiling: ScallopOption[Boolean] = opt[Boolean]("enableDependencyAnalysisProfiling", - descr = "Measures runtime of different parts of the dependency analysis", - default = Some(false), - noshort = true - ) - val pruneLines: ScallopOption[List[Int]] = opt[List[Int]]("pruneLines", descr = "Line numbers to prune the program with respect to. Part of the dependency analysis tool.", default = None, diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index d0a1024d8..e9bca8bc0 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -318,7 +318,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => if(!isDependencyAnalysisEnabled) return buildChunk(perm) - val labelNodeOpt = getOrCreateAnalysisLabelNode() // if(createLabel) getOrCreateAnalysisLabelNode() else getOrCreateAnalysisLabelNode(sourceChunks) + val labelNodeOpt = getOrCreateAnalysisLabelNode() if(isExhale) dependencyAnalyzer.registerExhaleChunk(sourceChunks, buildChunk, perm, labelNodeOpt, analysisInfo) @@ -331,10 +331,6 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => if(!isDependencyAnalysisEnabled) return None -// if(sourceChunks.size == 1 && sourceTerms.isEmpty){ -// val chunkInhaleNode = dependencyAnalyzer.getChunkNode(sourceChunks.head) -// return chunkInhaleNode.map(_.labelNode) -// } val (label, _) = fresh(ast.LocalVar(DependencyAnalyzer.analysisLabelName, ast.Bool)()) val labelNode = dependencyAnalyzer.createLabelNode(label, sourceChunks, sourceTerms) val smtLabel = DependencyAnalyzer.createAssumptionLabel(labelNode.map(_.id)) @@ -496,7 +492,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => checkNode foreach dependencyAnalyzer.addAssertionNode dependencyAnalyzer.processUnsatCoreAndAddDependencies(prover.getLastUnsatCore, label) val infeasibleNodeId = dependencyAnalyzer.addInfeasibilityNode(!isAssert, analysisInfos) -// THIS WOULD BE UNSOUND! Unsoundness is introduced when infeasibility is introduced while executing a package statements and pontentially in other cases as well. +// Adding the following line would be UNSOUND! Unsoundness is introduced when infeasibility is introduced while executing a package statements and pontentially in other cases as well. // assumeWithoutSmokeChecks(InsertionOrderedSet((False, DependencyAnalyzer.createAssumptionLabel(infeasibleNodeId)))) dependencyAnalyzer.addDependency(checkNode.map(_.id), infeasibleNodeId) pcs.setCurrentInfeasibilityNode(infeasibleNodeId) @@ -564,10 +560,6 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => if(result) { assertNode foreach dependencyAnalyzer.addAssertionNode } -// }else if(!isCheck){ -// // An assertion only truly fails once state.retryLevel == 0. Instead of here, we add AssertFailedNodes at the caller-side. -// assertNode foreach {node => dependencyAnalyzer.addAssertionNode(node.getAssertFailedNode())} -// } symbExLog.closeScope(sepIdentifier) (result, assertNode) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index b7b2cb047..aa29facde 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -409,4 +409,4 @@ class NoDependencyAnalyzer extends DependencyAnalyzer { override def addCustomDependenciesBetweenMergeInfos(sourceExps: Seq[Exp], targetExps: Seq[Exp]): Unit = {} -} \ No newline at end of file +} diff --git a/src/main/scala/dependencyAnalysis/cliTool/DependencyGraphImporter.scala b/src/main/scala/dependencyAnalysis/cliTool/DependencyGraphImporter.scala index e98caf2f6..bbb88a128 100644 --- a/src/main/scala/dependencyAnalysis/cliTool/DependencyGraphImporter.scala +++ b/src/main/scala/dependencyAnalysis/cliTool/DependencyGraphImporter.scala @@ -180,4 +180,4 @@ private case class DummyChunk() extends Chunk { val permExp: Option[ast.Exp] = None override protected def substitute(terms: silicon.Map[Term, Term]): Chunk = this -} \ No newline at end of file +} diff --git a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisProgressSupporter.scala b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisProgressSupporter.scala index 747a82efd..0059f7488 100644 --- a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisProgressSupporter.scala +++ b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisProgressSupporter.scala @@ -245,4 +245,4 @@ case class DAMemo[A,B](f: A => B) extends (A => B) { def contains(a: A): Boolean = { cache.contains(a) } -} \ No newline at end of file +} diff --git a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala index f33068ad7..db953d690 100644 --- a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala @@ -165,5 +165,3 @@ class DependencyGraphInterpreter[T <: DependencyGraphState](name: String, depend (proofCoverage, uncoveredUserLevelNodes.map(_.toString)) } } - - diff --git a/src/main/scala/dependencyAnalysis/neo4j_importer.py b/src/main/scala/dependencyAnalysis/neo4j_importer.py index aca7cb099..fdb8adb54 100644 --- a/src/main/scala/dependencyAnalysis/neo4j_importer.py +++ b/src/main/scala/dependencyAnalysis/neo4j_importer.py @@ -203,4 +203,4 @@ def import_graph_with_explicit_nodes_only(label, is_overwrite=True): import_graph_without_internal_nodes(node_label) print("Viper graph imported successfully") session.close() -driver.close() \ No newline at end of file +driver.close() diff --git a/src/main/scala/interfaces/state/Chunks.scala b/src/main/scala/interfaces/state/Chunks.scala index 1bdd81e6e..4a763cabc 100644 --- a/src/main/scala/interfaces/state/Chunks.scala +++ b/src/main/scala/interfaces/state/Chunks.scala @@ -124,4 +124,4 @@ object QuantifiedChunk { chunk.withSnapshotMap(snap)}, chunk.perm, analysisInfo, isExhale=false, createLabel=false) } -} \ No newline at end of file +} From c065adf5ecf0093d615353d387eb3515dd1725e8 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 12 May 2026 11:20:32 +0200 Subject: [PATCH 455/474] clean up silicon --- .../DependencyAnalysisInfos.scala | 24 ++++++++++++------- .../cliTool/DependencyAnalysisCliTool.scala | 2 +- .../NonQuantifiedPropertyInterpreter.scala | 2 +- .../DependencyAnalysisTestFramework.scala | 4 ++-- 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisInfos.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisInfos.scala index c187826c3..2cee072d5 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisInfos.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisInfos.scala @@ -12,8 +12,10 @@ import viper.silver.dependencyAnalysis._ */ case class DependencyAnalysisInfos(sourceInfos: List[AnalysisSourceInfo], dependencyTypes: List[DependencyTypeInfo], mergeInfos: List[DependencyAnalysisMergeInfo], joinInfos: List[DependencyAnalysisJoinInfo], nodes: List[ast.Node], analysisEnabled: Boolean = true) { + private def isAnalysisEnabled = Verifier.config.enableDependencyAnalysis() && analysisEnabled + def addInfo(info: ast.Info, node: ast.Node): DependencyAnalysisInfos = { - if(!Verifier.config.enableDependencyAnalysis()) return this + if(!isAnalysisEnabled) return this val newSourceInfos = sourceInfos ++ info.getUniqueInfo[AnalysisSourceInfo].toList val newDependencyInfos = dependencyTypes ++ info.getUniqueInfo[DependencyTypeInfo].toList @@ -23,7 +25,7 @@ case class DependencyAnalysisInfos(sourceInfos: List[AnalysisSourceInfo], depend } def addInfo(info: ast.Info): DependencyAnalysisInfos = { - if(!Verifier.config.enableDependencyAnalysis()) return this + if(!isAnalysisEnabled) return this val newSourceInfos = sourceInfos ++ info.getUniqueInfo[AnalysisSourceInfo].toList val newDependencyInfos = dependencyTypes ++ info.getUniqueInfo[DependencyTypeInfo].toList @@ -33,18 +35,18 @@ case class DependencyAnalysisInfos(sourceInfos: List[AnalysisSourceInfo], depend } def addInfo(infoString: String, pos: ast.Position, dependencyType: DependencyType): DependencyAnalysisInfos = { - if(!Verifier.config.enableDependencyAnalysis()) return this + if(!isAnalysisEnabled) return this this.copy(sourceInfos = sourceInfos ++ List(StringAnalysisSourceInfo(infoString, pos)), dependencyTypes = dependencyTypes ++ List(DependencyTypeInfo(dependencyType))) } def withDependencyType(dependencyType: DependencyType): DependencyAnalysisInfos = { - if(!Verifier.config.enableDependencyAnalysis()) return this + if(!isAnalysisEnabled) return this this.copy(dependencyTypes = DependencyTypeInfo(dependencyType) +: dependencyTypes) } def withSource(source: AnalysisSourceInfo): DependencyAnalysisInfos = { - if(!Verifier.config.enableDependencyAnalysis()) return this + if(!isAnalysisEnabled) return this this.copy(sourceInfos = source +: sourceInfos) } @@ -65,6 +67,7 @@ case class DependencyAnalysisInfos(sourceInfos: List[AnalysisSourceInfo], depend } def getSourceInfo: AnalysisSourceInfo = { + if(!isAnalysisEnabled) return StringAnalysisSourceInfo("Unknown", NoPosition) val sourceInfoOpt = sourceInfos.headOption if(sourceInfoOpt.isDefined){ sourceInfoOpt.get @@ -75,6 +78,7 @@ case class DependencyAnalysisInfos(sourceInfos: List[AnalysisSourceInfo], depend } def getDependencyType: DependencyType = { + if(!isAnalysisEnabled) return DependencyType.make(AssumptionType.Unknown) val dependencyTypeOpt = dependencyTypes.headOption.map(_.dependencyType) if(dependencyTypeOpt.isDefined) { dependencyTypeOpt.get @@ -84,9 +88,13 @@ case class DependencyAnalysisInfos(sourceInfos: List[AnalysisSourceInfo], depend } } - def getMergeInfo: DependencyAnalysisMergeInfo = mergeInfos.headOption.getOrElse(SimpleDependencyAnalysisMerge(getSourceInfo)) + def getMergeInfo: DependencyAnalysisMergeInfo = { + if(!isAnalysisEnabled) return NoDependencyAnalysisMerge() + mergeInfos.headOption.getOrElse(SimpleDependencyAnalysisMerge(getSourceInfo)) + } def getJoinInfo: List[SimpleDependencyAnalysisJoin] = { + if(!isAnalysisEnabled) return List.empty joinInfos.map { case EvalStackDependencyAnalysisJoin(joinType, edgeType) => SimpleDependencyAnalysisJoin(sourceInfos.last, joinType, edgeType) case a: SimpleDependencyAnalysisJoin => a @@ -94,13 +102,13 @@ case class DependencyAnalysisInfos(sourceInfos: List[AnalysisSourceInfo], depend } def withMergeInfo(mergeInfo: DependencyAnalysisMergeInfo): DependencyAnalysisInfos = { - if(!Verifier.config.enableDependencyAnalysis()) return this + if(!isAnalysisEnabled) return this this.copy(mergeInfos = mergeInfo +: mergeInfos) } def withJoinInfo(joinInfo: DependencyAnalysisJoinInfo): DependencyAnalysisInfos = { - if(!Verifier.config.enableDependencyAnalysis()) return this + if(!isAnalysisEnabled) return this this.copy(joinInfos = joinInfo +: joinInfos) } diff --git a/src/main/scala/dependencyAnalysis/cliTool/DependencyAnalysisCliTool.scala b/src/main/scala/dependencyAnalysis/cliTool/DependencyAnalysisCliTool.scala index bdf64c8fd..4cce34e13 100644 --- a/src/main/scala/dependencyAnalysis/cliTool/DependencyAnalysisCliTool.scala +++ b/src/main/scala/dependencyAnalysis/cliTool/DependencyAnalysisCliTool.scala @@ -98,7 +98,7 @@ class DependencyAnalysisCliTool(fullGraphInterpreter: DependencyGraphInterpreter } private def handleProofCoverageLineQuery(memberNames: Seq[String]): Unit = { - if(memberNames.isEmpty) return // TODO ake: invalid input handling + if(memberNames.isEmpty) return println("Proof Coverage") val lines = memberNames.tail.flatMap(_.toIntOption) diff --git a/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala b/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala index 8290a3f40..32aba1059 100644 --- a/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala +++ b/src/main/scala/resources/NonQuantifiedPropertyInterpreter.scala @@ -122,7 +122,7 @@ class NonQuantifiedPropertyInterpreter(heap: Iterable[Chunk], verifier: Verifier otherwise: PropertyExpression[K], info: Info): (Term, Option[ast.Exp]) = { val conditionTerm = buildPathCondition(condition, info)._1 - if (verifier.decider.check(conditionTerm, Verifier.config.checkTimeout(), DependencyAnalysisInfos.create("property interpreter", DependencyType.Internal) /* TODO ake */)) { + if (verifier.decider.check(conditionTerm, Verifier.config.checkTimeout(), DependencyAnalysisInfos.create(s"property interpreter: ${conditionTerm.toString}", DependencyType.Internal))) { val (t, e) = buildPathCondition(thenDo, info) (verifier.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(conditionTerm)), e) // TODO ake: causes imprecision! } else { diff --git a/src/test/scala/DependencyAnalysisTestFramework.scala b/src/test/scala/DependencyAnalysisTestFramework.scala index b836aa2fc..632d44e7e 100644 --- a/src/test/scala/DependencyAnalysisTestFramework.scala +++ b/src/test/scala/DependencyAnalysisTestFramework.scala @@ -87,7 +87,7 @@ trait DependencyAnalysisTestFramework { def execute(): Unit = { val triggerNodeLines = fullGraphInterpreter.getNodes.filter(node => node.getUserLevelRepresentation.contains("@trigger()")).flatMap(_.sourceInfo.getLineNumber) var id: Int = 0 - // TODO ake: safer would be to work with position string instead of line numbers + // TODO ake: it would be better to work with position string instead of line numbers fullGraphInterpreter.getExplicitAssertionNodes flatMap (_.sourceInfo.getLineNumber) foreach {line => pruneAndVerify(Set(line) ++ triggerNodeLines, "src/test/resources/" + fileName + s"_test$id.out") id += 1 @@ -124,7 +124,7 @@ trait DependencyAnalysisTestFramework { Paths.get(folderName).toFile.mkdirs() val triggerNodeLines = fullGraphInterpreter.getNodes.filter(node => node.getUserLevelRepresentation.contains("@trigger()")).flatMap(_.sourceInfo.getLineNumber) var id: Int = 0 - // TODO ake: safer would be to work with position string instead of line numbers + // TODO ake: it would be better to work with position string instead of line numbers fullGraphInterpreter.getExplicitAssertionNodes flatMap (_.sourceInfo.getLineNumber) foreach {line => evaluatePrecision(Set(line) ++ triggerNodeLines) resetBaselineFrontend() From da0fe071bf3d3478d667520b563e7e34d368c1fd Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 12 May 2026 11:58:58 +0200 Subject: [PATCH 456/474] clean up DA tests --- ...DependencyAnalysisPrecisionBenchmark.scala | 117 ----------------- .../DependencyAnalysisTestFramework.scala | 69 +--------- src/test/scala/DependencyAnalysisTests.scala | 120 +----------------- 3 files changed, 3 insertions(+), 303 deletions(-) delete mode 100644 src/test/scala/DependencyAnalysisPrecisionBenchmark.scala diff --git a/src/test/scala/DependencyAnalysisPrecisionBenchmark.scala b/src/test/scala/DependencyAnalysisPrecisionBenchmark.scala deleted file mode 100644 index 613c6e670..000000000 --- a/src/test/scala/DependencyAnalysisPrecisionBenchmark.scala +++ /dev/null @@ -1,117 +0,0 @@ -package viper.silicon.tests - -import viper.silicon.dependencyAnalysis._ -import viper.silicon.dependencyAnalysis.graphInterpretation.{DependencyAnalysisPruningSupporter, DependencyGraphInterpreter} -import viper.silver.ast.Program -import viper.silver.verifier -import viper.silver.verifier.VerificationResult - -import java.io.{File, PrintWriter} -import java.time.LocalDateTime -import java.time.format.DateTimeFormatter - - -object DependencyAnalysisPrecisionBenchmark extends DependencyAnalysisTestFramework { - val ignores: Seq[String] = Seq.empty - - def main(args: Array[String]): Unit = { - val basePath = "src/test/resources/dependencyAnalysisTests/precisionTests/results" - val directory = new File(basePath) - directory.mkdir() - val now: LocalDateTime = LocalDateTime.now() - val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss") - val writer = new PrintWriter(s"$basePath/result_${now.format(formatter)}.out") - visitFiles("dependencyAnalysisTests/precisionTests", executePrecisionBenchmark(_, _, writer)) - writer.close() - } - - def executePrecisionBenchmark(filePrefix: String, - fileName: String, - writer: PrintWriter): Unit = { - resetFrontend() - try{ - val program: Program = tests.loadProgram(filePrefix + "/", fileName, frontend) - val result = frontend.verifier.verify(program) - if(result.isInstanceOf[verifier.Failure]) { - writer.println(f"Program $filePrefix/$fileName does not verify. Skip") - println(f"Program $filePrefix/$fileName does not verify. Skip.\n$result") - return - } - val dependencyGraphInterpreters = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].dependencyGraphInterpretersPerMember - val fullGraphInterpreter = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].joinedDependencyGraphInterpreter - - - println(s"Precision Benchmark for $filePrefix - $fileName started...") - writer.println(s"$filePrefix - $fileName") - new AnnotatedPrecisionBenchmark(filePrefix + "/" + fileName, program, dependencyGraphInterpreters, fullGraphInterpreter.get, writer).execute() - writer.println() - writer.flush() - println(s"Precision Benchmark for $filePrefix - $fileName done.") - }catch{ - case t: Throwable => - writer.println("Failed. Skip") - println(s"Exception caught: ${t.getMessage}") - } - } - - class AnnotatedPrecisionBenchmark(fileName: String, program: Program, - dependencyGraphInterpreters: List[DependencyGraphInterpreter[IntraProcedural]], - fullGraphInterpreter: DependencyGraphInterpreter[Final], - writer: PrintWriter) extends AnnotatedTest(program, dependencyGraphInterpreters, true) { - lazy val pruningSupporter = new DependencyAnalysisPruningSupporter[Final](fullGraphInterpreter) - - override def execute(): Unit = { - if(!verifyTestSoundness()){ - writer.println(s"!!!!!!!!!!!\nFailed to verify soundness of precision test $fileName\n") - println(s"!!!!!!!!!!!\nFailed to verify soundness of precision test $fileName\n") - return - } - - dependencyGraphInterpreters foreach { a => - val prec = computePrecision(a) - writer.println(s"${a.getMember.map(_.name).getOrElse("unknown")}: $prec") - } - } - - protected def computePrecision[T <: DependencyGraphState](dependencyGraphInterpreter: DependencyGraphInterpreter[T]): Double = { - val irrelevantAssumptionNodes = getTestIrrelevantAssumptionNodes(dependencyGraphInterpreter.getNonInternalAssumptionNodes) - val irrelevantAssumptionsPerSource = irrelevantAssumptionNodes groupBy (n => extractSourceLine(n.sourceInfo.getPosition)) - val assertionNodes = getTestAssertionNodes(dependencyGraphInterpreter.getNonInternalAssertionNodes) - - val dependencies = dependencyGraphInterpreter.getAllNonInternalDependencies(assertionNodes.map(_.id)) - val dependenciesPerSource = dependencies groupBy (n => extractSourceLine(n.sourceInfo.getPosition)) - val dependencyIds = dependencies.map(_.id) - - if(dependenciesPerSource.nonEmpty){ - val wrongDependencies = irrelevantAssumptionsPerSource.filter({ case (_, irrelevantAssumptions) => dependencyIds.intersect(irrelevantAssumptions.map(_.id)).nonEmpty }) - 1.0 - (wrongDependencies.size.toDouble / dependenciesPerSource.size.toDouble) - }else{ - 1.0 - } - } - - protected def verifyTestSoundness(): Boolean = { - val irrelevantNodes = fullGraphInterpreter.getNodes.filter(node => node.sourceInfo.toString.contains("@irrelevant(")).flatMap(_.sourceInfo.getLineNumber) - - val relevantLines = fullGraphInterpreter.getNodes.flatMap(_.sourceInfo.getLineNumber).diff(irrelevantNodes) - - pruneAndVerify(relevantLines, "src/test/resources/" + fileName + s"_test.out") - } - - protected def pruneAndVerify(relevantLines: Set[Int], exportFileName: String): Boolean = { - val crucialNodes = relevantLines.flatMap(line => fullGraphInterpreter.getNodesByLine(line)) - val (newProgram, pruningFactor) = pruningSupporter.getPrunedProgram(crucialNodes, program) - val result = frontend.verifier.verify(newProgram) -// exportPrunedProgram(exportFileName, newProgram, pruningFactor, result) // can be used for debugging - !result.isInstanceOf[verifier.Failure] - } - - protected def exportPrunedProgram(exportFileName: String, newProgram: Program, pruningFactor: Double, result: VerificationResult): Unit = { - val writer = new PrintWriter(exportFileName) - writer.println("// test result: " + !result.isInstanceOf[verifier.Failure]) - writer.println("// pruning factor: " + pruningFactor) - writer.println(newProgram.toString()) - writer.close() - } - } -} diff --git a/src/test/scala/DependencyAnalysisTestFramework.scala b/src/test/scala/DependencyAnalysisTestFramework.scala index 632d44e7e..9d1154639 100644 --- a/src/test/scala/DependencyAnalysisTestFramework.scala +++ b/src/test/scala/DependencyAnalysisTestFramework.scala @@ -62,9 +62,9 @@ trait DependencyAnalysisTestFramework { fe } - def resetFrontend(): Unit = { + def resetFrontend(additionalArguments: Seq[String] = Seq.empty): Unit = { frontend.verifier.stop() - frontend = createFrontend(analysisCommandLineArguments) + frontend = createFrontend(analysisCommandLineArguments ++ additionalArguments) } var baselineFrontend: SiliconFrontend = createFrontend(baseCommandLineArguments) @@ -116,69 +116,6 @@ trait DependencyAnalysisTestFramework { } } - class PrecisionEvaluation(filePrefix: String, fileName: String, program: Program, fullGraphInterpreter: DependencyGraphInterpreter[Final]) { - lazy val pruningSupporter = new DependencyAnalysisPruningSupporter(fullGraphInterpreter) - protected val folderName = s"src/test/resources/precision_groundTruths/$filePrefix" - - def execute(): Unit = { - Paths.get(folderName).toFile.mkdirs() - val triggerNodeLines = fullGraphInterpreter.getNodes.filter(node => node.getUserLevelRepresentation.contains("@trigger()")).flatMap(_.sourceInfo.getLineNumber) - var id: Int = 0 - // TODO ake: it would be better to work with position string instead of line numbers - fullGraphInterpreter.getExplicitAssertionNodes flatMap (_.sourceInfo.getLineNumber) foreach {line => - evaluatePrecision(Set(line) ++ triggerNodeLines) - resetBaselineFrontend() - id += 1 - } - } - - protected def evaluatePrecision(relevantLines: Set[Int]): Unit = { - val relevantNodes = relevantLines.flatMap(line => fullGraphInterpreter.getNodesByLine(line)) - val sourceInfos = relevantNodes.groupBy(_.sourceInfo).keySet - println(s"Evaluating precision of\n\t${sourceInfos.mkString("\n\t")}") - - val reportedDependencies = fullGraphInterpreter.getAllNonInternalDependencies(relevantNodes.map(_.id)).diff(relevantNodes) - val verifies = pruneAndVerify(reportedDependencies ++ relevantNodes) - if(!verifies) { - println("Unsound. The program pruned with respect to all reported dependencies does not verify!") - return - } - - var minNumDeps = reportedDependencies.size - var bestDepSet = reportedDependencies - - for(currDepSubset <- reportedDependencies.subsets()){ - if(currDepSubset.size < minNumDeps){ - println(currDepSubset.size) - val verifies = pruneAndVerify(currDepSubset ++ relevantNodes) - if(verifies){ - minNumDeps = currDepSubset.size - bestDepSet = currDepSubset - } - } - } - - val (minProgram, _) = pruningSupporter.getPrunedProgram(bestDepSet ++ relevantNodes, program) - - exportPrunedProgram(s"${fileName}_${relevantLines.mkString("_")}.vpr", minProgram) - val precision = minNumDeps.toDouble / reportedDependencies.size - println(s"Precision: $minNumDeps / ${reportedDependencies.size}=$precision") - } - - protected def pruneAndVerify(crucialNodes: Set[DependencyAnalysisNode]): Boolean = { - val (newProgram, _) = pruningSupporter.getPrunedProgram(crucialNodes, program) - resetBaselineFrontend() - val result = baselineFrontend.verifier.verify(newProgram) - !result.isInstanceOf[verifier.Failure] - } - - protected def exportPrunedProgram(exportFileName: String, newProgram: Program): Unit = { - val writer = new PrintWriter(folderName + "/" + exportFileName) - writer.println(newProgram.toString()) - writer.close() - } - } - /** * Takes a Viper program and its dependency analysis results and checks whether the analysis found the * assumptions, assertions and dependencies between them, as annotated by the user. @@ -302,7 +239,5 @@ trait DependencyAnalysisTestFramework { protected def getTestIrrelevantAssumptionNodes(nodes: Set[DependencyAnalysisNode]): Set[DependencyAnalysisNode] = nodes.filter(_.sourceInfo.toString.contains("@" + irrelevantKeyword + "(")) - } - } diff --git a/src/test/scala/DependencyAnalysisTests.scala b/src/test/scala/DependencyAnalysisTests.scala index e9d735d2e..e38cfb9ff 100644 --- a/src/test/scala/DependencyAnalysisTests.scala +++ b/src/test/scala/DependencyAnalysisTests.scala @@ -1,65 +1,31 @@ package viper.silicon.tests -import dependencyAnalysis.UserLevelDependencyAnalysisNode import org.scalatest.funsuite.AnyFunSuite import viper.silicon.dependencyAnalysis._ -import viper.silicon.dependencyAnalysis.cliTool.DependencyAnalysisCliTool import viper.silver.ast._ import viper.silver.frontend.SilFrontend import viper.silver.verifier -import java.io.{File, PrintWriter} -import java.time._ -import java.time.format.DateTimeFormatter -import scala.annotation.unused - class DependencyAnalysisTests extends AnyFunSuite with DependencyAnalysisTestFramework { val CHECK_PRECISION = false val EXECUTE_TEST = true - val EXECUTE_PERFORMANCE_BENCHMARK = false - val ANNOTATE_PROGRAMS = false override val EXPORT_PRUNED_PROGRAMS: Boolean = false val ignores: Seq[String] = Seq() val testDirectories: Seq[String] = Seq( "dependencyAnalysisTests/all", "dependencyAnalysisTests/unitTests", "dependencyAnalysisTests/real-world-examples", -// "dependencyAnalysisTests/viper-online-examples" - ) -// override val ignores = Seq("quickselect", "adts", "graph-marking-qps", "linked-list-iterator", "linked-list-predicates") - - - - if(EXECUTE_PERFORMANCE_BENCHMARK) - test("performance benchmark") { - val basePath = "src/test/resources/dependencyAnalysisTests/performanceBenchmark" - val directory = new File(basePath) - directory.mkdir() - val now: LocalDateTime = LocalDateTime.now() - val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss") - val writer = new PrintWriter(s"$basePath/result_${now.format(formatter)}.out") - val proofCoverageWriter = new PrintWriter(s"$basePath/proofCoverage_${now.format(formatter)}.out") - writer.println(f"test name,baseline (ms),analysis (ms),analysis/baseline,program size") - testDirectories foreach (dir => visitFiles(dir, executePerformanceBenchmark(_, _, writer, proofCoverageWriter))) - writer.close() - proofCoverageWriter.close() - } - -// createSingleTest("dependencyAnalysisTests/real-world-examples", "listAppend") - if(EXECUTE_TEST) { testDirectories foreach (dir => visitFiles(dir, createSingleTest)) + // TODO ake: more complete exhale tests // analysisCommandLineArguments = Seq("--enableMoreCompleteExhale") ++ analysisCommandLineArguments // visitFiles("dependencyAnalysisTests/mce", createSingleTest) } - - - private def createSingleTest(dirName: String, fileName: String): Unit = { test(dirName + "/" + fileName) { try{ @@ -71,7 +37,6 @@ class DependencyAnalysisTests extends AnyFunSuite with DependencyAnalysisTestFra } } - def executeTest(filePrefix: String, fileName: String, frontend: SilFrontend): Unit = { @@ -86,91 +51,8 @@ class DependencyAnalysisTests extends AnyFunSuite with DependencyAnalysisTestFra val dependencyGraphInterpreters = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].dependencyGraphInterpretersPerMember val joinedDependencyGraphInterpreter = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].joinedDependencyGraphInterpreter - if(ANNOTATE_PROGRAMS){ - val basePathAnnotatedPrograms = "src/test/resources/dependencyAnalysisTests/viper-online-examples-annotated" - val directory = new File(basePathAnnotatedPrograms) - directory.mkdir() - val directoryTestCase = new File(s"$basePathAnnotatedPrograms/$fileName") - directoryTestCase.mkdir() - val dependencyAnalysisUserTool = new DependencyAnalysisCliTool(joinedDependencyGraphInterpreter.get, dependencyGraphInterpreters, program, List()) - dependencyAnalysisUserTool.run(s"annotate $basePathAnnotatedPrograms/$fileName/$fileName.vpr") - } new AnnotatedTest(program, dependencyGraphInterpreters, CHECK_PRECISION).execute() new PruningTest(filePrefix + "/" + fileName, program, joinedDependencyGraphInterpreter.get).execute() -// new PrecisionEvaluation(filePrefix, fileName, program, joinedDependencyGraphInterpreter.get).execute() - } - - def executePerformanceBenchmark(filePrefix: String, - fileName: String, - writer: PrintWriter, - proofCoverageWriter: PrintWriter): Unit = { - if(fileName.endsWith("_naive")) return - - val program: Program = tests.loadProgram(filePrefix + "/", fileName, frontend) - val naiveProgram: Option[Program] = try{ - Some(tests.loadProgram(filePrefix + "/", fileName + "_naive", frontend)) - }catch{ - case _: Throwable => None - } - - val frontend_ = createFrontend(analysisCommandLineArguments) - val result = frontend_.verifier.verify(program) - if(result.isInstanceOf[verifier.Failure]) { - cancel(f"Program does not verify. Skip test.\n$result") - return - } - - val dependencyGraphInterpreters = frontend_.reporter.asInstanceOf[DependencyAnalysisReporter].dependencyGraphInterpretersPerMember - - proofCoverageWriter.println(filePrefix + "/" + fileName) - dependencyGraphInterpreters foreach (memberInterpreter => { - UserLevelDependencyAnalysisNode.from(memberInterpreter.getExplicitAssertionNodes) foreach {node => - proofCoverageWriter.println(memberInterpreter.getName + " " + node.source.toString.replace("\n", " ") + " ---> " + memberInterpreter.computeProofCoverage(node.lowerLevelNodes)._1)} - proofCoverageWriter.println("overall " + memberInterpreter.getName + " ---> + " + memberInterpreter.computeProofCoverage()._1) - }) - proofCoverageWriter.println() - - new PerformanceBenchmark(filePrefix + "/" + fileName, program, naiveProgram, writer, program.toString().split("\n").length).execute() - } - - - class PerformanceBenchmark(name: String, program: Program, @unused naiveProgram: Option[Program], writer: PrintWriter, programSize: Int) { - - private def printResult(str: String): Unit = { - writer.print(str) - print(str) - } - - def execute(): Unit = { - printResult(f"$name,") - - val analysisDurationMs: Double = verifyAndMeasure(program, analysisCommandLineArguments) - val baselineDurationMs: Double = verifyAndMeasure(program, baseCommandLineArguments) - - printResult(f"$baselineDurationMs,$analysisDurationMs,${analysisDurationMs/baselineDurationMs},$programSize\n") - } - - private def verifyAndMeasure(program_ : Program, commandLineArgs: Seq[String]) = { - val NUM_RUNS = 5 - val frontend_ = createFrontend(commandLineArgs) - try { - val startAnalysis = System.nanoTime() - for (i <- 0 until NUM_RUNS) { - val result = frontend_.verifier.verify(program_) - if(result.isInstanceOf[verifier.Failure]) { - println(f"$i ${result.toString}") - throw new Exception(f"Failed $result") - } - } - val endAnalysis = System.nanoTime() - val analysisDurationMs = (endAnalysis - startAnalysis) / 1e6 / NUM_RUNS - analysisDurationMs - }catch{ - case t: Throwable => - println(t) - Double.NaN - } - } } } From 142af18f1f523f5be9cad0ba0012b9431a0d72c8 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 12 May 2026 18:03:45 +0200 Subject: [PATCH 457/474] fix implication in combination with infeasibility --- src/main/scala/rules/Evaluator.scala | 2 +- src/main/scala/rules/Joiner.scala | 3 +-- .../all/infeasible.vpr | 19 +++++++++++++++++++ 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index c4a609bdd..8b1316f18 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -1154,7 +1154,7 @@ object evaluator extends EvaluationRules { : VerificationResult = { joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s, v, analysisInfos)((s1, v1, QB) => - brancher.branch(s1.copy(parallelizeBranches = false), tLhs, eLhs, v1, analysisInfos.withDependencyType(DependencyType.Internal), fromShortCircuitingAnd = fromShortCircuitingAnd)( + brancher.branch(s1.copy(parallelizeBranches = false), tLhs, eLhs, v1, analysisInfos, fromShortCircuitingAnd = fromShortCircuitingAnd)( (s2, v2) => eval(s2.copy(parallelizeBranches = s1.parallelizeBranches), eRhs, pve, v2, analysisInfos)((s2, tRhs, eRhsNew, v2) => QB(s2, (tRhs, eRhsNew), v2)), (s2, v2) => QB(s2.copy(parallelizeBranches = s1.parallelizeBranches), (True, Option.when(withExp)(ast.TrueLit()())), v2)) )(entries => { diff --git a/src/main/scala/rules/Joiner.scala b/src/main/scala/rules/Joiner.scala index 8f87c83b0..c3f189500 100644 --- a/src/main/scala/rules/Joiner.scala +++ b/src/main/scala/rules/Joiner.scala @@ -102,8 +102,7 @@ object joiner extends JoiningRules { var feasibleBranchesExp: Option[List[ast.Exp]] = Option.when(withExp)(Nil) var feasibleBranchesExpNew: Option[List[ast.Exp]] = Option.when(withExp)(Nil) - // infeasible branches can be skipped - entries filter (entry => !Verifier.config.disableInfeasibilityChecks() || entry.pathConditions.infeasibilityNodeId.isEmpty) foreach (entry => { + entries foreach (entry => { val pcs = entry.pathConditions.conditionalized val pcsExp = Option.when(withExp)(entry.pathConditions.conditionalizedExp) val comment = "Joined path conditions" diff --git a/src/test/resources/dependencyAnalysisTests/all/infeasible.vpr b/src/test/resources/dependencyAnalysisTests/all/infeasible.vpr index afd90bd16..d861630e4 100644 --- a/src/test/resources/dependencyAnalysisTests/all/infeasible.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/infeasible.vpr @@ -46,4 +46,23 @@ method infeasibleBranchNoPerm(a: Int, b: Ref) @testAssertion("Explicit") assert false } +} + +method implicationTest() +{ + var a: Int + @dependency("SourceCode") + a := 10 + @testAssertion("Explicit") + assert a == 0 ==> false +} + +method implicationTest2() +{ + var a: Int + var x: Ref + @dependency("SourceCode") + a := 10 + @testAssertion("Explicit") + exhale a == 0 ==> acc(x.f) } \ No newline at end of file From 8f6668db4139c9b2303cfd301a8bbc54b1028ce1 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 13 May 2026 11:42:46 +0200 Subject: [PATCH 458/474] add tests for node types and dependencies (for Silicon and Gobra) --- src/main/scala/Config.scala | 13 +++ .../UserLevelDependencyAnalysisNode.scala | 3 +- .../AbstractDependencyAnalysisCliTool.scala | 5 +- ...chmarkDependencyAnalysisCliExtension.scala | 4 +- .../DebugDependencyAnalysisCliExtension.scala | 1 - .../cliTool/DependencyAnalysisCliTool.scala | 2 +- .../TestDependencyAnalysisCliExtension.scala | 51 +++++++++++ .../DependencyAnalysisProgressSupporter.scala | 1 - .../DependencyGraphInterpreter.scala | 1 - .../DependencyGraphTestSupporter.scala | 85 +++++++++++++++++++ .../scala/verifier/DefaultMainVerifier.scala | 7 ++ .../DependencyAnalysisTestFramework.scala | 4 +- 12 files changed, 163 insertions(+), 14 deletions(-) create mode 100644 src/main/scala/dependencyAnalysis/cliTool/TestDependencyAnalysisCliExtension.scala create mode 100644 src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphTestSupporter.scala diff --git a/src/main/scala/Config.scala b/src/main/scala/Config.scala index 3d9dbfbe2..91c5c6ff6 100644 --- a/src/main/scala/Config.scala +++ b/src/main/scala/Config.scala @@ -728,6 +728,12 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { noshort = true ) + val executeDependencyAnalysisTests: ScallopOption[Boolean] = opt[Boolean]("executeDependencyAnalysisTests", + descr = "Automatically executes dependency analysis tests", + default = Some(false), + noshort = true + ) + val enableUnsatCores: ScallopOption[Boolean] = opt[Boolean]("enableUnsatCores", descr = "Enables UNSAT cores", default = Some(false), @@ -828,6 +834,13 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { Left(s"Option ${dependencyAnalysisExportPath.name} requires option ${enableDependencyAnalysis.name}") } + validateOpt(executeDependencyAnalysisTests, enableDependencyAnalysis) { + case (None, _) => Right(()) + case (Some(_), Some(true)) => Right(()) + case (Some(_), Some(false)) => + Left(s"Option ${executeDependencyAnalysisTests.name} requires option ${enableDependencyAnalysis.name}") + } + validateOpt(startDependencyAnalysisTool, enableDependencyAnalysis) { case (Some(false), _) => Right(()) case (_, Some(true)) => Right(()) diff --git a/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala b/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala index 6d67fd14d..4c1b2a0cb 100644 --- a/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala +++ b/src/main/scala/dependencyAnalysis/UserLevelDependencyAnalysisNode.scala @@ -1,6 +1,5 @@ -package dependencyAnalysis +package viper.silicon.dependencyAnalysis -import viper.silicon.dependencyAnalysis._ import viper.silicon.state.terms.{And, Term} import viper.silver.ast.Position import viper.silver.dependencyAnalysis.AssumptionType.AssumptionType diff --git a/src/main/scala/dependencyAnalysis/cliTool/AbstractDependencyAnalysisCliTool.scala b/src/main/scala/dependencyAnalysis/cliTool/AbstractDependencyAnalysisCliTool.scala index a63c3666e..6dd1afc83 100644 --- a/src/main/scala/dependencyAnalysis/cliTool/AbstractDependencyAnalysisCliTool.scala +++ b/src/main/scala/dependencyAnalysis/cliTool/AbstractDependencyAnalysisCliTool.scala @@ -1,8 +1,7 @@ -package dependencyAnalysis.cliTool +package viper.silicon.dependencyAnalysis.cliTool -import dependencyAnalysis.UserLevelDependencyAnalysisNode import viper.silicon.dependencyAnalysis.graphInterpretation.DependencyGraphInterpreter -import viper.silicon.dependencyAnalysis.{DependencyAnalysisNode, Final} +import viper.silicon.dependencyAnalysis.{DependencyAnalysisNode, Final, UserLevelDependencyAnalysisNode} trait AbstractDependencyAnalysisCliTool { val interpreter: DependencyGraphInterpreter[Final] diff --git a/src/main/scala/dependencyAnalysis/cliTool/BenchmarkDependencyAnalysisCliExtension.scala b/src/main/scala/dependencyAnalysis/cliTool/BenchmarkDependencyAnalysisCliExtension.scala index 202eb7cee..72692ce0f 100644 --- a/src/main/scala/dependencyAnalysis/cliTool/BenchmarkDependencyAnalysisCliExtension.scala +++ b/src/main/scala/dependencyAnalysis/cliTool/BenchmarkDependencyAnalysisCliExtension.scala @@ -1,9 +1,7 @@ package viper.silicon.dependencyAnalysis.cliTool -import dependencyAnalysis.UserLevelDependencyAnalysisNode -import dependencyAnalysis.cliTool.{DependencyAnalysisCliCommand, DependencyAnalysisCliToolExtension} import viper.silicon.dependencyAnalysis.graphInterpretation.DependencyGraphInterpreter -import viper.silicon.dependencyAnalysis.{DependencyAnalysisNode, Final} +import viper.silicon.dependencyAnalysis.{DependencyAnalysisNode, Final, UserLevelDependencyAnalysisNode} import viper.silver.ast import viper.silver.ast.{AnnotationInfo, AnonymousDomainAxiom, Assume, Goto, If, Inhale, Label, LocalVarDeclStmt, MakeInfoPair, NamedDomainAxiom, Package, Seqn, While} diff --git a/src/main/scala/dependencyAnalysis/cliTool/DebugDependencyAnalysisCliExtension.scala b/src/main/scala/dependencyAnalysis/cliTool/DebugDependencyAnalysisCliExtension.scala index 06750098a..34597c259 100644 --- a/src/main/scala/dependencyAnalysis/cliTool/DebugDependencyAnalysisCliExtension.scala +++ b/src/main/scala/dependencyAnalysis/cliTool/DebugDependencyAnalysisCliExtension.scala @@ -1,6 +1,5 @@ package viper.silicon.dependencyAnalysis.cliTool -import dependencyAnalysis.cliTool.{DependencyAnalysisCliCommand, DependencyAnalysisCliToolExtension} import viper.silicon.dependencyAnalysis._ import viper.silicon.dependencyAnalysis.graphInterpretation.DependencyGraphInterpreter import viper.silver.dependencyAnalysis.AnalysisSourceInfo diff --git a/src/main/scala/dependencyAnalysis/cliTool/DependencyAnalysisCliTool.scala b/src/main/scala/dependencyAnalysis/cliTool/DependencyAnalysisCliTool.scala index 4cce34e13..aebd16450 100644 --- a/src/main/scala/dependencyAnalysis/cliTool/DependencyAnalysisCliTool.scala +++ b/src/main/scala/dependencyAnalysis/cliTool/DependencyAnalysisCliTool.scala @@ -1,6 +1,5 @@ package viper.silicon.dependencyAnalysis.cliTool -import dependencyAnalysis.cliTool.{AbstractDependencyAnalysisCliTool, DependencyAnalysisCliToolExtension} import viper.silicon.dependencyAnalysis._ import viper.silicon.dependencyAnalysis.graphInterpretation.{DependencyAnalysisProgressSupporter, DependencyGraphInterpreter} import viper.silicon.interfaces.Failure @@ -16,6 +15,7 @@ class DependencyAnalysisCliTool(fullGraphInterpreter: DependencyGraphInterpreter val extensions: List[DependencyAnalysisCliToolExtension] = List( new DebugDependencyAnalysisCliExtension(fullGraphInterpreter), + new TestDependencyAnalysisCliExtension(fullGraphInterpreter), new BenchmarkDependencyAnalysisCliExtension(fullGraphInterpreter, program) ) diff --git a/src/main/scala/dependencyAnalysis/cliTool/TestDependencyAnalysisCliExtension.scala b/src/main/scala/dependencyAnalysis/cliTool/TestDependencyAnalysisCliExtension.scala new file mode 100644 index 000000000..a11b2c6f4 --- /dev/null +++ b/src/main/scala/dependencyAnalysis/cliTool/TestDependencyAnalysisCliExtension.scala @@ -0,0 +1,51 @@ +package viper.silicon.dependencyAnalysis.cliTool + +import viper.silicon.dependencyAnalysis.graphInterpretation.{DependencyGraphInterpreter, DependencyGraphTestSupporter} +import viper.silicon.dependencyAnalysis.{Final, UserLevelDependencyAnalysisNode} + +class TestDependencyAnalysisCliExtension(override val interpreter: DependencyGraphInterpreter[Final]) extends DependencyAnalysisCliToolExtension{ + override val name: String = "Test Features" + override val commands: List[DependencyAnalysisCliCommand] = List( + new NodeTypeTestCommand, + new DependenciesTestCommand, + ) + + class NodeTypeTestCommand extends DependencyAnalysisCliCommand { + override val cmdName: String = "testNodeTypes" + override val description: String = s"""'$cmdName [line numbers]' to test the node type according to the @dependencyInfo(...) annotation""" + override val cmd: Seq[String] => Unit = { inputs => + try{ + val testSupporter = new DependencyGraphTestSupporter(interpreter) + if(inputs.isEmpty) + testSupporter.testNodeTypes() + else + inputs.flatMap(_.toIntOption).foreach(line => testSupporter.testNodeTypes(interpreter.getNodesByLine(line))) + }catch { + case a: AssertionError => println(a.getMessage) + } + } + } + + class DependenciesTestCommand extends DependencyAnalysisCliCommand { + override val cmdName: String = "testDependencies" + override val description: String = s"""'$cmdName [line numbers]' to test the node type according to the @dependencyInfo(...) annotation""" + override val cmd: Seq[String] => Unit = { inputs => + try{ + val testSupporter = new DependencyGraphTestSupporter(interpreter) + if(inputs.isEmpty) + testSupporter.testDependencies() + else + inputs.flatMap(_.toIntOption).foreach(line => { + val testResult = UserLevelDependencyAnalysisNode.from(interpreter.getNodesByLine(line)) map testSupporter.testDependencies + val resultStr = if(testResult.forall(_.isEmpty)) "Skipped." + else if(testResult.forall(test => test.isEmpty || test.get)) "Passed." + else "Failed." + println(s"Line $line: $resultStr") + }) + + }catch { + case a: AssertionError => println(a.getMessage) + } + } + } +} diff --git a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisProgressSupporter.scala b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisProgressSupporter.scala index 0059f7488..3f3eef4f6 100644 --- a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisProgressSupporter.scala +++ b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisProgressSupporter.scala @@ -1,6 +1,5 @@ package viper.silicon.dependencyAnalysis.graphInterpretation -import dependencyAnalysis.{CompactUserLevelDependencyAnalysisNode, UserLevelDependencyAnalysisNode} import viper.silicon.dependencyAnalysis._ import viper.silicon.dependencyAnalysis.graphInterpretation.DATraversalMode.DATraversalMode import viper.silver.dependencyAnalysis.{AnalysisSourceInfo, AssumptionType} diff --git a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala index db953d690..4017ffe24 100644 --- a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala +++ b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphInterpreter.scala @@ -1,6 +1,5 @@ package viper.silicon.dependencyAnalysis.graphInterpretation -import dependencyAnalysis.UserLevelDependencyAnalysisNode import viper.silicon.dependencyAnalysis._ import viper.silicon.interfaces.Failure import viper.silicon.verifier.Verifier diff --git a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphTestSupporter.scala b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphTestSupporter.scala new file mode 100644 index 000000000..200e8928f --- /dev/null +++ b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphTestSupporter.scala @@ -0,0 +1,85 @@ +package viper.silicon.dependencyAnalysis.graphInterpretation + +import viper.silicon.dependencyAnalysis.{DependencyAnalysisNode, Final, UserLevelDependencyAnalysisNode} +import viper.silver.dependencyAnalysis.AssumptionType + +class DependencyGraphTestSupporter(interpreter: DependencyGraphInterpreter[Final]) { + + private val assumptionTypeRegex = """assumptionType:([^\s,)\"]+)""".r + private val assertionTypeRegex = """assertionType:([^\s,)\"]+)""".r + private val nodeLabelRegex = """label:([^\s,)\"]+)""".r + private val expectedDependenciesRegex = """expectedDependencies:\[([^\]]+)\]""".r + private val dependencyInfoRegex = """@dependencyInfo\(([^()]*)\)""".r + + def testNodeTypes(): Unit = { + testNodeTypes(interpreter.getNonInternalAssertionNodes ++ interpreter.getNonInternalAssumptionNodes) + } + + def testNodeTypes(nodes: Set[DependencyAnalysisNode]): Unit = { + val userLevelNodes = UserLevelDependencyAnalysisNode.from(nodes) + val tests = userLevelNodes.toList map testUserLevelNode + val numExecutedTests = tests.count(_.isDefined) + val numPassedTests = tests.count(_.getOrElse(false)) + println(s"Passed $numPassedTests/$numExecutedTests tests.") + assert(numPassedTests == numExecutedTests, s"Node type test failed. Only $numPassedTests/$numExecutedTests tests passed.") + } + + private def testUserLevelNode(ulNode: UserLevelDependencyAnalysisNode): Option[Boolean] = { + val dependencyInfoOpt = dependencyInfoRegex.findFirstMatchIn(ulNode.source.toString).map(_.group(1)) + if(dependencyInfoOpt.isEmpty) return None + + val dependencyInfo = dependencyInfoOpt.get + var isTested = false + + val expectedAssumptionTypeOpt: Option[String] = assumptionTypeRegex.findFirstMatchIn(dependencyInfo).map(_.group(1)) + val isAssumptionTypeCorrect = expectedAssumptionTypeOpt match { + case Some(expectedTypeStr) => + val expectedType = AssumptionType.fromString(expectedTypeStr).get + isTested = true + ulNode.assumptionTypes.equals(Set(expectedType)) + case None => true + } + + val expectedAssertionTypeOpt: Option[String] = assertionTypeRegex.findFirstMatchIn(dependencyInfo).map(_.group(1)) + val isAssertionTypeCorrect = expectedAssertionTypeOpt match { + case Some(expectedTypeStr) => + val expectedType = AssumptionType.fromString(expectedTypeStr).get + isTested = true + ulNode.assertionTypes.equals(Set(expectedType)) + case None => true + } + + printIfFalse(isAssumptionTypeCorrect, s"Wrong assumption type for node ${ulNode.source.toString} having assumption types ${ulNode.assumptionTypes}.") + printIfFalse(isAssertionTypeCorrect, s"Wrong assertion type for node ${ulNode.source.toString} having assertion types ${ulNode.assertionTypes}.") + Option.when(isTested)(isAssumptionTypeCorrect && isAssertionTypeCorrect) + } + + private def printIfFalse(test: Boolean, message: String) = + if(!test) + println(message) + + def testDependencies(): Unit = { + val testResults = UserLevelDependencyAnalysisNode.from(interpreter.getNonInternalAssertionNodes).toList map testDependencies + val numExecutedTests = testResults.count(_.isDefined) + val numPassedTests = testResults.count(_.getOrElse(false)) + println(s"Passed $numPassedTests/$numExecutedTests tests.") + assert(numPassedTests == numExecutedTests, s"Dependency test failed. Only $numPassedTests/$numExecutedTests tests passed.") + } + + def testDependencies(assertionNode: UserLevelDependencyAnalysisNode): Option[Boolean] = { + val expectedLabelsOpt = expectedDependenciesRegex.findFirstMatchIn(assertionNode.source.toString).map(_.group(1).split(",").map(_.trim).toSet) + if(expectedLabelsOpt.isEmpty) return None + val expectedLabels = expectedLabelsOpt.get + + val queriedAssertions = assertionNode.lowLevelAssertionNodes + val allDependencies = interpreter.getAllNonInternalDependencies(queriedAssertions.map(_.id)) + val sourceDependencies = UserLevelDependencyAnalysisNode.from(allDependencies).getSourceSet().diff(UserLevelDependencyAnalysisNode.from(queriedAssertions).getSourceSet()) + + val labelsInReportedDeps: Set[Set[String]] = sourceDependencies.map(node => nodeLabelRegex.findAllMatchIn(node.toString).map(_.group(1)).toSet) + val actualLabelInReportedDeps = labelsInReportedDeps.filter(_.size == 1).flatten + + val isSound = expectedLabels.diff(actualLabelInReportedDeps).isEmpty + printIfFalse(isSound, s"Missing dependencies for ${assertionNode.source.toString}. Reported dependencies: $actualLabelInReportedDeps") + Some(isSound) + } +} diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index 7c955639f..46d7f4e87 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -13,6 +13,7 @@ import viper.silicon.debugger.SiliconDebugger import viper.silicon.decider.SMTLib2PreambleReader import viper.silicon.dependencyAnalysis._ import viper.silicon.dependencyAnalysis.cliTool.DependencyAnalysisCliTool +import viper.silicon.dependencyAnalysis.graphInterpretation.DependencyGraphTestSupporter import viper.silicon.extensions.ConditionalPermissionRewriter import viper.silicon.interfaces._ import viper.silicon.interfaces.decider.ProverLike @@ -676,6 +677,12 @@ class DefaultMainVerifier(config: Config, commandLineTool.handleVerificationProgressQuery(Seq.empty, Some(exportFileName)) } + if (Verifier.config.executeDependencyAnalysisTests()) { + val testSupporter = new DependencyGraphTestSupporter(result.getFullDependencyGraphInterpreter) + testSupporter.testDependencies() + testSupporter.testNodeTypes() + } + if (Verifier.config.startDependencyAnalysisTool()) { val commandLineTool = new DependencyAnalysisCliTool(result.getFullDependencyGraphInterpreter, dependencyGraphInterpreters, program, verificationErrors) commandLineTool.run() diff --git a/src/test/scala/DependencyAnalysisTestFramework.scala b/src/test/scala/DependencyAnalysisTestFramework.scala index 9d1154639..f143e27c8 100644 --- a/src/test/scala/DependencyAnalysisTestFramework.scala +++ b/src/test/scala/DependencyAnalysisTestFramework.scala @@ -170,7 +170,7 @@ trait DependencyAnalysisTestFramework { extractSourceLine(analysisNode.sourceInfo.getPosition) == pos && assumptionType.forall(_.equals(analysisNode.assumptionType)) }) - Option.when(!nodeExists)(s"Missing analysis node:\n${node.toString}\n$pos") + Option.when(!nodeExists)(s"Missing analysis node or wrong assumption type at line $pos: ${node.toString.replaceAll("\n", " ")}") } protected def extractSourceLine(pos: ast.Position): Int = { @@ -184,7 +184,7 @@ trait DependencyAnalysisTestFramework { val assumptionNodes = getTestAssumptionNodes(dependencyGraphInterpreter.getNonInternalAssumptionNodes) ++ getTestIrrelevantAssumptionNodes(dependencyGraphInterpreter.getNonInternalAssumptionNodes) val assertionNodes = getTestAssertionNodes(dependencyGraphInterpreter.getNonInternalAssertionNodes) if (assumptionNodes.nonEmpty && assertionNodes.isEmpty) - Seq(s"Missing testAssertion for member: ${dependencyGraphInterpreter.getName}") + Seq(s"Missing testAssertion for ${dependencyGraphInterpreter.getName}") else Seq.empty } From 662c1c4478fdefc85320539187c411044b54a9af Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 13 May 2026 14:37:22 +0200 Subject: [PATCH 459/474] minor fix --- src/main/scala/Config.scala | 6 +++--- .../graphInterpretation/DependencyGraphTestSupporter.scala | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/scala/Config.scala b/src/main/scala/Config.scala index 91c5c6ff6..95a0d6e73 100644 --- a/src/main/scala/Config.scala +++ b/src/main/scala/Config.scala @@ -835,9 +835,9 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") { } validateOpt(executeDependencyAnalysisTests, enableDependencyAnalysis) { - case (None, _) => Right(()) - case (Some(_), Some(true)) => Right(()) - case (Some(_), Some(false)) => + case (Some(false), _) => Right(()) + case (_, Some(true)) => Right(()) + case (_, _) => Left(s"Option ${executeDependencyAnalysisTests.name} requires option ${enableDependencyAnalysis.name}") } diff --git a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphTestSupporter.scala b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphTestSupporter.scala index 200e8928f..7e11e66a6 100644 --- a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphTestSupporter.scala +++ b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphTestSupporter.scala @@ -20,7 +20,7 @@ class DependencyGraphTestSupporter(interpreter: DependencyGraphInterpreter[Final val tests = userLevelNodes.toList map testUserLevelNode val numExecutedTests = tests.count(_.isDefined) val numPassedTests = tests.count(_.getOrElse(false)) - println(s"Passed $numPassedTests/$numExecutedTests tests.") + println(s"Node type tests: Passed $numPassedTests/$numExecutedTests tests.") assert(numPassedTests == numExecutedTests, s"Node type test failed. Only $numPassedTests/$numExecutedTests tests passed.") } @@ -62,7 +62,7 @@ class DependencyGraphTestSupporter(interpreter: DependencyGraphInterpreter[Final val testResults = UserLevelDependencyAnalysisNode.from(interpreter.getNonInternalAssertionNodes).toList map testDependencies val numExecutedTests = testResults.count(_.isDefined) val numPassedTests = testResults.count(_.getOrElse(false)) - println(s"Passed $numPassedTests/$numExecutedTests tests.") + println(s"Dependency tests: Passed $numPassedTests/$numExecutedTests tests.") assert(numPassedTests == numExecutedTests, s"Dependency test failed. Only $numPassedTests/$numExecutedTests tests passed.") } From 8be7baa683f717558ef144ec2ecae160a7b23907 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 14 May 2026 14:50:50 +0200 Subject: [PATCH 460/474] redesign annotated tests --- .../DependencyGraphTestSupporter.scala | 4 +- .../dependencyAnalysisTests/all/aliasing.vpr | 62 ++---- .../all/assume_assert.vpr | 19 -- .../dependencyAnalysisTests/all/branches.vpr | 94 +++------ .../dependencyAnalysisTests/all/divBy0.vpr | 47 ++--- .../all/function-sum.vpr | 53 ++--- .../all/function_vs_method.vpr | 45 ----- .../dependencyAnalysisTests/all/functions.vpr | 98 --------- .../all/implicitUpperBounds.vpr | 48 ----- .../all/imprecision-fixed.vpr | 105 ---------- .../all/imprecision.vpr | 187 ------------------ .../all/infeasible.vpr | 70 ++++--- .../all/infeasiblePath-completeness.vpr | 14 -- .../all/invariant_ordering_imprecise.vpr | 52 ----- .../all/join-precision.vpr | 41 ---- .../dependencyAnalysisTests/all/join-test.vpr | 66 ------- .../all/method-sum.vpr | 96 +++++---- .../all/predicate-tuples.vpr | 50 +++++ .../all/presentation-examples.vpr | 27 --- .../all/quantified-perm.vpr | 37 +--- .../all/quasihavoc.vpr | 36 ---- .../dependencyAnalysisTests/all/sequences.vpr | 43 ++-- .../all/state_consolidation.vpr | 49 ++--- .../dependencyAnalysisTests/all/sum-loops.vpr | 72 +++---- .../dependencyAnalysisTests/all/tuples.vpr | 32 --- .../dependencyAnalysisTests/all/unfolding.vpr | 45 ++--- .../implicitUpperBounds.vpr | 45 +++++ .../missingAnnotations/imprecision-fixed.vpr | 99 ++++++++++ .../missingAnnotations/imprecision.vpr | 175 ++++++++++++++++ .../missingAnnotations/joins.vpr | 81 ++++++++ src/test/scala/DependencyAnalysisTests.scala | 6 +- 31 files changed, 721 insertions(+), 1177 deletions(-) delete mode 100644 src/test/resources/dependencyAnalysisTests/all/assume_assert.vpr delete mode 100644 src/test/resources/dependencyAnalysisTests/all/function_vs_method.vpr delete mode 100644 src/test/resources/dependencyAnalysisTests/all/functions.vpr delete mode 100644 src/test/resources/dependencyAnalysisTests/all/implicitUpperBounds.vpr delete mode 100644 src/test/resources/dependencyAnalysisTests/all/imprecision-fixed.vpr delete mode 100644 src/test/resources/dependencyAnalysisTests/all/imprecision.vpr delete mode 100644 src/test/resources/dependencyAnalysisTests/all/infeasiblePath-completeness.vpr delete mode 100644 src/test/resources/dependencyAnalysisTests/all/invariant_ordering_imprecise.vpr delete mode 100644 src/test/resources/dependencyAnalysisTests/all/join-precision.vpr delete mode 100644 src/test/resources/dependencyAnalysisTests/all/join-test.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/all/predicate-tuples.vpr delete mode 100644 src/test/resources/dependencyAnalysisTests/all/presentation-examples.vpr delete mode 100644 src/test/resources/dependencyAnalysisTests/all/quasihavoc.vpr delete mode 100644 src/test/resources/dependencyAnalysisTests/all/tuples.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/missingAnnotations/implicitUpperBounds.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/missingAnnotations/imprecision-fixed.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/missingAnnotations/imprecision.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/missingAnnotations/joins.vpr diff --git a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphTestSupporter.scala b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphTestSupporter.scala index 7e11e66a6..d6b21735b 100644 --- a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphTestSupporter.scala +++ b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphTestSupporter.scala @@ -36,7 +36,7 @@ class DependencyGraphTestSupporter(interpreter: DependencyGraphInterpreter[Final case Some(expectedTypeStr) => val expectedType = AssumptionType.fromString(expectedTypeStr).get isTested = true - ulNode.assumptionTypes.equals(Set(expectedType)) + ulNode.assumptionTypes.diff(AssumptionType.internalTypes).equals(Set(expectedType)) case None => true } @@ -45,7 +45,7 @@ class DependencyGraphTestSupporter(interpreter: DependencyGraphInterpreter[Final case Some(expectedTypeStr) => val expectedType = AssumptionType.fromString(expectedTypeStr).get isTested = true - ulNode.assertionTypes.equals(Set(expectedType)) + ulNode.assertionTypes.diff(AssumptionType.internalTypes).equals(Set(expectedType)) case None => true } diff --git a/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr b/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr index fdb5f5647..cd715d84f 100644 --- a/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/aliasing.vpr @@ -2,67 +2,47 @@ field f: Int method maybeAlias(a: Ref, b: Ref, c: Bool, n: Int) - requires @dependency("Precondition")(acc(a.f, 1/2)) - requires @dependency("Precondition")(acc(b.f, 1/2)) - requires @dependency("Precondition")(c ==> a == b) - requires @irrelevant("Precondition")(a.f > 0 && n > 0 && b.f >= 0) - requires @irrelevant("Precondition")(a.f < 100) - requires @irrelevant("Precondition")(!c ==> a.f < b.f) + requires @dependencyInfo("assumptionType:Precondition, label:L2")((acc(a.f, 1/2))) + requires @dependencyInfo("assumptionType:Precondition, label:L3")((acc(b.f, 1/2))) + requires @dependencyInfo("assumptionType:Precondition, label:L4")((c ==> a == b)) + requires @dependencyInfo("assumptionType:Precondition, label:L5")((a.f > 0 && n > 0 && b.f >= 0)) + requires @dependencyInfo("assumptionType:Precondition, label:L6")((a.f < 100)) + requires @dependencyInfo("assumptionType:Precondition, label:L7")((!c ==> a.f < b.f)) { - if(@dependency("PathCondition")(c)){ - @testAssertion("Implicit") + if(@dependencyInfo("assumptionType:PathCondition, label:L8")(c)){ + @dependencyInfo("assumptionType:SourceCode, assertionType:SourceCode, label:L9, expectedDependencies:[L2,L3,L4,L8]") a.f := n + 1 } } method aliasing1(x: Ref, n: Int) - requires @dependency("Precondition")(acc(x.f)) - requires @irrelevant("Precondition")(n > 0) - requires @irrelevant("Precondition")(x.f > n) + requires @dependencyInfo("assumptionType:Precondition, label:L10")((acc(x.f))) + requires @dependencyInfo("assumptionType:Precondition, label:L11")((n > 0)) + requires @dependencyInfo("assumptionType:Precondition, label:L12")((x.f > n)) { - @dependency("SourceCode") + @dependencyInfo("assumptionType:SourceCode, assertionType:SourceCode, label:L13") x.f := n + 1 var y: Ref - @dependency("SourceCode") + + @dependencyInfo("assumptionType:SourceCode, label:L14") y := x - @testAssertion("Explicit") + @dependencyInfo("assertionType:Explicit, label:L15, expectedDependencies:[L10,L13,L14]") assert y.f > n } method aliasing2(x: Ref, y: Ref, n: Int) - requires @dependency("Precondition")(acc(x.f, 1/2)) - requires @dependency("Precondition")(acc(y.f, 1/2)) - requires @irrelevant("Precondition")(n > 0) - requires @irrelevant("Precondition")(x.f > n) + requires @dependencyInfo("assumptionType:Precondition, label:L16")((acc(x.f, 1/2))) + requires @dependencyInfo("assumptionType:Precondition, label:L17")((acc(y.f, 1/2))) + requires @dependencyInfo("assumptionType:Precondition, label:L18")((n > 0)) + requires @dependencyInfo("assumptionType:Precondition, label:L19")((x.f > n)) { - if(@dependency("PathCondition")(x == y)){ - @testAssertion("Implicit") + if(@dependencyInfo("assumptionType:PathCondition, label:L20")((x == y))){ + @dependencyInfo("assumptionType:SourceCode, assertionType:SourceCode, label:L21, expectedDependencies:[L16,L17,L20]") x.f := n + 1 } } -method alias1(a: Ref, b: Ref, c: Ref) - requires @dependency("Precondition")(acc(a.f)) - requires @irrelevant("Explicit")(acc(b.f)) -{ - var x: Int - if (@dependency("PathCondition")(c == a)) { - @dependency("SourceCode") - x := c.f - } - if(@dependency("PathCondition")(c == b)) { - @irrelevant("SourceCode") - x := c.f - } - - @dependency("Explicit") - assume a == c - - @testAssertion("Explicit") - assert x == a.f -} - diff --git a/src/test/resources/dependencyAnalysisTests/all/assume_assert.vpr b/src/test/resources/dependencyAnalysisTests/all/assume_assert.vpr deleted file mode 100644 index 58e3b18a1..000000000 --- a/src/test/resources/dependencyAnalysisTests/all/assume_assert.vpr +++ /dev/null @@ -1,19 +0,0 @@ -field f: Int - -method assert1(a: Int) - requires @dependency("Precondition")(a > 0) -{ - @irrelevant() - assert a >= 0 - @testAssertion("Explicit") - assert a >= 0 -} - -method assume1(a: Int) - requires @dependency("Precondition")(a > 0) -{ - @irrelevant("Explicit") - assume a >= 0 - @testAssertion("Explicit") - assert a > 0 -} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/branches.vpr b/src/test/resources/dependencyAnalysisTests/all/branches.vpr index c21e6a358..93a6f4548 100644 --- a/src/test/resources/dependencyAnalysisTests/all/branches.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/branches.vpr @@ -2,27 +2,22 @@ method branches1(a: Int, b: Int) { var n:Int, c: Bool - @dependency("Explicit") - assume 0 < a - @dependency("Explicit") - assume b > 4 + assume @dependencyInfo("assumptionType:Explicit, label:L1")(0 < a) + assume @dependencyInfo("assumptionType:Explicit, label:L2")(b > 4) - @irrelevant("Explicit") - assume a < 100 - @irrelevant("Explicit") - assume b < 50 - @irrelevant("Explicit") - assume c ==> a > 5 + assume @dependencyInfo("assumptionType:Explicit, label:L3")(a < 100) + assume @dependencyInfo("assumptionType:Explicit, label:L4")(b < 50) + assume @dependencyInfo("assumptionType:Explicit, label:L5")(!c ==> a > 5) - if(@irrelevant("PathCondition")(c)){ - @dependency("SourceCode") + if(@dependencyInfo("assumptionType:PathCondition, label:L6")(c)){ + @dependencyInfo("assumptionType:SourceCode, label:L7") n := a + 1 }else{ - @dependency("SourceCode") + @dependencyInfo("assumptionType:SourceCode, label:L8") n := b + 1 } - @testAssertion("Explicit") + @dependencyInfo("assertionType:Explicit, label:L9, expectedDependencies:[L1,L2,L7,L8]") assert n > 1 } @@ -30,44 +25,23 @@ method branches2(a: Int, b: Int) { var n:Int, c: Bool - @dependency("Explicit") - assume 0 < a - @irrelevant("Explicit") - assume a < 100 - @irrelevant("Explicit") - assume a < b - @irrelevant("Explicit") - assume b < 50 + assume @dependencyInfo("assumptionType:Explicit, label:L10")(((0 < a))) + assume @dependencyInfo("assumptionType:Explicit, label:L11")(((a < 100))) + assume @dependencyInfo("assumptionType:Explicit, label:L12")(((a < b))) + assume @dependencyInfo("assumptionType:Explicit, label:L13")(((b < 50))) var x: Int - if(@dependency("PathCondition")(a >= n)){ - @irrelevant("SourceCode") + if(@dependencyInfo("assumptionType:PathCondition, label:L14")(((a >= n)))){ + @dependencyInfo("assumptionType:SourceCode, label:L15") x := a + b }else{ - @dependency("SourceCode") + @dependencyInfo("assumptionType:SourceCode, label:L16") x := n + 1 - @testAssertion("Explicit") + @dependencyInfo("assertionType:Explicit, label:L17, expectedDependencies:[L10,L14,L16]") assert x > 1 } -} - -method branches3(a: Int, b: Int) -{ - var n:Int, c: Bool - - @dependency("Explicit") - assume 0 < b - - var x: Int - if(@dependency("PathCondition")(a >= n)){ - @dependency("SourceCode") - x := a + b - }else{ - @dependency("SourceCode") - x := n + 1 - } - @testAssertion("Explicit") + @dependencyInfo("assertionType:Explicit, label:L22, expectedDependencies:[L16,L15,L14,L12]") assert x > n } @@ -76,33 +50,29 @@ method nestedBranches1(a: Int, b: Int) { var n:Int, c: Bool - @irrelevant("Explicit") - assume 0 < a - @irrelevant("Explicit") - assume a < 100 - @dependency("Explicit") - assume 0 < b - @irrelevant("Explicit") - assume b < 50 - @irrelevant("Explicit") - assume c ==> a > 5 + assume @dependencyInfo("assumptionType:Explicit, label:L23")(((0 < a))) + assume @dependencyInfo("assumptionType:Explicit, label:L24")(((a < 100))) + assume @dependencyInfo("assumptionType:Explicit, label:L25")(((0 < b))) + assume @dependencyInfo("assumptionType:Explicit, label:L26")(((b < 50))) + assume @dependencyInfo("assumptionType:Explicit, label:L27")(((c ==> a > 5))) - if(@irrelevant("PathCondition")(c)){ - if(@irrelevant("PathCondition")(a > b)){ - @dependency("SourceCode") + if(@dependencyInfo("assumptionType:PathCondition, label:L28")(((c)))){ + if(@dependencyInfo("assumptionType:PathCondition, label:L29")(((a > b)))){ + @dependencyInfo("assumptionType:SourceCode, label:L30") n := a - b }else{ - @dependency("SourceCode") + @dependencyInfo("assumptionType:SourceCode, label:L31") n := a + b } - @dependency("SourceCode") + @dependencyInfo("assumptionType:SourceCode, label:L32") n := n - 1 }else{ - @dependency("SourceCode") + @dependencyInfo("assumptionType:SourceCode, label:L33") n := a + b } - @testAssertion("Explicit") + @dependencyInfo("assertionType:Explicit, label:L34, expectedDependencies:[L33,L32,L31,L30,L25]") assert n <= a + b + @dependencyInfo("assertionType:Explicit, label:L35, expectedDependencies:[L28,L25,L30,L31,L32]") assert c ==> n < a + b -} +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/divBy0.vpr b/src/test/resources/dependencyAnalysisTests/all/divBy0.vpr index 461c49a55..88a2711e8 100644 --- a/src/test/resources/dependencyAnalysisTests/all/divBy0.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/divBy0.vpr @@ -1,12 +1,13 @@ field f: Int method sum(x: Int, y: Int) returns(res: Int) - requires x >= 0 - requires y >= 0 - ensures res == x + y - ensures res > 100 + requires @dependencyInfo("assumptionType:Precondition, label:L2, expectedDependencies:[L15]")(x >= 0) + requires @dependencyInfo("assumptionType:Precondition, label:L3, expectedDependencies:[L15,L17]")(y >= 0) + ensures @dependencyInfo("assertionType:ImplicitPostcondition, label:L4, expectedDependencies:[L7]")(res == x + y) + ensures @dependencyInfo("assertionType:ImplicitPostcondition, label:L5, expectedDependencies:[L6,L3,L7,L15,L17]")(res > 100) { - assume x > 100 + assume @dependencyInfo("assumptionType:Explicit, label:L6")(x > 100) + @dependencyInfo("assumptionType:SourceCode, label:L7") res := x + y } @@ -14,9 +15,8 @@ method divBy0() { var x: Int var y: Int - @dependency("Explicit") - assume y > 0 - @testAssertion("Implicit") + assume @dependencyInfo("assumptionType:Explicit, label:L8")(y > 0) + @dependencyInfo("assumptionType:SourceCode, assertionType:SourceCode, label:L9, expectedDependencies:[L8]") x := x/y } @@ -24,42 +24,35 @@ method basic() { var x: Int var y: Int - @irrelevant("Explicit") - assume x > 0 - @dependency("Explicit") - assume y > 0 - @dependency("Explicit") - assume y < x - @dependency("SourceCode") + assume @dependencyInfo("assumptionType:Explicit, label:L10")(x > 0) + assume @dependencyInfo("assumptionType:Explicit, label:L11")(y > 0) + assume @dependencyInfo("assumptionType:Explicit, label:L12")(y < x) + @dependencyInfo("assumptionType:SourceCode, assertionType:SourceCode, label:L13") x := x/y - @testAssertion("Explicit") + @dependencyInfo("assertionType:Explicit, label:L14, expectedDependencies:[L11,L12,L13]") assert x >= 1 } method sumClient(x: Int, y: Int) { - @dependency("Explicit") - assume x >= 0 - @dependency("Explicit") - assume y >= 0 - @dependency("Explicit") - assume x < y + assume @dependencyInfo("assumptionType:Explicit, label:L15")(x >= 0) + assume @dependencyInfo("assumptionType:Explicit, label:L17")(x < y) var n: Int - @dependency("MethodCall") + @dependencyInfo("assumptionType:MethodCall,assertionType:MethodCall, label:L18, expectedDependencies:[L15,L17]") n := sum(x, y) // the following stmt reports dependency on n := sum(x, y) because (x < y && n == x + y && n > 100 ==> y != 0) // although you could also prove it via (0 <= x && x < y ==> y != 0) var n2: Int var arg1: Int - @dependency("SourceCode") + @dependencyInfo("assumptionType:SourceCode,assertionType:SourceCode, label:L19, expectedDependencies:[L15,L17]") arg1 := x/y - @dependency("MethodCall") + @dependencyInfo("assumptionType:MethodCall,assertionType:MethodCall, label:L20, expectedDependencies:[L15,L17,L19]") n2 := sum(arg1, y) - @dependency("SourceCode") + @dependencyInfo("assumptionType:SourceCode, label:L21") n := n + n2 - @testAssertion("Explicit") + @dependencyInfo("assertionType:Explicit, label:L22, expectedDependencies:[L15,L17,L18,L19,L20,L21,L4,L7]") assert n >= x + 2*y } \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/function-sum.vpr b/src/test/resources/dependencyAnalysisTests/all/function-sum.vpr index eca159f1d..bab00300a 100644 --- a/src/test/resources/dependencyAnalysisTests/all/function-sum.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/function-sum.vpr @@ -1,49 +1,52 @@ field f: Int function sum(x: Int, y: Int) : Int - requires x >= 0 - requires y >= 0 - ensures result == x + y - ensures result >= 0 + requires @dependencyInfo("assumptionType:Precondition, label:L2, expectedDependencies:[L11,L19]")(x >= 0) + requires @dependencyInfo("assumptionType:Precondition, label:L3, expectedDependencies:[L12,L20]")(y >= 0) + ensures @dependencyInfo("assertionType:ImplicitPostcondition, label:L4, expectedDependencies:[L6]")(result == x + y) + ensures @dependencyInfo("assertionType:ImplicitPostcondition, label:L5, expectedDependencies:[L6,L2,L3,L11,L12,L19,L20]")(result >= 0) { - x + y + @dependencyInfo("assumptionType:FunctionBody, label:L6")(x + y) } function sumUnverified(x: Int, y: Int): Int - requires x >= 0 - requires y >= 0 - ensures result == x + y - ensures result >= 0 + requires @dependencyInfo("assumptionType:Precondition, label:L7, expectedDependencies:[L15,L19]")(x >= 0) + requires @dependencyInfo("assumptionType:Precondition, label:L8, expectedDependencies:[L16,L20]")(y >= 0) + ensures @dependencyInfo("assertionType:ExplicitPostcondition, label:L9, expectedDependencies:[L7,L8,L15,L16,L19,L20]")(result == x + y) + ensures @dependencyInfo("assertionType:ExplicitPostcondition, label:L10, expectedDependencies:[L7,L8,L15,L16,L19,L20]")(result >= 0) method call2(){ var x: Int, y: Int, z: Int - @dependency("Explicit") - assume x > 10 - @dependency("Explicit") - assume y > 0 + assume @dependencyInfo("assumptionType:Explicit, label:L11")(x > 10) + assume @dependencyInfo("assumptionType:Explicit, label:L12")(y > 0) - @dependency("SourceCode") + @dependencyInfo("assumptionType:SourceCode, label:L13, expectedDependencies:[L11,L12]") z := sum(x, y) - // $PrecisionTest: $READ_ONLY=x,y,z $INVARIANT=x>0 - - @testAssertion("Explicit") + @dependencyInfo("assertionType:Explicit, label:L14, expectedDependencies:[L11,L12,L13,L6]") assert z == x + y } method call2Unv(){ var x: Int, y: Int, z: Int - @dependency("Explicit") - assume x > 10 - @dependency("Explicit") - assume y > 0 + assume @dependencyInfo("assumptionType:Explicit, label:L15")(x > 10) + assume @dependencyInfo("assumptionType:Explicit, label:L16")(y > 0) - @dependency("SourceCode") + @dependencyInfo("assumptionType:SourceCode, label:L17, expectedDependencies:[L15,L16]") z := sumUnverified(x, y) - // $PrecisionTest: $READ_ONLY=x,y,z $INVARIANT=x>0 - - @testAssertion("Explicit") + @dependencyInfo("assertionType:Explicit, label:L18, expectedDependencies:[L15,L16,L17,L7,L8,L9]") assert z == x + y } +method client2() { + var x: Int, y: Int, z: Int + assume @dependencyInfo("assumptionType:Explicit, label:L19")(x > 10) + assume @dependencyInfo("assumptionType:Explicit, label:L20")(y > 0) + + @dependencyInfo("assumptionType:SourceCode, label:L21, expectedDependencies:[L19,L20]") + z := sum(x, y) + sumUnverified(x+x, y) + 4 + + @dependencyInfo("assertionType:Explicit, label:L22, expectedDependencies:[L19,L20,L21,L6,L7,L8,L9]") + assert z == 3*x + 2*y + 4 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/function_vs_method.vpr b/src/test/resources/dependencyAnalysisTests/all/function_vs_method.vpr deleted file mode 100644 index 768037a47..000000000 --- a/src/test/resources/dependencyAnalysisTests/all/function_vs_method.vpr +++ /dev/null @@ -1,45 +0,0 @@ - -field f: Int - -function add(x: Ref, a: Int): Int - requires acc(x.f) - { - x.f + a - } - - method add_M(x: Ref, a: Int) returns(res: Int) - requires acc(x.f) - ensures acc(x.f) - ensures res == x.f + a - { - res := x.f + a - } - -method client_func(x: Ref, a: Int) - requires @dependency("Precondition")(acc(x.f)) - { - var b: Int - @irrelevant("SourceCode") - b := add(x, a) + add(x, a) - - @irrelevant("SourceCode") - x.f := 0 - - @testAssertion("Explicit") - exhale acc(x.f) -} - -method client_meth(x: Ref, a: Int) - requires @dependency("Precondition")(acc(x.f)) -{ - var b: Int - @dependency("MethodCall") - b := add_M(x, a) - - @irrelevant("SourceCode") - x.f := 0 - - @testAssertion("Explicit") - exhale acc(x.f) -} - diff --git a/src/test/resources/dependencyAnalysisTests/all/functions.vpr b/src/test/resources/dependencyAnalysisTests/all/functions.vpr deleted file mode 100644 index f318ba261..000000000 --- a/src/test/resources/dependencyAnalysisTests/all/functions.vpr +++ /dev/null @@ -1,98 +0,0 @@ -field f: Int - -predicate greater0(x: Ref){ - acc(x.f) && x.f > 0 -} - - -function div100(n: Int): Int - requires n > 0 - ensures result >= 0 -{ - 100/n -} - -function div100Postcond(n: Int): Int - requires n > 0 - ensures result >= 0 - ensures result == 100/n - - -function foo(x: Ref): Int - requires greater0(x) -{ - unfolding greater0(x) in x.f -} - -function fooPostcond(x: Ref): Int - requires greater0(x) - ensures result == unfolding greater0(x) in x.f - - -method predicateClient(x: Ref) - requires acc(x.f) -{ - var a: Int - @dependency("SourceCode") - x.f := 10 - @dependency("Rewrite") - fold greater0(x) - @dependency("SourceCode") - a := foo(x) - - @testAssertion("Explicit") - assert a == 10 -} - -method predicateClientPostcond(x: Ref) - requires acc(x.f) -{ - var a: Int - @dependency("SourceCode") - x.f := 10 - @dependency("Rewrite") - fold greater0(x) - @dependency("SourceCode") - a := fooPostcond(x) - - @testAssertion("Explicit") - assert a == 10 -} - - -method callDiv(){ - var x: Int - @dependency("Explicit") - assume x > 10 - @dependency("SourceCode") - x := div100(x) - - @testAssertion("Explicit") - assert x < 10 -} - -method callDivPostcond(){ - var x: Int - @dependency("Explicit") - assume x > 10 - @dependency("SourceCode") - x := div100Postcond(x) - - @testAssertion("Explicit") - assert x < 10 -} - -function fooInput(a: Int): Int - ensures a > 0 - ensures result > 0 - - -method client(a: Int) -{ - var res: Int - @dependency("SourceCode") - res := fooInput(a) - - @testAssertion("Explicit") - assert a > 0 -} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/implicitUpperBounds.vpr b/src/test/resources/dependencyAnalysisTests/all/implicitUpperBounds.vpr deleted file mode 100644 index 743e16374..000000000 --- a/src/test/resources/dependencyAnalysisTests/all/implicitUpperBounds.vpr +++ /dev/null @@ -1,48 +0,0 @@ -field f: Int - -method implicitUpperBounds(x: Ref, y: Ref) - requires @dependency("Precondition")(acc(x.f)) - requires @dependency("Precondition")(acc(y.f, wildcard)) -{ - var z: Ref - @irrelevant("Explicit") - inhale true --* acc(z.f) - @irrelevant("Rewrite") - apply true --* acc(z.f) - - - @testAssertion("Explicit") - assert x != y -} - -method implicitUpperBounds_quantified(gen_xs: Seq[Ref], y: Ref) - requires @dependency("Precondition")(|gen_xs|> 2) - requires @dependency("Precondition")(forall gen_xi: Ref :: gen_xi in gen_xs ==> acc(gen_xi.f)) - requires @dependency("Precondition")(acc(y.f, wildcard)) -{ - var z: Ref - @irrelevant("Explicit") - inhale true --* acc(z.f) - @irrelevant("Rewrite") - apply true --* acc(z.f) - - - @testAssertion("Explicit") - assert gen_xs[0] != y -} - -method implicitUpperBounds_quantified_2(gen_xs: Seq[Ref], y: Ref) - requires @dependency("Precondition")(|gen_xs|> 2) - requires @dependency("Precondition")(forall gen_xi: Ref :: gen_xi in gen_xs ==> acc(gen_xi.f)) - requires @dependency("Precondition")(acc(y.f, 1/2)) -{ - var z: Ref - @irrelevant("Explicit") - inhale true --* acc(z.f) - @irrelevant("Rewrite") - apply true --* acc(z.f) - - - @testAssertion("Explicit") - assert gen_xs[0] != y -} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/imprecision-fixed.vpr b/src/test/resources/dependencyAnalysisTests/all/imprecision-fixed.vpr deleted file mode 100644 index 64f3f6ac3..000000000 --- a/src/test/resources/dependencyAnalysisTests/all/imprecision-fixed.vpr +++ /dev/null @@ -1,105 +0,0 @@ -field f: Int - -method permTest(a: Ref, b: Ref, n: Int) - requires @dependency("Precondition")(acc(a.f)) - requires @irrelevant("Precondition")(acc(b.f)) && @irrelevant("Explicit")(b.f > 0) -{ - @dependency("Explicit") - assume n > 0 - @irrelevant("SourceCode") // fixed imprecision (field assign) - a.f := b.f + 2 - @dependency("SourceCode") - a.f := n - @testAssertion("Explicit") - assert a.f >= 0 -} - - -method quantifiedPerm4(xs: Seq[Ref], ys: Seq[Ref]) - requires @dependency("Precondition")(|xs| > 5) - requires @irrelevant("Precondition")(|ys| > 3) -{ - @dependency("Explicit") - inhale (forall x: Ref :: x in xs ==> acc(x.f) && x.f > 0) - @irrelevant("Explicit") // fixed imprecision (heap summary) - inhale forall y: Ref :: y in ys ==> acc(y.f) - - @testAssertion("Explicit") - assert xs[0].f > 0 -} - -// issue #02 - imprecision due to field assign with qp -// issue: access to a quantified field depended on all writes to any location covered by this qp resource -// expl: when exhaling permission to a single location of a qp resource (e.g. for field assign), -// permissions for other locations are inhaled. The inhale of full permission after the field assign -// depends on this inhale. -// solution: -// - inhale of remaining permissions / full permission after assignments are labelled internal -// - exhale is implicit -// - lhs and rhs are split such that transitive edges only go from lhs to rhs -method quantifiedPerm5(xs: Seq[Ref], ys: Seq[Ref]) - requires @dependency("Precondition")(|xs| > 5) - requires @irrelevant("Precondition")(|ys| > 3) -{ - @dependency("Explicit") - inhale forall x: Ref :: x in xs ==> acc(x.f) - @irrelevant("Explicit") - inhale forall y: Ref :: y in ys ==> acc(y.f) - - @irrelevant("SourceCode") // fixed imprecision (field assign with qp) - xs[0].f := ys[0].f + 2 - @testAssertion("Implicit") - xs[0].f := 2 -} - -// issue #03 -// Imprecision due to state consolidation triggered by assertion -// state consolidation infers pair-wise non-aliasing of all 3 resources in one assumption. -// This assumption depends on all preconditions. -// The assertion depends on this assumption. -// solution: wrap with analysis labels (see viper.silicon.resources.NonQuantifiedPropertyInterpreter.buildForEach) -// and disable transitive edges (see viper.silicon.rules.DefaultStateConsolidator.consolidate) -method noAlias(a: Ref, b: Ref, c: Ref) - requires @dependency("Precondition")(acc(a.f)) - requires @dependency("Precondition")(acc(b.f, 1/2)) - requires @irrelevant("Precondition")(acc(c.f, 1/2)) // fixed imprecision (state consolidation) -{ - @testAssertion("Explicit") - assert a != b -} - -method fieldAccessPrecision(){ - var x: Ref - var y: Ref - - @dependency("Explicit") - inhale acc(x.f) - @dependency("Explicit") - inhale x.f > 0 - - @irrelevant("Explicit") - inhale acc(y.f) - @irrelevant("SourceCode") - y.f := y.f + 1 - - @testAssertion("Explicit") - assert x.f >= 0 -} - -method fieldAccessPrecision2(){ - var x: Ref - var y: Ref - - @dependency("Explicit") - inhale acc(x.f) - @irrelevant("Explicit") - inhale x.f > 0 - - @irrelevant("Explicit") - inhale acc(y.f) - @irrelevant("SourceCode") - x.f := y.f + 1 - - @testAssertion("Explicit") - exhale acc(x.f) -} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr b/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr deleted file mode 100644 index f00c1e030..000000000 --- a/src/test/resources/dependencyAnalysisTests/all/imprecision.vpr +++ /dev/null @@ -1,187 +0,0 @@ -field f: Int -field g: Int - -// When executing the branch, the 1st inhale is reported as a dependency, the 2nd inhale is ignored. -// If the branch is not executed, the 2nd inhale is reported as a dependency -// In reality, the 2nd inhale is all we need for the assertion. -method nonUniqueUnsatCore(x: Ref) - requires @irrelevant("Precondition")(x != null ==> acc(x.f)) -{ - var a: Int - if(@irrelevant("PathCondition")(x == null)){ - @irrelevant("Explicit") // imprecise - inhale a >= 0 - } - - @dependency("Explicit") - inhale a >= 0 - - @testAssertion("Explicit") - assert a >= 0 -} - -method unprovableInfeasibility(){ - var a: Int - - if (exists x: Int, y: Int, z: Int :: x > 0 && y > 0 && z > 0 && x*x*x + y*y*y == z*z*z) { - // complicated condition that is equivalent to false, but can’t be proven by solver - // effectively unreachable code - @irrelevant("Explicit") - assume a == 0 // assumption 1 - } else { - @dependency("Explicit") - assume a == 0 // assumption 2 - } - - @testAssertion("Explicit") - assert a == 0 -} - -// Implication causes branching. -// If x == null is assumed, only the assignment is reported as a dependency -// If x != null, the branch is infeasible, but we executed it anyways. The implication and branch condition are reported as dependencies. -method infeasible2(x: Ref) - requires @irrelevant("Precondition")(x != null ==> acc(x.f)) // unexpected dependency -{ - if(@irrelevant("PathCondition")(x == null)){ // unexpected dependency - var a: Int - - @dependency("SourceCode") - a := 0 - - @testAssertion("Explicit") - assert a >= 0 - } -} - -method exhaleImprecision(){ - var x: Ref - - @dependency("Explicit") - inhale acc(x.f) - - @dependency("Explicit") - inhale x.f > 0 - - @dependency("SourceCode") - x.f := x.f + 1 - - @irrelevant("Implicit") // unexpected dependency - exhale acc(x.f, 1/2) - - @testAssertion("Explicit") - assert x.f > 1 -} - -// this is precise as opposed to later examples with wildcards -method quantifiedPerm2(xs: Seq[Ref], ys: Seq[Ref]) - requires @dependency("Precondition")(|xs| > 5) - requires @irrelevant("Precondition")(|ys| > 3) -{ - @dependency("Explicit") - inhale (forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0)) - @irrelevant("Explicit") // this is precise (as opposed to quantifiedPerm3 - inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard)) - - @testAssertion("Explicit") - assert xs[0].f > 0 -} - -// issue #01 - imprecision due to wildcards -// when accessing a qp resource, we check Z < (x in xs? k1:Z) + (y in ys? k2:Z) the unsat core contains dependencies to all qp perm amounts (e.g. wildcard) -method quantifiedPerm3(xs: Seq[Ref], ys: Seq[Ref]) - requires @dependency("Precondition")(|xs| > 5) - requires @irrelevant("Precondition")(|ys| > 3) -{ - @dependency("Explicit") - inhale (forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0)) - @irrelevant("Explicit") // unexpected dependency (wildcards) - inhale forall y: Ref :: y in ys ==> (acc(y.f, wildcard) && y.f > 0) - - @testAssertion("Explicit") - assert xs[0].f > 0 -} - -// issue #01 - imprecision due to perm amounts -// when accessing a qp resource, we check Z < (x in xs? k1:Z) + (y in ys? k2:Z) the unsat core contains dependencies to all qp perm amounts -method quantifiedPerm4(xs: Seq[Ref], ys: Seq[Ref]) - requires @dependency("Precondition")(|xs| > 5) - requires @irrelevant("Precondition")(|ys| > 3) -{ - var p1: Perm - @dependency("Explicit") - assume none < p1 && p1 < 1/2 - - var p2: Perm - @irrelevant("Explicit") - assume none < p2 && p2 < 1/2 // unexpected dependency - - @dependency("Explicit") - inhale (forall x: Ref :: x in xs ==> (acc(x.f, p1) && x.f > 0)) - - @irrelevant("Explicit") // unexpected dependency - inhale forall y: Ref :: y in ys ==> (acc(y.f, p2) && y.f > 0) - - @testAssertion("Explicit") - assert xs[0].f > 0 -} - -// havocs all resources of the given fields conditionally on whether it can prove -// non-aliasing or not (e.g. x.f --> x == y? new snap : old snap) -method quasihavoc1(x: Ref, y: Ref) - requires @dependency("Precondition")(acc(x.f)) - requires @dependency("Precondition")(x.f == 3) - requires @dependency("Precondition")(x != y) -{ - @irrelevant() // unexpected dependency - quasihavoc y.f // does nothing. we have no permission - @testAssertion("Explicit") - assert x.f == 3 -} - - -method inhaleImprecision(){ - var x: Ref - - @dependency("Explicit") - inhale acc(x.f, 1/2) - @dependency("Explicit") - inhale x.f > 0 - - @irrelevant("Explicit") - inhale acc(x.f, 1/2) - - @testAssertion("Explicit") - assert x.f > 0 -} - -method packageExhale2(x: Ref) - requires @dependency("Precondition")(acc(x.f)) - { - - @irrelevant("Rewrite") - package acc(x.f, 1/2) --* acc(x.f) - - @irrelevant("Rewrite") - apply acc(x.f, 1/2) --* acc(x.f) - - @testAssertion("Explicit") - exhale acc(x.f) -} - -method quantifiedFieldAssign(){ - var a: Int - var idx: Int - var xs: Seq[Ref] - - @irrelevant("Explicit") - inhale |xs| > 2 // imprecise - @irrelevant("SourceCode") - idx := 0 // imprecise - inhale forall xi: Ref :: xi in xs ==> acc(xi.f) - @irrelevant("SourceCode") - xs[idx].f := a // internal dependency - - @testAssertion("Explicit") - exhale acc(xs[0].f) -} diff --git a/src/test/resources/dependencyAnalysisTests/all/infeasible.vpr b/src/test/resources/dependencyAnalysisTests/all/infeasible.vpr index d861630e4..5965bd08e 100644 --- a/src/test/resources/dependencyAnalysisTests/all/infeasible.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/infeasible.vpr @@ -1,49 +1,58 @@ field f: Int method infeasibleBranch1(a: Int, b: Int) - requires @dependency("Precondition")(a > 0) + requires @dependencyInfo("assumptionType:Precondition, label:L2")(a > 0) { - if(@dependency("PathCondition")(a < 0)){ - @testAssertion("Explicit") + if(@dependencyInfo("assumptionType:PathCondition, label:L3")(a < 0)){ + @dependencyInfo("assertionType:Explicit, label:L4, expectedDependencies:[L2,L3]") assert false } } -method infeasibleBranch2(a: Int, b: Int) - requires @dependency("Precondition")(a > 0) +method infeasibleBranch2(a: Int, b: Int) returns (res: Int) + requires @dependencyInfo("assumptionType:Precondition, label:L5")(a > 0) + ensures @dependencyInfo("assertionType:ImplicitPostcondition, label:L9b, expectedDependencies:[L5,L6,L8]")(res > 0) { - var res: Int - if(@dependency("PathCondition")(a < 0)){ - @irrelevant("Explicit") - assume res < 0 + if(@dependencyInfo("assumptionType:PathCondition, label:L6")(a < 0)){ + assume @dependencyInfo("assumptionType:Explicit, label:L7")(res < 0) }else{ - @dependency("Explicit") - assume res > 0 + assume @dependencyInfo("assumptionType:Explicit, label:L8")(res > 0) } - @testAssertion("Explicit") + @dependencyInfo("assertionType:Explicit, label:L9, expectedDependencies:[L5,L6,L8]") + assert res > 0 +} + +method clientInfeasibleBranch2() { + var a: Int, b: Int, res: Int + @dependencyInfo("assumptionType:SourceCode, label:LC1") + a := 10 + @dependencyInfo("assumptionType:MethodCall, assertionType:MethodCall, label:LC2, expectedDependencies:[LC1]") + res := infeasibleBranch2(a, b) + + @dependencyInfo("assertionType:Explicit, label:LC3, expectedDependencies:[LC1,LC2,L5,L6,L8,L9b]") assert res > 0 } method infeasibleBranchPerm(a: Ref, b: Ref) - requires @dependency("Precondition")(acc(a.f, 1/2)) - requires @dependency("Precondition")(a.f > 0) + requires @dependencyInfo("assumptionType:Precondition, label:L10")(acc(a.f, 1/2)) + requires @dependencyInfo("assumptionType:Precondition, label:L11, expectedDependencies:[L10]")(a.f > 0) { - if(@dependency("PathCondition")(a.f < 0)){ - @testAssertion("Implicit") + if(@dependencyInfo("assumptionType:PathCondition, label:L12, expectedDependencies:[L10]")(a.f < 0)){ + @dependencyInfo("assumptionType:SourceCode, assertionType:SourceCode, label:L13, expectedDependencies:[L11,L12]") a.f := a.f + 1 } } method infeasibleBranchNoPerm(a: Int, b: Ref) - requires @dependency("Precondition")(a > 0) + requires @dependencyInfo("assumptionType:Precondition, label:L14")(a > 0) { var res: Int - if(@dependency("PathCondition")(a < 0)){ - @irrelevant("SourceCode") + if(@dependencyInfo("assumptionType:PathCondition, label:L15")(a < 0)){ + @dependencyInfo("assumptionType:SourceCode, assertionType:SourceCode, label:L16, expectedDependencies:[L14,L15]") res := b.f + 1 - @testAssertion("Explicit") + @dependencyInfo("assertionType:Explicit, label:L17, expectedDependencies:[L14,L15]") assert false } } @@ -51,9 +60,9 @@ method infeasibleBranchNoPerm(a: Int, b: Ref) method implicationTest() { var a: Int - @dependency("SourceCode") + @dependencyInfo("assumptionType:SourceCode, label:L18") a := 10 - @testAssertion("Explicit") + @dependencyInfo("assertionType:Explicit, label:L19, expectedDependencies:[L18]") assert a == 0 ==> false } @@ -61,8 +70,21 @@ method implicationTest2() { var a: Int var x: Ref - @dependency("SourceCode") + @dependencyInfo("assumptionType:SourceCode, label:L20") a := 10 - @testAssertion("Explicit") + @dependencyInfo("assertionType:Explicit, label:L21, expectedDependencies:[L20]") exhale a == 0 ==> acc(x.f) +} + +method clientA(b: Bool, x: Ref) + requires @dependencyInfo("assumptionType:Precondition, label:L22")(b ==> acc(x.f)) +{ + + while(@dependencyInfo("assumptionType:PathCondition, label:L23")(b)) + invariant @dependencyInfo("assumptionType:LoopInvariant, assertionType:LoopInvariant, label:L24, expectedDependencies:[L22,L26]")(b ==> acc(x.f)) + { + @dependencyInfo("assertionType:Explicit, label:L25, expectedDependencies:[L22,L23,L24]") + exhale acc(x.f) + inhale @dependencyInfo("assumptionType:Explicit, label:L26")(acc(x.f)) + } } \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/infeasiblePath-completeness.vpr b/src/test/resources/dependencyAnalysisTests/all/infeasiblePath-completeness.vpr deleted file mode 100644 index 9d8718e15..000000000 --- a/src/test/resources/dependencyAnalysisTests/all/infeasiblePath-completeness.vpr +++ /dev/null @@ -1,14 +0,0 @@ -field f: Int - - -method client(b: Bool, x: Ref) - requires b ==> acc(x.f) -{ - - while(b) - invariant b ==> acc(x.f) - { - exhale acc(x.f) - inhale acc(x.f) - } -} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/invariant_ordering_imprecise.vpr b/src/test/resources/dependencyAnalysisTests/all/invariant_ordering_imprecise.vpr deleted file mode 100644 index 1399c06ae..000000000 --- a/src/test/resources/dependencyAnalysisTests/all/invariant_ordering_imprecise.vpr +++ /dev/null @@ -1,52 +0,0 @@ - -// imprecision due to non-uniqueness of unsat core -// in this example the loop condition and invariant res >= 0 would be enough -// to prove the invariant res >= 0. However, the SMT solver chooses to prove it -// via the invariant i>=0 -// interestingly, proving "res >= 0 is preserved by the loop body" has the following unsat core -// old(res) >= 0, res = old(res) + old(i), i = old(i) - 1, i >= 0 -// the last one comes from the asserting that invariant i >= 0 is preserved! -// the last two terms could be replaced by old(i) > 0 (the loop condition) -method loop1(){ - var i: Int - var res: Int - @dependency("SourceCode") - res := 0 - - @irrelevant("SourceCode") - i := 10 - while(@dependency("PathCondition")(i > 0)) - invariant @irrelevant("LoopInvariant")(i <= 10) - invariant @irrelevant("LoopInvariant")(i >= 0) - invariant @dependency("LoopInvariant")(res >= 0) - { - @dependency("SourceCode") - res := res + i - @irrelevant("SourceCode") - i := i - 1 - } - @testAssertion("Explicit") - assert res >= 0 -} - -method loop1_reordered(){ - var i: Int - var res: Int - @dependency("SourceCode") - res := 0 - - @irrelevant("SourceCode") - i := 10 - while(@dependency("PathCondition")(i > 0)) - invariant @dependency("LoopInvariant")(res >= 0) - invariant @irrelevant("LoopInvariant")(i <= 10) - invariant @irrelevant("LoopInvariant")(i >= 0) - { - @dependency("SourceCode") - res := res + i - @irrelevant("SourceCode") - i := i - 1 - } - @testAssertion("Explicit") - assert res >= 0 -} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/join-precision.vpr b/src/test/resources/dependencyAnalysisTests/all/join-precision.vpr deleted file mode 100644 index 2131f7aff..000000000 --- a/src/test/resources/dependencyAnalysisTests/all/join-precision.vpr +++ /dev/null @@ -1,41 +0,0 @@ - -field f: Int - -function foo2(a: Int): Int - requires a > 0 -{ - 10 -} - - - function add_M(x: Ref, a: Int, b: Int, c: Int): Int - requires acc(x.f) - requires c == a + b - requires b > 0 - requires a >= 0 - ensures result > a + b - { - 10 + c - } - - -method client_meth2(x: Ref, a: Int) - requires acc(x.f) - requires a >= 0 - requires a < 100 -{ - var b: Int, c: Int - assume a != 0 - b := 10 - - var arg1: Int := 1000/a - var arg2: Int := foo2(b) - var arg3a: Int := 1000/a - var arg3: Int := arg3a + foo2(b) - c := add_M(x, arg1, arg2, arg3) - - assert c > 1000/a + b - assert c >= b - exhale acc(x.f) -} - diff --git a/src/test/resources/dependencyAnalysisTests/all/join-test.vpr b/src/test/resources/dependencyAnalysisTests/all/join-test.vpr deleted file mode 100644 index 81d75b480..000000000 --- a/src/test/resources/dependencyAnalysisTests/all/join-test.vpr +++ /dev/null @@ -1,66 +0,0 @@ -method foo(n: Int) returns (res: Int) - requires n > 0 - ensures res > 0 -{ - res := n - var a: Int - a := 10 / res - while(true) - invariant res > 0 - { - res := foo(res) - } -} - -method bar(n: Int) returns (res: Int) - requires n > 0 - ensures res > 0 -{ - res := n -} - -method barClient(n: Int) returns (res: Int) - requires n > 0 -{ - res := bar(n) -} - -method client() -{ - var n: Int - n := 1 - while(n < 100) - invariant n > 0 - { - n := n + 1 - n := bar(n) - } - assert n > 0 -} - -method recWrapped(n: Int) returns (res: Int) - requires n >= 0 - ensures res >= 0 -{ - res := rec(n) -} - - -method rec(n: Int) returns (res: Int) - requires n >= 0 - ensures res >= 0 -{ - var i: Int - i := 0 - res := 0 - while(i < n) - invariant res >= 0 - invariant i >= 0 - invariant i <= n - { - var tmp: Int - tmp := recWrapped(n) - res := res + tmp - i := i + 1 - } -} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/method-sum.vpr b/src/test/resources/dependencyAnalysisTests/all/method-sum.vpr index 7d7bb7220..d668317a1 100644 --- a/src/test/resources/dependencyAnalysisTests/all/method-sum.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/method-sum.vpr @@ -1,59 +1,57 @@ field f: Int -method sum(x: Int, y: Int) returns(res: Int) - requires y >= 0 - ensures res == x + y - ensures res > 100 +method sum(x: Int, y: Int) returns (res: Int) + requires @dependencyInfo("assumptionType:Precondition, label:L2, expectedDependencies:[L11,L19]")(x >= 0) + requires @dependencyInfo("assumptionType:Precondition, label:L3, expectedDependencies:[L12,L20]")(y >= 0) + ensures @dependencyInfo("assertionType:ImplicitPostcondition, label:L4, expectedDependencies:[L6]")(res == x + y) + ensures @dependencyInfo("assertionType:ImplicitPostcondition, label:L5, expectedDependencies:[L6,L2,L3,L11,L12,L19,L20]")(res >= 0) { - assume x > 100 + @dependencyInfo("assumptionType:SourceCode, label:L6") res := x + y } +method sumUnverified(x: Int, y: Int) returns (res: Int) + requires @dependencyInfo("assumptionType:Precondition, label:L7, expectedDependencies:[L15,L19]")(x >= 0) + requires @dependencyInfo("assumptionType:Precondition, label:L8, expectedDependencies:[L16,L20]")(y >= 0) + ensures @dependencyInfo("assertionType:ExplicitPostcondition, label:L9, expectedDependencies:[L7,L8,L15,L16,L19,L20]")(res == x + y) + ensures @dependencyInfo("assertionType:ExplicitPostcondition, label:L10, expectedDependencies:[L7,L8,L15,L16,L19,L20]")(res >= 0) -method sumClient(x: Int, y: Int) -{ - @dependency("Explicit") - assume x >= 0 - @dependency("Explicit") - assume y >= 0 - @dependency("Explicit") - assume x < y - var n: Int - @dependency("MethodCall") - n := sum(x, y) - - // the following stmt reports dependency on n := sum(x, y) because (x < y && n == x + y && n > 100 ==> y != 0) - // although you could also prove it via (0 <= x && x < y ==> y != 0) - var n2: Int - var arg1: Int - @dependency("SourceCode") - arg1 := x/y - @dependency("MethodCall") - n2 := sum(arg1, y) - @dependency("SourceCode") - n := n + n2 - - @testAssertion("Explicit") - assert n >= x + 2*y +method call2(){ + var x: Int, y: Int, z: Int + assume @dependencyInfo("assumptionType:Explicit, label:L11")(x > 10) + assume @dependencyInfo("assumptionType:Explicit, label:L12")(y > 0) + + @dependencyInfo("assumptionType:MethodCall, assertionType:MethodCall, label:L13, expectedDependencies:[L11,L12]") + z := sum(x, y) + + @dependencyInfo("assertionType:Explicit, label:L14, expectedDependencies:[L11,L12,L13,L6,L4]") + assert z == x + y } -method sumClient2(x: Int, y: Int) -{ - @irrelevant("Explicit") - assume x >= 0 - @dependency("Explicit") - assume y >= 0 - @irrelevant("Explicit") - assume x < y - var n: Int - @irrelevant("MethodCall") - n := sum(x, x) - @irrelevant() - assert n >= 100 - - var n2: Int - @dependency("MethodCall") - n2 := sum(y, y) - @testAssertion("Explicit") - assert n2 == 2*y +method call2Unv(){ + var x: Int, y: Int, z: Int + assume @dependencyInfo("assumptionType:Explicit, label:L15")(x > 10) + assume @dependencyInfo("assumptionType:Explicit, label:L16")(y > 0) + + @dependencyInfo("assumptionType:MethodCall, assertionType:MethodCall, label:L17, expectedDependencies:[L15,L16]") + z := sumUnverified(x, y) + + @dependencyInfo("assertionType:Explicit, label:L18, expectedDependencies:[L15,L16,L17,L7,L8,L9]") + assert z == x + y +} + +method client2() { + var x: Int, y: Int, z1: Int, z2: Int, z3: Int + assume @dependencyInfo("assumptionType:Explicit, label:L19")(x > 10) + assume @dependencyInfo("assumptionType:Explicit, label:L20")(y > 0) + + @dependencyInfo("assumptionType:MethodCall, assertionType:MethodCall, label:L21a, expectedDependencies:[L19,L20]") + z1 := sum(x, y) + @dependencyInfo("assumptionType:MethodCall, assertionType:MethodCall, label:L21b, expectedDependencies:[L19,L20]") + z2 := sumUnverified(x+x, y) + @dependencyInfo("assumptionType:SourceCode, label:L21c, expectedDependencies:[L19,L20]") + z3 := z1 + z2 + 4 + + @dependencyInfo("assertionType:Explicit, label:L22, expectedDependencies:[L19,L20,L21a,L21b,L21c,L4,L6,L7,L8,L9]") + assert z3 == 3*x + 2*y + 4 } \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/predicate-tuples.vpr b/src/test/resources/dependencyAnalysisTests/all/predicate-tuples.vpr new file mode 100644 index 000000000..4375f61de --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/all/predicate-tuples.vpr @@ -0,0 +1,50 @@ +field left: Int +field right: Int + +predicate tuple(this: Ref) { + acc(this.left) && acc(this.right) +} + +method setTuple(this: Ref, l: Int, r: Int) + requires @dependencyInfo("assumptionType:Precondition, label:L4")((tuple(this))) + ensures @dependencyInfo("assertionType:ImplicitPostcondition, label:L5, expectedDependencies:[L4,L6]")(tuple(this)) +{ + @dependencyInfo("assumptionType:Rewrite, assertionType:Rewrite, label:L6, expectedDependencies:[L4]") + unfold tuple(this) + @dependencyInfo("assumptionType:SourceCode, assertionType:SourceCode, label:L7, expectedDependencies:[L4,L6]") + this.left := l + @dependencyInfo("assumptionType:SourceCode, assertionType:SourceCode, label:L8, expectedDependencies:[L4,L6]") + this.right := r + @dependencyInfo("assumptionType:Rewrite, assertionType:Rewrite, label:L9, expectedDependencies:[L4,L6]") + fold tuple(this) +} + +predicate tupleB(this: Ref) { + acc(this.left) && acc(this.right) && this.left > 0 && this.right > 0 +} + +method addTuple(this: Ref) returns (sum: Int) + requires @dependencyInfo("assumptionType:Precondition, label:L10")((acc(tupleB(this), 1/2))) + ensures @dependencyInfo("assertionType:ImplicitPostcondition, label:L11, expectedDependencies:[L10,L12,L14]")(acc(tupleB(this), 1/2)) +{ + @dependencyInfo("assumptionType:Rewrite, assertionType:Rewrite, label:L12, expectedDependencies:[L10]") + unfold acc(tupleB(this), 1/2) + @dependencyInfo("assumptionType:SourceCode, assertionType:SourceCode, label:L13, expectedDependencies:[L10,L12]") + sum := this.left + this.right + @dependencyInfo("assumptionType:Rewrite, assertionType:Rewrite, label:L14, expectedDependencies:[L10,L12]") + fold acc(tupleB(this), 1/2) +} + +method addTupleAndUpdate(this: Ref) returns (sum: Int) + requires @dependencyInfo("assumptionType:Precondition, label:L15")((acc(tupleB(this)))) + ensures @dependencyInfo("assertionType:ImplicitPostcondition, label:L16, expectedDependencies:[L15,L17,L18,L19,L20]")(acc(tupleB(this))) +{ + @dependencyInfo("assumptionType:Rewrite, assertionType:Rewrite, label:L17, expectedDependencies:[L15]") + unfold acc(tupleB(this)) + @dependencyInfo("assumptionType:SourceCode, assertionType:SourceCode, label:L18, expectedDependencies:[L15,L17]") + sum := this.left + this.right + @dependencyInfo("assumptionType:SourceCode, assertionType:SourceCode, label:L19, expectedDependencies:[L15,L17]") + this.left := sum + @dependencyInfo("assumptionType:Rewrite, assertionType:Rewrite, label:L20, expectedDependencies:[L15,L17,L18,L19]") + fold acc(tupleB(this)) +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/presentation-examples.vpr b/src/test/resources/dependencyAnalysisTests/all/presentation-examples.vpr deleted file mode 100644 index 41bad87fd..000000000 --- a/src/test/resources/dependencyAnalysisTests/all/presentation-examples.vpr +++ /dev/null @@ -1,27 +0,0 @@ -field f: Int -field g: Int - -method foo(x: Ref, y: Ref) - -{ - @dependency("Explicit") - inhale acc(x.f) - @dependency("Explicit") - inhale acc(y.f) - @irrelevant("Explicit") - inhale acc(y.g) - - @dependency("Explicit") - assume 0 < x.f - @irrelevant("Explicit") - assume x.f < 100 - @irrelevant("Explicit") - assume y.f < 100 - @irrelevant("Explicit") - assume 0 <= y.f - @dependency("Explicit") - assume x.f < y.f - - @testAssertion("Explicit") - assert x.f / y.f <= 1 -} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/quantified-perm.vpr b/src/test/resources/dependencyAnalysisTests/all/quantified-perm.vpr index d711c0c37..05571149f 100644 --- a/src/test/resources/dependencyAnalysisTests/all/quantified-perm.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/quantified-perm.vpr @@ -4,42 +4,21 @@ field second : Ref -method quantifiedWritePerm(nodes: Set[Ref], x: Ref) - requires @irrelevant("Precondition")(forall n:Ref :: { n.first } n in nodes ==> - acc(n.first) && - (n.first != null ==> n.first in nodes)) - requires @dependency("Precondition")(forall n:Ref :: { n.second } n in nodes ==> - acc(n.second) && - (n.second != null ==> n.second in nodes)) - requires @dependency("Precondition")(x in nodes) -{ - var y : Ref - if(@dependency("PathCondition")(x.second != null)) { - @dependency("SourceCode") - y := x.second // permissions covered by preconditions - @dependency("SourceCode") - y.second := y - @testAssertion("Explicit") - assert x.second.second == x.second - } -} - method quantifiedSum(nodes: Set[Ref], x: Ref) - requires @dependency("Precondition")(forall n:Ref :: { n.first } n in nodes ==> - acc(n.first) && + requires @dependencyInfo("assumptionType:Precondition, label:L15")(forall n:Ref :: { n.first } n in nodes ==> + acc(n.first) && (n.first != null ==> n.first in nodes)) - requires @dependency("Precondition")(forall n:Ref :: { n.f } n in nodes ==> + requires @dependencyInfo("assumptionType:Precondition, label:L18")(forall n:Ref :: { n.f } n in nodes ==> acc(n.f) && 0 <= n.f && n.f <= 100) - requires @dependency("Precondition")(x in nodes) + requires @dependencyInfo("assumptionType:Precondition, label:L20")((x in nodes)) { var a: Int - @dependency("SourceCode") + @dependencyInfo("assumptionType:SourceCode, assertionType:SourceCode, label:L21") a := x.f - if(@dependency("PathCondition")(x.first != null)) { - @dependency("SourceCode") + if(@dependencyInfo("assumptionType:PathCondition, label:L22")((x.first != null))) { + @dependencyInfo("assumptionType:SourceCode, assertionType:SourceCode, label:L23, expectedDependencies:[L20,L15,L18,L22]") a := a + x.first.f } - @testAssertion("Explicit") + @dependencyInfo("assertionType:Explicit, label:L24, expectedDependencies:[L21,L23,L20,L15,L18,L22]") assert a >= 0 } - diff --git a/src/test/resources/dependencyAnalysisTests/all/quasihavoc.vpr b/src/test/resources/dependencyAnalysisTests/all/quasihavoc.vpr deleted file mode 100644 index 5adda6930..000000000 --- a/src/test/resources/dependencyAnalysisTests/all/quasihavoc.vpr +++ /dev/null @@ -1,36 +0,0 @@ -field f: Int - -method quasihavoc1(x: Ref, y: Ref) - requires @dependency("Precondition")(acc(x.f)) - requires @dependency("Precondition")(x.f == 3) - requires @dependency("Precondition")(x != y) -{ - @irrelevant() - quasihavoc y.f // does nothing. we have no permission - @testAssertion("Explicit") - assert x.f == 3 -} - -method quasihavoc2(x: Ref, y: Ref) - requires @dependency("Precondition")(acc(x.f)) - requires @irrelevant("Precondition")(acc(y.f)) - requires @dependency("Precondition")(x.f == 42) -{ - @irrelevant() - quasihavoc y.f - @testAssertion("Explicit") - assert x.f == 42 -} - -method quasihavoc3(x: Ref) - requires @dependency("Precondition")(acc(x.f)) - requires @irrelevant("Precondition")(x.f == 0) -{ - @irrelevant() - quasihavoc x.f - @dependency("SourceCode") - x.f := 3 - @testAssertion("Explicit") - assert x.f == 3 -} - diff --git a/src/test/resources/dependencyAnalysisTests/all/sequences.vpr b/src/test/resources/dependencyAnalysisTests/all/sequences.vpr index 7853b518d..4fa512775 100644 --- a/src/test/resources/dependencyAnalysisTests/all/sequences.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/sequences.vpr @@ -1,48 +1,31 @@ method sequenceUpdate(xs: Seq[Int], ys: Seq[Int]) returns (res: Seq[Int]) - requires @dependency("Precondition")(|xs| > 5) - requires @irrelevant("Precondition")(|ys| > 3) + requires @dependencyInfo("assumptionType:Precondition, label:L1")((|xs| > 5)) + requires @dependencyInfo("assumptionType:Precondition, label:L2")((|ys| > 3)) { - @irrelevant("Explicit") - inhale forall x: Int :: x in xs ==> x > 0 - @irrelevant("Explicit") - inhale forall y: Int :: y in ys ==> y >= 0 + inhale @dependencyInfo("assumptionType:Explicit, label:L3")(forall x: Int :: x in xs ==> x > 0) + inhale @dependencyInfo("assumptionType:Explicit, label:L4")(forall y: Int :: y in ys ==> y >= 0) - @dependency("SourceCode") + @dependencyInfo("assumptionType:SourceCode, assertionType:SourceCode, label:L5, expectedDependencies:[L1]") res := xs[0 := 1] - @testAssertion("Explicit") + @dependencyInfo("assertionType:Explicit, label:L6, expectedDependencies:[L5,L1]") assert res[0] == 1 -} - -method sequence1(xs: Seq[Int], ys: Seq[Int]) returns (res: Seq[Int]) - requires @dependency("Precondition")(|xs| > 5) - requires @irrelevant("Precondition")(|ys| > 3) -{ - @dependency("Explicit") - inhale forall x: Int :: x in xs ==> x > 0 - @irrelevant("Explicit") - inhale forall y: Int :: y in ys ==> y >= 0 - - @dependency("SourceCode") - res := xs[0 := 1] - @testAssertion("Explicit") + @dependencyInfo("assertionType:Explicit, label:L12, expectedDependencies:[L5,L1,L3]") assert forall x: Int :: x in res ==> x > 0 } method sequence2(xs: Seq[Int], ys: Seq[Int]) returns (res: Seq[Int]) - requires @irrelevant("Precondition")(|xs| > 5) - requires @irrelevant("Precondition")(|ys| > 3) + requires @dependencyInfo("assumptionType:Precondition, label:L13")((|xs| > 5)) + requires @dependencyInfo("assumptionType:Precondition, label:L14")((|ys| > 3)) { - @irrelevant("Explicit") - inhale forall x: Int :: x in xs ==> x > 0 - @dependency("Explicit") - inhale forall y: Int :: y in ys ==> y >= 0 + inhale @dependencyInfo("assumptionType:Explicit, label:L15")(forall x: Int :: x in xs ==> x > 0) + inhale @dependencyInfo("assumptionType:Explicit, label:L16")(forall y: Int :: y in ys ==> y >= 0) - @irrelevant("SourceCode") + @dependencyInfo("assumptionType:SourceCode, label:L17") res := xs[0 := 1] - @testAssertion("Explicit") + @dependencyInfo("assertionType:Explicit, label:L18, expectedDependencies:[L16]") assert forall y: Int :: y in ys ==> y >= 0 } \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/state_consolidation.vpr b/src/test/resources/dependencyAnalysisTests/all/state_consolidation.vpr index ec2ef06ac..7d4f727e7 100644 --- a/src/test/resources/dependencyAnalysisTests/all/state_consolidation.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/state_consolidation.vpr @@ -1,59 +1,42 @@ field f: Int method stateConsolidation(x: Ref, y: Ref, z: Ref) - requires @dependency("Precondition")(acc(x.f, 1/2)) - requires @dependency("Precondition")(acc(y.f, 1/2)) - requires @irrelevant("Precondition")(acc(z.f, 1/2)) + requires @dependencyInfo("assumptionType:Precondition, label:L2")((acc(x.f, 1/2))) + requires @dependencyInfo("assumptionType:Precondition, label:L3")((acc(y.f, 1/2))) + requires @dependencyInfo("assumptionType:Precondition, label:L4")((acc(z.f, 1/2))) { var a: Ref - @dependency("Explicit") - inhale acc(a.f, 1/2) + inhale @dependencyInfo("assumptionType:Explicit, label:L5")(acc(a.f, 1/2)) - @dependency("Explicit") - assume a == x + assume @dependencyInfo("assumptionType:Explicit, label:L6")(a == x) - @testAssertion("Explicit") + @dependencyInfo("assertionType:Explicit, label:L7, expectedDependencies:[L2,L3,L5,L6]") assert a != y // triggers state consolidation -} - -method stateConsolidation2(x: Ref, y: Ref, z: Ref) - requires @dependency("Precondition")(acc(x.f, 1/2)) - requires @dependency("Precondition")(acc(y.f, 1/2)) - requires @irrelevant("Precondition")(acc(z.f, 1/2)) -{ - var a: Ref - @dependency("Explicit") - inhale acc(a.f, 1/2) - - @dependency("Explicit") - assume a == x // chunks are merged somewhere - @testAssertion("Explicit") + @dependencyInfo("assertionType:Explicit, label:L13, expectedDependencies:[L2,L5,L6]") assert perm(x.f) == write } method stateConsolidation3(x: Ref, y: Ref, z: Ref) - requires @dependency("Precondition")(acc(x.f, 1/2)) - requires @dependency("Precondition")(acc(y.f, 1/2)) - requires @irrelevant("Precondition")(acc(z.f, 1/2)) + requires @dependencyInfo("assumptionType:Precondition, label:L14")((acc(x.f, 1/2))) + requires @dependencyInfo("assumptionType:Precondition, label:L15")((acc(y.f, 1/2))) + requires @dependencyInfo("assumptionType:Precondition, label:L16")((acc(z.f, 1/2))) { var a: Ref - @dependency("Explicit") - inhale acc(x.f, 1/2) + inhale @dependencyInfo("assumptionType:Explicit, label:L17")(acc(x.f, 1/2)) - @testAssertion("Explicit") + @dependencyInfo("assertionType:Explicit, label:L18, expectedDependencies:[L14,L17]") assert perm(x.f) == write } method noAlias(a: Ref, b: Ref, c: Ref) - requires @dependency("Precondition")(acc(a.f)) - requires @dependency("Precondition")(acc(b.f, 1/2)) - requires @irrelevant("Precondition")(acc(c.f, 1/2)) + requires @dependencyInfo("assumptionType:Precondition, label:L19")((acc(a.f))) + requires @dependencyInfo("assumptionType:Precondition, label:L20")((acc(b.f, 1/2))) + requires @dependencyInfo("assumptionType:Precondition, label:L21")((acc(c.f, 1/2))) { - @testAssertion("Explicit") + @dependencyInfo("assertionType:Explicit, label:L22, expectedDependencies:[L19,L20]") assert a != b } - diff --git a/src/test/resources/dependencyAnalysisTests/all/sum-loops.vpr b/src/test/resources/dependencyAnalysisTests/all/sum-loops.vpr index fab0707f9..67c24a364 100644 --- a/src/test/resources/dependencyAnalysisTests/all/sum-loops.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/sum-loops.vpr @@ -1,76 +1,48 @@ field f: Int method sum(n: Int) returns (res: Int) - requires @dependency("Precondition")(0 <= n) - ensures res == n * (n + 1) / 2 + requires @dependencyInfo("assumptionType:Precondition, label:L2")((0 <= n)) + ensures @dependencyInfo("assertionType:ImplicitPostcondition, label:L3, expectedDependencies:[L6,L8,L4,L5,L7,L9,L10]")(res == n * (n + 1) / 2) { - @dependency("SourceCode") + @dependencyInfo("assumptionType:SourceCode, label:L4") res := 0 var i: Int - @dependency("SourceCode") + @dependencyInfo("assumptionType:SourceCode, label:L5") i := 0; - while(@dependency("PathCondition")(i <= n)) - invariant @dependency("LoopInvariant")(i <= (n + 1)) - invariant @dependency("LoopInvariant")(res == (i - 1) * i / 2) + while(@dependencyInfo("assumptionType:PathCondition, label:L6")((i <= n))) + invariant @dependencyInfo("assumptionType:LoopInvariant, assertionType:LoopInvariant, label:L7, expectedDependencies:[L2,L5,L6,L10]")((i <= (n + 1))) + invariant @dependencyInfo("assumptionType:LoopInvariant, assertionType:LoopInvariant, label:L8, expectedDependencies:[L4,L5,L9,L10]")((res == (i - 1) * i / 2)) { - @dependency("SourceCode") + @dependencyInfo("assumptionType:SourceCode, label:L9") res := res + i - @dependency("SourceCode") + @dependencyInfo("assumptionType:SourceCode, label:L10") i := i + 1 } - @testAssertion("Explicit") + @dependencyInfo("assertionType:Explicit, label:L11, expectedDependencies:[L6,L8,L4,L5,L9,L10]") assert res == n * (n + 1) / 2 } method sumPerm1(n: Int, res: Ref) - requires @dependency("Precondition")(acc(res.f)) - requires @dependency("Precondition")(0 <= n) - ensures acc(res.f) - ensures res.f == n * (n + 1) / 2 + requires @dependencyInfo("assumptionType:Precondition, label:L12")((acc(res.f))) + requires @dependencyInfo("assumptionType:Precondition, label:L13")((0 <= n)) + ensures @dependencyInfo("assertionType:ImplicitPostcondition, label:L14, expectedDependencies:[L12,L19]")(acc(res.f)) + ensures @dependencyInfo("assertionType:ImplicitPostcondition, label:L15, expectedDependencies:[L12,L19,L16,L17,L22,L23,L18,L20,L21]")(res.f == n * (n + 1) / 2) { - @dependency("SourceCode") + @dependencyInfo("assumptionType:SourceCode, assertionType:SourceCode, label:L16, expectedDependencies:[L12]") res.f := 0 var i: Int - @dependency("SourceCode") + @dependencyInfo("assumptionType:SourceCode, label:L17") i := 0 - while(@dependency("PathCondition")(i <= n)) - invariant @dependency("LoopInvariant")(acc(res.f)) - invariant @dependency("LoopInvariant")(i <= (n + 1)) - invariant @dependency("LoopInvariant")(res.f == (i - 1) * i / 2) + while(@dependencyInfo("assumptionType:PathCondition, label:L18")((i <= n))) + invariant @dependencyInfo("assumptionType:LoopInvariant, assertionType:LoopInvariant, label:L19, expectedDependencies:[L12]")((acc(res.f))) + invariant @dependencyInfo("assumptionType:LoopInvariant, assertionType:LoopInvariant, label:L20, expectedDependencies:[L13,L17,L18,L23]")((i <= (n + 1))) + invariant @dependencyInfo("assumptionType:LoopInvariant, assertionType:LoopInvariant, label:L21, expectedDependencies:[L12,L19,L16,L17,L22,L23]")((res.f == (i - 1) * i / 2)) { - @dependency("SourceCode") + @dependencyInfo("assumptionType:SourceCode, assertionType:SourceCode, label:L22, expectedDependencies:[L12]") res.f := res.f + i - @dependency("SourceCode") + @dependencyInfo("assumptionType:SourceCode, label:L23") i := i + 1 } - - @testAssertion("Explicit") - assert res.f == n * (n + 1) / 2 -} - -method sumPerm2(n: Int, res: Ref) - requires @dependency("Precondition")(acc(res.f)) - requires @irrelevant("Precondition")(0 <= n) - ensures acc(res.f) -{ - @irrelevant("SourceCode") - res.f := 0 - var i: Int - @irrelevant("SourceCode") - i := 0; - while(@irrelevant("PathCondition")(i <= n)) - invariant @dependency("LoopInvariant")(acc(res.f)) - invariant @irrelevant("LoopInvariant")(i <= (n + 1)) - invariant @irrelevant("LoopInvariant")(res.f == (i - 1) * i / 2) - { - @irrelevant("SourceCode") - res.f := res.f + i - @irrelevant("SourceCode") - i := i + 1 - } - - @testAssertion("Implicit") // only check permission flow - res.f := 5 } \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/tuples.vpr b/src/test/resources/dependencyAnalysisTests/all/tuples.vpr deleted file mode 100644 index 028843ba8..000000000 --- a/src/test/resources/dependencyAnalysisTests/all/tuples.vpr +++ /dev/null @@ -1,32 +0,0 @@ -field left: Int -field right: Int - -predicate tuple(this: Ref) { - acc(this.left) && acc(this.right) -} - -method setTuple(this: Ref, l: Int, r: Int) - requires @dependency("Precondition")(tuple(this)) - ensures tuple(this) -{ - @dependency("Rewrite") - unfold tuple(this) - @irrelevant("SourceCode") - this.left := l - @irrelevant("SourceCode") - this.right := r - @testAssertion("Implicit") - fold tuple(this) -} - -method addTuple(this: Ref) returns (sum: Int) - requires @dependency("Precondition")(acc(tuple(this), 1/2)) - ensures acc(tuple(this), 1/2) -{ - @dependency("Rewrite") - unfold acc(tuple(this), 1/2) - @irrelevant("SourceCode") - sum := this.left + this.right - @testAssertion("Implicit") - fold acc(tuple(this), 1/2) -} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/unfolding.vpr b/src/test/resources/dependencyAnalysisTests/all/unfolding.vpr index 588add9c9..38e83d78a 100644 --- a/src/test/resources/dependencyAnalysisTests/all/unfolding.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/unfolding.vpr @@ -6,12 +6,15 @@ predicate pred(x: Ref) } method foo(x: Ref) returns(res: Int) - requires pred(x) - ensures pred(x) - ensures res > 0 + requires @dependencyInfo("assumptionType:Precondition, label:L3, expectedDependencies:[L9,L10,L11]")((pred(x))) + ensures @dependencyInfo("assertionType:ImplicitPostcondition, label:L4, expectedDependencies:[L9,L10,L11,L3,L6,L8]")((pred(x))) + ensures @dependencyInfo("assertionType:ImplicitPostcondition, label:L5, expectedDependencies:[L9,L10,L11,L3,L6,L7]")((res > 0)) { + @dependencyInfo("assumptionType:Rewrite, assertionType:Rewrite, label:L6, expectedDependencies:[L9,L10,L11,L3]") unfold pred(x) + @dependencyInfo("assumptionType:SourceCode, assertionType:SourceCode, label:L7, expectedDependencies:[L9,L10,L11,L3,L6]") res := x.f + @dependencyInfo("assumptionType:Rewrite, assertionType:Rewrite, label:L8, expectedDependencies:[L9,L10,L11,L3,L6]") fold pred(x) } @@ -19,42 +22,20 @@ predicate pred(x: Ref) method client1(x: Ref) { var b: Int - @dependency("Explicit") - inhale acc(x.f) - @dependency("Explicit") - inhale x.f > 0 - @dependency("Rewrite") + inhale @dependencyInfo("assumptionType:Explicit, label:L9")((acc(x.f))) + inhale @dependencyInfo("assumptionType:Explicit, assertionType:Implicit, label:L10, expectedDependencies:[L9]")((x.f > 0)) + @dependencyInfo("assumptionType:Rewrite, assertionType:Rewrite, label:L11, expectedDependencies:[L9,L10]") fold pred(x) - @dependency("MethodCall") + @dependencyInfo("assumptionType:MethodCall, label:L12, expectedDependencies:[L9,L10,L11]") b := foo(x) - @irrelevant("SourceCode") + @dependencyInfo("assumptionType:SourceCode, assertionType:SourceCode, label:L13, expectedDependencies:[L9,L10,L11,L3,L6,L8,L12]") b := unfolding pred(x) in x.f - @irrelevant() + @dependencyInfo("assertionType:Explicit, label:L14, expectedDependencies:[L9,L10,L11,L3,L6,L8,L12,L13]") assert b > 0 - @testAssertion("Explicit") + @dependencyInfo("assertionType:Explicit, label:L15, expectedDependencies:[L9,L10,L11,L3,L6,L8,L12]") exhale pred(x) } - - method client2(x: Ref) - { - var b: Int - @dependency("Explicit") - inhale acc(x.f) - @dependency("Explicit") - inhale x.f > 0 - @dependency("Rewrite") - fold pred(x) - - @dependency("SourceCode") - b := foo(x) - - @dependency("SourceCode") - b := unfolding pred(x) in x.f - - @testAssertion("Explicit") - assert b > 0 - } \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/missingAnnotations/implicitUpperBounds.vpr b/src/test/resources/dependencyAnalysisTests/missingAnnotations/implicitUpperBounds.vpr new file mode 100644 index 000000000..88db5389f --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/missingAnnotations/implicitUpperBounds.vpr @@ -0,0 +1,45 @@ +field f: Int + +method implicitUpperBounds(x: Ref, y: Ref) + requires @dependencyInfo("assumptionType:Precondition, label:L2")((acc(x.f))) + requires @dependencyInfo("assumptionType:Precondition, label:L3")((acc(y.f, wildcard))) +{ + var z: Ref + inhale @dependencyInfo("assumptionType:Explicit, label:L4")(true --* acc(z.f)) + @dependencyInfo("assumptionType:Rewrite, assertionType:Rewrite, label:L5") + apply true --* acc(z.f) + + + @dependencyInfo("assertionType:Explicit, label:L6, expectedDependencies:[]") + assert x != y +} + +method implicitUpperBounds_quantified(gen_xs: Seq[Ref], y: Ref) + requires @dependencyInfo("assumptionType:Precondition, label:L7")((|gen_xs|> 2)) + requires @dependencyInfo("assumptionType:Precondition, label:L8")((forall gen_xi: Ref :: gen_xi in gen_xs ==> acc(gen_xi.f))) + requires @dependencyInfo("assumptionType:Precondition, label:L9")((acc(y.f, wildcard))) +{ + var z: Ref + inhale @dependencyInfo("assumptionType:Explicit, label:L10")(true --* acc(z.f)) + @dependencyInfo("assumptionType:Rewrite, assertionType:Rewrite, label:L11") + apply true --* acc(z.f) + + + @dependencyInfo("assertionType:Explicit, label:L12, expectedDependencies:[]") + assert gen_xs[0] != y +} + +method implicitUpperBounds_quantified_2(gen_xs: Seq[Ref], y: Ref) + requires @dependencyInfo("assumptionType:Precondition, label:L13")((|gen_xs|> 2)) + requires @dependencyInfo("assumptionType:Precondition, label:L14")((forall gen_xi: Ref :: gen_xi in gen_xs ==> acc(gen_xi.f))) + requires @dependencyInfo("assumptionType:Precondition, label:L15")((acc(y.f, 1/2))) +{ + var z: Ref + inhale @dependencyInfo("assumptionType:Explicit, label:L16")(true --* acc(z.f)) + @dependencyInfo("assumptionType:Rewrite, assertionType:Rewrite, label:L17") + apply true --* acc(z.f) + + + @dependencyInfo("assertionType:Explicit, label:L18, expectedDependencies:[]") + assert gen_xs[0] != y +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/missingAnnotations/imprecision-fixed.vpr b/src/test/resources/dependencyAnalysisTests/missingAnnotations/imprecision-fixed.vpr new file mode 100644 index 000000000..f071143ea --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/missingAnnotations/imprecision-fixed.vpr @@ -0,0 +1,99 @@ +field f: Int + +method permTest(a: Ref, b: Ref, n: Int) + requires @dependencyInfo("assumptionType:Precondition, label:L2")(acc(a.f)) + requires @dependencyInfo("assumptionType:Precondition, label:L3")(acc(b.f)) + requires @dependencyInfo("assumptionType:Precondition, label:L3")(b.f > 0) +{ + assume @dependencyInfo("assumptionType:Explicit, label:L4")(n > 0) + // fixed imprecision (field assign) + @dependencyInfo("assumptionType:SourceCode, assertionType:SourceCode, label:L6") + a.f := b.f + 2 + @dependencyInfo("assumptionType:SourceCode, assertionType:SourceCode, label:L7") + a.f := n + @dependencyInfo("assertionType:Explicit, label:L8, expectedDependencies:[L4,L7]") + assert a.f >= 0 +} + + +method quantifiedPerm4(xs: Seq[Ref], ys: Seq[Ref]) + requires @dependencyInfo("assumptionType:Precondition, label:L9")(|xs| > 5) + requires @dependencyInfo("assumptionType:Precondition, label:L10")(|ys| > 3) +{ + inhale @dependencyInfo("assumptionType:Explicit, assertionType:Implicit, label:L11")(forall x: Ref :: x in xs ==> acc(x.f) && x.f > 0) + + // fixed imprecision (heap summary) + inhale @dependencyInfo("assumptionType:Explicit, label:L13")(forall y: Ref :: y in ys ==> acc(y.f)) + + @dependencyInfo("assertionType:Explicit, label:L14, expectedDependencies:[L11]") + assert xs[0].f > 0 +} + +// issue #02 - imprecision due to field assign with qp +// issue: access to a quantified field depended on all writes to any location covered by this qp resource +// expl: when exhaling permission to a single location of a qp resource (e.g. for field assign), +// permissions for other locations are inhaled. The inhale of full permission after the field assign +// depends on this inhale. +// solution: +// - inhale of remaining permissions / full permission after assignments are labelled internal +// - exhale is implicit +// - lhs and rhs are split such that transitive edges only go from lhs to rhs +method quantifiedPerm5(xs: Seq[Ref], ys: Seq[Ref]) + requires @dependencyInfo("assumptionType:Precondition, label:L15")(|xs| > 5) + requires @dependencyInfo("assumptionType:Precondition, label:L16")(|ys| > 3) +{ + inhale @dependencyInfo("assumptionType:Explicit, label:L17")(forall x: Ref :: x in xs ==> acc(x.f)) + inhale @dependencyInfo("assumptionType:Explicit, label:L18")(forall y: Ref :: y in ys ==> acc(y.f)) + + // fixed imprecision (field assign with qp) + @dependencyInfo("assumptionType:SourceCode, assertionType:SourceCode, label:L20") + xs[0].f := ys[0].f + 2 + @dependencyInfo("assumptionType:SourceCode, assertionType:SourceCode, label:L21") + xs[0].f := 2 +} + +// issue #03 +// Imprecision due to state consolidation triggered by assertion +// state consolidation infers pair-wise non-aliasing of all 3 resources in one assumption. +// This assumption depends on all preconditions. +// The assertion depends on this assumption. +// solution: wrap with analysis labels (see viper.silicon.resources.NonQuantifiedPropertyInterpreter.buildForEach) +// and disable transitive edges (see viper.silicon.rules.DefaultStateConsolidator.consolidate) +method noAlias(a: Ref, b: Ref, c: Ref) + requires @dependencyInfo("assumptionType:Precondition, label:L22")(acc(a.f)) + requires @dependencyInfo("assumptionType:Precondition, label:L23")(acc(b.f, 1/2)) + requires @dependencyInfo("assumptionType:Precondition, label:L24")(acc(c.f, 1/2)) // fixed imprecision (state consolidation)) +{ + @dependencyInfo("assertionType:Explicit, label:L25, expectedDependencies:[]") + assert a != b +} + +method fieldAccessPrecision(){ + var x: Ref + var y: Ref + + inhale @dependencyInfo("assumptionType:Explicit, label:L26")(acc(x.f)) + inhale @dependencyInfo("assumptionType:Explicit, assertionType:Implicit, label:L27")(x.f > 0) + + inhale @dependencyInfo("assumptionType:Explicit, label:L28")(acc(y.f)) + @dependencyInfo("assumptionType:SourceCode, assertionType:SourceCode, label:L29") + y.f := y.f + 1 + + @dependencyInfo("assertionType:Explicit, label:L30, expectedDependencies:[L26,L27]") + assert x.f >= 0 +} + +method fieldAccessPrecision2(){ + var x: Ref + var y: Ref + + inhale @dependencyInfo("assumptionType:Explicit, label:L31")(acc(x.f)) + inhale @dependencyInfo("assumptionType:Explicit, assertionType:Implicit, label:L32")(x.f > 0) + + inhale @dependencyInfo("assumptionType:Explicit, label:L33")(acc(y.f)) + @dependencyInfo("assumptionType:SourceCode, assertionType:SourceCode, label:L34") + x.f := y.f + 1 + + @dependencyInfo("assertionType:Explicit, label:L35, expectedDependencies:[L31]") + exhale acc(x.f) +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/missingAnnotations/imprecision.vpr b/src/test/resources/dependencyAnalysisTests/missingAnnotations/imprecision.vpr new file mode 100644 index 000000000..8ba3b92cd --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/missingAnnotations/imprecision.vpr @@ -0,0 +1,175 @@ +field f: Int +field g: Int + +// When executing the branch, the 1st inhale is reported as a dependency, the 2nd inhale is ignored. +// If the branch is not executed, the 2nd inhale is reported as a dependency +// In reality, the 2nd inhale is all we need for the assertion. +method nonUniqueUnsatCore(x: Ref) + requires @dependencyInfo("assumptionType:Precondition, label:L3")(x != null ==> acc(x.f)) +{ + var a: Int + if(@dependencyInfo("assumptionType:PathCondition, label:L4")(x == null)){ // imprecise + inhale @dependencyInfo("assumptionType:Explicit, label:L6")(a >= 0) + } + + inhale @dependencyInfo("assumptionType:Explicit, label:L7")(a >= 0) + + @dependencyInfo("assertionType:Explicit, label:L8, expectedDependencies:[L7]") + assert a >= 0 +} + +method unprovableInfeasibility(){ + var a: Int + + if(@dependencyInfo("assumptionType:PathCondition, label:L9")(exists x: Int, y: Int, z: Int :: x > 0 && y > 0 && z > 0 && x*x*x + y*y*y == z*z*z)) { + // complicated condition that is equivalent to false, but can’t be proven by solver + // effectively unreachable code + assume @dependencyInfo("assumptionType:Explicit, label:L10")(a == 0) + } else { + assume @dependencyInfo("assumptionType:Explicit, label:L11")(a == 0) + } + + @dependencyInfo("assertionType:Explicit, label:L12, expectedDependencies:[L11]") + assert a == 0 +} + +// Implication causes branching. +// If x == null is assumed, only the assignment is reported as a dependency +// If x != null, the branch is infeasible, but we executed it anyways. The implication and branch condition are reported as dependencies. +method infeasible2(x: Ref) + requires @dependencyInfo("assumptionType:Precondition, label:L13")(x != null ==> acc(x.f)) // unexpected dependency +{ + if(@dependencyInfo("assumptionType:PathCondition, label:L14")(x == null)){ // unexpected dependency + var a: Int + + @dependencyInfo("assumptionType:SourceCode, label:L15") + a := 0 + + @dependencyInfo("assertionType:Explicit, label:L16, expectedDependencies:[L15]") + assert a >= 0 + } +} + +method exhaleImprecision(){ + var x: Ref + + inhale @dependencyInfo("assumptionType:Explicit, label:L17")(acc(x.f)) + + inhale @dependencyInfo("assumptionType:Explicit, assertionType:Implicit, label:L18")(x.f > 0) + + @dependencyInfo("assumptionType:SourceCode, assertionType:SourceCode, label:L19") + x.f := x.f + 1 + + // unexpected dependency + @dependencyInfo("assertionType:Explicit, label:L21") + exhale acc(x.f, 1/2) + + @dependencyInfo("assertionType:Explicit, label:L22, expectedDependencies:[L17,L18,L19]") + assert x.f > 1 +} + +// this is precise as opposed to later examples with wildcards +method quantifiedPerm2(xs: Seq[Ref], ys: Seq[Ref]) + requires @dependencyInfo("assumptionType:Precondition, label:L23")(|xs| > 5) + requires @dependencyInfo("assumptionType:Precondition, label:L24")(|ys| > 3) +{ + inhale @dependencyInfo("assumptionType:Explicit, assertionType:Implicit, label:L25")(forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0)) + // this is precise (as opposed to quantifiedPerm3 + inhale @dependencyInfo("assumptionType:Explicit, label:L27")(forall y: Ref :: y in ys ==> acc(y.f, wildcard)) + + @dependencyInfo("assertionType:Explicit, label:L28, expectedDependencies:[L23, L25]") + assert xs[0].f > 0 +} + +// issue #01 - imprecision due to wildcards +// when accessing a qp resource, we check Z < (x in xs? k1:Z) + (y in ys? k2:Z) the unsat core contains dependencies to all qp perm amounts (e.g. wildcard) +method quantifiedPerm3(xs: Seq[Ref], ys: Seq[Ref]) + requires @dependencyInfo("assumptionType:Precondition, label:L29")(|xs| > 5) + requires @dependencyInfo("assumptionType:Precondition, label:L30")(|ys| > 3) +{ + inhale @dependencyInfo("assumptionType:Explicit, assertionType:Implicit, label:L31")(forall x: Ref :: x in xs ==> (acc(x.f, wildcard) && x.f > 0)) + // unexpected dependency (wildcards) + inhale @dependencyInfo("assumptionType:Explicit, assertionType:Implicit, label:L33")(forall y: Ref :: y in ys ==> (acc(y.f, wildcard) && y.f > 0)) + + @dependencyInfo("assertionType:Explicit, label:L34, expectedDependencies:[L31]") + assert xs[0].f > 0 +} + +// issue #01 - imprecision due to perm amounts +// when accessing a qp resource, we check Z < (x in xs? k1:Z) + (y in ys? k2:Z) the unsat core contains dependencies to all qp perm amounts +method quantifiedPerm4(xs: Seq[Ref], ys: Seq[Ref]) + requires @dependencyInfo("assumptionType:Precondition, label:L35")(|xs| > 5) + requires @dependencyInfo("assumptionType:Precondition, label:L36")(|ys| > 3) +{ + var p1: Perm + assume @dependencyInfo("assumptionType:Explicit, label:L37")(none < p1) + assume @dependencyInfo("assumptionType:Explicit, label:L37b")(p1 < 1/2) + + var p2: Perm + assume @dependencyInfo("assumptionType:Explicit, label:L38")(none < p2) // unexpected dependency + assume @dependencyInfo("assumptionType:Explicit, label:L38b")(p2 < 1/2) + + inhale @dependencyInfo("assumptionType:Explicit, assertionType:Implicit, label:L39")(forall x: Ref :: x in xs ==> (acc(x.f, p1) && x.f > 0)) + + // unexpected dependency + inhale @dependencyInfo("assumptionType:Explicit, assertionType:Implicit, label:L41")(forall y: Ref :: y in ys ==> (acc(y.f, p2) && y.f > 0)) + + @dependencyInfo("assertionType:Explicit, label:L42, expectedDependencies:[L35,L37,L39]") + assert xs[0].f > 0 +} + +// havocs all resources of the given fields conditionally on whether it can prove +// non-aliasing or not (e.g. x.f --> x == y? new snap : old snap) +method quasihavoc1(x: Ref, y: Ref) + requires @dependencyInfo("assumptionType:Precondition, label:L43")((acc(x.f))) + requires @dependencyInfo("assumptionType:Precondition, label:L44")((x.f == 3)) + requires @dependencyInfo("assumptionType:Precondition, label:L45")((x != y)) +{ + // unexpected dependency + quasihavoc y.f // does nothing. we have no permission + @dependencyInfo("assertionType:Explicit, label:L48, expectedDependencies:[]") + assert x.f == 3 +} + + +method inhaleImprecision(){ + var x: Ref + + inhale @dependencyInfo("assumptionType:Explicit, label:L49")(acc(x.f, 1/2)) + inhale @dependencyInfo("assumptionType:Explicit, assertionType:Implicit, label:L50")(x.f > 0) + + inhale @dependencyInfo("assumptionType:Explicit, label:L51")(acc(x.f, 1/2)) + + @dependencyInfo("assertionType:Explicit, label:L52, expectedDependencies:[L49,L50]") + assert x.f > 0 +} + +method packageExhale2(x: Ref) + requires @dependencyInfo("assumptionType:Precondition, label:L53")(acc(x.f)) + { + + @dependencyInfo("assumptionType:Rewrite, assertionType:Rewrite, label:L54") + package acc(x.f, 1/2) --* acc(x.f) + + @dependencyInfo("assumptionType:Rewrite, assertionType:Rewrite, label:L55") + apply acc(x.f, 1/2) --* acc(x.f) + + @dependencyInfo("assertionType:Explicit, label:L56, expectedDependencies:[]") + exhale acc(x.f) +} + +method quantifiedFieldAssign(){ + var a: Int + var idx: Int + var xs: Seq[Ref] + + inhale @dependencyInfo("assumptionType:Explicit, label:L57")(|xs| > 2 ) // imprecise + @dependencyInfo("assumptionType:SourceCode, label:L58") + idx := 0 // imprecise + inhale @dependencyInfo("assumptionType:Explicit, label:L59")(forall xi: Ref :: xi in xs ==> acc(xi.f)) + @dependencyInfo("assumptionType:SourceCode, label:L60") + xs[idx].f := a // internal dependency + + @dependencyInfo("assertionType:Explicit, label:L61, expectedDependencies:[L57,L59]") + exhale acc(xs[0].f) +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/missingAnnotations/joins.vpr b/src/test/resources/dependencyAnalysisTests/missingAnnotations/joins.vpr new file mode 100644 index 000000000..3e429c9ce --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/missingAnnotations/joins.vpr @@ -0,0 +1,81 @@ +method foo(n: Int) returns (res: Int) + requires @dependencyInfo("assumptionType:Precondition, label:L1")((n > 0)) + ensures @dependencyInfo("assertionType:ImplicitPostcondition, label:L2")((res > 0)) +{ + @dependencyInfo("assumptionType:SourceCode, label:L3") + res := n + var a: Int + @dependencyInfo("assumptionType:SourceCode, label:L4") + a := 10 / res + while(@dependencyInfo("assumptionType:PathCondition, label:L5")((true))) + invariant @dependencyInfo("assumptionType:LoopInvariant, assertionType:LoopInvariant, label:L6")((res > 0)) + { + @dependencyInfo("assumptionType:MethodCall, label:L7") + res := foo(res) + } +} + +method bar(n: Int) returns (res: Int) + requires @dependencyInfo("assumptionType:Precondition, label:L8")((n > 0)) + ensures @dependencyInfo("assertionType:ImplicitPostcondition, label:L9")((res > 0)) +{ + @dependencyInfo("assumptionType:SourceCode, label:L10") + res := n +} + +method barClient(n: Int) returns (res: Int) + requires @dependencyInfo("assumptionType:Precondition, label:L11")((n > 0)) +{ + @dependencyInfo("assumptionType:MethodCall, label:L12") + res := bar(n) +} + +method client() +{ + var n: Int + @dependencyInfo("assumptionType:SourceCode, label:L13") + n := 1 + while(@dependencyInfo("assumptionType:PathCondition, label:L14")((n < 100))) + invariant @dependencyInfo("assumptionType:LoopInvariant, assertionType:LoopInvariant, label:L15")((n > 0)) + { + @dependencyInfo("assumptionType:SourceCode, label:L16") + n := n + 1 + @dependencyInfo("assumptionType:MethodCall, label:L17") + n := bar(n) + } + @dependencyInfo("assertionType:Explicit, label:L18") + assert n > 0 +} + +method recWrapped(n: Int) returns (res: Int) + requires @dependencyInfo("assumptionType:Precondition, label:L19")((n >= 0)) + ensures @dependencyInfo("assertionType:ImplicitPostcondition, label:L20")((res >= 0)) +{ + @dependencyInfo("assumptionType:MethodCall, label:L21") + res := rec(n) +} + + +method rec(n: Int) returns (res: Int) + requires @dependencyInfo("assumptionType:Precondition, label:L22")((n >= 0)) + ensures @dependencyInfo("assertionType:ImplicitPostcondition, label:L23")((res >= 0)) +{ + var i: Int + @dependencyInfo("assumptionType:SourceCode, label:L24") + i := 0 + @dependencyInfo("assumptionType:SourceCode, label:L25") + res := 0 + while(@dependencyInfo("assumptionType:PathCondition, label:L26")((i < n))) + invariant @dependencyInfo("assumptionType:LoopInvariant, assertionType:LoopInvariant, label:L27")((res >= 0)) + invariant @dependencyInfo("assumptionType:LoopInvariant, assertionType:LoopInvariant, label:L28")((i >= 0)) + invariant @dependencyInfo("assumptionType:LoopInvariant, assertionType:LoopInvariant, label:L29")((i <= n)) + { + var tmp: Int + @dependencyInfo("assumptionType:MethodCall, label:L30") + tmp := recWrapped(n) + @dependencyInfo("assumptionType:SourceCode, label:L31") + res := res + tmp + @dependencyInfo("assumptionType:SourceCode, label:L32") + i := i + 1 + } +} \ No newline at end of file diff --git a/src/test/scala/DependencyAnalysisTests.scala b/src/test/scala/DependencyAnalysisTests.scala index e38cfb9ff..6203699dd 100644 --- a/src/test/scala/DependencyAnalysisTests.scala +++ b/src/test/scala/DependencyAnalysisTests.scala @@ -13,7 +13,8 @@ class DependencyAnalysisTests extends AnyFunSuite with DependencyAnalysisTestFra val EXECUTE_TEST = true override val EXPORT_PRUNED_PROGRAMS: Boolean = false val ignores: Seq[String] = Seq() - val testDirectories: Seq[String] = Seq( + analysisCommandLineArguments = analysisCommandLineArguments ++ Seq("--executeDependencyAnalysisTests") + val testDirectories: Seq[String] = Seq( "dependencyAnalysisTests/all", "dependencyAnalysisTests/unitTests", "dependencyAnalysisTests/real-world-examples", @@ -51,9 +52,8 @@ class DependencyAnalysisTests extends AnyFunSuite with DependencyAnalysisTestFra val dependencyGraphInterpreters = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].dependencyGraphInterpretersPerMember val joinedDependencyGraphInterpreter = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].joinedDependencyGraphInterpreter + // TODO ake: annotated tests can be removed once all tests are migrated to the new test annotations (TestSupporter) new AnnotatedTest(program, dependencyGraphInterpreters, CHECK_PRECISION).execute() new PruningTest(filePrefix + "/" + fileName, program, joinedDependencyGraphInterpreter.get).execute() } } - - From 39958bc44dcfe7db52329cba024405801d36c83c Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 19 May 2026 15:56:46 +0200 Subject: [PATCH 461/474] fix failing tests --- src/main/scala/rules/Executor.scala | 1 - src/test/scala/DependencyAnalysisTestFramework.scala | 4 +++- src/test/scala/DependencyAnalysisTests.scala | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 870206eb6..1843ec930 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -20,7 +20,6 @@ import viper.silicon.state.terms._ import viper.silicon.utils.ast.{BigAnd, extractPTypeFromExp, simplifyVariableName} import viper.silicon.utils.freshSnap import viper.silicon.verifier.Verifier -import viper.silver.ast.Literal import viper.silver.cfg.silver.SilverCfg import viper.silver.cfg.silver.SilverCfg.{SilverBlock, SilverEdge} import viper.silver.cfg.{ConditionalEdge, StatementBlock} diff --git a/src/test/scala/DependencyAnalysisTestFramework.scala b/src/test/scala/DependencyAnalysisTestFramework.scala index f143e27c8..40ba8dbb6 100644 --- a/src/test/scala/DependencyAnalysisTestFramework.scala +++ b/src/test/scala/DependencyAnalysisTestFramework.scala @@ -88,10 +88,12 @@ trait DependencyAnalysisTestFramework { val triggerNodeLines = fullGraphInterpreter.getNodes.filter(node => node.getUserLevelRepresentation.contains("@trigger()")).flatMap(_.sourceInfo.getLineNumber) var id: Int = 0 // TODO ake: it would be better to work with position string instead of line numbers - fullGraphInterpreter.getExplicitAssertionNodes flatMap (_.sourceInfo.getLineNumber) foreach {line => + val testCases = fullGraphInterpreter.getExplicitAssertionNodes flatMap (_.sourceInfo.getLineNumber) + testCases foreach {line => pruneAndVerify(Set(line) ++ triggerNodeLines, "src/test/resources/" + fileName + s"_test$id.out") id += 1 } + println(s"Passed all ${testCases.size} pruning tests.") } protected def pruneAndVerify(relevantLines: Set[Int], exportFileName: String): Unit = { diff --git a/src/test/scala/DependencyAnalysisTests.scala b/src/test/scala/DependencyAnalysisTests.scala index 6203699dd..84db1e21e 100644 --- a/src/test/scala/DependencyAnalysisTests.scala +++ b/src/test/scala/DependencyAnalysisTests.scala @@ -12,7 +12,7 @@ class DependencyAnalysisTests extends AnyFunSuite with DependencyAnalysisTestFra val CHECK_PRECISION = false val EXECUTE_TEST = true override val EXPORT_PRUNED_PROGRAMS: Boolean = false - val ignores: Seq[String] = Seq() + val ignores: Seq[String] = Seq("iterativeTreeDelete") analysisCommandLineArguments = analysisCommandLineArguments ++ Seq("--executeDependencyAnalysisTests") val testDirectories: Seq[String] = Seq( "dependencyAnalysisTests/all", From 92cf5db207a93c54974efc56d2084bcbf05b000d Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 26 May 2026 15:57:24 +0200 Subject: [PATCH 462/474] merge silver/master --- silver | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/silver b/silver index 0f67b51b6..7c8cbe19f 160000 --- a/silver +++ b/silver @@ -1 +1 @@ -Subproject commit 0f67b51b6c59f69d026e0229c0911c84b3ee5dac +Subproject commit 7c8cbe19f8e6287839f212e20a634523e446f84e From 1b0c7918b2e841827b1ef8de008013de426e77fe Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 26 May 2026 16:01:00 +0200 Subject: [PATCH 463/474] merge silver/master --- silver | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/silver b/silver index 7c8cbe19f..e7203458c 160000 --- a/silver +++ b/silver @@ -1 +1 @@ -Subproject commit 7c8cbe19f8e6287839f212e20a634523e446f84e +Subproject commit e7203458cb63f85730c286ff0151016d057f8c2d From 1ccf5d7fe48a133765930d6b7f77ba20aadc8b96 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Wed, 17 Jun 2026 17:10:51 +0200 Subject: [PATCH 464/474] code review --- src/main/scala/decider/Decider.scala | 28 ++++++++-------- src/main/scala/decider/ProverStdIO.scala | 13 ++++---- .../DependencyAnalysisInfos.scala | 22 ++++++------- .../DependencyAnalysisNode.scala | 2 +- .../AbstractDependencyAnalysisCliTool.scala | 2 +- .../scala/interfaces/decider/Prover.scala | 6 ++-- src/main/scala/rules/Brancher.scala | 6 ++-- src/main/scala/rules/ChunkSupporter.scala | 17 +++++----- src/main/scala/rules/Consumer.scala | 4 +-- src/main/scala/rules/Evaluator.scala | 8 ++--- src/main/scala/rules/Executor.scala | 12 +++---- src/main/scala/rules/HavocSupporter.scala | 4 +-- src/main/scala/rules/HeapSupporter.scala | 4 +-- src/main/scala/rules/MagicWandSupporter.scala | 4 +-- .../rules/MoreCompleteExhaleSupporter.scala | 32 +++++++++---------- .../scala/rules/PermissionSupporter.scala | 8 ++--- src/main/scala/rules/Producer.scala | 4 +-- .../scala/rules/QuantifiedChunkSupport.scala | 8 ++--- src/main/scala/rules/StateConsolidator.scala | 4 +-- .../scala/supporters/MethodSupporter.scala | 6 ++-- .../functions/FunctionVerificationUnit.scala | 2 +- 21 files changed, 98 insertions(+), 98 deletions(-) diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index e9bca8bc0..e94bae5f1 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -154,7 +154,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => override def initDependencyAnalyzer(member: Member, preambleNodes: Iterable[DependencyAnalysisNode]): Unit = { val isAnalysisEnabled = DependencyAnalyzer.extractEnableAnalysisFromInfo(member.info).getOrElse(Verifier.config.enableDependencyAnalysis()) - if(isAnalysisEnabled) { + if (isAnalysisEnabled) { dependencyAnalyzer = new DefaultDependencyAnalyzer(member) dependencyAnalyzer.addNodes(preambleNodes) }else{ @@ -288,7 +288,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } def setCurrentBranchCondition(t: Term, te: (ast.Exp, Option[ast.Exp]), analysisInfos: DependencyAnalysisInfos): Unit = { - if(isPathInfeasible) return + if (isPathInfeasible) return pathConditions.setCurrentBranchCondition(t, te) assume(t, Option.when(te._2.isDefined)(te._1), te._2, analysisInfos) @@ -315,12 +315,12 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } def registerDerivedChunk[CH <: GeneralChunk](sourceChunks: Set[Chunk], buildChunk: Term => CH, perm: Term, analysisInfo: AnalysisInfo, isExhale: Boolean, createLabel: Boolean=true): CH = { - if(!isDependencyAnalysisEnabled) + if (!isDependencyAnalysisEnabled) return buildChunk(perm) val labelNodeOpt = getOrCreateAnalysisLabelNode() - if(isExhale) + if (isExhale) dependencyAnalyzer.registerExhaleChunk(sourceChunks, buildChunk, perm, labelNodeOpt, analysisInfo) else { dependencyAnalyzer.registerInhaleChunk(sourceChunks, buildChunk, perm, labelNodeOpt, analysisInfo) @@ -328,7 +328,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } private def getOrCreateAnalysisLabelNode(sourceChunks: Iterable[Chunk] = Set.empty, sourceTerms: Iterable[Term] = Set.empty): Option[LabelNode] = { - if(!isDependencyAnalysisEnabled) + if (!isDependencyAnalysisEnabled) return None val (label, _) = fresh(ast.LocalVar(DependencyAnalyzer.analysisLabelName, ast.Bool)()) @@ -339,7 +339,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => } def wrapWithDependencyAnalysisLabel(term: Term, sourceChunks: Iterable[Chunk] = Set.empty, sourceTerms: Iterable[Term] = Set.empty): Term = { - if(!isDependencyAnalysisEnabled || term.equals(True) || sourceChunks.size + sourceTerms.size == 0) + if (!isDependencyAnalysisEnabled || term.equals(True) || sourceChunks.size + sourceTerms.size == 0) return term val labelNode = getOrCreateAnalysisLabelNode(sourceChunks, sourceTerms) @@ -404,7 +404,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => if (enforceAssumption) assumptions else assumptions filterNot isKnownToBeTrue - if(filteredTerms.isEmpty) return + if (filteredTerms.isEmpty) return val assumptionsWithLabels = addAssumptionLabels(filteredTerms, analysisInfos) @@ -427,7 +427,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => if (enforceAssumption) terms else terms filterNot isKnownToBeTrue - if(filteredTerms.isEmpty) return + if (filteredTerms.isEmpty) return if (debugMode) { addDebugExp(debugExp.get.withTerm(And(filteredTerms))) @@ -479,7 +479,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => val checkNode = dependencyAnalyzer.createAssertOrCheckNode(False, analysisInfos, !isAssert) val label = DependencyAnalyzer.createAssertionLabel(checkNode.map(_.id)) - if(isPathInfeasible) { + if (isPathInfeasible) { checkNode foreach dependencyAnalyzer.addAssertionNode dependencyAnalyzer.addDependency(pcs.getCurrentInfeasibilityNode, checkNode.map(_.id)) return true @@ -488,7 +488,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => val timeout = if (isAssert) Verifier.config.assertTimeout.toOption else Verifier.config.checkTimeout.toOption val result = prover.check(timeout, label) == Unsat - if(result){ + if (result) { checkNode foreach dependencyAnalyzer.addAssertionNode dependencyAnalyzer.processUnsatCoreAndAddDependencies(prover.getLastUnsatCore, label) val infeasibleNodeId = dependencyAnalyzer.addInfeasibilityNode(!isAssert, analysisInfos) @@ -496,7 +496,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => // assumeWithoutSmokeChecks(InsertionOrderedSet((False, DependencyAnalyzer.createAssumptionLabel(infeasibleNodeId)))) dependencyAnalyzer.addDependency(checkNode.map(_.id), infeasibleNodeId) pcs.setCurrentInfeasibilityNode(infeasibleNodeId) - }else if(isAssert){ + } else if (isAssert) { checkNode foreach (node => dependencyAnalyzer.addAssertionNode(node.getAssertFailedNode)) } result @@ -504,7 +504,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => override def handleFailedAssertion(failedAssertion: Term, analysisInfos: DependencyAnalysisInfos, assumeFailedAssertion: Boolean): Unit = { dependencyAnalyzer.addAssertionFailedNode(failedAssertion, analysisInfos) - if(assumeFailedAssertion){ + if (assumeFailedAssertion) { assume(failedAssertion, None, None, analysisInfos) failedAssertion match { case False => checkSmoke(analysisInfos) @@ -518,7 +518,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => info.getAllInfos[AdditionalDependencyNodeInfo].foreach { case AdditionalAssertionNode() => dependencyAnalyzer.createAssertOrCheckNode(True, newAnalysisInfos, isCheck = false).foreach(n => { dependencyAnalyzer.addAssertionNode(n) - if(isPathInfeasible) dependencyAnalyzer.addDependency(pcs.getCurrentInfeasibilityNode, Some(n.id)) + if (isPathInfeasible) dependencyAnalyzer.addDependency(pcs.getCurrentInfeasibilityNode, Some(n.id)) }) case AdditionalAssumptionNode() => dependencyAnalyzer.addAssumption(True, newAnalysisInfos) } @@ -551,7 +551,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => val assertRecord = new DeciderAssertRecord(t, timeout) val sepIdentifier = symbExLog.openScope(assertRecord) - val asserted = if(isDependencyAnalysisEnabled) t.equals(True) else isKnownToBeTrue(t) + val asserted = if (isDependencyAnalysisEnabled) t.equals(True) else isKnownToBeTrue(t) val assertNode = if(!asserted) dependencyAnalyzer.createAssertOrCheckNode(t, analysisInfos, isCheck) else None diff --git a/src/main/scala/decider/ProverStdIO.scala b/src/main/scala/decider/ProverStdIO.scala index 6c580cf90..61f4ab863 100644 --- a/src/main/scala/decider/ProverStdIO.scala +++ b/src/main/scala/decider/ProverStdIO.scala @@ -8,7 +8,6 @@ package viper.silicon.decider import com.typesafe.scalalogging.LazyLogging import viper.silicon.common.config.Version -import viper.silicon.dependencyAnalysis.DependencyAnalyzer import viper.silicon.interfaces.decider._ import viper.silicon.reporting.{ExternalToolError, ProverInteractionFailed} import viper.silicon.state.IdentifierFactory @@ -239,7 +238,7 @@ abstract class ProverStdIO(uniqueId: String, // problems.foreach(p => quantificationLogger.println(s" $p")) // } // }) - val finalLabel = if(label.isEmpty) nextProverLabel() else label + val finalLabel = if (label.isEmpty) nextProverLabel() else label assume(termConverter.convert(term), finalLabel) } @@ -252,9 +251,9 @@ abstract class ProverStdIO(uniqueId: String, def assume(term: String, label: String): Unit = { // bookkeeper.assumptionCounter += 1 - if((Verifier.config.enableDependencyAnalysis() && label.nonEmpty) || Verifier.config.enableUnsatCores()){ + if ((Verifier.config.enableDependencyAnalysis() && label.nonEmpty) || Verifier.config.enableUnsatCores()) { writeLine("(assert (! " + term + " :named " + (if(label.nonEmpty) label else nextProverLabel()) + "))") - }else{ + }else { writeLine("(assert " + term + ")") } @@ -282,7 +281,7 @@ abstract class ProverStdIO(uniqueId: String, push() setTimeout(timeout) - if((Verifier.config.enableDependencyAnalysis() && label.nonEmpty) || Verifier.config.enableUnsatCores()){ + if ((Verifier.config.enableDependencyAnalysis() && label.nonEmpty) || Verifier.config.enableUnsatCores()){ writeLine("(assert (! (not " + goal + ") :named " + (if(label.nonEmpty) label else nextProverLabel()) + "))") }else{ writeLine("(assert (not " + goal + "))") @@ -298,7 +297,7 @@ abstract class ProverStdIO(uniqueId: String, if (!result) { retrieveAndSaveModel() retrieveReasonUnknown() - }else if(Verifier.config.enableDependencyAnalysis()){ + }else if (Verifier.config.enableDependencyAnalysis()) { lastUnsatCore_ = extractUnsatCore() } @@ -392,7 +391,7 @@ abstract class ProverStdIO(uniqueId: String, case "unknown" => Unknown } - if(result == Unsat && Verifier.config.enableDependencyAnalysis()) + if (result == Unsat && Verifier.config.enableDependencyAnalysis()) lastUnsatCore_ = extractUnsatCore() result diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisInfos.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisInfos.scala index 2cee072d5..ff3fcc449 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisInfos.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisInfos.scala @@ -15,7 +15,7 @@ case class DependencyAnalysisInfos(sourceInfos: List[AnalysisSourceInfo], depend private def isAnalysisEnabled = Verifier.config.enableDependencyAnalysis() && analysisEnabled def addInfo(info: ast.Info, node: ast.Node): DependencyAnalysisInfos = { - if(!isAnalysisEnabled) return this + if (!isAnalysisEnabled) return this val newSourceInfos = sourceInfos ++ info.getUniqueInfo[AnalysisSourceInfo].toList val newDependencyInfos = dependencyTypes ++ info.getUniqueInfo[DependencyTypeInfo].toList @@ -25,7 +25,7 @@ case class DependencyAnalysisInfos(sourceInfos: List[AnalysisSourceInfo], depend } def addInfo(info: ast.Info): DependencyAnalysisInfos = { - if(!isAnalysisEnabled) return this + if (!isAnalysisEnabled) return this val newSourceInfos = sourceInfos ++ info.getUniqueInfo[AnalysisSourceInfo].toList val newDependencyInfos = dependencyTypes ++ info.getUniqueInfo[DependencyTypeInfo].toList @@ -35,18 +35,18 @@ case class DependencyAnalysisInfos(sourceInfos: List[AnalysisSourceInfo], depend } def addInfo(infoString: String, pos: ast.Position, dependencyType: DependencyType): DependencyAnalysisInfos = { - if(!isAnalysisEnabled) return this + if (!isAnalysisEnabled) return this this.copy(sourceInfos = sourceInfos ++ List(StringAnalysisSourceInfo(infoString, pos)), dependencyTypes = dependencyTypes ++ List(DependencyTypeInfo(dependencyType))) } def withDependencyType(dependencyType: DependencyType): DependencyAnalysisInfos = { - if(!isAnalysisEnabled) return this + if (!isAnalysisEnabled) return this this.copy(dependencyTypes = DependencyTypeInfo(dependencyType) +: dependencyTypes) } def withSource(source: AnalysisSourceInfo): DependencyAnalysisInfos = { - if(!isAnalysisEnabled) return this + if (!isAnalysisEnabled) return this this.copy(sourceInfos = source +: sourceInfos) } @@ -62,25 +62,25 @@ case class DependencyAnalysisInfos(sourceInfos: List[AnalysisSourceInfo], depend private def getDebugInfo: String = { val sourceInfo = sourceInfos.headOption.map("source info: " + _.toString + " ").getOrElse("") - val nodeInfo = if(nodes.nonEmpty) "nodes: " + nodes.map(getNodeInfo).mkString(", ") else "" + val nodeInfo = if (nodes.nonEmpty) "nodes: " + nodes.map(getNodeInfo).mkString(", ") else "" s"$sourceInfo$nodeInfo" } def getSourceInfo: AnalysisSourceInfo = { - if(!isAnalysisEnabled) return StringAnalysisSourceInfo("Unknown", NoPosition) + if (!isAnalysisEnabled) return StringAnalysisSourceInfo("Unknown", NoPosition) val sourceInfoOpt = sourceInfos.headOption - if(sourceInfoOpt.isDefined){ + if (sourceInfoOpt.isDefined) { sourceInfoOpt.get - }else{ + } else { SiliconRunner.logger.warn(s"WARN: Missing source info for $getDebugInfo") nodes.headOption.map(AnalysisSourceInfo.createAnalysisSourceInfo).getOrElse(StringAnalysisSourceInfo("Unknown", NoPosition)) } } def getDependencyType: DependencyType = { - if(!isAnalysisEnabled) return DependencyType.make(AssumptionType.Unknown) + if (!isAnalysisEnabled) return DependencyType.make(AssumptionType.Unknown) val dependencyTypeOpt = dependencyTypes.headOption.map(_.dependencyType) - if(dependencyTypeOpt.isDefined) { + if (dependencyTypeOpt.isDefined) { dependencyTypeOpt.get }else { SiliconRunner.logger.warn(s"WARN: Missing dependency type for $getDebugInfo") diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala index d471da068..8e09ac16a 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisNode.scala @@ -13,7 +13,7 @@ trait DependencyAnalysisNode { * The unique node id, which is also given to the SMT solver such that unsat cores can be mapped back to dependency nodes. */ val _id: Option[Int] - val id: Int = if(_id.isEmpty) DependencyGraphHelper.nextId() else _id.get + val id: Int = if (_id.isEmpty) DependencyGraphHelper.nextId() else _id.get /** diff --git a/src/main/scala/dependencyAnalysis/cliTool/AbstractDependencyAnalysisCliTool.scala b/src/main/scala/dependencyAnalysis/cliTool/AbstractDependencyAnalysisCliTool.scala index 6dd1afc83..888b36ebd 100644 --- a/src/main/scala/dependencyAnalysis/cliTool/AbstractDependencyAnalysisCliTool.scala +++ b/src/main/scala/dependencyAnalysis/cliTool/AbstractDependencyAnalysisCliTool.scala @@ -13,7 +13,7 @@ trait AbstractDependencyAnalysisCliTool { protected def getQueriedNodesFromInput(inputs: Set[String]): Set[DependencyAnalysisNode] = { inputs flatMap (input => { val parts = input.split("@") - if(parts.size == 2) + if (parts.size == 2) parts(1).toIntOption.map(interpreter.getNodesByPosition(parts(0), _)).getOrElse(Set.empty) else if(parts.size == 1){ parts(0).toIntOption map interpreter.getNodesByLine getOrElse Set.empty diff --git a/src/main/scala/interfaces/decider/Prover.scala b/src/main/scala/interfaces/decider/Prover.scala index d4e513afe..b84d2d617 100644 --- a/src/main/scala/interfaces/decider/Prover.scala +++ b/src/main/scala/interfaces/decider/Prover.scala @@ -27,7 +27,7 @@ trait ProverLike { protected val debugMode = Verifier.config.enableDebugging() var preambleAssumptions: Seq[DebugAxiom] = Seq() protected var preambleDependencyAnalyzer: DependencyAnalyzer = - if(Verifier.config.enableDependencyAnalysis()) new DefaultDependencyAnalyzer(ast.Method("none", Seq(), Seq(), Seq(), Seq(), None)()) + if (Verifier.config.enableDependencyAnalysis()) new DefaultDependencyAnalyzer(ast.Method("none", Seq(), Seq(), Seq(), Seq(), None)()) else new NoDependencyAnalyzer() def emit(content: String): Unit def emit(contents: Iterable[String]): Unit = { contents foreach emit } @@ -42,10 +42,10 @@ trait ProverLike { if (debugMode) preambleAssumptions :+= new DebugAxiom(description, axioms.map(_._1)) - if(Verifier.config.enableDependencyAnalysis()){ + if (Verifier.config.enableDependencyAnalysis()) { axioms.foreach(axiom => { val analysisInfos = axiom._2 - if(analysisInfos.analysisEnabled){ + if (analysisInfos.analysisEnabled) { val id = preambleDependencyAnalyzer.addAxiom(axiom._1, analysisInfos) assume(axiom._1, DependencyAnalyzer.createAxiomLabel(id)) }else { diff --git a/src/main/scala/rules/Brancher.scala b/src/main/scala/rules/Brancher.scala index c0f424137..50a0ca747 100644 --- a/src/main/scala/rules/Brancher.scala +++ b/src/main/scala/rules/Brancher.scala @@ -45,7 +45,7 @@ object brancher extends BranchingRules { fElse: (State, Verifier) => VerificationResult) : VerificationResult = { - if(v.decider.isPathInfeasible){ + if (v.decider.isPathInfeasible) { val analysisInfos1 = v.decider.handleAndGetUpdatedAnalysisInfos(analysisInfos, conditionExp._1.info, conditionExp._1) v.decider.dependencyAnalyzer.addAssumption(condition, analysisInfos1) return fThen(s, v).combine(fElse(s, v)) @@ -156,7 +156,7 @@ object brancher extends BranchingRules { executionFlowController.locally(s, v0)((s1, v1) => { v1.decider.prover.comment(s"[else-branch: $cnt | $negatedCondition]") v1.decider.setCurrentBranchCondition(negatedCondition, (negatedConditionExp, negatedConditionExpNew), analysisInfos1) - if(v.decider.isDependencyAnalysisEnabled && !executeElseBranch) v.decider.checkSmoke(analysisInfos1.withDependencyType(DependencyType.Internal).withMergeInfo(NoDependencyAnalysisMerge())) + if (v.decider.isDependencyAnalysisEnabled && !executeElseBranch) v.decider.checkSmoke(analysisInfos1.withDependencyType(DependencyType.Internal).withMergeInfo(NoDependencyAnalysisMerge())) var functionsOfElseBranchdDeciderBefore: Set[FunctionDecl] = null var nMacrosOfElseBranchDeciderBefore: Int = 0 @@ -207,7 +207,7 @@ object brancher extends BranchingRules { executionFlowController.locally(s, v)((s1, v1) => { v1.decider.prover.comment(s"[then-branch: $cnt | $condition]") v1.decider.setCurrentBranchCondition(condition, conditionExp, analysisInfos1) - if(v.decider.isDependencyAnalysisEnabled && !executeThenBranch) v.decider.checkSmoke(analysisInfos1.withDependencyType(DependencyType.Internal).withMergeInfo(NoDependencyAnalysisMerge())) + if (v.decider.isDependencyAnalysisEnabled && !executeThenBranch) v.decider.checkSmoke(analysisInfos1.withDependencyType(DependencyType.Internal).withMergeInfo(NoDependencyAnalysisMerge())) fThen(v1.stateConsolidator(s1).consolidateOptionally(s1, v1), v1) }) diff --git a/src/main/scala/rules/ChunkSupporter.scala b/src/main/scala/rules/ChunkSupporter.scala index 2307774c6..5c2797f37 100644 --- a/src/main/scala/rules/ChunkSupporter.scala +++ b/src/main/scala/rules/ChunkSupporter.scala @@ -8,6 +8,7 @@ package viper.silicon.rules import viper.silicon.debugger.DebugExp import viper.silicon.dependencyAnalysis.DependencyAnalysisInfos +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfos.DefaultDependencyAnalysisInfos import viper.silicon.interfaces.state._ import viper.silicon.interfaces.{Success, VerificationResult} import viper.silicon.resources.{NonQuantifiedPropertyInterpreter, Resources} @@ -84,7 +85,7 @@ object chunkSupporter extends ChunkSupportRules { analysisInfos: DependencyAnalysisInfos) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult) : VerificationResult = { - if(v.decider.isPathInfeasible){ + if (v.decider.isPathInfeasible) { v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfos) return Q(s, h, Option.when(returnSnap)(Unit), v) } @@ -151,14 +152,14 @@ object chunkSupporter extends ChunkSupportRules { } QS(s2.copy(h = s.h), h2, snap, v1) case (_, s2, h2, _) if v1.decider.checkSmoke(analysisInfos, isAssert = true) => - if(Verifier.config.disableInfeasibilityChecks()) + if (Verifier.config.disableInfeasibilityChecks()) QS(s2.copy(h = s.h), h2, None, v1) else Success() // TODO: Mark branch as dead? case _ => val failure = createFailure(ve, v1, s1, "consuming chunk", true) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertion(False, analysisInfos, v1.reportFurtherErrors()) - if(s1.retryLevel == 0 && v1.reportFurtherErrors() && Verifier.config.disableInfeasibilityChecks()){ + if (s1.retryLevel == 0) v1.decider.handleFailedAssertion(False, analysisInfos, v1.reportFurtherErrors()) + if (s1.retryLevel == 0 && v1.reportFurtherErrors() && Verifier.config.disableInfeasibilityChecks()){ failure combine QS(s1.copy(h = s.h), s1.h, None, v1) }else{ failure @@ -235,7 +236,7 @@ object chunkSupporter extends ChunkSupportRules { def produce(s: State, h: Heap, ch: NonQuantifiedChunk, v: Verifier) (Q: (State, Heap, Verifier) => VerificationResult) : VerificationResult = { - val analysisInfos = DependencyAnalysisInfos.DefaultDependencyAnalysisInfos.withSource(StringAnalysisSourceInfo("produce", ast.NoPosition)).withDependencyType(DependencyType.Internal) + val analysisInfos = DefaultDependencyAnalysisInfos.withSource(StringAnalysisSourceInfo("produce", ast.NoPosition)).withDependencyType(DependencyType.Internal) // Try to merge the chunk into the heap by finding an alias. // In any case, property assumptions are added after the merge step. val (fr1, h1) = v.stateConsolidator(s).merge(s.functionRecorder, s, h, ch, v, analysisInfos) @@ -252,7 +253,7 @@ object chunkSupporter extends ChunkSupportRules { analysisInfos: DependencyAnalysisInfos) (Q: (State, Heap, Term, Verifier) => VerificationResult) : VerificationResult = { - if(v.decider.isPathInfeasible){ + if (v.decider.isPathInfeasible) { v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfos) return Q(s, h, Unit, v) } @@ -291,8 +292,8 @@ object chunkSupporter extends ChunkSupportRules { } case _ => val failure = createFailure(ve, v, s, "looking up chunk", true) - if(s.retryLevel == 0) v.decider.handleFailedAssertion(False, analysisInfos, v.reportFurtherErrors()) - if(s.retryLevel == 0 && v.reportFurtherErrors() && Verifier.config.disableInfeasibilityChecks()){ + if (s.retryLevel == 0) v.decider.handleFailedAssertion(False, analysisInfos, v.reportFurtherErrors()) + if (s.retryLevel == 0 && v.reportFurtherErrors() && Verifier.config.disableInfeasibilityChecks()){ val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) failure combine Q(s, snap, v) }else{ diff --git a/src/main/scala/rules/Consumer.scala b/src/main/scala/rules/Consumer.scala index e5ccba532..c00dd114f 100644 --- a/src/main/scala/rules/Consumer.scala +++ b/src/main/scala/rules/Consumer.scala @@ -199,7 +199,7 @@ object consumer extends ConsumptionRules { * time permissions have been consumed. */ - if(v.decider.isPathInfeasible){ + if (v.decider.isPathInfeasible) { v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfos) return Q(s, h, Option.when(returnSnap)(Unit), v) } @@ -435,7 +435,7 @@ object consumer extends ConsumptionRules { QS(s3, v2) case false => val failure = createFailure(pve dueTo AssertionFalse(e), v2, s3, termToAssert, eNew) - if(s3.retryLevel == 0) v2.decider.handleFailedAssertion(t, analysisInfos, assumeFailedAssertion=false) + if (s3.retryLevel == 0) v2.decider.handleFailedAssertion(t, analysisInfos, assumeFailedAssertion=false) if (s3.retryLevel == 0 && v2.reportFurtherErrors()){ v2.decider.assume(t, Option.when(withExp)(e), eNew, analysisInfos.withDependencyType(DependencyType.Explicit)) failure combine QS(s3, v2) diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 229fbe10e..1c95fae68 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -240,9 +240,9 @@ object evaluator extends EvaluationRules { s.oldHeaps.get(heapName) match { case None => val failure = createFailure(pve dueTo LabelledStateNotReached(ast.LabelledOld(e0, heapName)(old.pos, old.info, old.errT)), v, s, "labelled state reached") - if(s.retryLevel == 0) v.decider.handleFailedAssertion(False, analysisInfos, v.reportFurtherErrors()) + if (s.retryLevel == 0) v.decider.handleFailedAssertion(False, analysisInfos, v.reportFurtherErrors()) val freshVar = v.decider.fresh(v.symbolConverter.toSort(old.typ), None) - if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, freshVar, None, v) else failure + if (s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, freshVar, None, v) else failure case _ => evalInOldState(s, heapName, e0, pve, v, analysisInfos)((s1, t0, _, v1) => Q(s1, t0, Some(old), v1)) @@ -252,9 +252,9 @@ object evaluator extends EvaluationRules { s.oldHeaps.get(lbl) match { case None => val failure = createFailure(pve dueTo LabelledStateNotReached(old), v, s, "labelled state reached") - if(s.retryLevel == 0) v.decider.handleFailedAssertion(False, analysisInfos, v.reportFurtherErrors()) + if (s.retryLevel == 0) v.decider.handleFailedAssertion(False, analysisInfos, v.reportFurtherErrors()) val freshVar = v.decider.fresh(v.symbolConverter.toSort(old.typ), None) - if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, freshVar, None, v) else failure + if (s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, freshVar, None, v) else failure case _ => evalInOldState(s, lbl, e0, pve, v, analysisInfos)((s1, t0, e0New, v1) => Q(s1, t0, e0New.map(ast.LabelledOld(_, lbl)(old.pos, old.info, old.errT)), v1))} diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 7673ef2bd..940e6b04d 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -292,7 +292,7 @@ object executor extends ExecutionRules { intermediateResult combine executionFlowController.locally(s2, v1)((s3, v2) => { v2.decider.declareAndRecordAsFreshFunctions(ff1 -- v2.decider.freshFunctions) /* [BRANCH-PARALLELISATION] */ v2.decider.declareAndRecordAsFreshMacros(fm1.filter(!v2.decider.freshMacros.contains(_))) /* [BRANCH-PARALLELISATION] */ - if(v2.decider.pcs.getCurrentInfeasibilityNode.isEmpty) v2.decider.pcs.setCurrentInfeasibilityNode(pcs.infeasibilityNodeId) + if (v2.decider.pcs.getCurrentInfeasibilityNode.isEmpty) v2.decider.pcs.setCurrentInfeasibilityNode(pcs.infeasibilityNodeId) v2.decider.assume(pcs.assumptions map (t => v.decider.wrapWithDependencyAnalysisLabel(t, Set.empty, Set(t))), Some(pcs.assumptionExps), "Loop invariant", enforceAssumption=false, analysisInfosLoopInternal) v2.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) if (!Verifier.config.disableInfeasibilityChecks() && v2.decider.checkSmoke(analysisInfosLoopInternal)) @@ -443,13 +443,13 @@ object executor extends ExecutionRules { case _ => produce(s, freshSnap, a, InhaleFailed(inhale), v, analysisInfos)((s1, v1) => { v1.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterInhale) - if(v1.decider.isDependencyAnalysisEnabled && a.isInstanceOf[ast.FalseLit]) v1.decider.checkSmoke(analysisInfos) + if (v1.decider.isDependencyAnalysisEnabled && a.isInstanceOf[ast.FalseLit]) v1.decider.checkSmoke(analysisInfos) Q(s1, v1)}) } case exhale @ ast.Exhale(a) => val pve = ExhaleFailed(exhale) - val analysisInfos1 = if(a.topLevelConjuncts.size > 1) analysisInfos.copy(sourceInfos = List.empty) else analysisInfos // needed to ensure that each top-level conjunct gets a dedicated assertion node + val analysisInfos1 = if (a.topLevelConjuncts.size > 1) analysisInfos.copy(sourceInfos = List.empty) else analysisInfos // needed to ensure that each top-level conjunct gets a dedicated assertion node consume(s, a, false, pve, v, analysisInfos1)((s1, _, v1) => Q(s1, v1)) @@ -460,8 +460,8 @@ object executor extends ExecutionRules { QS(s1.copy(h = s.h), v1) else { val failure = createFailure(AssertFailed(assert) dueTo AssertionFalse(a), v1, s1, False, true, Option.when(withExp)(a)) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertion(False, analysisInfos, v1.reportFurtherErrors()) - if(s1.retryLevel == 0 && v1.reportFurtherErrors()) failure combine QS(s1, v1) else failure + if (s1.retryLevel == 0) v1.decider.handleFailedAssertion(False, analysisInfos, v1.reportFurtherErrors()) + if (s1.retryLevel == 0 && v1.reportFurtherErrors()) failure combine QS(s1, v1) else failure } })((s2, v2) => if (Verifier.config.disableInfeasibilityChecks()) @@ -471,7 +471,7 @@ object executor extends ExecutionRules { ) case assert @ ast.Assert(a) if Verifier.config.disableSubsumption() => - val analysisInfos1 = if(a.topLevelConjuncts.size > 1) analysisInfos.copy(sourceInfos = List.empty) else analysisInfos // needed to ensure that each top-level conjunct gets a dedicated assertion node + val analysisInfos1 = if (a.topLevelConjuncts.size > 1) analysisInfos.copy(sourceInfos = List.empty) else analysisInfos // needed to ensure that each top-level conjunct gets a dedicated assertion node val r = consume(s, a, false, AssertFailed(assert), v, analysisInfos1)((_, _, _) => Success()) diff --git a/src/main/scala/rules/HavocSupporter.scala b/src/main/scala/rules/HavocSupporter.scala index 0cbcc164e..e7612df7a 100644 --- a/src/main/scala/rules/HavocSupporter.scala +++ b/src/main/scala/rules/HavocSupporter.scala @@ -127,8 +127,8 @@ object havocSupporter extends SymbolicExecutionRules { v.decider.assert(receiverInjectivityCheck, analysisInfos) { case false => val failure = createFailure(pve dueTo notInjectiveReason, v, s1, receiverInjectivityCheck, "QP receiver injective") - if(s1.retryLevel == 0) v.decider.handleFailedAssertion(receiverInjectivityCheck, analysisInfos, v.reportFurtherErrors()) - if(s1.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s1, v1) else failure + if (s1.retryLevel == 0) v.decider.handleFailedAssertion(receiverInjectivityCheck, analysisInfos, v.reportFurtherErrors()) + if (s1.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s1, v1) else failure case true => // Generate the inverse axioms val (inverseFunctions, imagesOfCodomain) = quantifiedChunkSupporter.getFreshInverseFunctions( diff --git a/src/main/scala/rules/HeapSupporter.scala b/src/main/scala/rules/HeapSupporter.scala index dd6573f53..d79d10c23 100644 --- a/src/main/scala/rules/HeapSupporter.scala +++ b/src/main/scala/rules/HeapSupporter.scala @@ -190,7 +190,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { analysisInfos: DependencyAnalysisInfos) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { - if(v.decider.isPathInfeasible){ + if (v.decider.isPathInfeasible) { v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfos) v.decider.dependencyAnalyzer.addAssumption(False, analysisInfos) return Q(s, v) @@ -240,7 +240,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { Q(s5, v) case (Incomplete(_, _), s3, _) => val failure = createFailure(ve, v, s3, "sufficient permission") - if(s3.retryLevel == 0) v.decider.handleFailedAssertion(False, analysisInfos, v.reportFurtherErrors()) + if (s3.retryLevel == 0) v.decider.handleFailedAssertion(False, analysisInfos, v.reportFurtherErrors()) if(s3.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s3, v) else failure } } else { diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 51297a127..a627f8785 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -214,8 +214,8 @@ object magicWandSupporter extends SymbolicExecutionRules { assert(consumedChunks.length == hs.length) Q(s1, heaps.reverse, consumedChunks.reverse, v) case Incomplete(_, _) => - if(s1.retryLevel == 0) v.decider.handleFailedAssertion(False, analysisInfos, v.reportFurtherErrors()) - if(s1.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s1, heaps.reverse, consumedChunks.reverse, v) else failure + if (s1.retryLevel == 0) v.decider.handleFailedAssertion(False, analysisInfos, v.reportFurtherErrors()) + if (s1.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s1, heaps.reverse, consumedChunks.reverse, v) else failure } } diff --git a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala index 1bf422ad1..8b0f04ba8 100644 --- a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala +++ b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala @@ -220,8 +220,8 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { } } else { val failure = createFailure(ve, v, s, False, "branch is dead") - if(s.retryLevel == 0) v.decider.handleFailedAssertion(False, analysisInfos, v.reportFurtherErrors()) - if(s.retryLevel == 0 && v.reportFurtherErrors() && Verifier.config.disableInfeasibilityChecks()){ + if (s.retryLevel == 0) v.decider.handleFailedAssertion(False, analysisInfos, v.reportFurtherErrors()) + if (s.retryLevel == 0 && v.reportFurtherErrors() && Verifier.config.disableInfeasibilityChecks()){ val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) failure combine Q(s, snap, v) }else{ @@ -235,8 +235,8 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { Q(s1, snap, v1) case false => val failure = createFailure(ve, v, s1, IsPositive(permSum), permSumExp.map(IsPositive(_)())) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertion(IsPositive(permSum), analysisInfos, v1.reportFurtherErrors()) - if(s1.retryLevel == 0 && v1.reportFurtherErrors()) failure combine Q(s1, snap, v1) else failure + if (s1.retryLevel == 0) v1.decider.handleFailedAssertion(IsPositive(permSum), analysisInfos, v1.reportFurtherErrors()) + if (s1.retryLevel == 0 && v1.reportFurtherErrors()) failure combine Q(s1, snap, v1) else failure }) } } @@ -284,8 +284,8 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { Q(s1, h, Some(snap), v1) case false => val failure = createFailure(ve, v, s1, IsPositive(permSum), permSumExp.map(IsPositive(_)())) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertion(Implies(IsPositive(perm), IsPositive(permSum)), analysisInfos, v1.reportFurtherErrors()) - if(s1.retryLevel == 0 && v1.reportFurtherErrors()) failure combine Q(s1, h, Some(snap), v1) else failure + if (s1.retryLevel == 0) v1.decider.handleFailedAssertion(Implies(IsPositive(perm), IsPositive(permSum)), analysisInfos, v1.reportFurtherErrors()) + if (s1.retryLevel == 0 && v1.reportFurtherErrors()) failure combine Q(s1, h, Some(snap), v1) else failure }) } else { val (s1, permSum, permSumExp) = permSummariseOnly(s, relevantChunks, resource, args, argsExp) @@ -294,8 +294,8 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { Q(s1, h, None, v) case false => val failure = createFailure(ve, v, s1, IsPositive(permSum), permSumExp.map(IsPositive(_)())) - if(s1.retryLevel == 0) v.decider.handleFailedAssertion(Implies(IsPositive(perm), IsPositive(permSum)), analysisInfos, v.reportFurtherErrors()) - if(s1.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s1, h, None, v) else failure + if (s1.retryLevel == 0) v.decider.handleFailedAssertion(Implies(IsPositive(perm), IsPositive(permSum)), analysisInfos, v.reportFurtherErrors()) + if (s1.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s1, h, None, v) else failure } } } @@ -328,8 +328,8 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { case true => Q(s, h, None, v) case false => val failure = createFailure(ve, v, s, perms === NoPerm, permsExp.map(pe => ast.EqCmp(pe, ast.NoPerm()())(pe.pos, pe.info, pe.errT))) - if(s.retryLevel == 0) v.decider.handleFailedAssertion(perms === NoPerm, analysisInfos, v.reportFurtherErrors()) - if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, h, None, v) else failure + if (s.retryLevel == 0) v.decider.handleFailedAssertion(perms === NoPerm, analysisInfos, v.reportFurtherErrors()) + if (s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, h, None, v) else failure } } else { if (!terms.utils.consumeExactRead(perms, s.constrainableARPs)) { @@ -431,8 +431,8 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { Q(s1, newHeap, condSnap, v1) case false => val failure = createFailure(ve, v1, s1, pNeeded === NoPerm, pNeededExp.map(pn => ast.EqCmp(pn, ast.NoPerm()())(pn.pos, pn.info, pn.errT))) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertion(pNeeded === NoPerm, analysisInfos, v1.reportFurtherErrors()) - if(s1.retryLevel == 0 && v1.reportFurtherErrors()) failure combine Q(s1, newHeap, condSnap, v1) else failure + if (s1.retryLevel == 0) v1.decider.handleFailedAssertion(pNeeded === NoPerm, analysisInfos, v1.reportFurtherErrors()) + if (s1.retryLevel == 0 && v1.reportFurtherErrors()) failure combine Q(s1, newHeap, condSnap, v1) else failure } } }) @@ -445,8 +445,8 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { Q(s0, newHeap, None, v) case false => val failure = createFailure(ve, v, s0, pNeeded === NoPerm, pNeededExp.map(pn => ast.EqCmp(pn, ast.NoPerm()())(pn.pos, pn.info, pn.errT))) - if(s0.retryLevel == 0) v.decider.handleFailedAssertion(pNeeded === NoPerm, analysisInfos, v.reportFurtherErrors()) - if(s0.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s0, newHeap, None, v) else failure + if (s0.retryLevel == 0) v.decider.handleFailedAssertion(pNeeded === NoPerm, analysisInfos, v.reportFurtherErrors()) + if (s0.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s0, newHeap, None, v) else failure } } } @@ -538,8 +538,8 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { case false => v.decider.finishDebugSubExp(s"consume permissions for ${resource.toString()}") val failure = createFailure(ve, v, s, totalPermTaken !== NoPerm, totalPermTakenExp.map(tpt => ast.NeCmp(tpt, ast.NoPerm()())())) - if(s.retryLevel == 0) v.decider.handleFailedAssertion(Implies(PermLess(NoPerm, perms), totalPermTaken !== NoPerm), analysisInfos, v.reportFurtherErrors()) - if(s.retryLevel == 0 && v.reportFurtherErrors()){ + if (s.retryLevel == 0) v.decider.handleFailedAssertion(Implies(PermLess(NoPerm, perms), totalPermTaken !== NoPerm), analysisInfos, v.reportFurtherErrors()) + if (s.retryLevel == 0 && v.reportFurtherErrors()){ val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) failure combine Q(s1, updatedChunks, if(returnSnap) Some(snap) else None, v) }else{ diff --git a/src/main/scala/rules/PermissionSupporter.scala b/src/main/scala/rules/PermissionSupporter.scala index 62e367b3a..34db39416 100644 --- a/src/main/scala/rules/PermissionSupporter.scala +++ b/src/main/scala/rules/PermissionSupporter.scala @@ -29,8 +29,8 @@ object permissionSupporter extends SymbolicExecutionRules { case false => val assertExp = ePermNew.map(ep => perms.IsNonNegative(ep)(ep.pos, ep.info, ep.errT)) val failure = createFailure(pve dueTo NegativePermission(ePerm), v, s, perms.IsNonNegative(tPerm), assertExp) - if(s.retryLevel == 0) v.decider.handleFailedAssertion(perms.IsNonNegative(tPerm), analysisInfos, v.reportFurtherErrors()) - if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, v) else failure + if (s.retryLevel == 0) v.decider.handleFailedAssertion(perms.IsNonNegative(tPerm), analysisInfos, v.reportFurtherErrors()) + if (s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, v) else failure } } } @@ -47,8 +47,8 @@ object permissionSupporter extends SymbolicExecutionRules { case true => Q(s, v) case false => val failure = createFailure(pve dueTo NonPositivePermission(ePerm), v, s, perms.IsPositive(tPerm), Option.when(withExp)(perms.IsPositive(ePerm)())) - if(s.retryLevel == 0) v.decider.handleFailedAssertion(perms.IsPositive(tPerm), analysisInfos, v.reportFurtherErrors()) - if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, v) else failure + if (s.retryLevel == 0) v.decider.handleFailedAssertion(perms.IsPositive(tPerm), analysisInfos, v.reportFurtherErrors()) + if (s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, v) else failure } } } diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index ca69370b5..f01160337 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -208,8 +208,8 @@ object producer extends ProductionRules { (Q: (State, Verifier) => VerificationResult) : VerificationResult = { - if(v.decider.isPathInfeasible){ - if(!Expressions.isKnownWellDefined(a, Some(s.program))){ + if (v.decider.isPathInfeasible) { + if (!Expressions.isKnownWellDefined(a, Some(s.program))) { v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfos) } v.decider.dependencyAnalyzer.addAssumption(True, analysisInfos) diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index b780f4363..6bf916437 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -1041,13 +1041,13 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { Q(s1, v) case false => val failure = createFailure(pve dueTo notInjectiveReason, v, s, receiverInjectivityCheck, "QP receiver is injective") - if(s.retryLevel == 0) v.decider.handleFailedAssertion(completeReceiverInjectivityCheck, analysisInfos, v.reportFurtherErrors()) - if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, v) else failure + if (s.retryLevel == 0) v.decider.handleFailedAssertion(completeReceiverInjectivityCheck, analysisInfos, v.reportFurtherErrors()) + if (s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, v) else failure } case false => val failure = createFailure(pve dueTo negativePermissionReason, v, s, nonNegImplication, nonNegImplicationExp) - if(s.retryLevel == 0) v.decider.handleFailedAssertion(nonNegTerm, analysisInfos, v.reportFurtherErrors()) - if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, v) else failure + if (s.retryLevel == 0) v.decider.handleFailedAssertion(nonNegTerm, analysisInfos, v.reportFurtherErrors()) + if (s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, v) else failure } } diff --git a/src/main/scala/rules/StateConsolidator.scala b/src/main/scala/rules/StateConsolidator.scala index e18b80875..d59d7cdc7 100644 --- a/src/main/scala/rules/StateConsolidator.scala +++ b/src/main/scala/rules/StateConsolidator.scala @@ -59,7 +59,7 @@ class MinimalStateConsolidator extends StateConsolidationRules { */ class DefaultStateConsolidator(protected val config: Config) extends StateConsolidationRules { def consolidate(s: State, v: Verifier): State = { - if(v.decider.isPathInfeasible) return s + if (v.decider.isPathInfeasible) return s val comLog = new CommentRecord("state consolidation", s, v.decider.pcs) val sepIdentifier = v.symbExLog.openScope(comLog) @@ -132,7 +132,7 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol } def merge(fr1: FunctionRecorder, s: State, h: Heap, newH: Heap, v: Verifier, analysisInfos: DependencyAnalysisInfos): (FunctionRecorder, Heap) = { - if(v.decider.isPathInfeasible) return (fr1, h) + if (v.decider.isPathInfeasible) return (fr1, h) val analysisInfos1 = analysisInfos.addInfo("merge", ast.NoPosition, DependencyType.Internal) diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index 3f7b12af5..32632abde 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -105,13 +105,13 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif && { executionFlowController.locally(s2a, v2)((s3, v3) => { val da = v3.decider.dependencyAnalyzer - if(method.body.isEmpty) v3.decider.removeDependencyAnalyzer() + if (method.body.isEmpty) v3.decider.removeDependencyAnalyzer() exec(s3, body, v3)((s4, v4) => { - if(method.body.isEmpty) v3.decider.dependencyAnalyzer = da + if (method.body.isEmpty) v3.decider.dependencyAnalyzer = da consumes(s4, posts, false, postViolated, v4, analysisInfosPostcondition)((_, _, _) => Success())})}) } )})}) - if(method.body.isEmpty){ + if (method.body.isEmpty) { v.decider.dependencyAnalyzer.addDependenciesForAbstractMembers(method.pres.flatMap(_.topLevelConjuncts), method.posts.flatMap(_.topLevelConjuncts), DependencyAnalysisInfos.DefaultDependencyAnalysisInfos) } diff --git a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala index f556f2906..de0fc1d37 100644 --- a/src/main/scala/supporters/functions/FunctionVerificationUnit.scala +++ b/src/main/scala/supporters/functions/FunctionVerificationUnit.scala @@ -314,7 +314,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver private def emitAndRecordFunctionAxioms(axiom: (Term, DependencyAnalysisInfos)*): Unit = { val cleanAxiom = - if(!Verifier.config.enableDependencyAnalysis()) axiom + if (!Verifier.config.enableDependencyAnalysis()) axiom else axiom.map(a => (a._1.transform{ case Var(name, _, _) if name.name.startsWith(DependencyAnalyzer.analysisLabelName) => True // replace dependency analysis labels by True to avoid errors }(), a._2)) From 3472259101c7885398e9d90a153fd359ad994318 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 19 Jun 2026 15:12:13 +0200 Subject: [PATCH 465/474] fix progress for failing assertions --- .../DependencyAnalysisProgressSupporter.scala | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisProgressSupporter.scala b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisProgressSupporter.scala index 3f3eef4f6..377939c38 100644 --- a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisProgressSupporter.scala +++ b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisProgressSupporter.scala @@ -93,19 +93,22 @@ class DependencyAnalysisProgressSupporter[T <: DependencyGraphState](interpreter /** * Computes the assertion quality given an assertion node and all of its dependencies. - * The assertion quality is defined as the fraction of non-assumption dependencies over all dependencies. - * The assertion quality is 0.0 if the assertion could not be proven to hold. + * It returns None if the assertion trivially verifies (it has no dependencies). Otherwise, it returns the assertion quality. + * If the assertion encountered a failure, i.e. it could not be proven to hold, then the assertion quality is 0.0. + * Otherwise, the assertion quality is defined as the fraction of non-assumption dependencies over all dependencies. */ - private def computeAssertionQuality(allDependencies: Set[CompactUserLevelDependencyAnalysisNode], assertion: AnalysisSourceInfo): Double = { + private def computeAssertionQuality(allDependencies: Set[CompactUserLevelDependencyAnalysisNode], assertion: AnalysisSourceInfo): Option[Double] = { val assertionNodes = sourceToAssertionNodesMap.getOrElse(assertion, Set.empty).filter(node => node.isInstanceOf[GeneralAssertionNode]) val failedAssertionNodes = assertionNodes.filter(node => node.asInstanceOf[GeneralAssertionNode].hasFailed || node.assumptionType.equals(AssumptionType.ExplicitPostcondition)) - // assertions with failures have quality = 0.0 + // assertions with failures have quality of 0.0 if(failedAssertionNodes.nonEmpty) - return 0.0 + return Some(0.0) + + if (allDependencies.isEmpty) return None // we filter out trivial assertions, e.g. assertions that do not have any dependencies val explicitDeps = allDependencies.filter(_.assumptionTypes.intersect(AssumptionType.explicitAssumptionTypes).nonEmpty).map(_.source) val numDepsTotal = allDependencies.size - (numDepsTotal - explicitDeps.size).toDouble / numDepsTotal.toDouble + Some((numDepsTotal - explicitDeps.size).toDouble / numDepsTotal.toDouble) } private def getAssertionsRelevantForProgress: Map[AnalysisSourceInfo, Set[DependencyAnalysisNode]] = { @@ -132,11 +135,8 @@ class DependencyAnalysisProgressSupporter[T <: DependencyGraphState](interpreter val specQuality = computeSpecQuality(allDependenciesPerAssertionNode.flatMap(_._1).toSet, enableDebugOutput) - // for assertion quality, we filter out assertions that do not have any dependencies - val depsOfNonTrivialAssertions = allDependenciesPerAssertionNode filter (_._1.nonEmpty) - val numNonTrivialAssertions = depsOfNonTrivialAssertions.size - - val assertionQualities = depsOfNonTrivialAssertions map (ass => (computeAssertionQuality(ass._1, ass._2), ass._2)) + val assertionQualities = allDependenciesPerAssertionNode map (ass => (computeAssertionQuality(ass._1, ass._2), ass._2)) collect { case (Some(q), s) => (q, s) } + val numNonTrivialAssertions = assertionQualities.size // compute Peter's proof quality val fullyVerifiedAssertions = assertionQualities.filter(_._1 == 1.0) @@ -148,8 +148,8 @@ class DependencyAnalysisProgressSupporter[T <: DependencyGraphState](interpreter if(enableDebugOutput) println( - s"fullyVerifiedAssertions:\n\t${fullyVerifiedAssertions.mkString("\n\t")}\n" + - s"assertionQualitiesSum:\n\t${assertionQualities.mkString("\n\t")}" + s"fullyVerifiedAssertions:\n\t${fullyVerifiedAssertions.sortBy(n => (n._2.getLineNumber, n._2.toString)).mkString("\n\t")}\n" + + s"assertionQualitiesSum:\n\t${assertionQualities.sortBy(n => (n._2.getLineNumber, n._2.toString)).mkString("\n\t")}" ) println( From cb8378b22961e4d7f78f7061b90b8804456e76f6 Mon Sep 17 00:00:00 2001 From: DovydasVad Date: Fri, 19 Jun 2026 16:31:29 +0200 Subject: [PATCH 466/474] Add dependency analysis test cases for guidance and verification progress --- .../guidance/assumptions-equal.vpr | 17 ++ .../guidance/assumptions.vpr | 13 + .../guidance/assumptions2.vpr | 18 ++ .../dependencyAnalysisTests/guidance/mix.vpr | 33 +++ .../guidance/uncovered-methods-equal.vpr | 114 +++++++++ .../guidance/uncovered-methods.vpr | 75 ++++++ .../guidance/uncovered-methods2.vpr | 34 +++ .../assertion-combinations.vpr | 16 ++ .../assume-false.vpr | 37 +++ .../verificationProgressTests/branch1.vpr | 17 ++ .../verificationProgressTests/branch2.vpr | 16 ++ .../verificationProgressTests/branch3.vpr | 16 ++ .../verificationProgressTests/branch4.vpr | 14 + .../verificationProgressTests/functions1.vpr | 19 ++ .../verificationProgressTests/functions2.vpr | 7 + .../implications.vpr | 14 + .../verificationProgressTests/inhale1.vpr | 14 + .../verificationProgressTests/inhale2.vpr | 16 ++ .../verificationProgressTests/loops.vpr | 20 ++ .../verificationProgressTests/method-add.vpr | 18 ++ .../verificationProgressTests/method-add2.vpr | 7 + .../method-divide.vpr | 19 ++ .../paper-branches-weaker.vpr | 24 ++ .../paper-branches.vpr | 24 ++ .../paper-gaussian.vpr | 21 ++ .../paper-no-obligations.vpr | 24 ++ .../permissions-quantified1.vpr | 14 + .../permissions-quantified2.vpr | 17 ++ .../permissions-wildcard.vpr | 16 ++ .../permissions1.vpr | 14 + .../permissions2.vpr | 25 ++ .../verificationProgressTests/predicates1.vpr | 17 ++ .../verificationProgressTests/predicates2.vpr | 20 ++ .../verificationProgressTests/predicates3.vpr | 18 ++ .../verificationProgressTests/predicates4.vpr | 17 ++ .../DependencyAnalysisTestFramework.scala | 241 +++++++++++++++++- src/test/scala/DependencyAnalysisTests.scala | 16 +- 37 files changed, 1056 insertions(+), 6 deletions(-) create mode 100644 src/test/resources/dependencyAnalysisTests/guidance/assumptions-equal.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/guidance/assumptions.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/guidance/assumptions2.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/guidance/mix.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/guidance/uncovered-methods-equal.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/guidance/uncovered-methods.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/guidance/uncovered-methods2.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgressTests/assertion-combinations.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgressTests/assume-false.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgressTests/branch1.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgressTests/branch2.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgressTests/branch3.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgressTests/branch4.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgressTests/functions1.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgressTests/functions2.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgressTests/implications.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgressTests/inhale1.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgressTests/inhale2.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgressTests/loops.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgressTests/method-add.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgressTests/method-add2.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgressTests/method-divide.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgressTests/paper-branches-weaker.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgressTests/paper-branches.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgressTests/paper-gaussian.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgressTests/paper-no-obligations.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgressTests/permissions-quantified1.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgressTests/permissions-quantified2.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgressTests/permissions-wildcard.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgressTests/permissions1.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgressTests/permissions2.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgressTests/predicates1.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgressTests/predicates2.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgressTests/predicates3.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgressTests/predicates4.vpr diff --git a/src/test/resources/dependencyAnalysisTests/guidance/assumptions-equal.vpr b/src/test/resources/dependencyAnalysisTests/guidance/assumptions-equal.vpr new file mode 100644 index 000000000..e7aa319b8 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/guidance/assumptions-equal.vpr @@ -0,0 +1,17 @@ +method client() +{ + var a1: Int, a2: Int, a3: Int, b1: Int, b2: Int, b3: Int, b4: Int + a1 := 10 + a2 := 20 + a3 := 30 + + assume @guidedAssumption("3")(b1 == 1) // least useful - is used besides three other facts in last assertion + assume @guidedAssumption("2")(b2 == 2) + assume @guidedAssumption("1")(b3 == 3) // most useful - used on its own in first assertion + assume @guidedAssumption("2")(b4 == 4) + + assert b3 == 3 + assert a1 + b2 == 12 + assert a1 + b4 == 14 + assert a1 + a2 + a3 + b1 == 61 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/guidance/assumptions.vpr b/src/test/resources/dependencyAnalysisTests/guidance/assumptions.vpr new file mode 100644 index 000000000..350c6332f --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/guidance/assumptions.vpr @@ -0,0 +1,13 @@ +method client() +{ + var a: Int, b: Int, c: Int, d: Int + assume @guidedAssumption("3")(a == 1) // used for last assert + assume @guidedAssumption("1")(b == 2) // used for every assert + assume @guidedAssumption("2")(c == 3) // used for last two asserts + + d := a + b + + assert b == 2 + assert b + c == 5 + assert d + c == 6 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/guidance/assumptions2.vpr b/src/test/resources/dependencyAnalysisTests/guidance/assumptions2.vpr new file mode 100644 index 000000000..a8c53ceb0 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/guidance/assumptions2.vpr @@ -0,0 +1,18 @@ +method client() +{ + var a1: Int, a2: Int, a3: Int, b1: Int, b2: Int, b3: Int, b4: Int + a1 := 10 + a2 := 20 + a3 := 30 + + assume @guidedAssumption("4")(b1 == 1) // least useful - is used besides three other facts in last assertion + assume @guidedAssumption("2")(b2 == 2) + assume @guidedAssumption("1")(b3 == 3) // most useful - used on its own in first assertion + assume @guidedAssumption("3")(b4 == 4) + assume 2 == 2 // is not used at all + + assert b3 == 3 + assert a1 + b2 == 12 + assert a1 + a2 + b4 == 34 + assert a1 + a2 + a3 + b1 == 61 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/guidance/mix.vpr b/src/test/resources/dependencyAnalysisTests/guidance/mix.vpr new file mode 100644 index 000000000..f96643dcd --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/guidance/mix.vpr @@ -0,0 +1,33 @@ +method add(a: Int, b: Int) returns (res: Int) + requires b != 0 + ensures @guidedAssumption("1")(res == a + b) + + +method sub(a: Int, b: Int) returns (res: Int) + requires b != 0 + ensures @guidedAssumption("2")(res == a - b) + + +method client() +{ + var a: Int, b: Int, c: Int, d: Int + a := 2 + b := 3 + @uncovered() + d := 5 + + c := add(a, b) + c := add(a, c) + c := sub(c, b) + + assert c == 4 +} + +method uncoveredClient() +{ + var e: Int, f: Int + @uncovered() + e := 6 + @uncovered() + f := 7 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/guidance/uncovered-methods-equal.vpr b/src/test/resources/dependencyAnalysisTests/guidance/uncovered-methods-equal.vpr new file mode 100644 index 000000000..e2d955845 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/guidance/uncovered-methods-equal.vpr @@ -0,0 +1,114 @@ +method m1() +{ + var a: Int, b: Int, c: Int, d: Int + a := 2 + b := 3 + c := 4 + d := 5 + + a := a + 1 + b := b + 1 + c := c + 1 + d := d + 1 + + assert a != d + assert b != c +} + +method m2() +{ + var a: Int, b: Int, c: Int, d: Int + a := 2 + @uncovered() + b := 3 + @uncovered() + c := 4 + d := 5 + + a := a + 1 + @uncovered() + b := b + 1 + @uncovered() + c := c + 1 + d := d + 1 + + assert a != d +} + +method m3() +{ + var a: Int, b: Int, c: Int, d: Int + a := 2 + b := 3 + c := 4 + d := 5 + + assert a != d + + a := a + 1 + b := b + 1 + c := c + 1 + @uncovered() + d := d + 1 + + assert b != c + assert a != 0 +} + +method m4() +{ + var a: Int, b: Int, c: Int, d: Int + a := 2 + @uncovered() + b := 3 + c := 4 + d := 5 + + a := a + 1 + @uncovered() + b := b + 1 + c := c + 1 + d := d + 1 + + assert a != d + assert c == 5 +} + +method m5() +{ + var a: Int, b: Int, c: Int, d: Int + a := 2 + b := 3 + c := 4 + d := 5 + + assert a != d + + @uncovered() + a := a + 1 + b := b + 1 + c := c + 1 + d := d + 1 + + assert b != c + assert d != 0 +} + +method m6() +{ + var a: Int, b: Int, c: Int, d: Int + @uncovered() + a := 2 + b := 3 + c := 4 + d := 5 + + @uncovered() + a := a + 1 + b := b + 1 + c := c + 1 + d := d + 1 + + assert b != c + assert d == 6 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/guidance/uncovered-methods.vpr b/src/test/resources/dependencyAnalysisTests/guidance/uncovered-methods.vpr new file mode 100644 index 000000000..49b9b219c --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/guidance/uncovered-methods.vpr @@ -0,0 +1,75 @@ +method m1() +{ + var a: Int, b: Int, c: Int, d: Int + a := 2 + b := 3 + c := 4 + d := 5 + + a := a + 1 + b := b + 1 + c := c + 1 + d := d + 1 + + assert a != d + assert b != c +} + +method m2() +{ + var a: Int, b: Int, c: Int, d: Int + a := 2 + @uncovered() + b := 3 + @uncovered() + c := 4 + d := 5 + + a := a + 1 + @uncovered() + b := b + 1 + @uncovered() + c := c + 1 + d := d + 1 + + assert a != d +} + +method m3() +{ + var a: Int, b: Int, c: Int, d: Int + a := 2 + b := 3 + c := 4 + d := 5 + + assert a != d + + a := a + 1 + b := b + 1 + c := c + 1 + @uncovered() + d := d + 1 + + assert b != c + assert a != 0 +} + +method m4() +{ + var a: Int, b: Int, c: Int, d: Int + a := 2 + @uncovered() + b := 3 + c := 4 + d := 5 + + a := a + 1 + @uncovered() + b := b + 1 + c := c + 1 + d := d + 1 + + assert a != d + assert c == 5 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/guidance/uncovered-methods2.vpr b/src/test/resources/dependencyAnalysisTests/guidance/uncovered-methods2.vpr new file mode 100644 index 000000000..476680d39 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/guidance/uncovered-methods2.vpr @@ -0,0 +1,34 @@ +method helper() returns (res: Int) + ensures res == 10 +{ + res := 10 +} + +method m1() +{ + var a: Int + @uncovered() + a := helper() +} + +method m2() +{ + var a: Int + a := helper() + + assert a == 10 +} + +method m3() +{ + var a: Int + a := helper() + assert a == 10 + + @uncovered() + a := helper() + @uncovered() + a := helper() + @uncovered() + a := helper() +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgressTests/assertion-combinations.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/assertion-combinations.vpr new file mode 100644 index 000000000..19004f096 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/assertion-combinations.vpr @@ -0,0 +1,16 @@ +// Spec quality: 1 +// Proof quality: 5/7 +// Progress: 5/7 + +method client() +{ + var a: Int + var b: Int + a := 10 + assume b == 10 + + assert a > 5 && a < 15 && a != 30 && b > 7 // PQ: 1/1 1/1 1/1 0/1 + assert a > 15 || a < 15 // PQ: 1/1 + assert a > 15 || b < 15 // PQ: 0/1 + assert a == 10 || b < 15 // PQ: 1/1 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgressTests/assume-false.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/assume-false.vpr new file mode 100644 index 000000000..026bad153 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/assume-false.vpr @@ -0,0 +1,37 @@ +// Spec quality: 3/4 +// Proof quality: 155/168 +// Progress: 155/224 + +field val: Int +define access(a) + (forall j: Int :: 0 <= j && j < len(a) ==> acc(slot(a,j).val)) + +domain IArray { + function slot(a: IArray, i: Int): Ref + function len(a: IArray): Int + function first(r: Ref): IArray + function second(r: Ref): Int + + axiom all_diff { + forall a: IArray, i: Int :: { slot(a,i) } + first(slot(a,i)) == a && second(slot(a,i)) == i + } +} + + +method domain3() +{ + var a: IArray + inhale len(a) == 3 + inhale access(a) // PQ: 1/1 + + var i: Int + i := 0 + while (i < len(a)) + invariant access(a) // PQ: 5/6 + invariant 0 <= i // PQ: 1/1 + { + slot(a,i).val := -i // PQ: 6/7 + i := i + 1 + } +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgressTests/branch1.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/branch1.vpr new file mode 100644 index 000000000..dbe6322da --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/branch1.vpr @@ -0,0 +1,17 @@ +// Spec quality: 2/3 +// Proof quality: 2/3 +// Progress: 4/9 + +method branch1() { + var x: Int, y: Int, z: Int + + if (x > 0) { + assume y == x + 1 + } + else { + y := -x + 1 + z := x * y + } + + assert y > 0 // PQ: 2/3 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgressTests/branch2.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/branch2.vpr new file mode 100644 index 000000000..edc103f31 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/branch2.vpr @@ -0,0 +1,16 @@ +// Spec quality: 0 +// Proof quality: 0 +// Progress: 0 + +method branch2() { + var x: Int, y: Int + + if (x > 0) { + assume y > 0 + } + else { + assume y >= 10 + } + + assert y > 0 // PQ: 0/2 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgressTests/branch3.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/branch3.vpr new file mode 100644 index 000000000..2fd7ca58f --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/branch3.vpr @@ -0,0 +1,16 @@ +// Spec quality: 2/3 +// Proof quality: 1 +// Progress: 2/3 + +method branch3() { + var x: Int, y: Int + + if (x > 10) { + y := x + 1 + } else { + y := 10 + assume y > 0 + } + + assert x > 10 ==> y > 0 // PQ: 2/2 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgressTests/branch4.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/branch4.vpr new file mode 100644 index 000000000..6a40e58a1 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/branch4.vpr @@ -0,0 +1,14 @@ +// Spec quality: 2/3 +// Proof quality: 1 +// Progress: 2/3 + +method branch4() { + var x: Int, y: Int + + if (x > 0) { + y := x + 1 + } else { + y := 10 - x + assert y > 0 // PQ: 2/2 + } +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgressTests/functions1.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/functions1.vpr new file mode 100644 index 000000000..9676534a3 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/functions1.vpr @@ -0,0 +1,19 @@ +// Spec quality: 1 +// Proof quality: 1/3 +// Progress: 1/3 + +function sum(x: Int, y: Int): Int + requires x >= 0 && y >= 0 + ensures result >= 0 // PQ: 4/6 +{ + x + y +} + +method client() +{ + var x: Int, y: Int + assume x > 10 + assume y > 0 + + x := sum(x, y) // PQ: 0/2 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgressTests/functions2.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/functions2.vpr new file mode 100644 index 000000000..6da44cada --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/functions2.vpr @@ -0,0 +1,7 @@ +// Spec quality: 1 +// Proof quality: 0 +// Progress: 0 + +function sumUnverified(x: Int, y: Int): Int + requires x >= 0 && y >= 0 + ensures result == x + y diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgressTests/implications.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/implications.vpr new file mode 100644 index 000000000..67f2c3466 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/implications.vpr @@ -0,0 +1,14 @@ +// Spec quality: 1/2 +// Proof quality: 1/2 +// Progress: 1/4 + +method client() +{ + var a: Int, b: Int, c: Int + a := 10 + assume b == 10 + c := 10 + + assert a == 0 ==> false // PQ: 1/1 + assert b == 0 ==> false // PQ: 0/1 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgressTests/inhale1.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/inhale1.vpr new file mode 100644 index 000000000..79fe6c74e --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/inhale1.vpr @@ -0,0 +1,14 @@ +// Spec quality: 1 +// Proof quality: 3/4 +// Progress: 3/4 + +field f: Int + +method exhaleInhale(a: Ref) + requires acc(a.f) +{ + exhale acc(a.f, 1/2) // PQ: 1/1 + inhale acc(a.f, 1/2) + + assert perm(a.f) == write // PQ: 1/2 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgressTests/inhale2.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/inhale2.vpr new file mode 100644 index 000000000..6e6590652 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/inhale2.vpr @@ -0,0 +1,16 @@ +// Spec quality: 1 +// Proof quality: 1/6 +// Progress: 1/6 + +field f: Int + +method exhale1(){ + var x: Ref + + inhale acc(x.f) + x.f := 1 // PQ: 0/1 + + exhale acc(x.f, 1/2) // PQ: 0/1 + + assert x.f > 0 // PQ: 1/2 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgressTests/loops.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/loops.vpr new file mode 100644 index 000000000..df83cde08 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/loops.vpr @@ -0,0 +1,20 @@ +// Spec quality: 1 +// Proof quality: 7/10 +// Progress: 7/10 + +method loop1() { + var i: Int + var res: Int + res := 0 + assume i == 10 + while (i > 0) + invariant i <= 10 // PQ: 1/2 + invariant i >= 0 // PQ: 2/3 + invariant res >= 0 // PQ: 4/5 + { + res := res + i + i := i - 1 + } + + assert res >= 0 // PQ: 5/6 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgressTests/method-add.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/method-add.vpr new file mode 100644 index 000000000..52c04d87e --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/method-add.vpr @@ -0,0 +1,18 @@ +// Spec quality: 1/2 +// Proof quality: 1/2 +// Progress: 1/4 + +method add(a: Int, b: Int) returns (res: Int) + requires b != 0 + ensures res == a + b +{ + res := a + b // PQ: 1/1 +} + +method client() +{ + var a: Int, b: Int, res: Int + assume b > 0 + + res := add(a, b) // PQ: 0/1 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgressTests/method-add2.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/method-add2.vpr new file mode 100644 index 000000000..84df5083f --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/method-add2.vpr @@ -0,0 +1,7 @@ +// Spec quality: 1 +// Proof quality: 0 +// Progress: 0 + +method add(a: Int, b: Int) returns (res: Int) + requires b != 0 + ensures res == a + b \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgressTests/method-divide.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/method-divide.vpr new file mode 100644 index 000000000..771ea97a5 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/method-divide.vpr @@ -0,0 +1,19 @@ +// Spec quality: 2/3 +// Proof quality: 13/18 +// Progress: 13/27 + +method divide(a: Int, b: Int) returns (res: Int) + requires b != 0 + ensures res == a / b // PQ: 3/4 +{ + res := a / b // PQ: 2/3 +} + +method client() +{ + var a: Int, b: Int, res: Int + a := 6 // uncovered + assume b == 3 + + res := divide(a, b) // PQ: 3/4 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgressTests/paper-branches-weaker.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/paper-branches-weaker.vpr new file mode 100644 index 000000000..17ed36d57 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/paper-branches-weaker.vpr @@ -0,0 +1,24 @@ +// Spec quality: 4/6 +// Proof quality: 4/5 +// Progress: 16/30 + +method branches(x: Bool, y: Bool) +{ + var a: Int + var b: Int + assume x == y + + if (x) { + a := 1 + } else { + a := -1 + } + + if (y) { + b := 1 + } else { + b := -1 + } + + assert x ==> a * b > 0 // PQ: 4/5 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgressTests/paper-branches.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/paper-branches.vpr new file mode 100644 index 000000000..e4779d022 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/paper-branches.vpr @@ -0,0 +1,24 @@ +// Spec quality: 1 +// Proof quality: 6/7 +// Progress: 6/7 + +method branches(x: Bool, y: Bool) +{ + var a: Int + var b: Int + assume x == y + + if (x) { + a := 1 + } else { + a := -1 + } + + if (y) { + b := 1 + } else { + b := -1 + } + + assert a * b > 0 // PQ: 6/7 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgressTests/paper-gaussian.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/paper-gaussian.vpr new file mode 100644 index 000000000..ab73deb04 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/paper-gaussian.vpr @@ -0,0 +1,21 @@ +// Spec quality: 1 +// Proof quality: 61/72 +// Progress: 61/72 + +method gaussianSum(n: Int) returns (res: Int) + requires n <= 1000 + ensures res == n * (n + 1) / 2 // PQ: 7/8 +{ + res := 0 + var i: Int := 0 + + assume i <= n + + while (i < n) + invariant i <= n // PQ: 2/3 + invariant res == i * (i + 1) / 2 // PQ: 4/4 + { + i := i + 1 + res := res + i + } +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgressTests/paper-no-obligations.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/paper-no-obligations.vpr new file mode 100644 index 000000000..8353faf42 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/paper-no-obligations.vpr @@ -0,0 +1,24 @@ +// Spec quality: 0 +// Proof quality: 1 +// Progress: 0 + +// Border case - there are no proof obligations + +method branches(x: Bool, y: Bool) +{ + var a: Int + var b: Int + assume x == y + + if (x) { + a := 1 + } else { + a := -1 + } + + if (y) { + b := 1 + } else { + b := -1 + } +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgressTests/permissions-quantified1.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/permissions-quantified1.vpr new file mode 100644 index 000000000..a1340e166 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/permissions-quantified1.vpr @@ -0,0 +1,14 @@ +// Spec quality: 1/2 +// Proof quality: 1/2 +// Progress: 1/4 + +field f: Int + +method quantifiedPerm() { + var xs: Seq[Ref] + xs := Seq(null, null, null, null, null) + + assume forall x: Ref :: x in xs ==> acc(x.f) // only used in line 14 + + xs[0].f := 10 // PQ: 1/2 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgressTests/permissions-quantified2.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/permissions-quantified2.vpr new file mode 100644 index 000000000..e83077db2 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/permissions-quantified2.vpr @@ -0,0 +1,17 @@ +// Spec quality: 1 +// Proof quality: 3/4 +// Progress: 3/4 + +field f: Int + +method quantifiedExhaleFully() { + var r1: Ref, r2: Ref, r3: Ref + r1 := new(f) + r2 := new(f) + inhale acc(r3.f) + + var xs: Seq[Ref] + xs := Seq(r1, r2, r3) + + assert forall x: Ref :: x in xs ==> acc(x.f) // PQ: 3/4 +} diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgressTests/permissions-wildcard.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/permissions-wildcard.vpr new file mode 100644 index 000000000..ccf9b162b --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/permissions-wildcard.vpr @@ -0,0 +1,16 @@ +// Spec quality: 0 +// Proof quality: 11/12 +// Progress: 0 + +field f: Int +field g: Int + +method setFive(x: Ref) + requires acc(x.f) + requires acc(x.g) + ensures acc(x.f, wildcard) // PQ: 1/1 + ensures x.f == 5 // PQ: 2/3 +{ + assume x.f == 5 // PQ: 1/1 + x.g := 5 // PQ: 1/1 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgressTests/permissions1.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/permissions1.vpr new file mode 100644 index 000000000..4bec86923 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/permissions1.vpr @@ -0,0 +1,14 @@ +// Spec quality: 1 +// Proof quality: 1/2 +// Progress: 1/2 + +field f: Int + +method permissions(x: Ref) + requires acc(x.f, 1/2) + ensures acc(x.f, 3/4) // PQ: 1/2 +{ + inhale acc(x.f, 1/4) + + assert perm(x.f) >= 3/4 // PQ: 1/2 +} diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgressTests/permissions2.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/permissions2.vpr new file mode 100644 index 000000000..8b100cf00 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/permissions2.vpr @@ -0,0 +1,25 @@ +// Spec quality: 1 +// Proof quality: 11/30 +// Progress: 11/30 + +field f: Int + +method permissions(x: Ref) + requires acc(x.f, 1/2) + ensures acc(x.f, 3/4) // PQ: 2/4 +{ + inhale acc(x.f, 1/4) +} + +method client() +{ + var y: Ref + inhale acc(y.f, 1/2) + + inhale true + exhale true + + permissions(y) // PQ: 0/1 + + assert perm(y.f) >= 3/4 // PQ: 3/5 +} diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgressTests/predicates1.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/predicates1.vpr new file mode 100644 index 000000000..222252ab8 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/predicates1.vpr @@ -0,0 +1,17 @@ +// Spec quality: 1 +// Proof quality: 2/3 +// Progress: 2/3 + +predicate greater0(n: Int){ + n > 0 +} + +method foldP(n: Int) + requires n > 10 +{ + var x: Int, y: Int + x := 1 + assume y == 1 + + fold greater0(x + y + n) // PQ: 2/3 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgressTests/predicates2.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/predicates2.vpr new file mode 100644 index 000000000..36ebda46c --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/predicates2.vpr @@ -0,0 +1,20 @@ +// Spec quality: 1 +// Proof quality: 3/8 +// Progress: 3/8 + +predicate greater0(n: Int){ + n > 0 +} + +method unfoldP(n: Int) +{ + var x: Int + x := 100 + + inhale greater0(n) + unfold greater0(n) // PQ: 0/1 + + x := n + x + + assert x > 100 // PQ: 3/4 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgressTests/predicates3.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/predicates3.vpr new file mode 100644 index 000000000..05e17d861 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/predicates3.vpr @@ -0,0 +1,18 @@ +// Spec quality: 1 +// Proof quality: 7/8 +// Progress: 7/8 + +predicate greater0(n: Int){ + n > 0 +} + +method unfoldP(n: Int) + requires greater0(n) +{ + var x: Int + assume x == 1 + unfold greater0(n) // PQ: 1/1 + x := n + x + + assert x > 1 // PQ: 3/4 +} \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgressTests/predicates4.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/predicates4.vpr new file mode 100644 index 000000000..95c82ca6c --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/predicates4.vpr @@ -0,0 +1,17 @@ +// Spec quality: 1 +// Proof quality: 29/36 +// Progress: 29/36 + +predicate greater0(n: Int){ + n > 0 +} + +method unfoldFoldP(a: Int, b: Int) // 29/36 + requires greater0(b) + ensures greater0(a + b) // PQ: 3/4 +{ + assume a >= 0 + unfold greater0(b) // PQ: 1/1 + + fold greater0(a + b) // PQ: 2/3 +} \ No newline at end of file diff --git a/src/test/scala/DependencyAnalysisTestFramework.scala b/src/test/scala/DependencyAnalysisTestFramework.scala index 40ba8dbb6..de10e5694 100644 --- a/src/test/scala/DependencyAnalysisTestFramework.scala +++ b/src/test/scala/DependencyAnalysisTestFramework.scala @@ -2,7 +2,8 @@ package viper.silicon.tests import viper.silicon.SiliconFrontend import viper.silicon.dependencyAnalysis._ -import viper.silicon.dependencyAnalysis.graphInterpretation.{DependencyAnalysisPruningSupporter, DependencyGraphInterpreter} +import viper.silicon.dependencyAnalysis.graphInterpretation.{DependencyAnalysisPruningSupporter, DependencyAnalysisProgressSupporter, DependencyGraphInterpreter} + import viper.silver.ast.utility.ViperStrategy import viper.silver.ast.{Infoed, Program} import viper.silver.dependencyAnalysis.AssumptionType @@ -242,4 +243,240 @@ trait DependencyAnalysisTestFramework { protected def getTestIrrelevantAssumptionNodes(nodes: Set[DependencyAnalysisNode]): Set[DependencyAnalysisNode] = nodes.filter(_.sourceInfo.toString.contains("@" + irrelevantKeyword + "(")) } -} + + /** + * Takes a Viper program and its verification progress results and checks whether they match + * with expected results as indicated in commented lines (at the top of the file). + * + * Comment types (parentheses indicate optional text): + * + * // Spec(ification) quality: [value] + * // Proof quality: [value] + * // (Verification) Progress: [value] + * + * [value] can be either a decimal number (e.g., 0.75) or a fraction (e.g., 5/6) + */ + class VerificationProgressTest(fileName: String, fullGraphInterpreter: DependencyGraphInterpreter[Final]) { + private val epsilon = 1e-8 + + def execute(): Unit = { + val (expectedSpecQuality, expectedProofQualityLea, expectedProgress) = readExpectedValues() + val baos = new java.io.ByteArrayOutputStream() + val (_, actualProgressLea) = Console.withOut(baos) { + new DependencyAnalysisProgressSupporter(fullGraphInterpreter).computeVerificationProgressOptimized() + } + val output = baos.toString + + // If a metric type does not exist, it is ignored + expectedSpecQuality.foreach { expected => + val actual = parseLine(output, "specQuality = ") + .getOrElse(throw new AssertionError(s"Could not parse specQuality from output:\n$output")) + assert(Math.abs(actual - expected) <= epsilon, + s"specQuality mismatch: expected $expected, got $actual") + } + expectedProofQualityLea.foreach { expected => + val actual = parseLine(output, "proof quality (Lea)") + .getOrElse(throw new AssertionError(s"Could not parse proofQualityLea from output:\n$output")) + assert(Math.abs(actual - expected) <= epsilon, + s"proofQualityLea mismatch: expected $expected, got $actual") + } + expectedProgress.foreach { expected => + assert(Math.abs(actualProgressLea - expected) <= epsilon, + s"progress mismatch: expected $expected, got $actualProgressLea") + } + } + + // Finds a relevant metric line by prefix, extracts its metric value + private def parseLine(output: String, prefix: String): Option[Double] = { + output.linesIterator + .find(_.toLowerCase.contains(prefix.toLowerCase)) + .flatMap(line => extractMetricValue(line)) + } + + // Reads expected metric values that are written at the top of .vpr test files + private def readExpectedValues(): (Option[Double], Option[Double], Option[Double]) = { + val resourcePath = fileName.replaceAll("/+", "/").stripPrefix("/") + ".vpr" + val url = getClass.getClassLoader.getResource(resourcePath) + val lines = Files.readAllLines(Paths.get(url.toURI)).asScala.take(5).toList + val commentText = lines.filter(_.trim.startsWith("//")).map(_.trim.stripPrefix("//").trim).mkString("\n") + + val specQuality = parseLine(commentText, "spec quality") + .orElse(parseLine(commentText, "specification quality")) + .orElse(parseLine(commentText, "spec:")) + .orElse(parseLine(commentText, "specification:")) + + val proofQualityLea = parseLine(commentText, "proof quality") + .orElse(parseLine(commentText, "proof:")) + + val progress = parseLine(commentText, "progress:") + .orElse(parseLine(commentText, "verification:")) + .orElse(parseLine(commentText, "verification progress")) + + (specQuality, proofQualityLea, progress) + } + + // Extract value from text with either "metric: value" or "metric = value" + private def extractMetricValue(line: String): Option[Double] = { + val delimiterIdx = List(line.lastIndexOf('='), line.lastIndexOf(':')).filter(_ >= 0).maxOption.getOrElse(-1) + if (delimiterIdx < 0) None + else parseValue(line.substring(delimiterIdx + 1).trim) + } + + // Value can be either a decimal number or a fraction + private def parseValue(str: String): Option[Double] = { + if (str.contains("/")) { + val parts = str.split("/").map(_.trim) + if (parts.length == 2) + try Some(parts(0).toDouble / parts(1).toDouble) + catch { case _: NumberFormatException => None } + else None + } else { + try Some(str.toDouble) + catch { case _: NumberFormatException => None } + } + } + } + + + + /** + * Tests the verification guidance output against expected values annotated inline in the .vpr file. + * + * Annotation types: + * - @guidedAssumption("N") -> this assumption should appear at rank N in the guidance output (1 = most important) + * - @uncovered() -> this statement should appear in the uncovered statements of its method + * + * Method ordering is checked implicitly: methods annotated with more @uncovered statements should + * rank higher (i.e., appear earlier) in the actual uncovered-statements-per-method ranking. + */ + class GuidanceTest(program: Program, + memberInterpreters: List[DependencyGraphInterpreter[IntraProcedural]], + fullGraphInterpreter: DependencyGraphInterpreter[Final]) + extends AnnotatedTest(program, memberInterpreters, checkPrecision = false) { + + val guidedAssumptionKeyword = "guidedAssumption" + val uncoveredKeyword = "uncovered" + + override def execute(): Unit = { + val actualAssumptionRanking = fullGraphInterpreter.progressSupporter.computeAssumptionRanking().filter(_._2 > 0.0) + // Compute uncovered per method once, suppressing stdout side effect + val actualUncoveredByMethod: Map[String, (Int, String)] = memberInterpreters + .filter(mi => mi.getMember.isDefined && mi.getMember.get.isInstanceOf[ast.Method]) + .map { mi => + val baos = new java.io.ByteArrayOutputStream() + val count = Console.withOut(baos)(new DependencyAnalysisProgressSupporter(mi).computeUncoveredStatements()) + (mi.getMember.get.name, (count, baos.toString)) + }.toMap + + val errorMsgs = + checkAssumptionRanking(actualAssumptionRanking) ++ + checkUncoveredStatements(actualUncoveredByMethod) ++ + checkMethodOrder(actualUncoveredByMethod) + + assert(errorMsgs.isEmpty, "\n" + errorMsgs.mkString("\n")) + } + + private def checkAssumptionRanking(actualRanking: List[(String, Double)]): Seq[String] = { + val annotated = extractAnnotatedStmts(_.values.contains(guidedAssumptionKeyword)) + val ranked: List[(Int, Int)] = annotated.toList.flatMap { node => + val rankStr = node.info.getUniqueInfo[ast.AnnotationInfo] + .flatMap(_.values.get(guidedAssumptionKeyword).flatMap(_.headOption)) + .getOrElse("") + val rankOpt = try Some(rankStr.toInt) catch { case _: NumberFormatException => None } + val line = extractSourceLine(node.asInstanceOf[ast.Positioned].pos) + rankOpt.map(rank => (rank, line)) + }.sortBy(_._1) + + if (ranked.isEmpty) return Seq.empty + + // Find each annotated assumption in the actual ranking; match by "line N)" in the toString + // Carry actual score so equal-scored pairs can be skipped in the ordering check. + val posResults: List[Either[String, (Int, Int, Double)]] = ranked.map { case (rank, line) => + val idx = actualRanking.indexWhere(_._1.contains(s"line $line)")) + if (idx < 0) + Left(s"@guidedAssumption($rank) at line $line not found in assumption ranking.\nActual ranking:\n\t${actualRanking.mkString("\n\t")}") + else + Right((rank, idx, actualRanking(idx)._2)) + } + + val missingErrors = posResults.collect { case Left(err) => err } + if (missingErrors.nonEmpty) return missingErrors + + val positions = posResults.collect { case Right(p) => p } + // Pairwise: smaller annotated rank should appear earlier in actual ranking, + // unless the two assumptions have equal actual scores (any order is valid then). + (for { + i <- positions.indices + j <- i + 1 until positions.size + (rankI, idxI, scoreI) = positions(i) + (rankJ, idxJ, scoreJ) = positions(j) + if rankI < rankJ && scoreI != scoreJ && idxI > idxJ + } yield s"Wrong assumption order: @guidedAssumption($rankI) at actual position $idxI should come before @guidedAssumption($rankJ) at position $idxJ").toSeq + } + + private def checkUncoveredStatements(actualUncoveredByMethod: Map[String, (Int, String)]): Seq[String] = { + val errors = scala.collection.mutable.ListBuffer.empty[String] + for (method <- program.methods) { + val annotatedLines = uncoveredAnnotatedLinesInMethod(method) + actualUncoveredByMethod.get(method.name) match { + case None => + if (annotatedLines.nonEmpty) + errors += s"No member interpreter found for method '${method.name}'" + case Some((actualCount, output)) => + for (line <- annotatedLines) { + if (!output.contains(s"line $line)")) + errors += s"@uncovered() at line $line not found in uncovered statements of method '${method.name}'.\nActual uncovered output:\n$output" + } + if (actualCount != annotatedLines.size) + errors += s"Method '${method.name}': expected ${annotatedLines.size} uncovered statement(s), got $actualCount" + } + } + errors.toSeq + } + + private def checkMethodOrder(actualUncoveredByMethod: Map[String, (Int, String)]): Seq[String] = { + // Derive expected order from @uncovered annotation counts per method (descending) + val expectedOrder: List[(String, Int)] = program.methods + .map(m => (m.name, uncoveredAnnotatedLinesInMethod(m).size)) + .filter(_._2 > 0) + .sortBy(-_._2) + .toList + + if (expectedOrder.size < 2) return Seq.empty + + val actualOrder: List[String] = actualUncoveredByMethod.toList + .filter(_._2._1 > 0) + .sortBy(-_._2._1) + .map(_._1) + + // Pairwise: method with strictly more @uncovered annotations should appear before one with fewer. + // Methods with equal counts may appear in any order. + (for { + i <- expectedOrder.indices + j <- i + 1 until expectedOrder.size + (methodA, countA) = expectedOrder(i) + (methodB, countB) = expectedOrder(j) + if countA != countB + idxA = actualOrder.indexOf(methodA) + idxB = actualOrder.indexOf(methodB) + if idxA >= 0 && idxB >= 0 && idxA > idxB + } yield s"Wrong method order: '$methodA' ($countA @uncovered) should appear before '$methodB' ($countB @uncovered), but actual order is: ${actualOrder.mkString(", ")}").toSeq + } + + private def uncoveredAnnotatedLinesInMethod(method: ast.Method): List[Int] = { + val lines = scala.collection.mutable.ListBuffer.empty[Int] + @unused + val _ignored: ast.Node = ViperStrategy.Slim({ + case s: ast.Seqn => s + case n: ast.Infoed => + val hasAnnotation = n.info.getUniqueInfo[ast.AnnotationInfo] + .exists(_.values.contains(uncoveredKeyword)) + if (hasAnnotation) + lines += extractSourceLine(n.asInstanceOf[ast.Positioned].pos) + n + }).execute(method) + lines.toList + } + } + +} \ No newline at end of file diff --git a/src/test/scala/DependencyAnalysisTests.scala b/src/test/scala/DependencyAnalysisTests.scala index 84db1e21e..ae7ea9296 100644 --- a/src/test/scala/DependencyAnalysisTests.scala +++ b/src/test/scala/DependencyAnalysisTests.scala @@ -18,6 +18,8 @@ class DependencyAnalysisTests extends AnyFunSuite with DependencyAnalysisTestFra "dependencyAnalysisTests/all", "dependencyAnalysisTests/unitTests", "dependencyAnalysisTests/real-world-examples", + "dependencyAnalysisTests/verificationProgressTests", + "dependencyAnalysisTests/guidance", ) if(EXECUTE_TEST) { @@ -33,7 +35,7 @@ class DependencyAnalysisTests extends AnyFunSuite with DependencyAnalysisTestFra resetFrontend() executeTest(dirName + "/", fileName, frontend) }catch{ - case t: Throwable => fail(t) + case t: Throwable => fail(t.getMessage, t) } } } @@ -52,8 +54,14 @@ class DependencyAnalysisTests extends AnyFunSuite with DependencyAnalysisTestFra val dependencyGraphInterpreters = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].dependencyGraphInterpretersPerMember val joinedDependencyGraphInterpreter = frontend.reporter.asInstanceOf[DependencyAnalysisReporter].joinedDependencyGraphInterpreter - // TODO ake: annotated tests can be removed once all tests are migrated to the new test annotations (TestSupporter) - new AnnotatedTest(program, dependencyGraphInterpreters, CHECK_PRECISION).execute() - new PruningTest(filePrefix + "/" + fileName, program, joinedDependencyGraphInterpreter.get).execute() + if (filePrefix.contains("verificationProgressTests")) { + new VerificationProgressTest(filePrefix + "/" + fileName, joinedDependencyGraphInterpreter.get).execute() + } else if (filePrefix.contains("guidance")) { + new GuidanceTest(program, dependencyGraphInterpreters, joinedDependencyGraphInterpreter.get).execute() + } else { + // TODO ake: annotated tests can be removed once all tests are migrated to the new test annotations (TestSupporter) + new AnnotatedTest(program, dependencyGraphInterpreters, CHECK_PRECISION).execute() + new PruningTest(filePrefix + "/" + fileName, program, joinedDependencyGraphInterpreter.get).execute() + } } } From 5fb923475aa6102d4f2d0609991bbe2257ee9ba0 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Fri, 19 Jun 2026 20:41:02 +0200 Subject: [PATCH 467/474] fix unknown constant (function recorder) --- src/main/scala/rules/Evaluator.scala | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 1c95fae68..4f64f2088 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -641,17 +641,20 @@ object evaluator extends EvaluationRules { tArgs zip eArgs.map(Some(_)) } else if (withExp) tArgs.zip(eArgsNew.get.map(Some(_))) else tArgs.zip(Seq.fill(tArgs.size)(None)) // encode the function call as a sequence of assignments to fresh variables (one for each argument) and a method call using the fresh variables as arguments + var s2b = s2 val argsFreshVar = - if(Verifier.config.enableDependencyAnalysis()){ + if (Verifier.config.enableDependencyAnalysis()) { argsPairs.map(arg => { val argNew = v1.decider.fresh(arg._1.sort, None) - v1.decider.assume(Equals(argNew, arg._1), None, analysisInfos.withMergeInfo(SimpleDependencyAnalysisMerge(AnalysisSourceInfo.createAnalysisSourceInfo(arg._2.get)))) + val constraint = Equals(argNew, arg._1) + v1.decider.assume(constraint, None, analysisInfos.withMergeInfo(SimpleDependencyAnalysisMerge(AnalysisSourceInfo.createAnalysisSourceInfo(arg._2.get)))) + s2b = s2b.copy(functionRecorder = s2b.functionRecorder.recordConstrainedVar(argNew, constraint)) (argNew, None) }) - }else argsPairs - val s3 = s2.copy(g = Store(fargs.zip(argsFreshVar)), + } else argsPairs + val s3 = s2b.copy(g = Store(fargs.zip(argsFreshVar)), recordVisited = true, - functionRecorder = s2.functionRecorder.changeDepthBy(+1), + functionRecorder = s2b.functionRecorder.changeDepthBy(+1), /* Temporarily disable the recorder: when recording (to later on * translate a particular function fun) and a function application * fapp is hit, then there is no need to record any information @@ -677,7 +680,7 @@ object evaluator extends EvaluationRules { smDomainNeeded = true, moreJoins = JoinMode.Off, assertReadAccessOnly = if (Verifier.config.respectFunctionPrePermAmounts()) - s2.assertReadAccessOnly /* should currently always be false */ else true) + s2b.assertReadAccessOnly /* should currently always be false */ else true) val precondAnalysisInfos = analysisInfos.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Source, EdgeType.Up)) consumes(s3, presWithDAInfo, true, _ => pvePre, v2, precondAnalysisInfos)((s4, snap, v3) => { val snap1 = snap.get.convert(sorts.Snap) From c9a00932eb73a6f999aadcab3b8901e6e7413a4d Mon Sep 17 00:00:00 2001 From: DovydasVad Date: Mon, 22 Jun 2026 12:30:32 +0200 Subject: [PATCH 468/474] Remove duplicate error messages --- src/test/scala/DependencyAnalysisTestFramework.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/scala/DependencyAnalysisTestFramework.scala b/src/test/scala/DependencyAnalysisTestFramework.scala index de10e5694..a9d1f7562 100644 --- a/src/test/scala/DependencyAnalysisTestFramework.scala +++ b/src/test/scala/DependencyAnalysisTestFramework.scala @@ -385,7 +385,7 @@ trait DependencyAnalysisTestFramework { val rankOpt = try Some(rankStr.toInt) catch { case _: NumberFormatException => None } val line = extractSourceLine(node.asInstanceOf[ast.Positioned].pos) rankOpt.map(rank => (rank, line)) - }.sortBy(_._1) + }.distinct.sortBy(_._1) if (ranked.isEmpty) return Seq.empty From fe01472097410346ae623dd07727288e93e6cb85 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Mon, 22 Jun 2026 14:22:08 +0200 Subject: [PATCH 469/474] fixes (function recorder and infeasibility) --- src/main/scala/rules/Evaluator.scala | 5 ++++- src/main/scala/rules/HeapSupporter.scala | 3 ++- src/main/scala/rules/Producer.scala | 4 ++-- .../all/infeasible.vpr | 20 ++++++++++++++++++- .../all/quantified-perm.vpr | 12 +++++++++++ 5 files changed, 39 insertions(+), 5 deletions(-) diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 4f64f2088..c80e30aa4 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -572,7 +572,10 @@ object evaluator extends EvaluationRules { case (s1, _, _, _, _, None, v1) => // This should not happen unless the current path is dead. if (v1.decider.checkSmoke(analysisInfos, isAssert = true)) { - Unreachable() + if (Verifier.config.disableInfeasibilityChecks()) { + val freshVar = v1.decider.fresh(v1.symbolConverter.toSort(sourceQuant.typ), None) + Q(s1, freshVar, None, v1) + } else Unreachable() } else { val failure = createFailure(pve.dueTo(InternalReason(sourceQuant, "Quantifier evaluation failed.")), v1, s1, "quantifier could be evaluated") if(s1.retryLevel == 0) v1.decider.handleFailedAssertion(False, analysisInfos, v1.reportFurtherErrors()) diff --git a/src/main/scala/rules/HeapSupporter.scala b/src/main/scala/rules/HeapSupporter.scala index d79d10c23..93068acb1 100644 --- a/src/main/scala/rules/HeapSupporter.scala +++ b/src/main/scala/rules/HeapSupporter.scala @@ -342,7 +342,8 @@ class DefaultHeapSupportRules extends HeapSupportRules { val sort = v.symbolConverter.toSort(fa.field.typ) val newVar = v.decider.fresh(sort, None) // just make sure the returned term typechecks - return Q(s, newVar, v) + val s2 = s.copy(functionRecorder = s.functionRecorder.recordConstrainedVar(newVar, True)) + return Q(s2, newVar, v) } if (s.qpFields.contains(fa.field)) { diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index f01160337..7d15b7059 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -173,8 +173,8 @@ object producer extends ProductionRules { // We will get an IllegalArgumentException from createSnapshotPair if sf(...) returns Unit. // This should never happen if we're in a reachable state, so here we check for that // (without timeout, since there is no fallback) and stop verifying the current branch. - case _: IllegalArgumentException if v.decider.check(False, Verifier.config.assertTimeout.getOrElse(0), analysisInfos) => - Unreachable() + case _: IllegalArgumentException if v.decider.checkSmoke(analysisInfos) => + if (Verifier.config.disableInfeasibilityChecks()) Q(s,v) else Unreachable() } } diff --git a/src/test/resources/dependencyAnalysisTests/all/infeasible.vpr b/src/test/resources/dependencyAnalysisTests/all/infeasible.vpr index 5965bd08e..0c791791a 100644 --- a/src/test/resources/dependencyAnalysisTests/all/infeasible.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/infeasible.vpr @@ -87,4 +87,22 @@ method clientA(b: Bool, x: Ref) exhale acc(x.f) inhale @dependencyInfo("assumptionType:Explicit, label:L26")(acc(x.f)) } -} \ No newline at end of file +} + +field next : Ref +field val : Int + +predicate list(start : Ref) +{ + acc(start.val) && acc(start.next) +} + +method infeasible_unfold(l1 : Ref) + { + + assume @dependencyInfo("assumptionType:Explicit, label:L27, expectedDependencies:[]")(false) + @dependencyInfo("assumptionType:Rewrite, assertionType:Rewrite, label:L28, expectedDependencies:[L27]") + unfold list(l1) + @dependencyInfo("assertionType:Explicit, label:L29, expectedDependencies:[L27]") + assert l1.next == l1 + } \ No newline at end of file diff --git a/src/test/resources/dependencyAnalysisTests/all/quantified-perm.vpr b/src/test/resources/dependencyAnalysisTests/all/quantified-perm.vpr index 05571149f..abb5e9ba8 100644 --- a/src/test/resources/dependencyAnalysisTests/all/quantified-perm.vpr +++ b/src/test/resources/dependencyAnalysisTests/all/quantified-perm.vpr @@ -22,3 +22,15 @@ method quantifiedSum(nodes: Set[Ref], x: Ref) @dependencyInfo("assertionType:Explicit, label:L24, expectedDependencies:[L21,L23,L20,L15,L18,L22]") assert a >= 0 } + +field b: Bool + +function F(r: Ref): Bool + requires @dependencyInfo("assumptionType:Precondition, label:L25, expectedDependencies:[L26]")(acc(r.b, write)) +{ true } + +function Target(i: Ref): Bool + requires @dependencyInfo("assumptionType:Precondition, label:L26")(forall r: Ref :: acc(r.b, write)) +{ + @dependencyInfo("assumptionType:FunctionBody, label:L27, expectedDependencies:[L26]")(F(i)) +} From ae36076349916c9393c263a0c78fd233243a07b9 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 23 Jun 2026 11:34:57 +0200 Subject: [PATCH 470/474] fix infeasible paths --- src/main/scala/rules/Producer.scala | 8 ++++++++ src/main/scala/rules/QuantifiedChunkSupport.scala | 7 +++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index 7d15b7059..1905f0d01 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -152,6 +152,14 @@ object producer extends ProductionRules { val pve = pves.head val analysisInfos1 = v.decider.handleAndGetUpdatedAnalysisInfos(analysisInfos, a.info, a) + if (v.decider.isPathInfeasible) { + if (!Expressions.isKnownWellDefined(a, Some(s.program))) { + v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfos1) + } + v.decider.dependencyAnalyzer.addAssumption(True, analysisInfos1) + return Q(s, v) + } + if (as.tail.isEmpty) wrappedProduceTlc(s, sf, a, pve, v, analysisInfos1)((s1, v1) => { Q(s1, v1) diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 6bf916437..7e2ba842f 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -1535,8 +1535,11 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { case wand: ast.MagicWand => createFailure(pve dueTo MagicWandChunkNotFound(wand), v, s, "single QP consume") case _ => sys.error(s"Found resource $resourceAccess, which is not yet supported as a quantified resource.") } - if(s.retryLevel == 0) v.decider.handleFailedAssertion(False, analysisInfos, v.reportFurtherErrors()) - if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, s.h, None, v) else failure + if (s.retryLevel == 0) v.decider.handleFailedAssertion(False, analysisInfos, v.reportFurtherErrors()) + if (s.retryLevel == 0 && v.reportFurtherErrors()) { + val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) + failure combine Q(s, s.h, Some(snap), v) + } else failure } } } From 08aa77f755fab69d86547a1b1ed0e76f2de319ba Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 23 Jun 2026 11:35:09 +0200 Subject: [PATCH 471/474] cleanup --- src/main/scala/decider/Decider.scala | 15 ++++------ src/main/scala/decider/ProverStdIO.scala | 4 +-- .../DependencyAnalysisInfos.scala | 8 ++--- .../DependencyAnalyzer.scala | 18 +++++------ .../dependencyAnalysis/DependencyGraph.scala | 14 ++++----- .../AbstractDependencyAnalysisCliTool.scala | 6 ++-- ...chmarkDependencyAnalysisCliExtension.scala | 22 ++++++-------- .../DebugDependencyAnalysisCliExtension.scala | 4 +-- .../cliTool/DependencyAnalysisCliTool.scala | 10 +++---- .../cliTool/DependencyGraphImporter.scala | 4 +-- .../TestDependencyAnalysisCliExtension.scala | 8 ++--- .../DependencyAnalysisProgressSupporter.scala | 14 ++++----- .../DependencyGraphTestSupporter.scala | 6 ++-- src/main/scala/rules/Evaluator.scala | 30 +++++++++---------- src/main/scala/rules/Executor.scala | 12 ++++---- src/main/scala/rules/HeapSupporter.scala | 18 +++++------ .../rules/MoreCompleteExhaleSupporter.scala | 2 +- .../scala/rules/QuantifiedChunkSupport.scala | 20 ++++++------- .../supporters/functions/FunctionData.scala | 8 ++--- .../scala/verifier/DefaultMainVerifier.scala | 2 +- .../DependencyAnalysisTestFramework.scala | 6 ++-- src/test/scala/DependencyAnalysisTests.scala | 4 +-- 22 files changed, 114 insertions(+), 121 deletions(-) diff --git a/src/main/scala/decider/Decider.scala b/src/main/scala/decider/Decider.scala index e94bae5f1..da1da8d4a 100644 --- a/src/main/scala/decider/Decider.scala +++ b/src/main/scala/decider/Decider.scala @@ -446,8 +446,8 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => }) } - private def assumeWithoutSmokeChecks(termsWithLabel: InsertionOrderedSet[(Term, String)], isDefinition: Boolean = false): None.type = { - if (isPathInfeasible) return None + private def assumeWithoutSmokeChecks(termsWithLabel: InsertionOrderedSet[(Term, String)], isDefinition: Boolean = false): Unit = { + if (isPathInfeasible) return val terms = termsWithLabel map (_._1) val assumeRecord = new DeciderAssumeRecord(terms) @@ -464,7 +464,6 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => termsWithLabel foreach { case (t, label) => prover.assume(t, label) } symbExLog.closeScope(sepIdentifier) - None } def assumeLabel(term: Term, assumptionLabel: String): Unit = { @@ -492,8 +491,6 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => checkNode foreach dependencyAnalyzer.addAssertionNode dependencyAnalyzer.processUnsatCoreAndAddDependencies(prover.getLastUnsatCore, label) val infeasibleNodeId = dependencyAnalyzer.addInfeasibilityNode(!isAssert, analysisInfos) -// Adding the following line would be UNSOUND! Unsoundness is introduced when infeasibility is introduced while executing a package statements and pontentially in other cases as well. -// assumeWithoutSmokeChecks(InsertionOrderedSet((False, DependencyAnalyzer.createAssumptionLabel(infeasibleNodeId)))) dependencyAnalyzer.addDependency(checkNode.map(_.id), infeasibleNodeId) pcs.setCurrentInfeasibilityNode(infeasibleNodeId) } else if (isAssert) { @@ -553,11 +550,11 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => val asserted = if (isDependencyAnalysisEnabled) t.equals(True) else isKnownToBeTrue(t) - val assertNode = if(!asserted) dependencyAnalyzer.createAssertOrCheckNode(t, analysisInfos, isCheck) else None + val assertNode = if (!asserted) dependencyAnalyzer.createAssertOrCheckNode(t, analysisInfos, isCheck) else None val result = asserted || proverAssert(t, timeout, DependencyAnalyzer.createAssertionLabel(assertNode map (_.id))) - if(result) { + if (result) { assertNode foreach dependencyAnalyzer.addAssertionNode } @@ -579,8 +576,8 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier => val result = isPathInfeasible || prover.assert(t, timeout, label) - if(isPathInfeasible) dependencyAnalyzer.addDependency(pcs.getCurrentInfeasibilityNode, Some(DependencyAnalyzer.getIdFromLabel(label))) - else if(result) dependencyAnalyzer.processUnsatCoreAndAddDependencies(prover.getLastUnsatCore, label) + if (isPathInfeasible) dependencyAnalyzer.addDependency(pcs.getCurrentInfeasibilityNode, Some(DependencyAnalyzer.getIdFromLabel(label))) + else if (result) dependencyAnalyzer.processUnsatCoreAndAddDependencies(prover.getLastUnsatCore, label) symbExLog.whenEnabled { assertRecord.statistics = Some(symbExLog.deltaStatistics(prover.statistics())) diff --git a/src/main/scala/decider/ProverStdIO.scala b/src/main/scala/decider/ProverStdIO.scala index 61f4ab863..df591a516 100644 --- a/src/main/scala/decider/ProverStdIO.scala +++ b/src/main/scala/decider/ProverStdIO.scala @@ -252,7 +252,7 @@ abstract class ProverStdIO(uniqueId: String, // bookkeeper.assumptionCounter += 1 if ((Verifier.config.enableDependencyAnalysis() && label.nonEmpty) || Verifier.config.enableUnsatCores()) { - writeLine("(assert (! " + term + " :named " + (if(label.nonEmpty) label else nextProverLabel()) + "))") + writeLine("(assert (! " + term + " :named " + (if (label.nonEmpty) label else nextProverLabel()) + "))") }else { writeLine("(assert " + term + ")") } @@ -282,7 +282,7 @@ abstract class ProverStdIO(uniqueId: String, setTimeout(timeout) if ((Verifier.config.enableDependencyAnalysis() && label.nonEmpty) || Verifier.config.enableUnsatCores()){ - writeLine("(assert (! (not " + goal + ") :named " + (if(label.nonEmpty) label else nextProverLabel()) + "))") + writeLine("(assert (! (not " + goal + ") :named " + (if (label.nonEmpty) label else nextProverLabel()) + "))") }else{ writeLine("(assert (not " + goal + "))") } diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalysisInfos.scala b/src/main/scala/dependencyAnalysis/DependencyAnalysisInfos.scala index ff3fcc449..8f32a5ced 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalysisInfos.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalysisInfos.scala @@ -89,12 +89,12 @@ case class DependencyAnalysisInfos(sourceInfos: List[AnalysisSourceInfo], depend } def getMergeInfo: DependencyAnalysisMergeInfo = { - if(!isAnalysisEnabled) return NoDependencyAnalysisMerge() + if (!isAnalysisEnabled) return NoDependencyAnalysisMerge() mergeInfos.headOption.getOrElse(SimpleDependencyAnalysisMerge(getSourceInfo)) } def getJoinInfo: List[SimpleDependencyAnalysisJoin] = { - if(!isAnalysisEnabled) return List.empty + if (!isAnalysisEnabled) return List.empty joinInfos.map { case EvalStackDependencyAnalysisJoin(joinType, edgeType) => SimpleDependencyAnalysisJoin(sourceInfos.last, joinType, edgeType) case a: SimpleDependencyAnalysisJoin => a @@ -102,13 +102,13 @@ case class DependencyAnalysisInfos(sourceInfos: List[AnalysisSourceInfo], depend } def withMergeInfo(mergeInfo: DependencyAnalysisMergeInfo): DependencyAnalysisInfos = { - if(!isAnalysisEnabled) return this + if (!isAnalysisEnabled) return this this.copy(mergeInfos = mergeInfo +: mergeInfos) } def withJoinInfo(joinInfo: DependencyAnalysisJoinInfo): DependencyAnalysisInfos = { - if(!isAnalysisEnabled) return this + if (!isAnalysisEnabled) return this this.copy(joinInfos = joinInfo +: joinInfos) } diff --git a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala index aa29facde..329c56adb 100644 --- a/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala +++ b/src/main/scala/dependencyAnalysis/DependencyAnalyzer.scala @@ -82,7 +82,7 @@ object DependencyAnalyzer { def extractEnableAnalysisFromInfo(info: ast.Info): Option[Boolean] = { val annotation = extractAnnotationFromInfo(info, enableDependencyAnalysisAnnotationKey) - if(annotation.isDefined && annotation.get.nonEmpty) annotation.get.head.toBooleanOption else None + if (annotation.isDefined && annotation.get.nonEmpty) annotation.get.head.toBooleanOption else None } def createAssumptionLabel(id: Option[Int]): String = { @@ -138,7 +138,7 @@ object DependencyAnalyzer { sinkNodesByJoinInfo.foreach{case (joinInfo, nodes) => val matchingSourceNodes = sourceNodesByJoinInfo.filter{case (sourceJoinInfo, _) => sourceJoinInfo.matches(joinInfo)}.values.flatten - if(joinInfo.edgeType.equals(EdgeType.Up)) + if (joinInfo.edgeType.equals(EdgeType.Up)) newGraph.addEdgesConnectingMethodsUpwards(matchingSourceNodes.map(_.id), nodes.map(_.id)) else newGraph.addEdgesConnectingMethodsDownwards(matchingSourceNodes.map(_.id), nodes.map(_.id)) @@ -187,7 +187,7 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { val labelNode = labelNodeOpt.get val chunk = buildChunk(Ite(labelNode.term, perm, NoPerm)) val chunkNode = addPermissionExhaleNode(chunk, chunk.perm, analysisInfo.analysisInfos, labelNode) - if(chunkNode.isDefined) addDependency(chunkNode, Some(labelNode.id)) + if (chunkNode.isDefined) addDependency(chunkNode, Some(labelNode.id)) chunk } @@ -195,7 +195,7 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { val labelNode = labelNodeOpt.get val chunk = buildChunk(Ite((labelNode.term, perm, NoPerm))) val chunkNode = addPermissionInhaleNode(chunk, chunk.perm, analysisInfo.analysisInfos, labelNode) - if(chunkNode.isDefined) addDependency(chunkNode, Some(labelNode.id)) + if (chunkNode.isDefined) addDependency(chunkNode, Some(labelNode.id)) chunk } @@ -219,7 +219,7 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { } override def createAssertOrCheckNode(term: Term, analysisInfos: DependencyAnalysisInfos, isCheck: Boolean): Option[GeneralAssertionNode] = { - if(isCheck) + if (isCheck) Some(SimpleCheckNode(term, analysisInfos.getSourceInfo, analysisInfos.getDependencyType.assertionType, analysisInfos.getMergeInfo, analysisInfos.getJoinInfo)) else Some(SimpleAssertionNode(term, analysisInfos.getSourceInfo, analysisInfos.getDependencyType.assertionType, analysisInfos.getMergeInfo, analysisInfos.getJoinInfo)) @@ -254,7 +254,7 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { override def addDependency(source: Option[Int], dest: Option[Int]): Unit = { - if(source.isDefined && dest.isDefined) + if (source.isDefined && dest.isDefined) dependencyGraph.addEdges(source.get, Set(dest.get)) } @@ -262,7 +262,7 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { val assumptionLabels = dep.replace("(", "").replace(")", "").split(" ") val assertionId = DependencyAnalyzer.getIdFromLabel(assertionLabel) val assumptionIds = assumptionLabels.map(DependencyAnalyzer.getIdFromLabel).toSet - if(!assumptionIds.contains(assertionId)) + if (!assumptionIds.contains(assertionId)) dependencyGraph.addVacuousProof(assertionId) dependencyGraph.addEdges(assumptionIds.diff(Set(assertionId)), assertionId) } @@ -297,9 +297,9 @@ class DefaultDependencyAnalyzer(member: ast.Member) extends DependencyAnalyzer { */ override def buildFinalGraph(): Option[DependencyGraph[IntraProcedural]] = { dependencyGraph.removeLabelNodes() - val mergedGraph = if(Verifier.config.enableDependencyAnalysisDebugging()) dependencyGraph.asInstanceOf[DependencyGraph[IntraProcedural]] else buildAndGetMergedGraph() + val mergedGraph = if (Verifier.config.enableDependencyAnalysisDebugging()) dependencyGraph.asInstanceOf[DependencyGraph[IntraProcedural]] else buildAndGetMergedGraph() addTransitiveEdges(mergedGraph) - if(!Verifier.config.enableDependencyAnalysisDebugging()) mergedGraph.removeInternalNodes() + if (!Verifier.config.enableDependencyAnalysisDebugging()) mergedGraph.removeInternalNodes() Some(mergedGraph) } diff --git a/src/main/scala/dependencyAnalysis/DependencyGraph.scala b/src/main/scala/dependencyAnalysis/DependencyGraph.scala index 1b41246df..6ced3ffd4 100644 --- a/src/main/scala/dependencyAnalysis/DependencyGraph.scala +++ b/src/main/scala/dependencyAnalysis/DependencyGraph.scala @@ -112,8 +112,8 @@ class DependencyGraph[T <: DependencyGraphState] extends ReadOnlyDependencyGraph def getAllEdges(includeDownwardEdges: Boolean, includeUpwardEdges: Boolean): Map[Int, Set[Int]] = { val intraMethodEdges = getIntraMethodEdges - val upwardEdges: mutable.Map[Int, Set[Int]] = if(includeUpwardEdges) edgesConnectingMethodsUpwards else mutable.Map.empty - val downwardEdges: mutable.Map[Int, Set[Int]] = if(includeDownwardEdges) edgesConnectingMethodsDownwards else mutable.Map.empty + val upwardEdges: mutable.Map[Int, Set[Int]] = if (includeUpwardEdges) edgesConnectingMethodsUpwards else mutable.Map.empty + val downwardEdges: mutable.Map[Int, Set[Int]] = if (includeDownwardEdges) edgesConnectingMethodsDownwards else mutable.Map.empty val keys = intraMethodEdges.keySet ++ downwardEdges.keySet ++ upwardEdges.keySet val allEdges = mutable.Map[Int, Set[Int]]() keys foreach {key => @@ -155,7 +155,7 @@ class DependencyGraph[T <: DependencyGraphState] extends ReadOnlyDependencyGraph def addEdges(sources: Iterable[Int], target: Int): Unit = { val oldSources = edges.getOrElse(target, Set.empty) val newSources = sources.filter(_ != target) - if(newSources.nonEmpty) + if (newSources.nonEmpty) edges.update(target, oldSources ++ newSources) } @@ -166,7 +166,7 @@ class DependencyGraph[T <: DependencyGraphState] extends ReadOnlyDependencyGraph def addEdgesConnectingMethodsDownwards(sources: Iterable[Int], target: Int): Unit = { val oldSources = edgesConnectingMethodsDownwards.getOrElse(target, Set.empty) val newSources = sources.filter(_ != target) - if(newSources.nonEmpty) + if (newSources.nonEmpty) edgesConnectingMethodsDownwards.update(target, oldSources ++ newSources) } @@ -181,7 +181,7 @@ class DependencyGraph[T <: DependencyGraphState] extends ReadOnlyDependencyGraph def addEdgesConnectingMethodsUpwards(sources: Iterable[Int], target: Int): Unit = { val oldSources = edgesConnectingMethodsUpwards.getOrElse(target, Set.empty) val newSources = sources.filter(_ != target) - if(newSources.nonEmpty) + if (newSources.nonEmpty) edgesConnectingMethodsUpwards.update(target, oldSources ++ newSources) } @@ -199,7 +199,7 @@ class DependencyGraph[T <: DependencyGraphState] extends ReadOnlyDependencyGraph } def getAllDependencies(targets: Set[Int], includeInfeasibilityNodes: Boolean, includeUpwardEdges: Boolean, includeDownwardEdges: Boolean): Set[Int] = { - val infeasibilityNodeIds: Set[Int] = if(includeInfeasibilityNodes) Set.empty else (getAssumptionNodes filter (_.isInstanceOf[InfeasibilityNode]) map (_.id)).toSet + val infeasibilityNodeIds: Set[Int] = if (includeInfeasibilityNodes) Set.empty else (getAssumptionNodes filter (_.isInstanceOf[InfeasibilityNode]) map (_.id)).toSet var visited: Set[Int] = Set.empty var queue: List[Int] = targets.toList val allEdges = getAllEdges(includeDownwardEdges, includeUpwardEdges) @@ -213,7 +213,7 @@ class DependencyGraph[T <: DependencyGraphState] extends ReadOnlyDependencyGraph } def getAllDependents(sources: Set[Int], includeInfeasibilityNodes: Boolean, includeUpwardEdges: Boolean, includeDownwardEdges: Boolean): Set[Int] = { - val infeasibilityNodeIds: Set[Int] = if(includeInfeasibilityNodes) Set.empty else (getAssumptionNodes filter (_.isInstanceOf[InfeasibilityNode]) map (_.id)).toSet + val infeasibilityNodeIds: Set[Int] = if (includeInfeasibilityNodes) Set.empty else (getAssumptionNodes filter (_.isInstanceOf[InfeasibilityNode]) map (_.id)).toSet var visited: Set[Int] = Set.empty var queue: Set[Int] = sources val allEdges = getAllEdges(includeDownwardEdges, includeUpwardEdges) diff --git a/src/main/scala/dependencyAnalysis/cliTool/AbstractDependencyAnalysisCliTool.scala b/src/main/scala/dependencyAnalysis/cliTool/AbstractDependencyAnalysisCliTool.scala index 888b36ebd..dad50a3e6 100644 --- a/src/main/scala/dependencyAnalysis/cliTool/AbstractDependencyAnalysisCliTool.scala +++ b/src/main/scala/dependencyAnalysis/cliTool/AbstractDependencyAnalysisCliTool.scala @@ -15,9 +15,9 @@ trait AbstractDependencyAnalysisCliTool { val parts = input.split("@") if (parts.size == 2) parts(1).toIntOption.map(interpreter.getNodesByPosition(parts(0), _)).getOrElse(Set.empty) - else if(parts.size == 1){ + else if (parts.size == 1) { parts(0).toIntOption map interpreter.getNodesByLine getOrElse Set.empty - }else{ + } else { Set.empty } }) @@ -49,6 +49,6 @@ trait DependencyAnalysisCliCommand { def accept(inputs: Seq[String]): Boolean = inputs.nonEmpty && inputs.head.equals(cmdName) - def visit(inputs: Seq[String]): Unit = if(accept(inputs)) cmd(inputs.tail) + def visit(inputs: Seq[String]): Unit = if (accept(inputs)) cmd(inputs.tail) } diff --git a/src/main/scala/dependencyAnalysis/cliTool/BenchmarkDependencyAnalysisCliExtension.scala b/src/main/scala/dependencyAnalysis/cliTool/BenchmarkDependencyAnalysisCliExtension.scala index 72692ce0f..309f94263 100644 --- a/src/main/scala/dependencyAnalysis/cliTool/BenchmarkDependencyAnalysisCliExtension.scala +++ b/src/main/scala/dependencyAnalysis/cliTool/BenchmarkDependencyAnalysisCliExtension.scala @@ -16,12 +16,13 @@ import scala.util.matching.Regex class BenchmarkDependencyAnalysisCliExtension(override val interpreter: DependencyGraphInterpreter[Final], program: ast.Program) extends DependencyAnalysisCliToolExtension { override val name: String = "Benchmark Features" - override val commands: List[DependencyAnalysisCliCommand] = List( - new PerformanceBenchmarkCommand, - new GraphSizeCommand, - new AnnotateProgramCommand, - new PrecisionEvaluationCommand - ) + override val commands: List[DependencyAnalysisCliCommand] = + List( + new PerformanceBenchmarkCommand, + new GraphSizeCommand, + new AnnotateProgramCommand, + new PrecisionEvaluationCommand + ) class PerformanceBenchmarkCommand extends DependencyAnalysisCliCommand { override val cmdName: String = "benchmark" @@ -39,10 +40,10 @@ class BenchmarkDependencyAnalysisCliExtension(override val interpreter: Dependen while(check){ println("enter line number(s) for query or 'q' to quit") val userInput = readLine() - if(userInput.equalsIgnoreCase("q")){ + if (userInput.equalsIgnoreCase("q")) { println("Quit.") check = false - }else{ + } else { val inputs = userInput.split(" ").toSet val queriedNodes = getQueriedNodesFromInput(inputs) @@ -122,11 +123,6 @@ class BenchmarkDependencyAnalysisCliExtension(override val interpreter: Dependen assert(actualLabelInReportedDeps.size <= callGraphLabels.size, "Call graph size is smaller than reported dependencies.") addOutput(bw, s"$assertionLabel,${if (isSound) "YES" else "NO"},${groundTruthLabels.size},${actualLabelInReportedDeps.size},${imprecise.size},${callGraphLabels.size},${durationMs}ms,${noise.size}") - - // println(s"Queried:\n\t${getSourceInfoString(queriedAssertions)}") - // println(s"\nAll Dependencies (${timeAll}ms):\n\t$sourceDependenciesString") - // - // if(queriedAssertions.exists(_.asInstanceOf[GeneralAssertionNode].hasFailed)) println("\nQueried assertions (partially) FAILED!\n") } val dir: Path = Paths.get(pathToTestFolder) diff --git a/src/main/scala/dependencyAnalysis/cliTool/DebugDependencyAnalysisCliExtension.scala b/src/main/scala/dependencyAnalysis/cliTool/DebugDependencyAnalysisCliExtension.scala index 34597c259..0201faef5 100644 --- a/src/main/scala/dependencyAnalysis/cliTool/DebugDependencyAnalysisCliExtension.scala +++ b/src/main/scala/dependencyAnalysis/cliTool/DebugDependencyAnalysisCliExtension.scala @@ -16,7 +16,7 @@ class DebugDependencyAnalysisCliExtension(override val interpreter: DependencyGr class AssumptionTypesCommand extends DependencyAnalysisCliCommand { override val cmdName: String = "assumptionTypes" override val cmd: Seq[String] => Unit = { inputs => - if(inputs.isEmpty) + if (inputs.isEmpty) println(getAssumptionTypesPerNode().mkString("\n")) else inputs.flatMap(_.toIntOption).foreach(i => println(s"$i: ${getAssumptionTypesByLine(i)}")) @@ -37,7 +37,7 @@ class DebugDependencyAnalysisCliExtension(override val interpreter: DependencyGr class AssertionTypesCommand extends DependencyAnalysisCliCommand { override val cmdName: String = "assertionTypes" override val cmd: Seq[String] => Unit = { inputs => - if(inputs.isEmpty) + if (inputs.isEmpty) println(getAssertionTypesPerNode().mkString("\n")) else inputs.flatMap(_.toIntOption).foreach(i => println(s"$i: ${getAssertionTypesByLine(i)}")) diff --git a/src/main/scala/dependencyAnalysis/cliTool/DependencyAnalysisCliTool.scala b/src/main/scala/dependencyAnalysis/cliTool/DependencyAnalysisCliTool.scala index aebd16450..57b4ce1df 100644 --- a/src/main/scala/dependencyAnalysis/cliTool/DependencyAnalysisCliTool.scala +++ b/src/main/scala/dependencyAnalysis/cliTool/DependencyAnalysisCliTool.scala @@ -27,7 +27,7 @@ class DependencyAnalysisCliTool(fullGraphInterpreter: DependencyGraphInterpreter "\n\t'progress' to compute the verification progress of the program or" + "\n\t'guide' to compute verification guidance or" + "\n\t'prune [line numbers]' to prune the program with respect to the given line numbers and export the new program or" + - (if(extensions.nonEmpty) "\n\t" else "") + + (if (extensions.nonEmpty) "\n\t" else "") + extensions.map(_.getInfoString("\n\t")).mkString("\n\t") + "\n\t'q' to quit" @@ -98,7 +98,7 @@ class DependencyAnalysisCliTool(fullGraphInterpreter: DependencyGraphInterpreter } private def handleProofCoverageLineQuery(memberNames: Seq[String]): Unit = { - if(memberNames.isEmpty) return + if (memberNames.isEmpty) return println("Proof Coverage") val lines = memberNames.tail.flatMap(_.toIntOption) @@ -108,10 +108,10 @@ class DependencyAnalysisCliTool(fullGraphInterpreter: DependencyGraphInterpreter case _ => false }) .foreach(aa => { - val ((coverage, uncoveredSources), time) = if(lines.nonEmpty){ + val ((coverage, uncoveredSources), time) = if (lines.nonEmpty) { val assertions = lines flatMap aa.getNodesByLine measureTime(aa.computeProofCoverage(assertions.toSet)) - }else{ + } else { measureTime(aa.computeProofCoverage()) } println(s"${aa.getMember.map(_.name).getOrElse("")} (${time}ms)") @@ -154,7 +154,7 @@ class DependencyAnalysisCliTool(fullGraphInterpreter: DependencyGraphInterpreter println(s"\nDependencies without infeasibility (${timeWithoutInfeasibility}ms):\n\t${getSourceInfoString(allDependenciesWithoutInfeasibility.diff(queriedNodes))}") println(s"\nExplicit Dependencies (${timeExplicit}ms):\n\t${getSourceInfoString(explicitDependencies.diff(queriedNodes))}") - if(queriedAssertions.exists(_.asInstanceOf[GeneralAssertionNode].hasFailed)) println("\nQueried assertions (partially) FAILED!\n") + if (queriedAssertions.exists(_.asInstanceOf[GeneralAssertionNode].hasFailed)) println("\nQueried assertions (partially) FAILED!\n") println("Done.") } diff --git a/src/main/scala/dependencyAnalysis/cliTool/DependencyGraphImporter.scala b/src/main/scala/dependencyAnalysis/cliTool/DependencyGraphImporter.scala index bbb88a128..e7abb9ecf 100644 --- a/src/main/scala/dependencyAnalysis/cliTool/DependencyGraphImporter.scala +++ b/src/main/scala/dependencyAnalysis/cliTool/DependencyGraphImporter.scala @@ -49,7 +49,7 @@ object DependencyGraphImporter { private def extractGraphFolderFromArgs(args: Array[String]): String = { val idx = args.indexOf("--graphFolder") - if(0 <= idx && idx < args.length - 1) + if (0 <= idx && idx < args.length - 1) args(idx + 1) else throw new IllegalArgumentException("Error: --graphFolder argument is required but not found.") @@ -60,7 +60,7 @@ object DependencyGraphImporter { val cmds = if (0 <= cmdsIndex && cmdsIndex < args.length - 1) Some(args(cmdsIndex + 1).split(";").map(_.trim)) else None - if(cmds.isEmpty) + if (cmds.isEmpty) userTool.run() else cmds.get foreach {c => diff --git a/src/main/scala/dependencyAnalysis/cliTool/TestDependencyAnalysisCliExtension.scala b/src/main/scala/dependencyAnalysis/cliTool/TestDependencyAnalysisCliExtension.scala index a11b2c6f4..8f40a2369 100644 --- a/src/main/scala/dependencyAnalysis/cliTool/TestDependencyAnalysisCliExtension.scala +++ b/src/main/scala/dependencyAnalysis/cliTool/TestDependencyAnalysisCliExtension.scala @@ -16,7 +16,7 @@ class TestDependencyAnalysisCliExtension(override val interpreter: DependencyGra override val cmd: Seq[String] => Unit = { inputs => try{ val testSupporter = new DependencyGraphTestSupporter(interpreter) - if(inputs.isEmpty) + if (inputs.isEmpty) testSupporter.testNodeTypes() else inputs.flatMap(_.toIntOption).foreach(line => testSupporter.testNodeTypes(interpreter.getNodesByLine(line))) @@ -32,13 +32,13 @@ class TestDependencyAnalysisCliExtension(override val interpreter: DependencyGra override val cmd: Seq[String] => Unit = { inputs => try{ val testSupporter = new DependencyGraphTestSupporter(interpreter) - if(inputs.isEmpty) + if (inputs.isEmpty) testSupporter.testDependencies() else inputs.flatMap(_.toIntOption).foreach(line => { val testResult = UserLevelDependencyAnalysisNode.from(interpreter.getNodesByLine(line)) map testSupporter.testDependencies - val resultStr = if(testResult.forall(_.isEmpty)) "Skipped." - else if(testResult.forall(test => test.isEmpty || test.get)) "Passed." + val resultStr = if (testResult.forall(_.isEmpty)) "Skipped." + else if (testResult.forall(test => test.isEmpty || test.get)) "Passed." else "Failed." println(s"Line $line: $resultStr") }) diff --git a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisProgressSupporter.scala b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisProgressSupporter.scala index 377939c38..d3574d916 100644 --- a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisProgressSupporter.scala +++ b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyAnalysisProgressSupporter.scala @@ -101,7 +101,7 @@ class DependencyAnalysisProgressSupporter[T <: DependencyGraphState](interpreter val assertionNodes = sourceToAssertionNodesMap.getOrElse(assertion, Set.empty).filter(node => node.isInstanceOf[GeneralAssertionNode]) val failedAssertionNodes = assertionNodes.filter(node => node.asInstanceOf[GeneralAssertionNode].hasFailed || node.assumptionType.equals(AssumptionType.ExplicitPostcondition)) // assertions with failures have quality of 0.0 - if(failedAssertionNodes.nonEmpty) + if (failedAssertionNodes.nonEmpty) return Some(0.0) if (allDependencies.isEmpty) return None // we filter out trivial assertions, e.g. assertions that do not have any dependencies @@ -140,13 +140,13 @@ class DependencyAnalysisProgressSupporter[T <: DependencyGraphState](interpreter // compute Peter's proof quality val fullyVerifiedAssertions = assertionQualities.filter(_._1 == 1.0) - val proofQualityPeter = if(numNonTrivialAssertions > 0) fullyVerifiedAssertions.size.toDouble / numNonTrivialAssertions.toDouble else 1.0 + val proofQualityPeter = if (numNonTrivialAssertions > 0) fullyVerifiedAssertions.size.toDouble / numNonTrivialAssertions.toDouble else 1.0 // compute Lea's proof quality val assertionQualitiesSum = assertionQualities.map(_._1).sum - val proofQualityLea = if(numNonTrivialAssertions > 0) assertionQualitiesSum / numNonTrivialAssertions.toDouble else 1.0 + val proofQualityLea = if (numNonTrivialAssertions > 0) assertionQualitiesSum / numNonTrivialAssertions.toDouble else 1.0 - if(enableDebugOutput) + if (enableDebugOutput) println( s"fullyVerifiedAssertions:\n\t${fullyVerifiedAssertions.sortBy(n => (n._2.getLineNumber, n._2.toString)).mkString("\n\t")}\n" + s"assertionQualitiesSum:\n\t${assertionQualities.sortBy(n => (n._2.getLineNumber, n._2.toString)).mkString("\n\t")}" @@ -172,10 +172,10 @@ class DependencyAnalysisProgressSupporter[T <: DependencyGraphState](interpreter val nonSourceCodeAssumptionTypes = AssumptionType.explicitAssumptionTypes ++ AssumptionType.verificationAnnotationTypes val allSourceCodeNodes = toCompactUserLevelNodes(interpreter.getNonInternalAssumptionNodes).filter(n => nonSourceCodeAssumptionTypes.intersect(n.assumptionTypes).isEmpty).map(_.source).diff(explicitAssertions.map(_.source)) - if(allSourceCodeNodes.isEmpty) return 1.0 + if (allSourceCodeNodes.isEmpty) return 1.0 val coveredSourceCodeNodes = coveredNodes.map(_.source).intersect(allSourceCodeNodes) - if(enableDebugOutput) + if (enableDebugOutput) println( s"Covered Source Code:\n\t${coveredSourceCodeNodes.toList.sortBy(n => (n.getLineNumber, n.toString())).mkString("\n\t")}\n" + s"Uncovered Source Code:\n\t${allSourceCodeNodes.diff(coveredSourceCodeNodes).toList.sortBy(n => (n.getLineNumber, n.toString())).mkString("\n\t")}" @@ -227,7 +227,7 @@ class DependencyAnalysisProgressSupporter[T <: DependencyGraphState](interpreter val allSourceCodeStmts = allNodes.getSourceSet().diff(UserLevelDependencyAnalysisNode.extractByAssumptionType(allNodes, AssumptionType.explicitAssumptionTypes ++ AssumptionType.verificationAnnotationTypes).getSourceSet()).diff(explicitAssertions.getSourceSet()) val uncoveredSourceCodeStmts = allSourceCodeStmts.diff(allDependencies) - if(uncoveredSourceCodeStmts.nonEmpty) + if (uncoveredSourceCodeStmts.nonEmpty) println(s"${interpreter.getName}:\n\t${allSourceCodeStmts.diff(allDependencies).toList.sortBy(n => (n.getLineNumber, n.toString())).mkString("\n\t")}") uncoveredSourceCodeStmts.size } diff --git a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphTestSupporter.scala b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphTestSupporter.scala index d6b21735b..ca8a04a51 100644 --- a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphTestSupporter.scala +++ b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphTestSupporter.scala @@ -26,7 +26,7 @@ class DependencyGraphTestSupporter(interpreter: DependencyGraphInterpreter[Final private def testUserLevelNode(ulNode: UserLevelDependencyAnalysisNode): Option[Boolean] = { val dependencyInfoOpt = dependencyInfoRegex.findFirstMatchIn(ulNode.source.toString).map(_.group(1)) - if(dependencyInfoOpt.isEmpty) return None + if (dependencyInfoOpt.isEmpty) return None val dependencyInfo = dependencyInfoOpt.get var isTested = false @@ -55,7 +55,7 @@ class DependencyGraphTestSupporter(interpreter: DependencyGraphInterpreter[Final } private def printIfFalse(test: Boolean, message: String) = - if(!test) + if (!test) println(message) def testDependencies(): Unit = { @@ -68,7 +68,7 @@ class DependencyGraphTestSupporter(interpreter: DependencyGraphInterpreter[Final def testDependencies(assertionNode: UserLevelDependencyAnalysisNode): Option[Boolean] = { val expectedLabelsOpt = expectedDependenciesRegex.findFirstMatchIn(assertionNode.source.toString).map(_.group(1).split(",").map(_.trim).toSet) - if(expectedLabelsOpt.isEmpty) return None + if (expectedLabelsOpt.isEmpty) return None val expectedLabels = expectedLabelsOpt.get val queriedAssertions = assertionNode.lowLevelAssertionNodes diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index c80e30aa4..6b8e33996 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -578,9 +578,9 @@ object evaluator extends EvaluationRules { } else Unreachable() } else { val failure = createFailure(pve.dueTo(InternalReason(sourceQuant, "Quantifier evaluation failed.")), v1, s1, "quantifier could be evaluated") - if(s1.retryLevel == 0) v1.decider.handleFailedAssertion(False, analysisInfos, v1.reportFurtherErrors()) + if (s1.retryLevel == 0) v1.decider.handleFailedAssertion(False, analysisInfos, v1.reportFurtherErrors()) val freshVar = v1.decider.fresh(v1.symbolConverter.toSort(sourceQuant.typ), None) - if(s1.retryLevel == 0 && v1.reportFurtherErrors()) failure combine Q(s1, freshVar, None, v1) else failure + if (s1.retryLevel == 0 && v1.reportFurtherErrors()) failure combine Q(s1, freshVar, None, v1) else failure } } @@ -640,7 +640,7 @@ object evaluator extends EvaluationRules { val pvePre = ErrorWrapperWithExampleTransformer(PreconditionInAppFalse(fapp).withReasonNodeTransformed(reasonOffendingNode => reasonOffendingNode.replace(formalsToActuals)), exampleTrafo) - val argsPairs: Seq[(Term, Option[ast.Exp])] = if(Verifier.config.enableDependencyAnalysis()){ + val argsPairs: Seq[(Term, Option[ast.Exp])] = if (Verifier.config.enableDependencyAnalysis()) { tArgs zip eArgs.map(Some(_)) } else if (withExp) tArgs.zip(eArgsNew.get.map(Some(_))) else tArgs.zip(Seq.fill(tArgs.size)(None)) // encode the function call as a sequence of assignments to fresh variables (one for each argument) and a method call using the fresh variables as arguments @@ -651,7 +651,7 @@ object evaluator extends EvaluationRules { val argNew = v1.decider.fresh(arg._1.sort, None) val constraint = Equals(argNew, arg._1) v1.decider.assume(constraint, None, analysisInfos.withMergeInfo(SimpleDependencyAnalysisMerge(AnalysisSourceInfo.createAnalysisSourceInfo(arg._2.get)))) - s2b = s2b.copy(functionRecorder = s2b.functionRecorder.recordConstrainedVar(argNew, constraint)) + s2b = s2b.copy(functionRecorder = s2b.functionRecorder.recordConstrainedVar(argNew, constraint)) // FIXME ake: the new variables are introduced as globals but they should appear as quantified variables in function axioms. (argNew, None) }) } else argsPairs @@ -722,7 +722,7 @@ object evaluator extends EvaluationRules { */ })(join(func.typ, s"joined_${func.name}", joinFunctionArgs, joinExp, v1, analysisInfos))((s6, r, v4) => { - if(v4.decider.isDependencyAnalysisEnabled) v4.decider.dependencyAnalyzer.addCustomDependenciesBetweenMergeInfos(presWithDAInfo, Seq(DependencyAnalysisMergeInfo.attachExpMergeInfo(fapp, analysisInfos.getMergeInfo))) + if (v4.decider.isDependencyAnalysisEnabled) v4.decider.dependencyAnalyzer.addCustomDependenciesBetweenMergeInfos(presWithDAInfo, Seq(DependencyAnalysisMergeInfo.attachExpMergeInfo(fapp, analysisInfos.getMergeInfo))) Q(s6, r._1, r._2, v4) })}) @@ -815,9 +815,9 @@ object evaluator extends EvaluationRules { case false => v2.decider.finishDebugSubExp(s"unfolded(${predicate.name})") val failure = createFailure(pve dueTo NonPositivePermission(ePerm.get), v2, s2a, IsPositive(tPerm), ePermNew.map(p => ast.PermGtCmp(p, ast.NoPerm()())(p.pos, p.info, p.errT))) - if(s2a.retryLevel == 0) v2.decider.handleFailedAssertion(False, analysisInfos, v2.reportFurtherErrors()) + if (s2a.retryLevel == 0) v2.decider.handleFailedAssertion(False, analysisInfos, v2.reportFurtherErrors()) val freshVar = v2.decider.fresh(v2.symbolConverter.toSort(e.typ), None) - if(s2a.retryLevel == 0 && v2.reportFurtherErrors() && Verifier.config.disableInfeasibilityChecks()) failure combine Q(s2a, freshVar, None, v2) else failure + if (s2a.retryLevel == 0 && v2.reportFurtherErrors() && Verifier.config.disableInfeasibilityChecks()) failure combine Q(s2a, freshVar, None, v2) else failure }})) } else { val unknownValue = v.decider.appliedFresh("recunf", v.symbolConverter.toSort(eIn.typ), s.relevantQuantifiedVariables.map(_._1)) @@ -865,7 +865,7 @@ object evaluator extends EvaluationRules { case false => val assertExp2 = Option.when(withExp)(ast.LtCmp(e1, ast.SeqLength(e0)())(e1.pos, e1.info, e1.errT)) val failure = createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, Less(t1, SeqLength(t0)), assertExp2) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertion(Less(t1, SeqLength(t0)), analysisInfos, assumeFailedAssertion=false) + if (s1.retryLevel == 0) v1.decider.handleFailedAssertion(Less(t1, SeqLength(t0)), analysisInfos, assumeFailedAssertion=false) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { val assertExp2 = Option.when(withExp)(ast.LeCmp(e1, ast.SeqLength(e0)())()) val assertExp2New = esNew.map(es => ast.LeCmp(es(1), ast.SeqLength(es.head)())()) @@ -876,7 +876,7 @@ object evaluator extends EvaluationRules { val assertExp1 = Option.when(withExp)(ast.GeCmp(e1, ast.IntLit(0)())(e1.pos, e1.info, e1.errT)) val assertExp1New = Option.when(withExp)(ast.GeCmp(esNew.get(1), ast.IntLit(0)())(e1.pos, e1.info, e1.errT)) val failure1 = createFailure(pve dueTo SeqIndexNegative(e0, e1), v1, s1, AtLeast(t1, IntLiteral(0)), assertExp1New) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertion(AtLeast(t1, IntLiteral(0)), analysisInfos, assumeFailedAssertion=false) + if (s1.retryLevel == 0) v1.decider.handleFailedAssertion(AtLeast(t1, IntLiteral(0)), analysisInfos, assumeFailedAssertion=false) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { v1.decider.assume(AtLeast(t1, IntLiteral(0)), assertExp1, assertExp1New, analysisInfos.withDependencyType(DependencyType.Explicit)) val assertExp2 = Option.when(withExp)(ast.LtCmp(e1, ast.SeqLength(e0)())(e1.pos, e1.info, e1.errT)) @@ -886,7 +886,7 @@ object evaluator extends EvaluationRules { failure1 combine Q(s1, SeqAt(t0, t1), eNew, v1) case false => val failure2 = failure1 combine createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, Less(t1, SeqLength(t0)), assertExp2New) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertion(Less(t1, SeqLength(t0)), analysisInfos, assumeFailedAssertion=false) + if (s1.retryLevel == 0) v1.decider.handleFailedAssertion(Less(t1, SeqLength(t0)), analysisInfos, assumeFailedAssertion=false) if (v1.reportFurtherErrors()) { v1.decider.assume(Less(t1, SeqLength(t0)), assertExp2, assertExp2New, analysisInfos.withDependencyType(DependencyType.Explicit)) failure2 combine Q(s1, SeqAt(t0, t1), eNew, v1) @@ -921,7 +921,7 @@ object evaluator extends EvaluationRules { Q(s1, SeqUpdate(t0, t1, t2), eNew, v1) case false => val failure = createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, Less(t1, SeqLength(t0)), assertExp2New) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertion(Less(t1, SeqLength(t0)), analysisInfos, assumeFailedAssertion=false) + if (s1.retryLevel == 0) v1.decider.handleFailedAssertion(Less(t1, SeqLength(t0)), analysisInfos, assumeFailedAssertion=false) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { val assertExp3 = Option.when(withExp)(ast.LeCmp(e1, ast.SeqLength(e0)())()) val assertExp3New = Option.when(withExp)(ast.LeCmp(esNew.get(1), ast.SeqLength(esNew.get(0))())()) @@ -930,7 +930,7 @@ object evaluator extends EvaluationRules { else failure} case false => val failure1 = createFailure(pve dueTo SeqIndexNegative(e0, e1), v1, s1, AtLeast(t1, IntLiteral(0)), assertExpNew) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertion(AtLeast(t1, IntLiteral(0)), analysisInfos, assumeFailedAssertion=false) + if (s1.retryLevel == 0) v1.decider.handleFailedAssertion(AtLeast(t1, IntLiteral(0)), analysisInfos, assumeFailedAssertion=false) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { v1.decider.assume(AtLeast(t1, IntLiteral(0)), assertExp, assertExpNew, analysisInfos.withDependencyType(DependencyType.Explicit)) val assertExp2 = Option.when(withExp)(ast.LtCmp(e1, ast.SeqLength(e0)())(e1.pos, e1.info, e1.errT)) @@ -940,7 +940,7 @@ object evaluator extends EvaluationRules { failure1 combine Q(s1, SeqUpdate(t0, t1, t2), eNew, v1) case false => val failure2 = failure1 combine createFailure(pve dueTo SeqIndexExceedsLength(e0, e1), v1, s1, Less(t1, SeqLength(t0)), assertExp2New) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertion(Less(t1, SeqLength(t0)), analysisInfos, assumeFailedAssertion=false) + if (s1.retryLevel == 0) v1.decider.handleFailedAssertion(Less(t1, SeqLength(t0)), analysisInfos, assumeFailedAssertion=false) if (v1.reportFurtherErrors()) { v1.decider.assume(Less(t1, SeqLength(t0)), assertExp2, assertExp2New, analysisInfos.withDependencyType(DependencyType.Explicit)) failure2 combine Q(s1, SeqUpdate(t0, t1, t2), eNew, v1) @@ -1060,7 +1060,7 @@ object evaluator extends EvaluationRules { val assertExp = Option.when(withExp)(ast.MapContains(key, base)(ml.pos, ml.info, ml.errT)) val assertExpNew = Option.when(withExp)(ast.MapContains(esNew.get(1), esNew.get(0))(ml.pos, ml.info, ml.errT)) val failure1 = createFailure(pve dueTo MapKeyNotContained(base, key), v1, s1, SetIn(keyT, MapDomain(baseT)), assertExpNew) - if(s1.retryLevel == 0) v1.decider.handleFailedAssertion(SetIn(keyT, MapDomain(baseT)), analysisInfos, assumeFailedAssertion=false) + if (s1.retryLevel == 0) v1.decider.handleFailedAssertion(SetIn(keyT, MapDomain(baseT)), analysisInfos, assumeFailedAssertion=false) if (s1.retryLevel == 0 && v1.reportFurtherErrors()) { v1.decider.assume(SetIn(keyT, MapDomain(baseT)), assertExp, assertExpNew, analysisInfos.withDependencyType(DependencyType.Explicit)) failure1 combine Q(s1, MapLookup(baseT, keyT), eNew, v1) @@ -1297,7 +1297,7 @@ object evaluator extends EvaluationRules { (Some(ast.NeCmp(eDivisor, ast.IntLit(0)())(eDivisor.pos, eDivisor.info, eDivisor.errT)), Some(ast.NeCmp(eDivisorNew.get, ast.IntLit(0)())(eDivisor.pos, eDivisor.info, eDivisor.errT))) } else { (None, None) } val failure = createFailure(pve dueTo DivisionByZero(eDivisor), v, s, tDivisor !== tZero, notZeroExpNew) - if(s.retryLevel == 0) v.decider.handleFailedAssertion(tDivisor !== tZero, analysisInfos, assumeFailedAssertion=false) + if (s.retryLevel == 0) v.decider.handleFailedAssertion(tDivisor !== tZero, analysisInfos, assumeFailedAssertion=false) if (s.retryLevel == 0 && v.reportFurtherErrors()) { v.decider.assume(tDivisor !== tZero, notZeroExp, notZeroExpNew, analysisInfos.withDependencyType(DependencyType.Explicit)) failure combine Q(s, t, v) diff --git a/src/main/scala/rules/Executor.scala b/src/main/scala/rules/Executor.scala index 940e6b04d..1a30a83b3 100644 --- a/src/main/scala/rules/Executor.scala +++ b/src/main/scala/rules/Executor.scala @@ -481,7 +481,7 @@ object executor extends ExecutionRules { case assert @ ast.Assert(a) => val pve = AssertFailed(assert) - val analysisInfos1 = if(a.topLevelConjuncts.size > 1) analysisInfos.copy(sourceInfos = List.empty) else analysisInfos // needed to ensure that each top-level conjunct gets a dedicated assertion node + val analysisInfos1 = if (a.topLevelConjuncts.size > 1) analysisInfos.copy(sourceInfos = List.empty) else analysisInfos // needed to ensure that each top-level conjunct gets a dedicated assertion node if (s.exhaleExt) { Predef.assert(s.h.values.isEmpty) @@ -552,7 +552,7 @@ object executor extends ExecutionRules { val preCondLog = new CommentRecord("Precondition", s1, v1.decider.pcs) val preCondId = v1.symbExLog.openScope(preCondLog) val argsWithExp: Seq[(Term, Option[ast.Exp])] = { - if(Verifier.config.enableDependencyAnalysis()){ + if (Verifier.config.enableDependencyAnalysis()) { tArgs zip eArgsWithDAInfo.map(Some(_)) } else if (withExp) tArgs zip (eArgsNew.get.map(Some(_))) @@ -561,18 +561,18 @@ object executor extends ExecutionRules { } // encode the method call as a sequence of assignments to fresh variables (one for each argument) and a method call using the fresh variables as arguments val argsFreshVar = - if(Verifier.config.enableDependencyAnalysis()){ + if (Verifier.config.enableDependencyAnalysis()) { argsWithExp.map(arg => { val argNew = v1.decider.fresh(arg._1.sort, None) v1.decider.assume(Equals(argNew, arg._1), None, analysisInfos.withMergeInfo(SimpleDependencyAnalysisMerge(AnalysisSourceInfo.createAnalysisSourceInfo(arg._2.get)))) (argNew, None) }) - }else argsWithExp + } else argsWithExp val s2 = s1.copy(g = Store(fargs.zip(argsFreshVar)), recordVisited = true) - val presWithDAInfo = if(!v1.decider.isDependencyAnalysisEnabled) meth.pres else DependencyAnalysisMergeInfo.attachExpMergeInfo(meth.pres.flatMap(_.topLevelConjuncts), Some(analysisInfos.getSourceInfo)) + val presWithDAInfo = if (!v1.decider.isDependencyAnalysisEnabled) meth.pres else DependencyAnalysisMergeInfo.attachExpMergeInfo(meth.pres.flatMap(_.topLevelConjuncts), Some(analysisInfos.getSourceInfo)) consumes(s2, presWithDAInfo, false, _ => pvePre, v1, analysisInfos.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Source, EdgeType.Up)))((s3, _, v2) => { v2.symbExLog.closeScope(preCondId) @@ -582,7 +582,7 @@ object executor extends ExecutionRules { val gOuts = Store(outs.map(x => (x, v2.decider.fresh(x))).toMap) val s4 = s3.copy(g = s3.g + gOuts, oldHeaps = s3.oldHeaps + (Verifier.PRE_STATE_LABEL -> magicWandSupporter.getEvalHeap(s1))) - val postsWithDAInfo = if(!v1.decider.isDependencyAnalysisEnabled) meth.posts else DependencyAnalysisMergeInfo.attachExpMergeInfo(meth.posts.flatMap(_.topLevelConjuncts), Some(analysisInfos.getSourceInfo)) + val postsWithDAInfo = if (!v1.decider.isDependencyAnalysisEnabled) meth.posts else DependencyAnalysisMergeInfo.attachExpMergeInfo(meth.posts.flatMap(_.topLevelConjuncts), Some(analysisInfos.getSourceInfo)) produces(s4, freshSnap, postsWithDAInfo, _ => pveCallTransformed, v2, analysisInfos.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Sink, EdgeType.Down)))((s5, v3) => { v3.symbExLog.closeScope(postCondId) v3.decider.prover.saturate(Verifier.config.proverSaturationTimeouts.afterContract) diff --git a/src/main/scala/rules/HeapSupporter.scala b/src/main/scala/rules/HeapSupporter.scala index 93068acb1..8f4d856d9 100644 --- a/src/main/scala/rules/HeapSupporter.scala +++ b/src/main/scala/rules/HeapSupporter.scala @@ -241,7 +241,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { case (Incomplete(_, _), s3, _) => val failure = createFailure(ve, v, s3, "sufficient permission") if (s3.retryLevel == 0) v.decider.handleFailedAssertion(False, analysisInfos, v.reportFurtherErrors()) - if(s3.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s3, v) else failure + if (s3.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s3, v) else failure } } else { val description = s"consume ${ass.pos}: $ass" @@ -269,7 +269,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { analysisInfos: DependencyAnalysisInfos) (Q: (State, Term, Verifier) => VerificationResult): VerificationResult = { - if(v.decider.isPathInfeasible){ + if (v.decider.isPathInfeasible) { v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfos) return Q(s, NoPerm, v) } @@ -337,7 +337,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { analysisInfos: DependencyAnalysisInfos) (Q: (State, Term, Verifier) => VerificationResult) : VerificationResult = { - if(v.decider.isPathInfeasible){ + if (v.decider.isPathInfeasible) { v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfos) val sort = v.symbolConverter.toSort(fa.field.typ) @@ -375,9 +375,9 @@ class DefaultHeapSupportRules extends HeapSupportRules { v.decider.assert(toAssert, analysisInfos) { case false => val failure = createFailure(ve, v, s, toAssert, Option.when(withExp)(perms.IsPositive(ast.CurrentPerm(fa)())())) - if(s.retryLevel == 0) v.decider.handleFailedAssertion(toAssert, analysisInfos, v.reportFurtherErrors()) + if (s.retryLevel == 0) v.decider.handleFailedAssertion(toAssert, analysisInfos, v.reportFurtherErrors()) val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(fa.field, s, v), Option.when(withExp)(PUnknown())) - if(s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, snap, v) else failure + if (s.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s, snap, v) else failure case true => val fvfLookup = Lookup(fa.field.name, fvfDef.sm, tRcvr) val fr1 = s.functionRecorder.recordSnapshot(fa, v.decider.pcs.branchConditions, fvfLookup).recordFvfAndDomain(fvfDef) @@ -429,9 +429,9 @@ class DefaultHeapSupportRules extends HeapSupportRules { v.decider.assert(permCheck,analysisInfos) { case false => val failure = createFailure(ve, v, s3, permCheck, permCheckExp) - if(s3.retryLevel == 0) v.decider.handleFailedAssertion(permCheck, analysisInfos, v.reportFurtherErrors()) + if (s3.retryLevel == 0) v.decider.handleFailedAssertion(permCheck, analysisInfos, v.reportFurtherErrors()) val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(fa.field, s3, v), Option.when(withExp)(PUnknown())) - if(s3.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s3, snap, v) else failure + if (s3.retryLevel == 0 && v.reportFurtherErrors()) failure combine Q(s3, snap, v) else failure case true => val smLookup = Lookup(fa.field.name, sm, tRcvr) val fr2 = @@ -544,7 +544,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { v: Verifier, analysisInfos: DependencyAnalysisInfos) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult): VerificationResult = { - if(v.decider.isPathInfeasible){ + if (v.decider.isPathInfeasible) { v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfos) return Q(s, h, Some(Unit), v) } @@ -660,7 +660,7 @@ class DefaultHeapSupportRules extends HeapSupportRules { v: Verifier, analysisInfos: DependencyAnalysisInfos) (Q: (State, Heap, Option[Term], Verifier) => VerificationResult): VerificationResult = { - if(v.decider.isPathInfeasible){ + if (v.decider.isPathInfeasible) { v.decider.dependencyAnalyzer.addAssertionWithDepToInfeasNode(v.decider.pcs.getCurrentInfeasibilityNode, analysisInfos) return Q(s, h, Some(Unit), v) } diff --git a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala index 8b0f04ba8..ed18932de 100644 --- a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala +++ b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala @@ -541,7 +541,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { if (s.retryLevel == 0) v.decider.handleFailedAssertion(Implies(PermLess(NoPerm, perms), totalPermTaken !== NoPerm), analysisInfos, v.reportFurtherErrors()) if (s.retryLevel == 0 && v.reportFurtherErrors()){ val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) - failure combine Q(s1, updatedChunks, if(returnSnap) Some(snap) else None, v) + failure combine Q(s1, updatedChunks, if (returnSnap) Some(snap) else None, v) }else{ failure } diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 7e2ba842f..450649627 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -1381,27 +1381,27 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { } case (Incomplete(_, _), s2, _) => val failure = createFailure(pve dueTo insufficientPermissionReason, v, s2, "QP consume") - if(s2.retryLevel == 0) v.decider.handleFailedAssertion(False, analysisInfos, v.reportFurtherErrors()) - if(s2.retryLevel == 0 && v.reportFurtherErrors() && Verifier.config.disableInfeasibilityChecks()) failure combine Q(s2, s2.h, None, v) else failure + if (s2.retryLevel == 0) v.decider.handleFailedAssertion(False, analysisInfos, v.reportFurtherErrors()) + if (s2.retryLevel == 0 && v.reportFurtherErrors() && Verifier.config.disableInfeasibilityChecks()) failure combine Q(s2, s2.h, None, v) else failure } } case false => val failure = createFailure(pve dueTo notInjectiveReason, v, s, receiverInjectivityCheck, "QP receiver injective") - if(s.retryLevel == 0) v.decider.handleFailedAssertion(receiverInjectivityCheck, analysisInfos, v.reportFurtherErrors()) - if(s.retryLevel == 0 && v.reportFurtherErrors()){ + if (s.retryLevel == 0) v.decider.handleFailedAssertion(receiverInjectivityCheck, analysisInfos, v.reportFurtherErrors()) + if (s.retryLevel == 0 && v.reportFurtherErrors()) { val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) - failure combine Q(s, s.h, if(returnSnap) Some(snap) else None, v) - }else{ + failure combine Q(s, s.h, if (returnSnap) Some(snap) else None, v) + } else { failure } } case false => val failure = createFailure(pve dueTo negativePermissionReason, v, s, nonNegTerm, nonNegExp) - if(s.retryLevel == 0) v.decider.handleFailedAssertion(nonNegTerm, analysisInfos, v.reportFurtherErrors()) - if(s.retryLevel == 0 && v.reportFurtherErrors()){ + if (s.retryLevel == 0) v.decider.handleFailedAssertion(nonNegTerm, analysisInfos, v.reportFurtherErrors()) + if (s.retryLevel == 0 && v.reportFurtherErrors()) { val snap = v.decider.fresh(v.snapshotSupporter.optimalSnapshotSort(resource, s, v), Option.when(withExp)(PUnknown())) - failure combine Q(s, s.h, if(returnSnap) Some(snap) else None, v) - }else{ + failure combine Q(s, s.h, if (returnSnap) Some(snap) else None, v) + } else { failure } } diff --git a/src/main/scala/supporters/functions/FunctionData.scala b/src/main/scala/supporters/functions/FunctionData.scala index a91fb99e0..6d41a6f1f 100644 --- a/src/main/scala/supporters/functions/FunctionData.scala +++ b/src/main/scala/supporters/functions/FunctionData.scala @@ -141,7 +141,7 @@ class FunctionData(val programFunction: ast.Function, private val bodyAnalysisInfos: DependencyAnalysisInfos = - if(programFunction.body.isDefined) + if (programFunction.body.isDefined) DependencyAnalysisInfos.DefaultDependencyAnalysisInfos.addInfo(programFunction.body.get.info, programFunction.body.get) .withJoinInfo(SimpleDependencyAnalysisJoin(AnalysisSourceInfo.createAnalysisSourceInfo(programFunction.body.get), JoinType.Sink, EdgeType.Down)) .withEnabled(isAnalysisEnabled) @@ -190,7 +190,7 @@ class FunctionData(val programFunction: ast.Function, ++ freshConstrainedVars.map(_._2) ++ freshConstraints) - val nested = if(!Verifier.config.enableDependencyAnalysis()) nestedTmp + val nested = if (!Verifier.config.enableDependencyAnalysis()) nestedTmp else nestedTmp.map(_.transform{ case Var(name, _, _) if name.name.startsWith(DependencyAnalyzer.analysisLabelName) => True // replace dependency analysis labels by True to avoid errors }()) @@ -247,13 +247,13 @@ class FunctionData(val programFunction: ast.Function, def wrapBody(body: Term): Term = Let(toMap(bodyBindings), body) val analysisInfos = DependencyAnalysisInfos.DefaultDependencyAnalysisInfos.withEnabled(isAnalysisEnabled) - if(isAnalysisEnabled){ + if (isAnalysisEnabled) { (Forall(arguments, wrapBody(And(generateNestedDefinitionalAxioms)), Trigger(limitedFunctionApplication)), bodyAnalysisInfos) +: programFunction.posts.flatMap(_.topLevelConjuncts).map({p => val terms = expressionTranslator.translatePostcondition(program, Seq(p), this) (And(Forall(arguments, wrapBody(Implies(pre, And(terms))), Trigger(limitedFunctionApplication)), True), analysisInfos.addInfo(p.info, p).withJoinInfo(SimpleDependencyAnalysisJoin(AnalysisSourceInfo.createAnalysisSourceInfo(p), JoinType.Sink, EdgeType.Down))) }) - }else{ + } else { val innermostBody = And(generateNestedDefinitionalAxioms ++ List(Implies(pre, And(translatedPosts)))) Seq((Forall(arguments, wrapBody(innermostBody), Trigger(limitedFunctionApplication)), analysisInfos)) } diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index 46d7f4e87..2d53a50af 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -652,7 +652,7 @@ class DefaultMainVerifier(config: Config, def runDependencyAnalysisWorkflow(verificationResults: List[VerificationResult], program: ast.Program, inputFile: Option[String]): Unit = { - if(!Verifier.config.enableDependencyAnalysis()) return + if (!Verifier.config.enableDependencyAnalysis()) return val dependencyGraphInterpreters = verificationResults.filter(_.dependencyGraphInterpreter.isDefined).map(_.dependencyGraphInterpreter.get) val verificationErrors: List[Failure] = (verificationResults filter (_.isInstanceOf[Failure])) map (_.asInstanceOf[Failure]) diff --git a/src/test/scala/DependencyAnalysisTestFramework.scala b/src/test/scala/DependencyAnalysisTestFramework.scala index a9d1f7562..85fc6e929 100644 --- a/src/test/scala/DependencyAnalysisTestFramework.scala +++ b/src/test/scala/DependencyAnalysisTestFramework.scala @@ -37,9 +37,9 @@ trait DependencyAnalysisTestFramework { for (filePath: Path <- dirContent.sorted if Files.isReadable(filePath)) { - if(Files.isDirectory(filePath)){ + if (Files.isDirectory(filePath)) { visitFiles(filePath, dirName + "/" + filePath.getFileName.toString, function) - }else{ + } else { val rawFileName = filePath.getFileName.toString if (rawFileName.endsWith(".vpr")) { val fileName = rawFileName.replace(".vpr", "") @@ -200,7 +200,7 @@ trait DependencyAnalysisTestFramework { val relevantAssumptionNodes = getTestAssumptionNodes(dependencyGraphInterpreter.getNonInternalAssumptionNodes) val resRelevant: Seq[String] = checkDependenciesAndGetErrorMsgs(relevantAssumptionNodes, dependencies, isDependencyExpected = true, "Missing dependency") - val resIrrelevant = if(checkPrecision){ + val resIrrelevant = if (checkPrecision) { val irrelevantNodes = getTestIrrelevantAssumptionNodes(dependencyGraphInterpreter.getNonInternalAssumptionNodes) checkDependenciesAndGetErrorMsgs(irrelevantNodes, dependencies, isDependencyExpected = false, "Unexpected dependency") } else Seq.empty diff --git a/src/test/scala/DependencyAnalysisTests.scala b/src/test/scala/DependencyAnalysisTests.scala index ae7ea9296..39c7f2f69 100644 --- a/src/test/scala/DependencyAnalysisTests.scala +++ b/src/test/scala/DependencyAnalysisTests.scala @@ -22,7 +22,7 @@ class DependencyAnalysisTests extends AnyFunSuite with DependencyAnalysisTestFra "dependencyAnalysisTests/guidance", ) - if(EXECUTE_TEST) { + if (EXECUTE_TEST) { testDirectories foreach (dir => visitFiles(dir, createSingleTest)) // TODO ake: more complete exhale tests // analysisCommandLineArguments = Seq("--enableMoreCompleteExhale") ++ analysisCommandLineArguments @@ -46,7 +46,7 @@ class DependencyAnalysisTests extends AnyFunSuite with DependencyAnalysisTestFra val program: Program = tests.loadProgram(filePrefix, fileName, frontend) val result = frontend.verifier.verify(program) - if(result.isInstanceOf[verifier.Failure]) { + if (result.isInstanceOf[verifier.Failure]) { cancel(f"Program does not verify. Skip test.\n$result") return } From 350b4afa7b45172f51a5df061443a204c32bea7d Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Tue, 23 Jun 2026 16:01:12 +0200 Subject: [PATCH 472/474] add progress test --- .../verificationProgressTests/functions3.vpr | 35 +++++++++++++++++++ .../DependencyAnalysisTestFramework.scala | 5 ++- 2 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 src/test/resources/dependencyAnalysisTests/verificationProgressTests/functions3.vpr diff --git a/src/test/resources/dependencyAnalysisTests/verificationProgressTests/functions3.vpr b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/functions3.vpr new file mode 100644 index 000000000..ed72cd3d6 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/verificationProgressTests/functions3.vpr @@ -0,0 +1,35 @@ +// Spec quality: 1 +// Proof quality: 0.68201 +// Progress: 0.68201 + +function div(x: Int, y: Int) : Int + requires x > 0 && y > 0 + ensures result >= 0 // PQ: 7/9 +{ + x / y // PQ: 5/7 +} + + +method client1() +{ + var a: Int + assume a > 0 + var b: Int := 100 + + var res: Int + res := div(a, b) // PQ: 1/2 + + assert res == a / 100 // PQ: 4/5 +} + +method client2() +{ + var a: Int := 100 + var b: Int + assume b > 0 + + var res: Int + res := div(a, b) // PQ: 1/2 + + assert res == 100 / b // PQ: 4/5 +} \ No newline at end of file diff --git a/src/test/scala/DependencyAnalysisTestFramework.scala b/src/test/scala/DependencyAnalysisTestFramework.scala index 85fc6e929..f742062ec 100644 --- a/src/test/scala/DependencyAnalysisTestFramework.scala +++ b/src/test/scala/DependencyAnalysisTestFramework.scala @@ -2,8 +2,7 @@ package viper.silicon.tests import viper.silicon.SiliconFrontend import viper.silicon.dependencyAnalysis._ -import viper.silicon.dependencyAnalysis.graphInterpretation.{DependencyAnalysisPruningSupporter, DependencyAnalysisProgressSupporter, DependencyGraphInterpreter} - +import viper.silicon.dependencyAnalysis.graphInterpretation.{DependencyAnalysisProgressSupporter, DependencyAnalysisPruningSupporter, DependencyGraphInterpreter} import viper.silver.ast.utility.ViperStrategy import viper.silver.ast.{Infoed, Program} import viper.silver.dependencyAnalysis.AssumptionType @@ -257,7 +256,7 @@ trait DependencyAnalysisTestFramework { * [value] can be either a decimal number (e.g., 0.75) or a fraction (e.g., 5/6) */ class VerificationProgressTest(fileName: String, fullGraphInterpreter: DependencyGraphInterpreter[Final]) { - private val epsilon = 1e-8 + private val epsilon = 1e-6 def execute(): Unit = { val (expectedSpecQuality, expectedProofQualityLea, expectedProgress) = readExpectedValues() From d830c5013ef06e351c446da6e140e4b13b282549 Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 25 Jun 2026 14:10:31 +0200 Subject: [PATCH 473/474] cleanup --- .../BenchmarkDependencyAnalysisCliExtension.scala | 6 +++--- .../cliTool/DependencyAnalysisCliTool.scala | 11 ++++++----- .../DependencyGraphTestSupporter.scala | 5 +++-- src/main/scala/supporters/MethodSupporter.scala | 5 +++-- src/main/scala/verifier/DefaultMainVerifier.scala | 1 + 5 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/main/scala/dependencyAnalysis/cliTool/BenchmarkDependencyAnalysisCliExtension.scala b/src/main/scala/dependencyAnalysis/cliTool/BenchmarkDependencyAnalysisCliExtension.scala index 309f94263..791ca8fb9 100644 --- a/src/main/scala/dependencyAnalysis/cliTool/BenchmarkDependencyAnalysisCliExtension.scala +++ b/src/main/scala/dependencyAnalysis/cliTool/BenchmarkDependencyAnalysisCliExtension.scala @@ -143,7 +143,7 @@ class BenchmarkDependencyAnalysisCliExtension(override val interpreter: Dependen callGraphs.foreach { case (assertionLabel, callGraphLabels) => evalSingleAssertion(assertionLabel, groundTruths(assertionLabel), callGraphLabels, bw) } bw.close() - println("Done.") + } catch { case e: Throwable => println(s"Failed. ${e.getMessage}") } finally { @@ -215,7 +215,7 @@ class BenchmarkDependencyAnalysisCliExtension(override val interpreter: Dependen val writer = new PrintWriter(resultFileName) writer.println(newProgram.toString()) writer.close() - println("Done.") + } } @@ -238,7 +238,7 @@ class BenchmarkDependencyAnalysisCliExtension(override val interpreter: Dependen println(s"#low-level Assumptions (all) = ${interpreter.getAssumptionNodes.size}") println(s"#low-level Assertions (non-internal) = ${allAssertions.size}") println(s"#low-level Assertions (all) = ${interpreter.getAssertionNodes.size}") - println("Done.") + } } diff --git a/src/main/scala/dependencyAnalysis/cliTool/DependencyAnalysisCliTool.scala b/src/main/scala/dependencyAnalysis/cliTool/DependencyAnalysisCliTool.scala index 57b4ce1df..87ee605ad 100644 --- a/src/main/scala/dependencyAnalysis/cliTool/DependencyAnalysisCliTool.scala +++ b/src/main/scala/dependencyAnalysis/cliTool/DependencyAnalysisCliTool.scala @@ -73,6 +73,7 @@ class DependencyAnalysisCliTool(fullGraphInterpreter: DependencyGraphInterpreter case "prune" => handlePruningRequest(inputParts.tail) case _ => extensions.foreach(_.visit(inputParts)) } + println("Done.") } else { println("Invalid input."); println(infoString) } @@ -94,7 +95,7 @@ class DependencyAnalysisCliTool(fullGraphInterpreter: DependencyGraphInterpreter println(s"uncovered nodes:\n\t${uncoveredSources.mkString("\n\t")}") println(s"#uncovered nodes:\n\t${uncoveredSources.size}") }) - println("Done.") + } private def handleProofCoverageLineQuery(memberNames: Seq[String]): Unit = { @@ -120,7 +121,7 @@ class DependencyAnalysisCliTool(fullGraphInterpreter: DependencyGraphInterpreter println(s"uncovered nodes:\n\t${uncoveredSources.mkString("\n\t")}") println(s"#uncovered nodes:\n\t${uncoveredSources.size}") }) - println("Done.") + } def handleVerificationProgressQuery(inputs: Seq[String], exportFileNameOpt: Option[String] = None): Unit = { @@ -155,7 +156,7 @@ class DependencyAnalysisCliTool(fullGraphInterpreter: DependencyGraphInterpreter println(s"\nExplicit Dependencies (${timeExplicit}ms):\n\t${getSourceInfoString(explicitDependencies.diff(queriedNodes))}") if (queriedAssertions.exists(_.asInstanceOf[GeneralAssertionNode].hasFailed)) println("\nQueried assertions (partially) FAILED!\n") - println("Done.") + } private def handleDependentsQuery(inputs: Set[String]): Unit = { @@ -171,7 +172,7 @@ class DependencyAnalysisCliTool(fullGraphInterpreter: DependencyGraphInterpreter println(s"\nAll Dependents (${timeAll}ms):\n\t${getSourceInfoString(allDependents)}") println(s"\nDependents without infeasibility (${timeWithoutInfeasibility}ms):\n\t${getSourceInfoString(dependentsWithoutInfeasibility)}") println(s"\nExplicit Dependents (${timeExplicit}ms):\n\t${getSourceInfoString(explicitDependents)}") - println("Done.") + } def handlePruningRequest(inputs: Seq[String], exportFileNameOpt: Option[String] = None): Unit = { @@ -181,7 +182,7 @@ class DependencyAnalysisCliTool(fullGraphInterpreter: DependencyGraphInterpreter } val queriedNodes = getQueriedNodesFromInput(inputs.toSet) fullGraphInterpreter.pruningSupporter.pruneProgramAndExport(queriedNodes, program, exportFileName) - println("Done.") + } private def handleVerificationGuidanceQuery(): Unit = { diff --git a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphTestSupporter.scala b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphTestSupporter.scala index ca8a04a51..bdfa29d29 100644 --- a/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphTestSupporter.scala +++ b/src/main/scala/dependencyAnalysis/graphInterpretation/DependencyGraphTestSupporter.scala @@ -78,8 +78,9 @@ class DependencyGraphTestSupporter(interpreter: DependencyGraphInterpreter[Final val labelsInReportedDeps: Set[Set[String]] = sourceDependencies.map(node => nodeLabelRegex.findAllMatchIn(node.toString).map(_.group(1)).toSet) val actualLabelInReportedDeps = labelsInReportedDeps.filter(_.size == 1).flatten - val isSound = expectedLabels.diff(actualLabelInReportedDeps).isEmpty - printIfFalse(isSound, s"Missing dependencies for ${assertionNode.source.toString}. Reported dependencies: $actualLabelInReportedDeps") + val labelDiff = expectedLabels.diff(actualLabelInReportedDeps) + val isSound = labelDiff.isEmpty + printIfFalse(isSound, s"Missing dependencies (${labelDiff.mkString(", ")}) for ${assertionNode.source.toString}. Reported dependencies: ${actualLabelInReportedDeps.mkString(", ")}") Some(isSound) } } diff --git a/src/main/scala/supporters/MethodSupporter.scala b/src/main/scala/supporters/MethodSupporter.scala index 32632abde..000351614 100644 --- a/src/main/scala/supporters/MethodSupporter.scala +++ b/src/main/scala/supporters/MethodSupporter.scala @@ -9,6 +9,7 @@ package viper.silicon.supporters import com.typesafe.scalalogging.Logger import viper.silicon.Map import viper.silicon.decider.Decider +import viper.silicon.dependencyAnalysis.DependencyAnalysisInfos.DefaultDependencyAnalysisInfos import viper.silicon.dependencyAnalysis._ import viper.silicon.dependencyAnalysis.graphInterpretation.DependencyGraphInterpreter import viper.silicon.interfaces._ @@ -83,8 +84,8 @@ trait DefaultMethodVerificationUnitProvider extends VerifierComponent { v: Verif val presAssertionNodeForJoin = pres.flatMap(_.topLevelConjuncts).map(pc => SimpleAssertionNode(True, AnalysisSourceInfo.createAnalysisSourceInfo(pc), AssumptionType.Precondition, SimpleDependencyAnalysisMerge(AnalysisSourceInfo.createAnalysisSourceInfo(pc)), List(SimpleDependencyAnalysisJoin(AnalysisSourceInfo.createAnalysisSourceInfo(pc), JoinType.Sink, EdgeType.Up)))) presAssertionNodeForJoin foreach v.decider.dependencyAnalyzer.addAssertionNode - val analysisInfosPrecondition = DependencyAnalysisInfos.DefaultDependencyAnalysisInfos.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Sink, EdgeType.Up)) - val analysisInfosPostcondition = DependencyAnalysisInfos.DefaultDependencyAnalysisInfos.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Source, EdgeType.Down)) + val analysisInfosPrecondition = DefaultDependencyAnalysisInfos.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Sink, EdgeType.Up)) + val analysisInfosPostcondition = DefaultDependencyAnalysisInfos.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Source, EdgeType.Down)) errorsReportedSoFar.set(0) val result = diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index 2d53a50af..8ff2b5c48 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -678,6 +678,7 @@ class DefaultMainVerifier(config: Config, } if (Verifier.config.executeDependencyAnalysisTests()) { + assert(verificationResults.count(result => result.isFatal) == 0) val testSupporter = new DependencyGraphTestSupporter(result.getFullDependencyGraphInterpreter) testSupporter.testDependencies() testSupporter.testNodeTypes() From 6128047584f6758d66f3f18737e32157949227ab Mon Sep 17 00:00:00 2001 From: Andrea Keusch Date: Thu, 25 Jun 2026 16:36:03 +0200 Subject: [PATCH 474/474] remove precision improvement regarding function arguments --- src/main/scala/rules/Evaluator.scala | 34 +++++++------------ .../all/function-args.vpr | 23 +++++++++++++ .../all/quantifiers-triggers.vpr | 15 ++++++++ src/test/scala/DependencyAnalysisTests.scala | 2 +- 4 files changed, 51 insertions(+), 23 deletions(-) create mode 100644 src/test/resources/dependencyAnalysisTests/all/function-args.vpr create mode 100644 src/test/resources/dependencyAnalysisTests/all/quantifiers-triggers.vpr diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 6b8e33996..ed8e00677 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -600,6 +600,7 @@ object evaluator extends EvaluationRules { val fappSourceInfo = AnalysisSourceInfo.createAnalysisSourceInfo(fapp) val presWithDAInfo = DependencyAnalysisMergeInfo.attachExpMergeInfo(pres.flatMap(_.topLevelConjuncts), Some(fappSourceInfo)) val eArgsWithDAInfO = DependencyAnalysisMergeInfo.attachExpMergeInfo(eArgs, None) + evals2(s, eArgsWithDAInfO, Nil, _ => pve, v, analysisInfos)((s1, tArgs, eArgsNew, v1) => { // bookkeeper.functionApplications += 1 val joinFunctionArgs = tArgs //++ c2a.quantifiedVariables.filterNot(tArgs.contains) @@ -640,24 +641,10 @@ object evaluator extends EvaluationRules { val pvePre = ErrorWrapperWithExampleTransformer(PreconditionInAppFalse(fapp).withReasonNodeTransformed(reasonOffendingNode => reasonOffendingNode.replace(formalsToActuals)), exampleTrafo) - val argsPairs: Seq[(Term, Option[ast.Exp])] = if (Verifier.config.enableDependencyAnalysis()) { - tArgs zip eArgs.map(Some(_)) - } else if (withExp) tArgs.zip(eArgsNew.get.map(Some(_))) else tArgs.zip(Seq.fill(tArgs.size)(None)) - // encode the function call as a sequence of assignments to fresh variables (one for each argument) and a method call using the fresh variables as arguments - var s2b = s2 - val argsFreshVar = - if (Verifier.config.enableDependencyAnalysis()) { - argsPairs.map(arg => { - val argNew = v1.decider.fresh(arg._1.sort, None) - val constraint = Equals(argNew, arg._1) - v1.decider.assume(constraint, None, analysisInfos.withMergeInfo(SimpleDependencyAnalysisMerge(AnalysisSourceInfo.createAnalysisSourceInfo(arg._2.get)))) - s2b = s2b.copy(functionRecorder = s2b.functionRecorder.recordConstrainedVar(argNew, constraint)) // FIXME ake: the new variables are introduced as globals but they should appear as quantified variables in function axioms. - (argNew, None) - }) - } else argsPairs - val s3 = s2b.copy(g = Store(fargs.zip(argsFreshVar)), + val argsPairs: Seq[(Term, Option[ast.Exp])] = if (withExp) tArgs.zip(eArgsNew.get.map(Some(_))) else tArgs.zip(Seq.fill(tArgs.size)(None)) + val s3 = s2.copy(g = Store(fargs.zip(argsPairs)), recordVisited = true, - functionRecorder = s2b.functionRecorder.changeDepthBy(+1), + functionRecorder = s2.functionRecorder.changeDepthBy(+1), /* Temporarily disable the recorder: when recording (to later on * translate a particular function fun) and a function application * fapp is hit, then there is no need to record any information @@ -683,7 +670,7 @@ object evaluator extends EvaluationRules { smDomainNeeded = true, moreJoins = JoinMode.Off, assertReadAccessOnly = if (Verifier.config.respectFunctionPrePermAmounts()) - s2b.assertReadAccessOnly /* should currently always be false */ else true) + s2.assertReadAccessOnly /* should currently always be false */ else true) val precondAnalysisInfos = analysisInfos.withJoinInfo(EvalStackDependencyAnalysisJoin(JoinType.Source, EdgeType.Up)) consumes(s3, presWithDAInfo, true, _ => pvePre, v2, precondAnalysisInfos)((s4, snap, v3) => { val snap1 = snap.get.convert(sorts.Snap) @@ -706,7 +693,7 @@ object evaluator extends EvaluationRules { s4.functionRecorder.changeDepthBy(-1) .recordSnapshot(fapp, v3.decider.pcs.branchConditions, snap1) val s5 = s4.copy(g = s2.g, - h = s2.h, + h = s2.h, // possibleTriggers = newPossibleTriggers, recordVisited = s2.recordVisited, functionRecorder = fr5, smDomainNeeded = s2.smDomainNeeded, @@ -722,10 +709,13 @@ object evaluator extends EvaluationRules { */ })(join(func.typ, s"joined_${func.name}", joinFunctionArgs, joinExp, v1, analysisInfos))((s6, r, v4) => { - if (v4.decider.isDependencyAnalysisEnabled) v4.decider.dependencyAnalyzer.addCustomDependenciesBetweenMergeInfos(presWithDAInfo, Seq(DependencyAnalysisMergeInfo.attachExpMergeInfo(fapp, analysisInfos.getMergeInfo))) + if (v4.decider.isDependencyAnalysisEnabled) { + v4.decider.dependencyAnalyzer.addCustomDependenciesBetweenMergeInfos(eArgsWithDAInfO, presWithDAInfo) + v4.decider.dependencyAnalyzer.addCustomDependenciesBetweenMergeInfos(presWithDAInfo, Seq(DependencyAnalysisMergeInfo.attachExpMergeInfo(fapp, analysisInfos.getMergeInfo))) + } Q(s6, r._1, r._2, v4) - })}) - + })} + ) case uf@ast.Unfolding( acc @ ast.PredicateAccessPredicate(pa @ ast.PredicateAccess(eArgs, predicateName), ePerm), eIn) => diff --git a/src/test/resources/dependencyAnalysisTests/all/function-args.vpr b/src/test/resources/dependencyAnalysisTests/all/function-args.vpr new file mode 100644 index 000000000..be359d465 --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/all/function-args.vpr @@ -0,0 +1,23 @@ +function foo(x: Int, y: Int) : Int + requires @dependencyInfo("assumptionType:Precondition, label:L1, expectedDependencies:[L4]")(x > 0) // importantly, L5 is NOT a dependency + requires @dependencyInfo("assumptionType:Precondition, label:L2, expectedDependencies:[L4, L5]")(y < 100) +{ + @dependencyInfo("assumptionType:FunctionBody, label:L3")(x) +} + +method clientFoo(){ + var x: Int, y: Int, z: Int + + @dependencyInfo("assumptionType:SourceCode, label:L4") + x := 19 + + @dependencyInfo("assumptionType:SourceCode, label:L5") + y := 10 + + @dependencyInfo("assumptionType:SourceCode, assertionType:SourceCode, label:L6, expectedDependencies:[L4, L5]") + z := foo(x, x/y) + + @dependencyInfo("assertionType:Explicit, label:L7, expectedDependencies:[L6, L3, L5, L4]") + assert z == x + +} diff --git a/src/test/resources/dependencyAnalysisTests/all/quantifiers-triggers.vpr b/src/test/resources/dependencyAnalysisTests/all/quantifiers-triggers.vpr new file mode 100644 index 000000000..9545c50ba --- /dev/null +++ b/src/test/resources/dependencyAnalysisTests/all/quantifiers-triggers.vpr @@ -0,0 +1,15 @@ +field val: Bool +function toBool(s: Ref): Bool +predicate GlobalMem() { + forall k: Ref :: { toBool(k) } toBool(k) ==> false +} + +method client(i: Ref, k: Ref) + requires toBool(k) + requires GlobalMem() + ensures GlobalMem() +{ + unfold GlobalMem() + assert false // should verify + fold GlobalMem() +} \ No newline at end of file diff --git a/src/test/scala/DependencyAnalysisTests.scala b/src/test/scala/DependencyAnalysisTests.scala index 39c7f2f69..2f198393e 100644 --- a/src/test/scala/DependencyAnalysisTests.scala +++ b/src/test/scala/DependencyAnalysisTests.scala @@ -12,7 +12,7 @@ class DependencyAnalysisTests extends AnyFunSuite with DependencyAnalysisTestFra val CHECK_PRECISION = false val EXECUTE_TEST = true override val EXPORT_PRUNED_PROGRAMS: Boolean = false - val ignores: Seq[String] = Seq("iterativeTreeDelete") + val ignores: Seq[String] = Seq() analysisCommandLineArguments = analysisCommandLineArguments ++ Seq("--executeDependencyAnalysisTests") val testDirectories: Seq[String] = Seq( "dependencyAnalysisTests/all",